aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_antlionguard.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mp/src/game/server/hl2/npc_antlionguard.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_antlionguard.cpp5048
1 files changed, 5048 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/npc_antlionguard.cpp b/mp/src/game/server/hl2/npc_antlionguard.cpp
new file mode 100644
index 00000000..02c880e9
--- /dev/null
+++ b/mp/src/game/server/hl2/npc_antlionguard.cpp
@@ -0,0 +1,5048 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Antlion Guard
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_hint.h"
+#include "ai_localnavigator.h"
+#include "ai_memory.h"
+#include "ai_moveprobe.h"
+#include "npcevent.h"
+#include "IEffects.h"
+#include "ndebugoverlay.h"
+#include "soundent.h"
+#include "soundenvelope.h"
+#include "ai_squad.h"
+#include "ai_network.h"
+#include "ai_pathfinder.h"
+#include "ai_navigator.h"
+#include "ai_senses.h"
+#include "npc_rollermine.h"
+#include "ai_blended_movement.h"
+#include "physics_prop_ragdoll.h"
+#include "iservervehicle.h"
+#include "player_pickup.h"
+#include "props.h"
+#include "antlion_dust.h"
+#include "npc_antlion.h"
+#include "decals.h"
+#include "prop_combine_ball.h"
+#include "eventqueue.h"
+#include "te_effect_dispatch.h"
+#include "Sprite.h"
+#include "particle_parse.h"
+#include "particle_system.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+inline void TraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin,
+ const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore,
+ int collisionGroup, trace_t *ptr, float minMass );
+
+ConVar g_debug_antlionguard( "g_debug_antlionguard", "0" );
+ConVar sk_antlionguard_dmg_charge( "sk_antlionguard_dmg_charge", "0" );
+ConVar sk_antlionguard_dmg_shove( "sk_antlionguard_dmg_shove", "0" );
+
+#if HL2_EPISODIC
+// When enabled, add code to have the antlion bleed profusely as it is badly injured.
+#define ANTLIONGUARD_BLOOD_EFFECTS 2
+ConVar g_antlionguard_hemorrhage( "g_antlionguard_hemorrhage", "1", FCVAR_NONE, "If 1, guard will emit a bleeding particle effect when wounded." );
+#endif
+
+// Spawnflags
+#define SF_ANTLIONGUARD_SERVERSIDE_RAGDOLL ( 1 << 16 )
+#define SF_ANTLIONGUARD_INSIDE_FOOTSTEPS ( 1 << 17 )
+
+#define ENVELOPE_CONTROLLER (CSoundEnvelopeController::GetController())
+#define ANTLIONGUARD_MODEL "models/antlion_guard.mdl"
+#define MIN_BLAST_DAMAGE 25.0f
+#define MIN_CRUSH_DAMAGE 20.0f
+
+//==================================================
+//
+// Antlion Guard
+//
+//==================================================
+
+#define ANTLIONGUARD_MAX_OBJECTS 128
+#define ANTLIONGUARD_MIN_OBJECT_MASS 8
+#define ANTLIONGUARD_MAX_OBJECT_MASS 750
+#define ANTLIONGUARD_FARTHEST_PHYSICS_OBJECT 350
+#define ANTLIONGUARD_OBJECTFINDING_FOV DOT_45DEGREE // 1/sqrt(2)
+
+//Melee definitions
+#define ANTLIONGUARD_MELEE1_RANGE 156.0f
+#define ANTLIONGUARD_MELEE1_CONE 0.7f
+
+// Antlion summoning
+#define ANTLIONGUARD_SUMMON_COUNT 3
+
+// Sight
+#define ANTLIONGUARD_FOV_NORMAL -0.4f
+
+// cavern guard's poisoning behavior
+#if HL2_EPISODIC
+#define ANTLIONGUARD_POISON_TO 12 // we only poison Gordon down to twelve to give him a chance to regen up to 20 by the next charge
+#endif
+
+#define ANTLIONGUARD_CHARGE_MIN 256
+#define ANTLIONGUARD_CHARGE_MAX 2048
+
+ConVar sk_antlionguard_health( "sk_antlionguard_health", "0" );
+
+int g_interactionAntlionGuardFoundPhysicsObject = 0; // We're moving to a physics object to shove it, don't all choose the same object
+int g_interactionAntlionGuardShovedPhysicsObject = 0; // We've punted an object, it is now clear to be chosen by others
+
+//==================================================
+// AntlionGuardSchedules
+//==================================================
+
+enum
+{
+ SCHED_ANTLIONGUARD_CHARGE = LAST_SHARED_SCHEDULE,
+ SCHED_ANTLIONGUARD_CHARGE_CRASH,
+ SCHED_ANTLIONGUARD_CHARGE_CANCEL,
+ SCHED_ANTLIONGUARD_PHYSICS_ATTACK,
+ SCHED_ANTLIONGUARD_PHYSICS_DAMAGE_HEAVY,
+ SCHED_ANTLIONGUARD_UNBURROW,
+ SCHED_ANTLIONGUARD_CHARGE_TARGET,
+ SCHED_ANTLIONGUARD_FIND_CHARGE_POSITION,
+ SCHED_ANTLIONGUARD_MELEE_ATTACK1,
+ SCHED_ANTLIONGUARD_SUMMON,
+ SCHED_ANTLIONGUARD_PATROL_RUN,
+ SCHED_ANTLIONGUARD_ROAR,
+ SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE,
+ SCHED_FORCE_ANTLIONGUARD_PHYSICS_ATTACK,
+ SCHED_ANTLIONGUARD_CANT_ATTACK,
+ SCHED_ANTLIONGUARD_TAKE_COVER_FROM_ENEMY,
+ SCHED_ANTLIONGUARD_CHASE_ENEMY
+};
+
+
+//==================================================
+// AntlionGuardTasks
+//==================================================
+
+enum
+{
+ TASK_ANTLIONGUARD_CHARGE = LAST_SHARED_TASK,
+ TASK_ANTLIONGUARD_GET_PATH_TO_PHYSOBJECT,
+ TASK_ANTLIONGUARD_SHOVE_PHYSOBJECT,
+ TASK_ANTLIONGUARD_SUMMON,
+ TASK_ANTLIONGUARD_SET_FLINCH_ACTIVITY,
+ TASK_ANTLIONGUARD_GET_PATH_TO_CHARGE_POSITION,
+ TASK_ANTLIONGUARD_GET_PATH_TO_NEAREST_NODE,
+ TASK_ANTLIONGUARD_GET_CHASE_PATH_ENEMY_TOLERANCE,
+ TASK_ANTLIONGUARD_OPPORTUNITY_THROW,
+ TASK_ANTLIONGUARD_FIND_PHYSOBJECT,
+};
+
+//==================================================
+// AntlionGuardConditions
+//==================================================
+
+enum
+{
+ COND_ANTLIONGUARD_PHYSICS_TARGET = LAST_SHARED_CONDITION,
+ COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID,
+ COND_ANTLIONGUARD_HAS_CHARGE_TARGET,
+ COND_ANTLIONGUARD_CAN_SUMMON,
+ COND_ANTLIONGUARD_CAN_CHARGE
+};
+
+enum
+{
+ SQUAD_SLOT_ANTLIONGUARD_CHARGE = LAST_SHARED_SQUADSLOT,
+};
+
+//==================================================
+// AntlionGuard Activities
+//==================================================
+
+Activity ACT_ANTLIONGUARD_SEARCH;
+Activity ACT_ANTLIONGUARD_PEEK_FLINCH;
+Activity ACT_ANTLIONGUARD_PEEK_ENTER;
+Activity ACT_ANTLIONGUARD_PEEK_EXIT;
+Activity ACT_ANTLIONGUARD_PEEK1;
+Activity ACT_ANTLIONGUARD_BARK;
+Activity ACT_ANTLIONGUARD_PEEK_SIGHTED;
+Activity ACT_ANTLIONGUARD_SHOVE_PHYSOBJECT;
+Activity ACT_ANTLIONGUARD_FLINCH_LIGHT;
+Activity ACT_ANTLIONGUARD_UNBURROW;
+Activity ACT_ANTLIONGUARD_ROAR;
+Activity ACT_ANTLIONGUARD_RUN_HURT;
+
+// Flinches
+Activity ACT_ANTLIONGUARD_PHYSHIT_FR;
+Activity ACT_ANTLIONGUARD_PHYSHIT_FL;
+Activity ACT_ANTLIONGUARD_PHYSHIT_RR;
+Activity ACT_ANTLIONGUARD_PHYSHIT_RL;
+
+// Charge
+Activity ACT_ANTLIONGUARD_CHARGE_START;
+Activity ACT_ANTLIONGUARD_CHARGE_CANCEL;
+Activity ACT_ANTLIONGUARD_CHARGE_RUN;
+Activity ACT_ANTLIONGUARD_CHARGE_CRASH;
+Activity ACT_ANTLIONGUARD_CHARGE_STOP;
+Activity ACT_ANTLIONGUARD_CHARGE_HIT;
+Activity ACT_ANTLIONGUARD_CHARGE_ANTICIPATION;
+
+// Anim events
+int AE_ANTLIONGUARD_CHARGE_HIT;
+int AE_ANTLIONGUARD_SHOVE_PHYSOBJECT;
+int AE_ANTLIONGUARD_SHOVE;
+int AE_ANTLIONGUARD_FOOTSTEP_LIGHT;
+int AE_ANTLIONGUARD_FOOTSTEP_HEAVY;
+int AE_ANTLIONGUARD_CHARGE_EARLYOUT;
+int AE_ANTLIONGUARD_VOICE_GROWL;
+int AE_ANTLIONGUARD_VOICE_BARK;
+int AE_ANTLIONGUARD_VOICE_PAIN;
+int AE_ANTLIONGUARD_VOICE_SQUEEZE;
+int AE_ANTLIONGUARD_VOICE_SCRATCH;
+int AE_ANTLIONGUARD_VOICE_GRUNT;
+int AE_ANTLIONGUARD_VOICE_ROAR;
+int AE_ANTLIONGUARD_BURROW_OUT;
+
+struct PhysicsObjectCriteria_t
+{
+ CBaseEntity *pTarget;
+ Vector vecCenter; // Center point to look around
+ float flRadius; // Radius to search within
+ float flTargetCone;
+ bool bPreferObjectsAlongTargetVector; // Prefer objects that we can strike easily as we move towards our target
+ float flNearRadius; // If we won't hit the player with the object, but get this close, throw anyway
+};
+
+#define MAX_FAILED_PHYSOBJECTS 8
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CNPC_AntlionGuard : public CAI_BlendedNPC
+{
+public:
+ DECLARE_CLASS( CNPC_AntlionGuard, CAI_BlendedNPC );
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CNPC_AntlionGuard( void );
+
+ Class_T Classify( void ) { return CLASS_ANTLION; }
+ virtual int GetSoundInterests( void ) { return (SOUND_WORLD|SOUND_COMBAT|SOUND_PLAYER|SOUND_DANGER); }
+ virtual bool QueryHearSound( CSound *pSound );
+
+ const impactdamagetable_t &GetPhysicsImpactDamageTable( void );
+
+ virtual int MeleeAttack1Conditions( float flDot, float flDist );
+ virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
+
+ virtual int TranslateSchedule( int scheduleType );
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ virtual void DeathSound( const CTakeDamageInfo &info );
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+ virtual int SelectSchedule( void );
+
+ virtual float GetAutoAimRadius() { return 36.0f; }
+
+ virtual void Precache( void );
+ virtual void Spawn( void );
+ virtual void Activate( void );
+ virtual void HandleAnimEvent( animevent_t *pEvent );
+ virtual void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); }
+ virtual void PrescheduleThink( void );
+ virtual void GatherConditions( void );
+ virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ virtual void StartTask( const Task_t *pTask );
+ virtual void RunTask( const Task_t *pTask );
+ virtual void StopLoopingSounds();
+ virtual bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sender );
+
+ // Input handlers.
+ void InputSetShoveTarget( inputdata_t &inputdata );
+ void InputSetChargeTarget( inputdata_t &inputdata );
+ void InputClearChargeTarget( inputdata_t &inputdata );
+ void InputUnburrow( inputdata_t &inputdata );
+ void InputRagdoll( inputdata_t &inputdata );
+ void InputEnableBark( inputdata_t &inputdata );
+ void InputDisableBark( inputdata_t &inputdata );
+ void InputSummonedAntlionDied( inputdata_t &inputdata );
+ void InputEnablePreferPhysicsAttack( inputdata_t &inputdata );
+ void InputDisablePreferPhysicsAttack( inputdata_t &inputdata );
+
+ virtual bool IsLightDamage( const CTakeDamageInfo &info );
+ virtual bool IsHeavyDamage( const CTakeDamageInfo &info );
+ virtual bool OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval );
+ virtual bool BecomeRagdollOnClient( const Vector &force );
+ virtual void UpdateOnRemove( void );
+ virtual bool IsUnreachable( CBaseEntity* pEntity ); // Is entity is unreachable?
+
+ virtual float MaxYawSpeed( void );
+ virtual bool OverrideMove( float flInterval );
+ virtual bool CanBecomeRagdoll( void );
+
+ virtual bool ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity );
+
+ virtual Activity NPC_TranslateActivity( Activity baseAct );
+
+#if HL2_EPISODIC
+ //---------------------------------
+ // Navigation & Movement -- prevent stopping paths for the guard
+ //---------------------------------
+ class CNavigator : public CAI_ComponentWithOuter<CNPC_AntlionGuard, CAI_Navigator>
+ {
+ typedef CAI_ComponentWithOuter<CNPC_AntlionGuard, CAI_Navigator> BaseClass;
+ public:
+ CNavigator( CNPC_AntlionGuard *pOuter )
+ : BaseClass( pOuter )
+ {
+ }
+
+ bool GetStoppingPath( CAI_WaypointList *pClippedWaypoints );
+ };
+ CAI_Navigator * CreateNavigator() { return new CNavigator( this ); }
+#endif
+
+ DEFINE_CUSTOM_AI;
+
+private:
+
+ inline bool CanStandAtPoint( const Vector &vecPos, Vector *pOut );
+ bool RememberFailedPhysicsTarget( CBaseEntity *pTarget );
+ void GetPhysicsShoveDir( CBaseEntity *pObject, float flMass, Vector *pOut );
+ void CreateGlow( CSprite **pSprite, const char *pAttachName );
+ void DestroyGlows( void );
+ void Footstep( bool bHeavy );
+ int SelectCombatSchedule( void );
+ int SelectUnreachableSchedule( void );
+ bool CanSummon( bool bIgnoreTime );
+ void SummonAntlions( void );
+
+ void ChargeLookAhead( void );
+ bool EnemyIsRightInFrontOfMe( CBaseEntity **pEntity );
+ bool HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity );
+ bool ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel );
+ bool ShouldWatchEnemy( void );
+
+ void ImpactShock( const Vector &origin, float radius, float magnitude, CBaseEntity *pIgnored = NULL );
+ void BuildScheduleTestBits( void );
+ void Shove( void );
+ void FoundEnemy( void );
+ void LostEnemy( void );
+ void UpdateHead( void );
+ void UpdatePhysicsTarget( bool bPreferObjectsAlongTargetVector, float flRadius = ANTLIONGUARD_FARTHEST_PHYSICS_OBJECT );
+ void MaintainPhysicsTarget( void );
+ void ChargeDamage( CBaseEntity *pTarget );
+ void StartSounds( void );
+ void SetHeavyDamageAnim( const Vector &vecSource );
+ float ChargeSteer( void );
+ CBaseEntity *FindPhysicsObjectTarget( const PhysicsObjectCriteria_t &criteria );
+ Vector GetPhysicsHitPosition( CBaseEntity *pObject, CBaseEntity *pTarget, Vector *vecTrajectory, float *flClearDistance );
+ bool CanStandAtShoveTarget( CBaseEntity *pShoveObject, CBaseEntity *pTarget, Vector *pOut );
+ CBaseEntity *GetNextShoveTarget( CBaseEntity *pLastEntity, AISightIter_t &iter );
+
+ int m_nFlinchActivity;
+
+ bool m_bStopped;
+ bool m_bIsBurrowed;
+ bool m_bBarkEnabled;
+ float m_flNextSummonTime;
+ int m_iNumLiveAntlions;
+
+ float m_flSearchNoiseTime;
+ float m_flAngerNoiseTime;
+ float m_flBreathTime;
+ float m_flChargeTime;
+ float m_flPhysicsCheckTime;
+ float m_flNextHeavyFlinchTime;
+ float m_flNextRoarTime;
+ int m_iChargeMisses;
+ bool m_bDecidedNotToStop;
+ bool m_bPreferPhysicsAttack;
+
+ CNetworkVar( bool, m_bCavernBreed ); // If this guard is meant to be a cavern dweller (uses different assets)
+ CNetworkVar( bool, m_bInCavern ); // Behavioral hint telling the guard to change his behavior
+
+ Vector m_vecPhysicsTargetStartPos;
+ Vector m_vecPhysicsHitPosition;
+
+ EHANDLE m_hShoveTarget;
+ EHANDLE m_hChargeTarget;
+ EHANDLE m_hChargeTargetPosition;
+ EHANDLE m_hOldTarget;
+ EHANDLE m_hPhysicsTarget;
+
+ CUtlVectorFixed<EHANDLE, MAX_FAILED_PHYSOBJECTS> m_FailedPhysicsTargets;
+
+ COutputEvent m_OnSummon;
+
+ CSoundPatch *m_pGrowlHighSound;
+ CSoundPatch *m_pGrowlLowSound;
+ CSoundPatch *m_pGrowlIdleSound;
+ CSoundPatch *m_pBreathSound;
+ CSoundPatch *m_pConfusedSound;
+
+ string_t m_iszPhysicsPropClass;
+ string_t m_strShoveTargets;
+
+ CSprite *m_hCaveGlow[2];
+
+#if ANTLIONGUARD_BLOOD_EFFECTS
+ CNetworkVar( uint8, m_iBleedingLevel );
+
+ unsigned char GetBleedingLevel( void ) const;
+#endif
+protected:
+
+ int m_poseThrow;
+ int m_poseHead_Yaw, m_poseHead_Pitch;
+ virtual void PopulatePoseParameters( void );
+
+
+// inline accessors
+public:
+ inline bool IsCavernBreed( void ) const { return m_bCavernBreed; }
+ inline bool IsInCavern( void ) const { return m_bInCavern; }
+};
+
+//==================================================
+// CNPC_AntlionGuard::m_DataDesc
+//==================================================
+
+BEGIN_DATADESC( CNPC_AntlionGuard )
+
+ DEFINE_FIELD( m_nFlinchActivity, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bStopped, FIELD_BOOLEAN ),
+ DEFINE_KEYFIELD( m_bIsBurrowed, FIELD_BOOLEAN, "startburrowed" ),
+ DEFINE_KEYFIELD( m_bBarkEnabled, FIELD_BOOLEAN, "allowbark" ),
+ DEFINE_FIELD( m_flNextSummonTime, FIELD_TIME ),
+ DEFINE_FIELD( m_iNumLiveAntlions, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_flSearchNoiseTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flAngerNoiseTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flBreathTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flChargeTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_hShoveTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hChargeTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hChargeTargetPosition, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hOldTarget, FIELD_EHANDLE ),
+ // m_FailedPhysicsTargets // We do not save/load these
+
+ DEFINE_FIELD( m_hPhysicsTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_vecPhysicsTargetStartPos, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecPhysicsHitPosition, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_flPhysicsCheckTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextHeavyFlinchTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextRoarTime, FIELD_TIME ),
+ DEFINE_FIELD( m_iChargeMisses, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bDecidedNotToStop, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bPreferPhysicsAttack, FIELD_BOOLEAN ),
+
+#if ANTLIONGUARD_BLOOD_EFFECTS
+ DEFINE_FIELD( m_iBleedingLevel, FIELD_CHARACTER ),
+#endif
+
+ DEFINE_KEYFIELD( m_bCavernBreed,FIELD_BOOLEAN, "cavernbreed" ),
+ DEFINE_KEYFIELD( m_bInCavern, FIELD_BOOLEAN, "incavern" ),
+ DEFINE_KEYFIELD( m_strShoveTargets, FIELD_STRING, "shovetargets" ),
+
+ DEFINE_AUTO_ARRAY( m_hCaveGlow, FIELD_CLASSPTR ),
+
+ DEFINE_OUTPUT( m_OnSummon, "OnSummon" ),
+
+ DEFINE_SOUNDPATCH( m_pGrowlHighSound ),
+ DEFINE_SOUNDPATCH( m_pGrowlLowSound ),
+ DEFINE_SOUNDPATCH( m_pGrowlIdleSound ),
+ DEFINE_SOUNDPATCH( m_pBreathSound ),
+ DEFINE_SOUNDPATCH( m_pConfusedSound ),
+
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetShoveTarget", InputSetShoveTarget ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetChargeTarget", InputSetChargeTarget ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ClearChargeTarget", InputClearChargeTarget ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Unburrow", InputUnburrow ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Ragdoll", InputRagdoll ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableBark", InputEnableBark ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableBark", InputDisableBark ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SummonedAntlionDied", InputSummonedAntlionDied ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnablePreferPhysicsAttack", InputEnablePreferPhysicsAttack ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisablePreferPhysicsAttack", InputDisablePreferPhysicsAttack ),
+
+END_DATADESC()
+
+
+//Fast Growl (Growl High)
+envelopePoint_t envAntlionGuardFastGrowl[] =
+{
+ { 1.0f, 1.0f,
+ 0.2f, 0.4f,
+ },
+ { 0.1f, 0.1f,
+ 0.8f, 1.0f,
+ },
+ { 0.0f, 0.0f,
+ 0.4f, 0.8f,
+ },
+};
+
+
+//Bark 1 (Growl High)
+envelopePoint_t envAntlionGuardBark1[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.2f,
+ },
+ { 0.0f, 0.0f,
+ 0.4f, 0.6f,
+ },
+};
+
+//Bark 2 (Confused)
+envelopePoint_t envAntlionGuardBark2[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.2f,
+ },
+ { 0.2f, 0.3f,
+ 0.1f, 0.2f,
+ },
+ { 0.0f, 0.0f,
+ 0.4f, 0.6f,
+ },
+};
+
+//Pain
+envelopePoint_t envAntlionGuardPain1[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.2f,
+ },
+ { -1.0f, -1.0f,
+ 0.5f, 0.8f,
+ },
+ { 0.1f, 0.2f,
+ 0.1f, 0.2f,
+ },
+ { 0.0f, 0.0f,
+ 0.5f, 0.75f,
+ },
+};
+
+//Squeeze (High Growl)
+envelopePoint_t envAntlionGuardSqueeze[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.2f,
+ },
+ { 0.0f, 0.0f,
+ 1.0f, 1.5f,
+ },
+};
+
+//Scratch (Low Growl)
+envelopePoint_t envAntlionGuardScratch[] =
+{
+ { 1.0f, 1.0f,
+ 0.4f, 0.6f,
+ },
+ { 0.5f, 0.5f,
+ 0.4f, 0.6f,
+ },
+ { 0.0f, 0.0f,
+ 1.0f, 1.5f,
+ },
+};
+
+//Grunt
+envelopePoint_t envAntlionGuardGrunt[] =
+{
+ { 0.6f, 1.0f,
+ 0.1f, 0.2f,
+ },
+ { 0.0f, 0.0f,
+ 0.1f, 0.2f,
+ },
+};
+
+envelopePoint_t envAntlionGuardGrunt2[] =
+{
+ { 0.2f, 0.4f,
+ 0.1f, 0.2f,
+ },
+ { 0.0f, 0.0f,
+ 0.4f, 0.6f,
+ },
+};
+
+//==============================================================================================
+// ANTLION GUARD PHYSICS DAMAGE TABLE
+//==============================================================================================
+static impactentry_t antlionGuardLinearTable[] =
+{
+ { 100*100, 10 },
+ { 250*250, 25 },
+ { 350*350, 50 },
+ { 500*500, 75 },
+ { 1000*1000,100 },
+};
+
+static impactentry_t antlionGuardAngularTable[] =
+{
+ { 50* 50, 10 },
+ { 100*100, 25 },
+ { 150*150, 50 },
+ { 200*200, 75 },
+};
+
+impactdamagetable_t gAntlionGuardImpactDamageTable =
+{
+ antlionGuardLinearTable,
+ antlionGuardAngularTable,
+
+ ARRAYSIZE(antlionGuardLinearTable),
+ ARRAYSIZE(antlionGuardAngularTable),
+
+ 200*200,// minimum linear speed squared
+ 180*180,// minimum angular speed squared (360 deg/s to cause spin/slice damage)
+ 15, // can't take damage from anything under 15kg
+
+ 10, // anything less than 10kg is "small"
+ 5, // never take more than 1 pt of damage from anything under 15kg
+ 128*128,// <15kg objects must go faster than 36 in/s to do damage
+
+ 45, // large mass in kg
+ 2, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
+ 1, // large mass falling scale
+ 0, // my min velocity
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const impactdamagetable_t
+//-----------------------------------------------------------------------------
+const impactdamagetable_t &CNPC_AntlionGuard::GetPhysicsImpactDamageTable( void )
+{
+ return gAntlionGuardImpactDamageTable;
+}
+
+//==================================================
+// CNPC_AntlionGuard
+//==================================================
+
+CNPC_AntlionGuard::CNPC_AntlionGuard( void )
+{
+ m_bCavernBreed = false;
+ m_bInCavern = false;
+
+ m_iszPhysicsPropClass = AllocPooledString( "prop_physics" );
+}
+
+LINK_ENTITY_TO_CLASS( npc_antlionguard, CNPC_AntlionGuard );
+
+IMPLEMENT_SERVERCLASS_ST(CNPC_AntlionGuard, DT_NPC_AntlionGuard)
+ SendPropBool( SENDINFO( m_bCavernBreed ) ),
+ SendPropBool( SENDINFO( m_bInCavern ) ),
+
+#if ANTLIONGUARD_BLOOD_EFFECTS
+ SendPropInt( SENDINFO( m_iBleedingLevel ), 2, SPROP_UNSIGNED ),
+#endif
+END_SEND_TABLE()
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::UpdateOnRemove( void )
+{
+ DestroyGlows();
+
+ // Chain to the base class
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::Precache( void )
+{
+ PrecacheModel( ANTLIONGUARD_MODEL );
+
+ PrecacheScriptSound( "NPC_AntlionGuard.Shove" );
+ PrecacheScriptSound( "NPC_AntlionGuard.HitHard" );
+
+ if ( HasSpawnFlags(SF_ANTLIONGUARD_INSIDE_FOOTSTEPS) )
+ {
+ PrecacheScriptSound( "NPC_AntlionGuard.Inside.StepLight" );
+ PrecacheScriptSound( "NPC_AntlionGuard.Inside.StepHeavy" );
+ }
+ else
+ {
+ PrecacheScriptSound( "NPC_AntlionGuard.StepLight" );
+ PrecacheScriptSound( "NPC_AntlionGuard.StepHeavy" );
+ }
+
+#if HL2_EPISODIC
+ PrecacheScriptSound( "NPC_AntlionGuard.NearStepLight" );
+ PrecacheScriptSound( "NPC_AntlionGuard.NearStepHeavy" );
+ PrecacheScriptSound( "NPC_AntlionGuard.FarStepLight" );
+ PrecacheScriptSound( "NPC_AntlionGuard.FarStepHeavy" );
+ PrecacheScriptSound( "NPC_AntlionGuard.BreatheLoop" );
+ PrecacheScriptSound( "NPC_AntlionGuard.ShellCrack" );
+ PrecacheScriptSound( "NPC_AntlionGuard.Pain_Roar" );
+ PrecacheModel( "sprites/grubflare1.vmt" );
+#endif // HL2_EPISODIC
+
+ PrecacheScriptSound( "NPC_AntlionGuard.Anger" );
+ PrecacheScriptSound( "NPC_AntlionGuard.Roar" );
+ PrecacheScriptSound( "NPC_AntlionGuard.Die" );
+
+ PrecacheScriptSound( "NPC_AntlionGuard.GrowlHigh" );
+ PrecacheScriptSound( "NPC_AntlionGuard.GrowlIdle" );
+ PrecacheScriptSound( "NPC_AntlionGuard.BreathSound" );
+ PrecacheScriptSound( "NPC_AntlionGuard.Confused" );
+ PrecacheScriptSound( "NPC_AntlionGuard.Fallover" );
+
+ PrecacheScriptSound( "NPC_AntlionGuard.FrustratedRoar" );
+
+ PrecacheParticleSystem( "blood_antlionguard_injured_light" );
+ PrecacheParticleSystem( "blood_antlionguard_injured_heavy" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::DestroyGlows( void )
+{
+ if ( m_hCaveGlow[0] )
+ {
+ UTIL_Remove( m_hCaveGlow[0] );
+
+ // reset it to NULL in case there is a double death cleanup for some reason.
+ m_hCaveGlow[0] = NULL;
+ }
+
+ if ( m_hCaveGlow[1] )
+ {
+ UTIL_Remove( m_hCaveGlow[1] );
+
+ // reset it to NULL in case there is a double death cleanup for some reason.
+ m_hCaveGlow[1] = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::CreateGlow( CSprite **pSprite, const char *pAttachName )
+{
+ if ( pSprite == NULL )
+ return;
+
+ // Create the glow sprite
+ *pSprite = CSprite::SpriteCreate( "sprites/grubflare1.vmt", GetLocalOrigin(), false );
+ Assert( *pSprite );
+ if ( *pSprite == NULL )
+ return;
+
+ (*pSprite)->TurnOn();
+ (*pSprite)->SetTransparency( kRenderWorldGlow, 156, 169, 121, 164, kRenderFxNoDissipation );
+ (*pSprite)->SetScale( 1.0f );
+ (*pSprite)->SetGlowProxySize( 16.0f );
+ int nAttachment = LookupAttachment( pAttachName );
+ (*pSprite)->SetParent( this, nAttachment );
+ (*pSprite)->SetLocalOrigin( vec3_origin );
+
+ // Don't uselessly animate, we're a static sprite!
+ (*pSprite)->SetThink( NULL );
+ (*pSprite)->SetNextThink( TICK_NEVER_THINK );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::Spawn( void )
+{
+ Precache();
+
+ SetModel( ANTLIONGUARD_MODEL );
+
+ // Switch our skin (for now), if we're the cavern guard
+ if ( m_bCavernBreed )
+ {
+ m_nSkin = 1;
+
+ // Add glows
+ CreateGlow( &(m_hCaveGlow[0]), "attach_glow1" );
+ CreateGlow( &(m_hCaveGlow[1]), "attach_glow2" );
+ }
+ else
+ {
+ m_hCaveGlow[0] = NULL;
+ m_hCaveGlow[1] = NULL;
+ }
+
+ SetHullType( HULL_LARGE );
+ SetHullSizeNormal();
+ SetDefaultEyeOffset();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+
+ SetNavType( NAV_GROUND );
+ SetBloodColor( BLOOD_COLOR_YELLOW );
+
+ m_iHealth = sk_antlionguard_health.GetFloat();
+ m_iMaxHealth = m_iHealth;
+ m_flFieldOfView = ANTLIONGUARD_FOV_NORMAL;
+
+ m_flPhysicsCheckTime = 0;
+ m_flChargeTime = 0;
+ m_flNextRoarTime = 0;
+ m_flNextSummonTime = 0;
+ m_iNumLiveAntlions = 0;
+ m_iChargeMisses = 0;
+ m_flNextHeavyFlinchTime = 0;
+ m_bDecidedNotToStop = false;
+
+ ClearHintGroup();
+
+ m_bStopped = false;
+
+ m_hShoveTarget = NULL;
+ m_hChargeTarget = NULL;
+ m_hChargeTargetPosition = NULL;
+ m_hPhysicsTarget = NULL;
+
+ m_HackedGunPos.x = 10;
+ m_HackedGunPos.y = 0;
+ m_HackedGunPos.z = 30;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_SQUAD );
+ CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
+
+ NPCInit();
+
+ BaseClass::Spawn();
+
+ //See if we're supposed to start burrowed
+ if ( m_bIsBurrowed )
+ {
+ AddEffects( EF_NODRAW );
+ AddFlag( FL_NOTARGET );
+
+ m_spawnflags |= SF_NPC_GAG;
+
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ m_takedamage = DAMAGE_NO;
+
+ if ( m_hCaveGlow[0] )
+ m_hCaveGlow[0]->TurnOff();
+
+ if ( m_hCaveGlow[1] )
+ m_hCaveGlow[1]->TurnOff();
+ }
+
+ // Do not dissolve
+ AddEFlags( EFL_NO_DISSOLVE );
+
+ // We get a minute of free knowledge about the target
+ GetEnemies()->SetEnemyDiscardTime( 120.0f );
+ GetEnemies()->SetFreeKnowledgeDuration( 60.0f );
+
+ // We need to bloat the absbox to encompass all the hitboxes
+ Vector absMin = -Vector(100,100,0);
+ Vector absMax = Vector(100,100,128);
+
+ CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &absMin, &absMax );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::Activate( void )
+{
+ BaseClass::Activate();
+
+ // Find all nearby physics objects and add them to the list of objects we will sense
+ CBaseEntity *pObject = NULL;
+ while ( ( pObject = gEntList.FindEntityInSphere( pObject, WorldSpaceCenter(), 2500 ) ) != NULL )
+ {
+ // Can't throw around debris
+ if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
+ continue;
+
+ // We can only throw a few types of things
+ if ( !FClassnameIs( pObject, "prop_physics" ) && !FClassnameIs( pObject, "func_physbox" ) )
+ continue;
+
+ // Ensure it's mass is within range
+ IPhysicsObject *pPhysObj = pObject->VPhysicsGetObject();
+ if( ( pPhysObj == NULL ) || ( pPhysObj->GetMass() > ANTLIONGUARD_MAX_OBJECT_MASS ) || ( pPhysObj->GetMass() < ANTLIONGUARD_MIN_OBJECT_MASS ) )
+ continue;
+
+ // Tell the AI sensing list that we want to consider this
+ g_AI_SensedObjectsManager.AddEntity( pObject );
+
+ if ( g_debug_antlionguard.GetInt() == 5 )
+ {
+ Msg("Antlion Guard: Added prop with model '%s' to sense list.\n", STRING(pObject->GetModelName()) );
+ pObject->m_debugOverlays |= OVERLAY_BBOX_BIT;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the guard's allowed to summon antlions
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::CanSummon( bool bIgnoreTime )
+{
+ if ( !m_bBarkEnabled )
+ return false;
+
+ if ( !bIgnoreTime && m_flNextSummonTime > gpGlobals->curtime )
+ return false;
+
+ // Hit the max number of them allowed? Only summon when we're 2 down.
+ if ( m_iNumLiveAntlions >= MAX(1, ANTLIONGUARD_SUMMON_COUNT-1) )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Our enemy is unreachable. Select a schedule.
+//-----------------------------------------------------------------------------
+int CNPC_AntlionGuard::SelectUnreachableSchedule( void )
+{
+ // If we're in the cavern setting, we opt out of this
+ if ( m_bInCavern )
+ return SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE;
+ // Summon antlions if we can
+ if ( HasCondition( COND_ANTLIONGUARD_CAN_SUMMON ) )
+ return SCHED_ANTLIONGUARD_SUMMON;
+
+ // First, look for objects near ourselves
+ PhysicsObjectCriteria_t criteria;
+ criteria.bPreferObjectsAlongTargetVector = false;
+ criteria.flNearRadius = (40*12);
+ criteria.flRadius = (250*12);
+ criteria.flTargetCone = -1.0f;
+ criteria.pTarget = GetEnemy();
+ criteria.vecCenter = GetAbsOrigin();
+
+ CBaseEntity *pTarget = FindPhysicsObjectTarget( criteria );
+ if ( pTarget == NULL && GetEnemy() )
+ {
+ // Use the same criteria, but search near the target instead of us
+ criteria.vecCenter = GetEnemy()->GetAbsOrigin();
+ pTarget = FindPhysicsObjectTarget( criteria );
+ }
+
+ // If we found one, we'll want to attack it
+ if ( pTarget )
+ {
+ m_hPhysicsTarget = pTarget;
+ SetCondition( COND_ANTLIONGUARD_PHYSICS_TARGET );
+ m_vecPhysicsTargetStartPos = m_hPhysicsTarget->WorldSpaceCenter();
+
+ // Tell any squadmates I'm going for this item so they don't as well
+ if ( GetSquad() != NULL )
+ {
+ GetSquad()->BroadcastInteraction( g_interactionAntlionGuardFoundPhysicsObject, (void *)((CBaseEntity *)m_hPhysicsTarget), this );
+ }
+
+ return SCHED_ANTLIONGUARD_PHYSICS_ATTACK;
+ }
+
+ // Deal with a visible player
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ // Roar at the player as show of frustration
+ if ( m_flNextRoarTime < gpGlobals->curtime )
+ {
+ m_flNextRoarTime = gpGlobals->curtime + RandomFloat( 20,40 );
+ return SCHED_ANTLIONGUARD_ROAR;
+ }
+
+ // If we're under attack, then let's leave for a bit
+ if ( GetEnemy() && HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ Vector dir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
+ VectorNormalize(dir);
+
+ GetMotor()->SetIdealYaw( -dir );
+
+ return SCHED_ANTLIONGUARD_TAKE_COVER_FROM_ENEMY;
+ }
+ }
+
+ // If we're too far away, try to close distance to our target first
+ float flDistToEnemySqr = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
+ if ( flDistToEnemySqr > Square( 100*12 ) )
+ return SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE;
+
+ // Fire that we're unable to reach our target!
+ if ( GetEnemy() && GetEnemy()->IsPlayer() )
+ {
+ m_OnLostPlayer.FireOutput( this, this );
+ }
+
+ m_OnLostEnemy.FireOutput( this, this );
+ GetEnemies()->MarkAsEluded( GetEnemy() );
+
+ // Move randomly for the moment
+ return SCHED_ANTLIONGUARD_CANT_ATTACK;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_AntlionGuard::SelectCombatSchedule( void )
+{
+ ClearHintGroup();
+
+ /*
+ bool bCanCharge = false;
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ bCanCharge = ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false );
+ }
+ */
+
+ // Attack if we can
+ if ( HasCondition(COND_CAN_MELEE_ATTACK1) )
+ return SCHED_MELEE_ATTACK1;
+
+ // Otherwise, summon antlions
+ if ( HasCondition(COND_ANTLIONGUARD_CAN_SUMMON) )
+ {
+ // If I can charge, and have antlions, charge instead
+ if ( HasCondition( COND_ANTLIONGUARD_CAN_CHARGE ) && m_iNumLiveAntlions )
+ return SCHED_ANTLIONGUARD_CHARGE;
+
+ return SCHED_ANTLIONGUARD_SUMMON;
+ }
+
+ // See if we can bark
+ if ( HasCondition( COND_ENEMY_UNREACHABLE ) )
+ return SelectUnreachableSchedule();
+
+ //Physics target
+ if ( HasCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ) && !m_bInCavern )
+ return SCHED_ANTLIONGUARD_PHYSICS_ATTACK;
+
+ // If we've missed a couple of times, and we can summon, make it harder
+ if ( m_iChargeMisses >= 2 && CanSummon(true) )
+ {
+ m_iChargeMisses--;
+ return SCHED_ANTLIONGUARD_SUMMON;
+ }
+
+ // Charging
+ if ( HasCondition( COND_ANTLIONGUARD_CAN_CHARGE ) )
+ {
+ // Don't let other squad members charge while we're doing it
+ OccupyStrategySlot( SQUAD_SLOT_ANTLIONGUARD_CHARGE );
+ return SCHED_ANTLIONGUARD_CHARGE;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel )
+{
+ // Don't charge in tight spaces unless forced to
+ if ( hl2_episodic.GetBool() && m_bInCavern && !(m_hChargeTarget.Get() && m_hChargeTarget->IsAlive()) )
+ return false;
+
+ // Must have a target
+ if ( !GetEnemy() )
+ return false;
+
+ // No one else in the squad can be charging already
+ if ( IsStrategySlotRangeOccupied( SQUAD_SLOT_ANTLIONGUARD_CHARGE, SQUAD_SLOT_ANTLIONGUARD_CHARGE ) )
+ return false;
+
+ // Don't check the distance once we start charging
+ if ( !bCheckForCancel )
+ {
+ // Don't allow use to charge again if it's been too soon
+ if ( useTime && ( m_flChargeTime > gpGlobals->curtime ) )
+ return false;
+
+ float distance = UTIL_DistApprox2D( startPos, endPos );
+
+ // Must be within our tolerance range
+ if ( ( distance < ANTLIONGUARD_CHARGE_MIN ) || ( distance > ANTLIONGUARD_CHARGE_MAX ) )
+ return false;
+ }
+
+ if ( GetSquad() )
+ {
+ // If someone in our squad is closer to the enemy, then don't charge (we end up hitting them more often than not!)
+ float flOurDistToEnemySqr = ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).LengthSqr();
+ AISquadIter_t iter;
+ for ( CAI_BaseNPC *pSquadMember = GetSquad()->GetFirstMember( &iter ); pSquadMember; pSquadMember = GetSquad()->GetNextMember( &iter ) )
+ {
+ if ( pSquadMember->IsAlive() == false || pSquadMember == this )
+ continue;
+
+ if ( ( pSquadMember->GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).LengthSqr() < flOurDistToEnemySqr )
+ return false;
+ }
+ }
+
+ //FIXME: We'd like to exclude small physics objects from this check!
+
+ // We only need to hit the endpos with the edge of our bounding box
+ Vector vecDir = endPos - startPos;
+ VectorNormalize( vecDir );
+ float flWidth = WorldAlignSize().x * 0.5;
+ Vector vecTargetPos = endPos - (vecDir * flWidth);
+
+ // See if we can directly move there
+ AIMoveTrace_t moveTrace;
+ GetMoveProbe()->MoveLimit( NAV_GROUND, startPos, vecTargetPos, MASK_NPCSOLID_BRUSHONLY, GetEnemy(), &moveTrace );
+
+ // Draw the probe
+ if ( g_debug_antlionguard.GetInt() == 1 )
+ {
+ Vector enemyDir = (vecTargetPos - startPos);
+ float enemyDist = VectorNormalize( enemyDir );
+
+ NDebugOverlay::BoxDirection( startPos, GetHullMins(), GetHullMaxs() + Vector(enemyDist,0,0), enemyDir, 0, 255, 0, 8, 1.0f );
+ }
+
+ // If we're not blocked, charge
+ if ( IsMoveBlocked( moveTrace ) )
+ {
+ // Don't allow it if it's too close to us
+ if ( UTIL_DistApprox( WorldSpaceCenter(), moveTrace.vEndPosition ) < ANTLIONGUARD_CHARGE_MIN )
+ return false;
+
+ // Allow some special cases to not block us
+ if ( moveTrace.pObstruction != NULL )
+ {
+ // If we've hit the world, see if it's a cliff
+ if ( moveTrace.pObstruction == GetContainingEntity( INDEXENT(0) ) )
+ {
+ // Can't be too far above/below the target
+ if ( fabs( moveTrace.vEndPosition.z - vecTargetPos.z ) > StepHeight() )
+ return false;
+
+ // Allow it if we got pretty close
+ if ( UTIL_DistApprox( moveTrace.vEndPosition, vecTargetPos ) < 64 )
+ return true;
+ }
+
+ // Hit things that will take damage
+ if ( moveTrace.pObstruction->m_takedamage != DAMAGE_NO )
+ return true;
+
+ // Hit things that will move
+ if ( moveTrace.pObstruction->GetMoveType() == MOVETYPE_VPHYSICS )
+ return true;
+ }
+
+ return false;
+ }
+
+ // Only update this if we've requested it
+ if ( useTime )
+ {
+ m_flChargeTime = gpGlobals->curtime + 4.0f;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_AntlionGuard::SelectSchedule( void )
+{
+ // Don't do anything if we're burrowed
+ if ( m_bIsBurrowed )
+ return SCHED_IDLE_STAND;
+
+#if 0
+ // Debug physics object finding
+ if ( 0 ) //g_debug_antlionguard.GetInt() == 3 )
+ {
+ m_flPhysicsCheckTime = 0;
+ UpdatePhysicsTarget( false );
+ return SCHED_IDLE_STAND;
+ }
+#endif
+
+ // Flinch on heavy damage, but not if we've flinched too recently.
+ // This is done to prevent stun-locks from grenades.
+ if ( !m_bInCavern && HasCondition( COND_HEAVY_DAMAGE ) && m_flNextHeavyFlinchTime < gpGlobals->curtime )
+ {
+ m_flNextHeavyFlinchTime = gpGlobals->curtime + 8.0f;
+ return SCHED_ANTLIONGUARD_PHYSICS_DAMAGE_HEAVY;
+ }
+
+ // Prefer to use physics, in this case
+ if ( m_bPreferPhysicsAttack )
+ {
+ // If we have a target, try to go for it
+ if ( HasCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ) )
+ return SCHED_ANTLIONGUARD_PHYSICS_ATTACK;
+ }
+
+ // Charge after a target if it's set
+ if ( m_hChargeTarget && m_hChargeTargetPosition )
+ {
+ ClearCondition( COND_ANTLIONGUARD_HAS_CHARGE_TARGET );
+ ClearHintGroup();
+
+ if ( m_hChargeTarget->IsAlive() == false )
+ {
+ m_hChargeTarget = NULL;
+ m_hChargeTargetPosition = NULL;
+ SetEnemy( m_hOldTarget );
+
+ if ( m_hOldTarget == NULL )
+ {
+ m_NPCState = NPC_STATE_ALERT;
+ }
+ }
+ else
+ {
+ m_hOldTarget = GetEnemy();
+ SetEnemy( m_hChargeTarget );
+ UpdateEnemyMemory( m_hChargeTarget, m_hChargeTarget->GetAbsOrigin() );
+
+ //If we can't see the target, run to somewhere we can
+ if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), false, false ) == false )
+ return SCHED_ANTLIONGUARD_FIND_CHARGE_POSITION;
+
+ return SCHED_ANTLIONGUARD_CHARGE_TARGET;
+ }
+ }
+
+ // See if we need to clear a path to our enemy
+ if ( HasCondition( COND_ENEMY_OCCLUDED ) || HasCondition( COND_ENEMY_UNREACHABLE ) )
+ {
+ CBaseEntity *pBlocker = GetEnemyOccluder();
+
+ if ( ( pBlocker != NULL ) && FClassnameIs( pBlocker, "prop_physics" ) && !m_bInCavern )
+ {
+ m_hPhysicsTarget = pBlocker;
+ return SCHED_ANTLIONGUARD_PHYSICS_ATTACK;
+ }
+ }
+
+ //Only do these in combat states
+ if ( m_NPCState == NPC_STATE_COMBAT && GetEnemy() )
+ return SelectCombatSchedule();
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_AntlionGuard::MeleeAttack1Conditions( float flDot, float flDist )
+{
+ // Don't attack again too soon
+ if ( GetNextAttack() > gpGlobals->curtime )
+ return 0;
+
+ // While charging, we can't melee attack
+ if ( IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) )
+ return 0;
+
+ if ( hl2_episodic.GetBool() && m_bInCavern )
+ {
+ // Predict where they'll be and see if THAT is within range
+ Vector vecPredPos;
+ UTIL_PredictedPosition( GetEnemy(), 0.25f, &vecPredPos );
+ if ( ( GetAbsOrigin() - vecPredPos ).Length() > ANTLIONGUARD_MELEE1_RANGE )
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+ else
+ {
+ // Must be close enough
+ if ( flDist > ANTLIONGUARD_MELEE1_RANGE )
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ // Must be within a viable cone
+ if ( flDot < ANTLIONGUARD_MELEE1_CONE )
+ return COND_NOT_FACING_ATTACK;
+
+ // If the enemy is on top of me, I'm allowed to hit the sucker
+ if ( GetEnemy()->GetGroundEntity() == this )
+ return COND_CAN_MELEE_ATTACK1;
+
+ trace_t tr;
+ TraceHull_SkipPhysics( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), Vector(-10,-10,-10), Vector(10,10,10), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 );
+
+ // If we hit anything, go for it
+ if ( tr.fraction < 1.0f )
+ return COND_CAN_MELEE_ATTACK1;
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CNPC_AntlionGuard::MaxYawSpeed( void )
+{
+ Activity eActivity = GetActivity();
+
+ // Stay still
+ if (( eActivity == ACT_ANTLIONGUARD_SEARCH ) ||
+ ( eActivity == ACT_ANTLIONGUARD_PEEK_ENTER ) ||
+ ( eActivity == ACT_ANTLIONGUARD_PEEK_EXIT ) ||
+ ( eActivity == ACT_ANTLIONGUARD_PEEK1 ) ||
+ ( eActivity == ACT_ANTLIONGUARD_BARK ) ||
+ ( eActivity == ACT_ANTLIONGUARD_PEEK_SIGHTED ) ||
+ ( eActivity == ACT_MELEE_ATTACK1 ) )
+ return 0.0f;
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if ( pEnemy != NULL && pEnemy->IsPlayer() == false )
+ return 16.0f;
+
+ // Turn slowly when you're charging
+ if ( eActivity == ACT_ANTLIONGUARD_CHARGE_START )
+ return 4.0f;
+
+ if ( hl2_episodic.GetBool() && m_bInCavern )
+ {
+ // Allow a better turning rate when moving quickly but not charging the player
+ if ( ( eActivity == ACT_ANTLIONGUARD_CHARGE_RUN ) && IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) == false )
+ return 16.0f;
+ }
+
+ // Turn more slowly as we close in on our target
+ if ( eActivity == ACT_ANTLIONGUARD_CHARGE_RUN )
+ {
+ if ( pEnemy == NULL )
+ return 2.0f;
+
+ float dist = UTIL_DistApprox2D( GetEnemy()->WorldSpaceCenter(), WorldSpaceCenter() );
+
+ if ( dist > 512 )
+ return 16.0f;
+
+ //FIXME: Alter by skill level
+ float yawSpeed = RemapVal( dist, 0, 512, 1.0f, 2.0f );
+ yawSpeed = clamp( yawSpeed, 1.0f, 2.0f );
+
+ return yawSpeed;
+ }
+
+ if ( eActivity == ACT_ANTLIONGUARD_CHARGE_STOP )
+ return 8.0f;
+
+ switch( eActivity )
+ {
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ return 40.0f;
+ break;
+
+ case ACT_RUN:
+ default:
+ return 20.0f;
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flInterval -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::OverrideMove( float flInterval )
+{
+ // If the guard's charging, we're handling the movement
+ if ( IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) )
+ return true;
+
+ // TODO: This code increases the guard's ability to successfully hit a target, but adds a new dimension of navigation
+ // trouble to do with him not being able to "close the distance" between himself and the object he wants to hit.
+ // Fixing this will require some thought on how he picks the correct distances to his targets and when he's "close enough". -- jdw
+
+ /*
+ if ( m_hPhysicsTarget != NULL )
+ {
+ float flWidth = m_hPhysicsTarget->CollisionProp()->BoundingRadius2D();
+ GetLocalNavigator()->AddObstacle( m_hPhysicsTarget->WorldSpaceCenter(), flWidth * 0.75f, AIMST_AVOID_OBJECT );
+ //NDebugOverlay::Sphere( m_hPhysicsTarget->WorldSpaceCenter(), vec3_angle, flWidth, 255, 255, 255, 0, true, 0.5f );
+ }
+ */
+
+ return BaseClass::OverrideMove( flInterval );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::Shove( void )
+{
+ if ( GetNextAttack() > gpGlobals->curtime )
+ return;
+
+ CBaseEntity *pHurt = NULL;
+ CBaseEntity *pTarget;
+
+ pTarget = ( m_hShoveTarget == NULL ) ? GetEnemy() : m_hShoveTarget.Get();
+
+ if ( pTarget == NULL )
+ {
+ m_hShoveTarget = NULL;
+ return;
+ }
+
+ //Always damage bullseyes if we're scripted to hit them
+ if ( pTarget->Classify() == CLASS_BULLSEYE )
+ {
+ pTarget->TakeDamage( CTakeDamageInfo( this, this, 1.0f, DMG_CRUSH ) );
+ m_hShoveTarget = NULL;
+ return;
+ }
+
+ float damage = ( pTarget->IsPlayer() ) ? sk_antlionguard_dmg_shove.GetFloat() : 250;
+
+ // If the target's still inside the shove cone, ensure we hit him
+ Vector vecForward, vecEnd;
+ AngleVectors( GetAbsAngles(), &vecForward );
+ float flDistSqr = ( pTarget->WorldSpaceCenter() - WorldSpaceCenter() ).LengthSqr();
+ Vector2D v2LOS = ( pTarget->WorldSpaceCenter() - WorldSpaceCenter() ).AsVector2D();
+ Vector2DNormalize(v2LOS);
+ float flDot = DotProduct2D (v2LOS, vecForward.AsVector2D() );
+ if ( flDistSqr < (ANTLIONGUARD_MELEE1_RANGE*ANTLIONGUARD_MELEE1_RANGE) && flDot >= ANTLIONGUARD_MELEE1_CONE )
+ {
+ vecEnd = pTarget->WorldSpaceCenter();
+ }
+ else
+ {
+ vecEnd = WorldSpaceCenter() + ( BodyDirection3D() * ANTLIONGUARD_MELEE1_RANGE );
+ }
+
+ // Use the melee trace to ensure we hit everything there
+ trace_t tr;
+ CTakeDamageInfo dmgInfo( this, this, damage, DMG_SLASH );
+ CTraceFilterMelee traceFilter( this, COLLISION_GROUP_NONE, &dmgInfo, 1.0, true );
+ Ray_t ray;
+ ray.Init( WorldSpaceCenter(), vecEnd, Vector(-16,-16,-16), Vector(16,16,16) );
+ enginetrace->TraceRay( ray, MASK_SHOT_HULL, &traceFilter, &tr );
+ pHurt = tr.m_pEnt;
+
+ // Knock things around
+ ImpactShock( tr.endpos, 100.0f, 250.0f );
+
+ if ( pHurt )
+ {
+ Vector traceDir = ( tr.endpos - tr.startpos );
+ VectorNormalize( traceDir );
+
+ // Generate enough force to make a 75kg guy move away at 600 in/sec
+ Vector vecForce = traceDir * ImpulseScale( 75, 600 );
+ CTakeDamageInfo info( this, this, vecForce, tr.endpos, damage, DMG_CLUB );
+ pHurt->TakeDamage( info );
+
+ m_hShoveTarget = NULL;
+
+ EmitSound( "NPC_AntlionGuard.Shove" );
+
+ // If the player, throw him around
+ if ( pHurt->IsPlayer() )
+ {
+ //Punch the view
+ pHurt->ViewPunch( QAngle(20,0,-20) );
+
+ //Shake the screen
+ UTIL_ScreenShake( pHurt->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START );
+
+ //Red damage indicator
+ color32 red = {128,0,0,128};
+ UTIL_ScreenFade( pHurt, red, 1.0f, 0.1f, FFADE_IN );
+
+ Vector forward, up;
+ AngleVectors( GetLocalAngles(), &forward, NULL, &up );
+ pHurt->ApplyAbsVelocityImpulse( forward * 400 + up * 150 );
+
+ // in the episodes, the cavern guard poisons the player
+#if HL2_EPISODIC
+ // If I am a cavern guard attacking the player, and he still lives, then poison him too.
+ if ( m_bInCavern && pHurt->IsPlayer() && pHurt->IsAlive() && pHurt->m_iHealth > ANTLIONGUARD_POISON_TO)
+ {
+ // That didn't finish them. Take them down to one point with poison damage. It'll heal.
+ pHurt->TakeDamage( CTakeDamageInfo( this, this, pHurt->m_iHealth - ANTLIONGUARD_POISON_TO, DMG_POISON ) );
+ }
+#endif
+
+ }
+ else
+ {
+ CBaseCombatCharacter *pVictim = ToBaseCombatCharacter( pHurt );
+
+ if ( pVictim )
+ {
+ if ( NPC_Rollermine_IsRollermine( pVictim ) )
+ {
+ Pickup_OnPhysGunDrop( pVictim, NULL, LAUNCHED_BY_CANNON );
+ }
+
+ // FIXME: This causes NPCs that are not physically motivated to hop into the air strangely -- jdw
+ // pVictim->ApplyAbsVelocityImpulse( BodyDirection2D() * 400 + Vector( 0, 0, 250 ) );
+ }
+
+ m_hShoveTarget = NULL;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTraceFilterCharge : public CTraceFilterEntitiesOnly
+{
+public:
+ // It does have a base, but we'll never network anything below here..
+ DECLARE_CLASS_NOBASE( CTraceFilterCharge );
+
+ CTraceFilterCharge( const IHandleEntity *passentity, int collisionGroup, CNPC_AntlionGuard *pAttacker )
+ : m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_pAttacker(pAttacker)
+ {
+ }
+
+ virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+ {
+ if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
+ return false;
+
+ if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
+ return false;
+
+ // Don't test if the game code tells us we should ignore this collision...
+ CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
+
+ if ( pEntity )
+ {
+ if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) )
+ return false;
+
+ if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) )
+ return false;
+
+ if ( pEntity->m_takedamage == DAMAGE_NO )
+ return false;
+
+ // Translate the vehicle into its driver for damage
+ if ( pEntity->GetServerVehicle() != NULL )
+ {
+ CBaseEntity *pDriver = pEntity->GetServerVehicle()->GetPassenger();
+
+ if ( pDriver != NULL )
+ {
+ pEntity = pDriver;
+ }
+ }
+
+ Vector attackDir = pEntity->WorldSpaceCenter() - m_pAttacker->WorldSpaceCenter();
+ VectorNormalize( attackDir );
+
+ float flDamage = ( pEntity->IsPlayer() ) ? sk_antlionguard_dmg_shove.GetFloat() : 250;;
+
+ CTakeDamageInfo info( m_pAttacker, m_pAttacker, flDamage, DMG_CRUSH );
+ CalculateMeleeDamageForce( &info, attackDir, info.GetAttacker()->WorldSpaceCenter(), 4.0f );
+
+ CBaseCombatCharacter *pVictimBCC = pEntity->MyCombatCharacterPointer();
+
+ // Only do these comparisons between NPCs
+ if ( pVictimBCC )
+ {
+ // Can only damage other NPCs that we hate
+ if ( m_pAttacker->IRelationType( pEntity ) == D_HT )
+ {
+ pEntity->TakeDamage( info );
+ return true;
+ }
+ }
+ else
+ {
+ // Otherwise just damage passive objects in our way
+ pEntity->TakeDamage( info );
+ Pickup_ForcePlayerToDropThisObject( pEntity );
+ }
+ }
+
+ return false;
+ }
+
+public:
+ const IHandleEntity *m_pPassEnt;
+ int m_collisionGroup;
+ CNPC_AntlionGuard *m_pAttacker;
+};
+
+#define MIN_FOOTSTEP_NEAR_DIST Square( 80*12.0f )// ft
+
+//-----------------------------------------------------------------------------
+// Purpose: Plays a footstep sound with temporary distance fades
+// Input : bHeavy - Larger back hoof is considered a "heavy" step
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::Footstep( bool bHeavy )
+{
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ Assert( pPlayer != NULL );
+ if ( pPlayer == NULL )
+ return;
+
+ float flDistanceToPlayerSqr = ( pPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
+ float flNearVolume = RemapValClamped( flDistanceToPlayerSqr, Square(10*12.0f), MIN_FOOTSTEP_NEAR_DIST, VOL_NORM, 0.0f );
+
+ EmitSound_t soundParams;
+ CPASAttenuationFilter filter( this );
+
+ if ( bHeavy )
+ {
+ if ( flNearVolume > 0.0f )
+ {
+ soundParams.m_pSoundName = "NPC_AntlionGuard.NearStepHeavy";
+ soundParams.m_flVolume = flNearVolume;
+ soundParams.m_nFlags = SND_CHANGE_VOL;
+ EmitSound( filter, entindex(), soundParams );
+ }
+
+ EmitSound( "NPC_AntlionGuard.FarStepHeavy" );
+ }
+ else
+ {
+ if ( flNearVolume > 0.0f )
+ {
+ soundParams.m_pSoundName = "NPC_AntlionGuard.NearStepLight";
+ soundParams.m_flVolume = flNearVolume;
+ soundParams.m_nFlags = SND_CHANGE_VOL;
+ EmitSound( filter, entindex(), soundParams );
+ }
+
+ EmitSound( "NPC_AntlionGuard.FarStepLight" );
+ }
+}
+
+float GetCurrentGravity( void );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pObject -
+// *pOut -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::GetPhysicsShoveDir( CBaseEntity *pObject, float flMass, Vector *pOut )
+{
+ const Vector vecStart = pObject->WorldSpaceCenter();
+ const Vector vecTarget = GetEnemy()->WorldSpaceCenter();
+
+ const float flBaseSpeed = 800.0f;
+ float flSpeed = RemapValClamped( flMass, 0.0f, 150.0f, flBaseSpeed * 2.0f, flBaseSpeed );
+
+ // Try the most direct route
+ Vector vecToss = VecCheckThrow( this, vecStart, vecTarget, flSpeed, 1.0f );
+
+ // If this failed then try a little faster (flattens the arc)
+ if ( vecToss == vec3_origin )
+ {
+ vecToss = VecCheckThrow( this, vecStart, vecTarget, flSpeed * 1.25f, 1.0f );
+ if ( vecToss == vec3_origin )
+ {
+ const float flGravity = GetCurrentGravity();
+
+ vecToss = (vecTarget - vecStart);
+
+ // throw at a constant time
+ float time = vecToss.Length( ) / flSpeed;
+ vecToss = vecToss * (1.0f / time);
+
+ // adjust upward toss to compensate for gravity loss
+ vecToss.z += flGravity * time * 0.5f;
+ }
+ }
+
+ // Save out the result
+ if ( pOut )
+ {
+ *pOut = vecToss;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEvent -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::HandleAnimEvent( animevent_t *pEvent )
+{
+ if ( pEvent->event == AE_ANTLIONGUARD_CHARGE_EARLYOUT )
+ {
+ // Robin: Removed this because it usually made him look less intelligent, not more.
+ // This code left here so we don't get warnings in the console.
+
+ /*
+ // Cancel the charge if it's no longer valid
+ if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), false, true ) == false )
+ {
+ SetSchedule( SCHED_ANTLIONGUARD_CHARGE_CANCEL );
+ }
+ */
+
+ return;
+ }
+
+ // Don't handle anim events after death
+ if ( m_NPCState == NPC_STATE_DEAD )
+ {
+ BaseClass::HandleAnimEvent( pEvent );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_SHOVE_PHYSOBJECT )
+ {
+ if ( m_hPhysicsTarget == NULL )
+ {
+ // Disrupt other objects near us anyway
+ ImpactShock( WorldSpaceCenter(), 150, 250.0f );
+ return;
+ }
+
+ // If we have no enemy, we don't really have a direction to throw the object
+ // in. But, we still want to clobber it so the animevent doesn't happen fruitlessly,
+ // and we want to clear the condition flags and other state regarding the m_hPhysicsTarget.
+ // So, skip the code relevant to computing a launch vector specific to the object I'm
+ // striking, but use the ImpactShock call further below to punch everything in the neighborhood.
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( pEnemy != NULL )
+ {
+ //Setup the throw velocity
+ IPhysicsObject *physObj = m_hPhysicsTarget->VPhysicsGetObject();
+ Vector vecShoveVel = ( pEnemy->GetAbsOrigin() - m_hPhysicsTarget->WorldSpaceCenter() );
+ float flTargetDist = VectorNormalize( vecShoveVel );
+
+ // Must still be close enough to our target
+ Vector shoveDir = m_hPhysicsTarget->WorldSpaceCenter() - WorldSpaceCenter();
+ float shoveDist = VectorNormalize( shoveDir );
+ if ( shoveDist > 300.0f )
+ {
+ // Pick a new target next time (this one foiled us!)
+ RememberFailedPhysicsTarget( m_hPhysicsTarget );
+ m_hPhysicsTarget = NULL;
+ return;
+ }
+
+ // Toss this if we're episodic
+ if ( hl2_episodic.GetBool() )
+ {
+ Vector vecTargetDir = vecShoveVel;
+
+ // Get our shove direction
+ GetPhysicsShoveDir( m_hPhysicsTarget, physObj->GetMass(), &vecShoveVel );
+
+ // If the player isn't looking at me, and isn't reachable, be more forgiving about hitting them
+ if ( HasCondition( COND_ENEMY_UNREACHABLE ) && HasCondition( COND_ENEMY_FACING_ME ) == false )
+ {
+ // Build an arc around the top of the target that we'll offset our aim by
+ Vector vecOffset;
+ float flSin, flCos;
+ float flRad = random->RandomFloat( 0, M_PI / 6.0f ); // +/- 30 deg
+ if ( random->RandomInt( 0, 1 ) )
+ flRad *= -1.0f;
+
+ SinCos( flRad, &flSin, &flCos );
+
+ // Rotate the 2-d circle to be "facing" along our shot direction
+ Vector vecArc;
+ QAngle vecAngles;
+ VectorAngles( vecTargetDir, vecAngles );
+ VectorRotate( Vector( 0.0f, flCos, flSin ), vecAngles, vecArc );
+
+ // Find the radius by which to avoid the player
+ float flOffsetRadius = ( m_hPhysicsTarget->CollisionProp()->BoundingRadius() + GetEnemy()->CollisionProp()->BoundingRadius() ) * 1.5f;
+
+ // Add this to our velocity to offset it
+ vecShoveVel += ( vecArc * flOffsetRadius );
+ }
+
+ // Factor out mass
+ vecShoveVel *= physObj->GetMass();
+
+ // FIXME: We need to restore this on the object at some point if we do this!
+ float flDragCoef = 0.0f;
+ physObj->SetDragCoefficient( &flDragCoef, &flDragCoef );
+ }
+ else
+ {
+ if ( flTargetDist < 512 )
+ flTargetDist = 512;
+
+ if ( flTargetDist > 1024 )
+ flTargetDist = 1024;
+
+ vecShoveVel *= flTargetDist * 3 * physObj->GetMass(); //FIXME: Scale by skill
+ vecShoveVel[2] += physObj->GetMass() * 350.0f;
+ }
+
+ if ( NPC_Rollermine_IsRollermine( m_hPhysicsTarget ) )
+ {
+ Pickup_OnPhysGunDrop( m_hPhysicsTarget, NULL, LAUNCHED_BY_CANNON );
+ }
+
+ //Send it flying
+ AngularImpulse angVel( random->RandomFloat(-180, 180), 100, random->RandomFloat(-360, 360) );
+ physObj->ApplyForceCenter( vecShoveVel );
+ physObj->AddVelocity( NULL, &angVel );
+ }
+
+ //Display dust
+ Vector vecRandom = RandomVector( -1, 1);
+ VectorNormalize( vecRandom );
+ g_pEffects->Dust( m_hPhysicsTarget->WorldSpaceCenter(), vecRandom, 64.0f, 32 );
+
+ // If it's being held by the player, break that bond
+ Pickup_ForcePlayerToDropThisObject( m_hPhysicsTarget );
+
+ EmitSound( "NPC_AntlionGuard.HitHard" );
+
+ //Clear the state information, we're done
+ ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET );
+ ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID );
+
+ // Disrupt other objects near it, including the m_hPhysicsTarget if we had no valid enemy
+ ImpactShock( m_hPhysicsTarget->WorldSpaceCenter(), 150, 250.0f, pEnemy != NULL ? m_hPhysicsTarget : NULL );
+
+ // Notify any squad members that we're no longer monopolizing this object
+ if ( GetSquad() != NULL )
+ {
+ GetSquad()->BroadcastInteraction( g_interactionAntlionGuardShovedPhysicsObject, (void *)((CBaseEntity *)m_hPhysicsTarget), this );
+ }
+
+ m_hPhysicsTarget = NULL;
+ m_FailedPhysicsTargets.RemoveAll();
+
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_CHARGE_HIT )
+ {
+ UTIL_ScreenShake( GetAbsOrigin(), 32.0f, 4.0f, 1.0f, 512, SHAKE_START );
+ EmitSound( "NPC_AntlionGuard.HitHard" );
+
+ Vector startPos = GetAbsOrigin();
+ float checkSize = ( CollisionProp()->BoundingRadius() + 8.0f );
+ Vector endPos = startPos + ( BodyDirection3D() * checkSize );
+
+ CTraceFilterCharge traceFilter( this, COLLISION_GROUP_NONE, this );
+
+ Ray_t ray;
+ ray.Init( startPos, endPos, GetHullMins(), GetHullMaxs() );
+
+ trace_t tr;
+ enginetrace->TraceRay( ray, MASK_SHOT, &traceFilter, &tr );
+
+ if ( g_debug_antlionguard.GetInt() == 1 )
+ {
+ Vector hullMaxs = GetHullMaxs();
+ hullMaxs.x += checkSize;
+
+ NDebugOverlay::BoxDirection( startPos, GetHullMins(), hullMaxs, BodyDirection2D(), 100, 255, 255, 20, 1.0f );
+ }
+
+ //NDebugOverlay::Box3D( startPos, endPos, BodyDirection2D(),
+ if ( m_hChargeTarget && m_hChargeTarget->IsAlive() == false )
+ {
+ m_hChargeTarget = NULL;
+ m_hChargeTargetPosition = NULL;
+ }
+
+ // Cause a shock wave from this point which will distrupt nearby physics objects
+ ImpactShock( tr.endpos, 200, 500 );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_SHOVE )
+ {
+ EmitSound("NPC_AntlionGuard.StepLight", pEvent->eventtime );
+ Shove();
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_FOOTSTEP_LIGHT )
+ {
+ if ( HasSpawnFlags(SF_ANTLIONGUARD_INSIDE_FOOTSTEPS) )
+ {
+#if HL2_EPISODIC
+ Footstep( false );
+#else
+ EmitSound("NPC_AntlionGuard.Inside.StepLight", pEvent->eventtime );
+#endif // HL2_EPISODIC
+ }
+ else
+ {
+#if HL2_EPISODIC
+ Footstep( false );
+#else
+ EmitSound("NPC_AntlionGuard.StepLight", pEvent->eventtime );
+#endif // HL2_EPISODIC
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_FOOTSTEP_HEAVY )
+ {
+ if ( HasSpawnFlags(SF_ANTLIONGUARD_INSIDE_FOOTSTEPS) )
+ {
+#if HL2_EPISODIC
+ Footstep( true );
+#else
+ EmitSound( "NPC_AntlionGuard.Inside.StepHeavy", pEvent->eventtime );
+#endif // HL2_EPISODIC
+ }
+ else
+ {
+#if HL2_EPISODIC
+ Footstep( true );
+#else
+ EmitSound( "NPC_AntlionGuard.StepHeavy", pEvent->eventtime );
+#endif // HL2_EPISODIC
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_VOICE_GROWL )
+ {
+ StartSounds();
+
+ float duration = 0.0f;
+
+ if ( random->RandomInt( 0, 10 ) < 6 )
+ {
+ duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardFastGrowl, ARRAYSIZE(envAntlionGuardFastGrowl) );
+ }
+ else
+ {
+ duration = 1.0f;
+ EmitSound( "NPC_AntlionGuard.FrustratedRoar" );
+ ENVELOPE_CONTROLLER.SoundFadeOut( m_pGrowlHighSound, 0.5f, false );
+ }
+
+ m_flAngerNoiseTime = gpGlobals->curtime + duration + random->RandomFloat( 2.0f, 4.0f );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, 0.1f );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f );
+
+ m_flBreathTime = gpGlobals->curtime + duration - 0.2f;
+
+ EmitSound( "NPC_AntlionGuard.Anger" );
+ return;
+ }
+
+
+ if ( pEvent->event == AE_ANTLIONGUARD_VOICE_BARK )
+ {
+ StartSounds();
+
+ float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardBark1, ARRAYSIZE(envAntlionGuardBark1) );
+ ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pConfusedSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardBark2, ARRAYSIZE(envAntlionGuardBark2) );
+
+ m_flAngerNoiseTime = gpGlobals->curtime + duration + random->RandomFloat( 2.0f, 4.0f );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, 0.1f );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f );
+
+ m_flBreathTime = gpGlobals->curtime + duration - 0.2f;
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_VOICE_ROAR )
+ {
+ StartSounds();
+
+ float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardFastGrowl, ARRAYSIZE(envAntlionGuardFastGrowl) );
+
+ m_flAngerNoiseTime = gpGlobals->curtime + duration + random->RandomFloat( 2.0f, 4.0f );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, 0.1f );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f );
+
+ m_flBreathTime = gpGlobals->curtime + duration - 0.2f;
+
+ EmitSound( "NPC_AntlionGuard.Roar" );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_VOICE_PAIN )
+ {
+ StartSounds();
+
+ float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pConfusedSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardPain1, ARRAYSIZE(envAntlionGuardPain1) );
+ ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardBark2, ARRAYSIZE(envAntlionGuardBark2) );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, 0.1f );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f );
+
+ m_flBreathTime = gpGlobals->curtime + duration - 0.2f;
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_VOICE_SQUEEZE )
+ {
+ StartSounds();
+
+ float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardSqueeze, ARRAYSIZE(envAntlionGuardSqueeze) );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.6f, random->RandomFloat( 2.0f, 4.0f ) );
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pBreathSound, random->RandomInt( 60, 80 ), random->RandomFloat( 2.0f, 4.0f ) );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f );
+
+ m_flBreathTime = gpGlobals->curtime + ( duration * 0.5f );
+
+ EmitSound( "NPC_AntlionGuard.Anger" );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_VOICE_SCRATCH )
+ {
+ StartSounds();
+
+ float duration = random->RandomFloat( 2.0f, 4.0f );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.6f, duration );
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pBreathSound, random->RandomInt( 60, 80 ), duration );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f );
+
+ m_flBreathTime = gpGlobals->curtime + duration;
+
+ EmitSound( "NPC_AntlionGuard.Anger" );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_VOICE_GRUNT )
+ {
+ StartSounds();
+
+ float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pConfusedSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardGrunt, ARRAYSIZE(envAntlionGuardGrunt) );
+ ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardGrunt2, ARRAYSIZE(envAntlionGuardGrunt2) );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, 0.1f );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f );
+
+ m_flBreathTime = gpGlobals->curtime + duration;
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLIONGUARD_BURROW_OUT )
+ {
+ EmitSound( "NPC_Antlion.BurrowOut" );
+
+ //Shake the screen
+ UTIL_ScreenShake( GetAbsOrigin(), 0.5f, 80.0f, 1.0f, 256.0f, SHAKE_START );
+
+ //Throw dust up
+ UTIL_CreateAntlionDust( GetAbsOrigin() + Vector(0,0,24), GetLocalAngles() );
+
+ RemoveEffects( EF_NODRAW );
+ RemoveFlag( FL_NOTARGET );
+
+ if ( m_bCavernBreed )
+ {
+ if ( m_hCaveGlow[0] )
+ m_hCaveGlow[0]->TurnOn();
+
+ if ( m_hCaveGlow[1] )
+ m_hCaveGlow[1]->TurnOn();
+ }
+
+ return;
+ }
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::SetHeavyDamageAnim( const Vector &vecSource )
+{
+ if ( !m_bInCavern )
+ {
+ Vector vFacing = BodyDirection2D();
+ Vector vDamageDir = ( vecSource - WorldSpaceCenter() );
+
+ vDamageDir.z = 0.0f;
+
+ VectorNormalize( vDamageDir );
+
+ Vector vDamageRight, vDamageUp;
+ VectorVectors( vDamageDir, vDamageRight, vDamageUp );
+
+ float damageDot = DotProduct( vFacing, vDamageDir );
+ float damageRightDot = DotProduct( vFacing, vDamageRight );
+
+ // See if it's in front
+ if ( damageDot > 0.0f )
+ {
+ // See if it's right
+ if ( damageRightDot > 0.0f )
+ {
+ m_nFlinchActivity = ACT_ANTLIONGUARD_PHYSHIT_FR;
+ }
+ else
+ {
+ m_nFlinchActivity = ACT_ANTLIONGUARD_PHYSHIT_FL;
+ }
+ }
+ else
+ {
+ // Otherwise it's from behind
+ if ( damageRightDot < 0.0f )
+ {
+ m_nFlinchActivity = ACT_ANTLIONGUARD_PHYSHIT_RR;
+ }
+ else
+ {
+ m_nFlinchActivity = ACT_ANTLIONGUARD_PHYSHIT_RL;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+//-----------------------------------------------------------------------------
+int CNPC_AntlionGuard::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo dInfo = info;
+
+ // Don't take damage from another antlion guard!
+ if ( dInfo.GetAttacker() && dInfo.GetAttacker() != this && FClassnameIs( dInfo.GetAttacker(), "npc_antlionguard" ) )
+ return 0;
+
+ if ( ( dInfo.GetDamageType() & DMG_CRUSH ) && !( dInfo.GetDamageType() & DMG_VEHICLE ) )
+ {
+ // Don't take damage from physics objects that weren't thrown by the player.
+ CBaseEntity *pInflictor = dInfo.GetInflictor();
+
+ IPhysicsObject *pObj = pInflictor->VPhysicsGetObject();
+ if ( !pObj || !( pObj->GetGameFlags() & FVPHYSICS_WAS_THROWN ) )
+ {
+ return 0;
+ }
+ }
+
+ // Hack to make antlion guard harder in HARD
+ if ( g_pGameRules->IsSkillLevel(SKILL_HARD) && !(info.GetDamageType() & DMG_CRUSH) )
+ {
+ dInfo.SetDamage( dInfo.GetDamage() * 0.75 );
+ }
+
+ // Cap damage taken by crushing (otherwise we can get crushed oddly)
+ if ( ( dInfo.GetDamageType() & DMG_CRUSH ) && dInfo.GetDamage() > 100 )
+ {
+ dInfo.SetDamage( 100 );
+ }
+
+ // Only take damage from what we classify as heavy damages (explosions, refrigerators, etc)
+ if ( IsHeavyDamage( dInfo ) )
+ {
+ // Always take a set amount of damage from a combine ball
+ if ( info.GetInflictor() && UTIL_IsCombineBall( info.GetInflictor() ) )
+ {
+ dInfo.SetDamage( 50 );
+ }
+
+ UTIL_ScreenShake( GetAbsOrigin(), 32.0f, 8.0f, 0.5f, 512, SHAKE_START );
+
+ // Set our response animation
+ SetHeavyDamageAnim( dInfo.GetDamagePosition() );
+
+ // Explosive barrels don't carry through their attacker, so this
+ // condition isn't set, and we still want to flinch. So we set it.
+ SetCondition( COND_HEAVY_DAMAGE );
+
+ // TODO: Make this its own effect!
+ CEffectData data;
+ data.m_vOrigin = dInfo.GetDamagePosition();
+ data.m_vNormal = -dInfo.GetDamageForce();
+ VectorNormalize( data.m_vNormal );
+ DispatchEffect( "HunterDamage", data );
+
+ // Play a sound for a physics impact
+ if ( dInfo.GetDamageType() & DMG_CRUSH )
+ {
+ EmitSound("NPC_AntlionGuard.ShellCrack");
+ }
+
+ // Roar in pain
+ EmitSound( "NPC_AntlionGuard.Pain_Roar" );
+
+ // TODO: This will require a more complete solution; for now let's shelve it!
+ /*
+ if ( dInfo.GetDamageType() & DMG_BLAST )
+ {
+ // Create the dust effect in place
+ CParticleSystem *pParticle = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
+ if ( pParticle != NULL )
+ {
+ // Setup our basic parameters
+ pParticle->KeyValue( "start_active", "1" );
+ pParticle->KeyValue( "effect_name", "fire_medium_02_nosmoke" );
+ pParticle->SetParent( this );
+ pParticle->SetParentAttachment( "SetParentAttachment", "attach_glow2", true );
+ pParticle->SetLocalOrigin( Vector( -16, 24, 0 ) );
+ DispatchSpawn( pParticle );
+ if ( gpGlobals->curtime > 0.5f )
+ pParticle->Activate();
+
+ pParticle->SetThink( &CBaseEntity::SUB_Remove );
+ pParticle->SetNextThink( gpGlobals->curtime + random->RandomFloat( 2.0f, 3.0f ) );
+ }
+ }
+ */
+ }
+
+ int nPreHealth = GetHealth();
+ int nDamageTaken = BaseClass::OnTakeDamage_Alive( dInfo );
+
+ // See if we've crossed a measured phase in our health and flinch from that to show we do take damage
+ if ( !m_bInCavern && HasCondition( COND_HEAVY_DAMAGE ) == false && ( info.GetDamageType() & DMG_BULLET ) )
+ {
+ bool bTakeHeavyDamage = false;
+
+ // Do an early flinch so that players understand the guard can be hurt by
+ if ( ( (float) GetHealth() / (float) GetMaxHealth() ) > 0.9f )
+ {
+ float flPrePerc = (float) nPreHealth / (float) GetMaxHealth();
+ float flPostPerc = (float) GetHealth() / (float) GetMaxHealth();
+ if ( flPrePerc >= 0.95f && flPostPerc < 0.95f )
+ {
+ bTakeHeavyDamage = true;
+ }
+ }
+
+ // Otherwise see if we've passed a measured point in our health
+ if ( bTakeHeavyDamage == false )
+ {
+ const float flNumDamagePhases = 5.0f;
+
+ float flDenom = ( (float) GetMaxHealth() / flNumDamagePhases );
+ int nPreDamagePhase = ceil( (float) nPreHealth / flDenom );
+ int nPostDamagePhase = ceil( (float) GetHealth() / flDenom );
+ if ( nPreDamagePhase != nPostDamagePhase )
+ {
+ bTakeHeavyDamage = true;
+ }
+ }
+
+ // Flinch if we should
+ if ( bTakeHeavyDamage )
+ {
+ // Set our response animation
+ SetHeavyDamageAnim( dInfo.GetDamagePosition() );
+
+ // Roar in pain
+ EmitSound( "NPC_AntlionGuard.Pain_Roar" );
+
+ // Flinch!
+ SetCondition( COND_HEAVY_DAMAGE );
+ }
+ }
+
+ return nDamageTaken;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pAttacker -
+// flDamage -
+// &vecDir -
+// *ptr -
+// bitsDamageType -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ // Bullets are weak against us, buckshot less so
+ if ( info.GetDamageType() & DMG_BUCKSHOT )
+ {
+ info.ScaleDamage( 0.5f );
+ }
+ else if ( info.GetDamageType() & DMG_BULLET )
+ {
+ info.ScaleDamage( 0.25f );
+ }
+
+ // Make sure we haven't rounded down to a minimal amount
+ if ( info.GetDamage() < 1.0f )
+ {
+ info.SetDamage( 1.0f );
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_ANTLIONGUARD_SET_FLINCH_ACTIVITY:
+ SetIdealActivity( (Activity) m_nFlinchActivity );
+ break;
+
+ case TASK_ANTLIONGUARD_SUMMON:
+ SummonAntlions();
+ m_OnSummon.FireOutput( this, this, 0 );
+ TaskComplete();
+ break;
+
+ case TASK_ANTLIONGUARD_SHOVE_PHYSOBJECT:
+ {
+ if ( ( m_hPhysicsTarget == NULL ) || ( GetEnemy() == NULL ) )
+ {
+ TaskFail( "Tried to shove a NULL physics target!\n" );
+ break;
+ }
+
+ //Get the direction and distance to our thrown object
+ Vector dirToTarget = ( m_hPhysicsTarget->WorldSpaceCenter() - WorldSpaceCenter() );
+ float distToTarget = VectorNormalize( dirToTarget );
+ dirToTarget.z = 0;
+
+ //Validate it's still close enough to shove
+ //FIXME: Real numbers
+ if ( distToTarget > 256.0f )
+ {
+ RememberFailedPhysicsTarget( m_hPhysicsTarget );
+ m_hPhysicsTarget = NULL;
+
+ TaskFail( "Shove target moved\n" );
+ break;
+ }
+
+ //Validate its offset from our facing
+ float targetYaw = UTIL_VecToYaw( dirToTarget );
+ float offset = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( GetLocalAngles().y ) );
+
+ if ( fabs( offset ) > 55 )
+ {
+ RememberFailedPhysicsTarget( m_hPhysicsTarget );
+ m_hPhysicsTarget = NULL;
+
+ TaskFail( "Shove target off-center\n" );
+ break;
+ }
+
+ //Blend properly
+ SetPoseParameter( m_poseThrow, offset );
+
+ //Start playing the animation
+ SetActivity( ACT_ANTLIONGUARD_SHOVE_PHYSOBJECT );
+ }
+ break;
+
+ case TASK_ANTLIONGUARD_FIND_PHYSOBJECT:
+ {
+ if ( m_bInCavern && !m_bPreferPhysicsAttack )
+ {
+ TaskFail( "Cavern guard is not allowed to use physics attacks." );
+ }
+
+ // Force the antlion guard to find a physobject
+ m_flPhysicsCheckTime = 0;
+ UpdatePhysicsTarget( false, (100*12) );
+ if ( m_hPhysicsTarget )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail( "Failed to find a physobject.\n" );
+ }
+ }
+ break;
+
+ case TASK_ANTLIONGUARD_GET_PATH_TO_PHYSOBJECT:
+ {
+ if ( ( m_hPhysicsTarget == NULL ) || ( GetEnemy() == NULL ) )
+ {
+ TaskFail( "Tried to find a path to NULL physics target!\n" );
+ break;
+ }
+
+ Vector vecGoalPos = m_vecPhysicsHitPosition;
+ AI_NavGoal_t goal( GOALTYPE_LOCATION, vecGoalPos, ACT_RUN );
+
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ if ( g_debug_antlionguard.GetInt() == 1 )
+ {
+ NDebugOverlay::Cross3D( vecGoalPos, Vector(8,8,8), -Vector(8,8,8), 0, 255, 0, true, 2.0f );
+ NDebugOverlay::Line( vecGoalPos, m_hPhysicsTarget->WorldSpaceCenter(), 0, 255, 0, true, 2.0f );
+ NDebugOverlay::Line( m_hPhysicsTarget->WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), 0, 255, 0, true, 2.0f );
+ }
+
+ // Face the enemy
+ GetNavigator()->SetArrivalDirection( GetEnemy() );
+ TaskComplete();
+ }
+ else
+ {
+ if ( g_debug_antlionguard.GetInt() == 1 )
+ {
+ NDebugOverlay::Cross3D( vecGoalPos, Vector(8,8,8), -Vector(8,8,8), 255, 0, 0, true, 2.0f );
+ NDebugOverlay::Line( vecGoalPos, m_hPhysicsTarget->WorldSpaceCenter(), 255, 0, 0, true, 2.0f );
+ NDebugOverlay::Line( m_hPhysicsTarget->WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), 255, 0, 0, true, 2.0f );
+ }
+
+ RememberFailedPhysicsTarget( m_hPhysicsTarget );
+ m_hPhysicsTarget = NULL;
+ TaskFail( "Unable to navigate to physics attack target!\n" );
+ break;
+ }
+
+ //!!!HACKHACK - this is a hack that covers a bug in antlion guard physics target selection! (Tracker #77601)
+ // This piece of code (combined with some code in GatherConditions) COVERS THE BUG by escaping the schedule
+ // if 30 seconds have passed (it should never take this long for the guard to get to an object and hit it).
+ // It's too scary to figure out why this particular antlion guard can't get to its object, but we're shipping
+ // like, tomorrow. (sjb) 8/22/2007
+ m_flWaitFinished = gpGlobals->curtime + 30.0f;
+ }
+ break;
+
+ case TASK_ANTLIONGUARD_CHARGE:
+ {
+ // HACK: Because the guard stops running his normal blended movement
+ // here, he also needs to remove his blended movement layers!
+ GetMotor()->MoveStop();
+
+ SetActivity( ACT_ANTLIONGUARD_CHARGE_START );
+ m_bDecidedNotToStop = false;
+ }
+ break;
+
+
+ case TASK_ANTLIONGUARD_GET_PATH_TO_CHARGE_POSITION:
+ {
+ if ( !m_hChargeTargetPosition )
+ {
+ TaskFail( "Tried to find a charge position without one specified.\n" );
+ break;
+ }
+
+ // Move to the charge position
+ AI_NavGoal_t goal( GOALTYPE_LOCATION, m_hChargeTargetPosition->GetAbsOrigin(), ACT_RUN );
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ // We want to face towards the charge target
+ Vector vecDir = m_hChargeTarget->GetAbsOrigin() - m_hChargeTargetPosition->GetAbsOrigin();
+ VectorNormalize( vecDir );
+ vecDir.z = 0;
+ GetNavigator()->SetArrivalDirection( vecDir );
+ TaskComplete();
+ }
+ else
+ {
+ m_hChargeTarget = NULL;
+ m_hChargeTargetPosition = NULL;
+ TaskFail( FAIL_NO_ROUTE );
+ }
+ }
+ break;
+
+ case TASK_ANTLIONGUARD_GET_PATH_TO_NEAREST_NODE:
+ {
+ if ( !GetEnemy() )
+ {
+ TaskFail( FAIL_NO_ENEMY );
+ break;
+ }
+
+ // Find the nearest node to the enemy
+ int node = GetNavigator()->GetNetwork()->NearestNodeToPoint( this, GetEnemy()->GetAbsOrigin(), false );
+ CAI_Node *pNode = GetNavigator()->GetNetwork()->GetNode( node );
+ if( pNode == NULL )
+ {
+ TaskFail( FAIL_NO_ROUTE );
+ break;
+ }
+
+ Vector vecNodePos = pNode->GetPosition( GetHullType() );
+ AI_NavGoal_t goal( GOALTYPE_LOCATION, vecNodePos, ACT_RUN );
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ GetNavigator()->SetArrivalDirection( GetEnemy() );
+ TaskComplete();
+ break;
+ }
+
+
+ TaskFail( FAIL_NO_ROUTE );
+ break;
+ }
+ break;
+
+ case TASK_ANTLIONGUARD_GET_CHASE_PATH_ENEMY_TOLERANCE:
+ {
+ // Chase the enemy, but allow local navigation to succeed if it gets within the goal tolerance
+ GetNavigator()->SetLocalSucceedOnWithinTolerance( true );
+
+ if ( GetNavigator()->SetGoal( GOALTYPE_ENEMY ) )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ RememberUnreachable(GetEnemy());
+ TaskFail(FAIL_NO_ROUTE);
+ }
+
+ GetNavigator()->SetLocalSucceedOnWithinTolerance( false );
+ }
+ break;
+
+ case TASK_ANTLIONGUARD_OPPORTUNITY_THROW:
+ {
+ // If we've got some live antlions, look for a physics object to throw
+ if ( m_iNumLiveAntlions >= 2 && RandomFloat(0,1) > 0.5 )
+ {
+ m_FailedPhysicsTargets.RemoveAll();
+ UpdatePhysicsTarget( false, m_bPreferPhysicsAttack ? (100*12) : ANTLIONGUARD_FARTHEST_PHYSICS_OBJECT );
+ if ( HasCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ) && !m_bInCavern )
+ {
+ SetSchedule( SCHED_ANTLIONGUARD_PHYSICS_ATTACK );
+ }
+ }
+
+ TaskComplete();
+ }
+ break;
+
+ default:
+ BaseClass::StartTask(pTask);
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculate & apply damage & force for a charge to a target.
+// Done outside of the guard because we need to do this inside a trace filter.
+//-----------------------------------------------------------------------------
+void ApplyChargeDamage( CBaseEntity *pAntlionGuard, CBaseEntity *pTarget, float flDamage )
+{
+ Vector attackDir = ( pTarget->WorldSpaceCenter() - pAntlionGuard->WorldSpaceCenter() );
+ VectorNormalize( attackDir );
+ Vector offset = RandomVector( -32, 32 ) + pTarget->WorldSpaceCenter();
+
+ // Generate enough force to make a 75kg guy move away at 700 in/sec
+ Vector vecForce = attackDir * ImpulseScale( 75, 700 );
+
+ // Deal the damage
+ CTakeDamageInfo info( pAntlionGuard, pAntlionGuard, vecForce, offset, flDamage, DMG_CLUB );
+ pTarget->TakeDamage( info );
+
+#if HL2_EPISODIC
+ // If I am a cavern guard attacking the player, and he still lives, then poison him too.
+ Assert( dynamic_cast<CNPC_AntlionGuard *>(pAntlionGuard) );
+
+ if ( static_cast<CNPC_AntlionGuard *>(pAntlionGuard)->IsInCavern() && pTarget->IsPlayer() && pTarget->IsAlive() && pTarget->m_iHealth > ANTLIONGUARD_POISON_TO)
+ {
+ // That didn't finish them. Take them down to one point with poison damage. It'll heal.
+ pTarget->TakeDamage( CTakeDamageInfo( pAntlionGuard, pAntlionGuard, pTarget->m_iHealth - ANTLIONGUARD_POISON_TO, DMG_POISON ) );
+ }
+#endif
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: A simple trace filter class to skip small moveable physics objects
+//-----------------------------------------------------------------------------
+class CTraceFilterSkipPhysics : public CTraceFilter
+{
+public:
+ // It does have a base, but we'll never network anything below here..
+ DECLARE_CLASS_NOBASE( CTraceFilterSkipPhysics );
+
+ CTraceFilterSkipPhysics( const IHandleEntity *passentity, int collisionGroup, float minMass )
+ : m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_minMass(minMass)
+ {
+ }
+ virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+ {
+ if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
+ return false;
+
+ if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
+ return false;
+
+ // Don't test if the game code tells us we should ignore this collision...
+ CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
+ if ( pEntity )
+ {
+ if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) )
+ return false;
+
+ if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) )
+ return false;
+
+ // don't test small moveable physics objects (unless it's an NPC)
+ if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
+ Assert(pPhysics);
+ if ( pPhysics->IsMoveable() && pPhysics->GetMass() < m_minMass )
+ return false;
+ }
+
+ // If we hit an antlion, don't stop, but kill it
+ if ( pEntity->Classify() == CLASS_ANTLION )
+ {
+ CBaseEntity *pGuard = (CBaseEntity*)EntityFromEntityHandle( m_pPassEnt );
+ ApplyChargeDamage( pGuard, pEntity, pEntity->GetHealth() );
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+
+private:
+ const IHandleEntity *m_pPassEnt;
+ int m_collisionGroup;
+ float m_minMass;
+};
+
+inline void TraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin,
+ const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore,
+ int collisionGroup, trace_t *ptr, float minMass )
+{
+ Ray_t ray;
+ ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax );
+ CTraceFilterSkipPhysics traceFilter( ignore, collisionGroup, minMass );
+ enginetrace->TraceRay( ray, mask, &traceFilter, ptr );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if our charge target is right in front of the guard
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::EnemyIsRightInFrontOfMe( CBaseEntity **pEntity )
+{
+ if ( !GetEnemy() )
+ return false;
+
+ if ( (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() < (156*156) )
+ {
+ Vector vecLOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ vecLOS.z = 0;
+ VectorNormalize( vecLOS );
+ Vector vBodyDir = BodyDirection2D();
+ if ( DotProduct( vecLOS, vBodyDir ) > 0.8 )
+ {
+ // He's in front of me, and close. Make sure he's not behind a wall.
+ trace_t tr;
+ UTIL_TraceLine( WorldSpaceCenter(), GetEnemy()->EyePosition(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.m_pEnt == GetEnemy() )
+ {
+ *pEntity = tr.m_pEnt;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: While charging, look ahead and see if we're going to run into anything.
+// If we are, start the gesture so it looks like we're anticipating the hit.
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::ChargeLookAhead( void )
+{
+ trace_t tr;
+ Vector vecForward;
+ GetVectors( &vecForward, NULL, NULL );
+ Vector vecTestPos = GetAbsOrigin() + ( vecForward * m_flGroundSpeed * 0.75 );
+ Vector testHullMins = GetHullMins();
+ testHullMins.z += (StepHeight() * 2);
+ TraceHull_SkipPhysics( GetAbsOrigin(), vecTestPos, testHullMins, GetHullMaxs(), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 );
+
+ //NDebugOverlay::Box( tr.startpos, testHullMins, GetHullMaxs(), 0, 255, 0, true, 0.1f );
+ //NDebugOverlay::Box( vecTestPos, testHullMins, GetHullMaxs(), 255, 0, 0, true, 0.1f );
+
+ if ( tr.fraction != 1.0 )
+ {
+ // Start playing the hit animation
+ AddGesture( ACT_ANTLIONGUARD_CHARGE_ANTICIPATION );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles the guard charging into something. Returns true if it hit the world.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity )
+{
+ // Cause a shock wave from this point which will disrupt nearby physics objects
+ ImpactShock( vecImpact, 128, 350 );
+
+ // Did we hit anything interesting?
+ if ( !pEntity || pEntity->IsWorld() )
+ {
+ // Robin: Due to some of the finicky details in the motor, the guard will hit
+ // the world when it is blocked by our enemy when trying to step up
+ // during a moveprobe. To get around this, we see if the enemy's within
+ // a volume in front of the guard when we hit the world, and if he is,
+ // we hit him anyway.
+ EnemyIsRightInFrontOfMe( &pEntity );
+
+ // Did we manage to find him? If not, increment our charge miss count and abort.
+ if ( pEntity->IsWorld() )
+ {
+ m_iChargeMisses++;
+ return true;
+ }
+ }
+
+ // Hit anything we don't like
+ if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) )
+ {
+ EmitSound( "NPC_AntlionGuard.Shove" );
+
+ if ( !IsPlayingGesture( ACT_ANTLIONGUARD_CHARGE_HIT ) )
+ {
+ RestartGesture( ACT_ANTLIONGUARD_CHARGE_HIT );
+ }
+
+ ChargeDamage( pEntity );
+
+ pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 200 ) );
+
+ if ( !pEntity->IsAlive() && GetEnemy() == pEntity )
+ {
+ SetEnemy( NULL );
+ }
+
+ SetNextAttack( gpGlobals->curtime + 2.0f );
+ SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP );
+
+ // We've hit something, so clear our miss count
+ m_iChargeMisses = 0;
+ return false;
+ }
+
+ // Hit something we don't hate. If it's not moveable, crash into it.
+ if ( pEntity->GetMoveType() == MOVETYPE_NONE || pEntity->GetMoveType() == MOVETYPE_PUSH )
+ return true;
+
+ // If it's a vphysics object that's too heavy, crash into it too.
+ if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
+ if ( pPhysics )
+ {
+ // If the object is being held by the player, knock it out of his hands
+ if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
+ {
+ Pickup_ForcePlayerToDropThisObject( pEntity );
+ return false;
+ }
+
+ if ( (!pPhysics->IsMoveable() || pPhysics->GetMass() > VPhysicsGetObject()->GetMass() * 0.5f ) )
+ return true;
+ }
+ }
+
+ return false;
+
+ /*
+
+ ROBIN: Wrote & then removed this. If we want to have large rocks that the guard
+ should smack around, then we should enable it.
+
+ else
+ {
+ // If we hit a physics prop, smack the crap out of it. (large rocks)
+ // Factor the object mass into it, because we want to move it no matter how heavy it is.
+ if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ CTakeDamageInfo info( this, this, 250, DMG_BLAST );
+ info.SetDamagePosition( vecImpact );
+ float flForce = ImpulseScale( pEntity->VPhysicsGetObject()->GetMass(), 250 );
+ flForce *= random->RandomFloat( 0.85, 1.15 );
+
+ // Calculate the vector and stuff it into the takedamageinfo
+ Vector vecForce = BodyDirection3D();
+ VectorNormalize( vecForce );
+ vecForce *= flForce;
+ vecForce *= phys_pushscale.GetFloat();
+ info.SetDamageForce( vecForce );
+
+ pEntity->VPhysicsTakeDamage( info );
+ }
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float CNPC_AntlionGuard::ChargeSteer( void )
+{
+ trace_t tr;
+ Vector testPos, steer, forward, right;
+ QAngle angles;
+ const float testLength = m_flGroundSpeed * 0.15f;
+
+ //Get our facing
+ GetVectors( &forward, &right, NULL );
+
+ steer = forward;
+
+ const float faceYaw = UTIL_VecToYaw( forward );
+
+ //Offset right
+ VectorAngles( forward, angles );
+ angles[YAW] += 45.0f;
+ AngleVectors( angles, &forward );
+
+ //Probe out
+ testPos = GetAbsOrigin() + ( forward * testLength );
+
+ //Offset by step height
+ Vector testHullMins = GetHullMins();
+ testHullMins.z += (StepHeight() * 2);
+
+ //Probe
+ TraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
+
+ //Debug info
+ if ( g_debug_antlionguard.GetInt() == 1 )
+ {
+ if ( tr.fraction == 1.0f )
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
+ }
+ else
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
+ }
+ }
+
+ //Add in this component
+ steer += ( right * 0.5f ) * ( 1.0f - tr.fraction );
+
+ //Offset left
+ angles[YAW] -= 90.0f;
+ AngleVectors( angles, &forward );
+
+ //Probe out
+ testPos = GetAbsOrigin() + ( forward * testLength );
+
+ // Probe
+ TraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
+
+ //Debug
+ if ( g_debug_antlionguard.GetInt() == 1 )
+ {
+ if ( tr.fraction == 1.0f )
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
+ }
+ else
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
+ }
+ }
+
+ //Add in this component
+ steer -= ( right * 0.5f ) * ( 1.0f - tr.fraction );
+
+ //Debug
+ if ( g_debug_antlionguard.GetInt() == 1 )
+ {
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steer * 512.0f ), 255, 255, 0, true, 0.1f );
+ NDebugOverlay::Cross3D( GetAbsOrigin() + ( steer * 512.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 255, 0, true, 0.1f );
+
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), 255, 0, 255, true, 0.1f );
+ NDebugOverlay::Cross3D( GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 0, 255, true, 0.1f );
+ }
+
+ return UTIL_AngleDiff( UTIL_VecToYaw( steer ), faceYaw );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::RunTask( const Task_t *pTask )
+{
+ switch (pTask->iTask)
+ {
+ case TASK_ANTLIONGUARD_SET_FLINCH_ACTIVITY:
+
+ AutoMovement( );
+
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+
+ case TASK_ANTLIONGUARD_SHOVE_PHYSOBJECT:
+
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+
+ break;
+
+ /*
+ case TASK_RUN_PATH:
+ {
+
+
+ }
+ break;
+ */
+
+ case TASK_ANTLIONGUARD_CHARGE:
+ {
+ Activity eActivity = GetActivity();
+
+ // See if we're trying to stop after hitting/missing our target
+ if ( eActivity == ACT_ANTLIONGUARD_CHARGE_STOP || eActivity == ACT_ANTLIONGUARD_CHARGE_CRASH )
+ {
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ return;
+ }
+
+ // Still in the process of slowing down. Run movement until it's done.
+ AutoMovement();
+ return;
+ }
+
+ // Check for manual transition
+ if ( ( eActivity == ACT_ANTLIONGUARD_CHARGE_START ) && ( IsActivityFinished() ) )
+ {
+ SetActivity( ACT_ANTLIONGUARD_CHARGE_RUN );
+ }
+
+ // See if we're still running
+ if ( eActivity == ACT_ANTLIONGUARD_CHARGE_RUN || eActivity == ACT_ANTLIONGUARD_CHARGE_START )
+ {
+ if ( HasCondition( COND_NEW_ENEMY ) || HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) )
+ {
+ SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP );
+ return;
+ }
+ else
+ {
+ if ( GetEnemy() != NULL )
+ {
+ Vector goalDir = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ VectorNormalize( goalDir );
+
+ if ( DotProduct( BodyDirection2D(), goalDir ) < 0.25f )
+ {
+ if ( !m_bDecidedNotToStop )
+ {
+ // We've missed the target. Randomly decide not to stop, which will cause
+ // the guard to just try and swing around for another pass.
+ m_bDecidedNotToStop = true;
+ if ( RandomFloat(0,1) > 0.3 )
+ {
+ m_iChargeMisses++;
+ SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP );
+ }
+ }
+ }
+ else
+ {
+ m_bDecidedNotToStop = false;
+ }
+ }
+ }
+ }
+
+ // Steer towards our target
+ float idealYaw;
+ if ( GetEnemy() == NULL )
+ {
+ idealYaw = GetMotor()->GetIdealYaw();
+ }
+ else
+ {
+ idealYaw = CalcIdealYaw( GetEnemy()->GetAbsOrigin() );
+ }
+
+ // Add in our steering offset
+ idealYaw += ChargeSteer();
+
+ // Turn to face
+ GetMotor()->SetIdealYawAndUpdate( idealYaw );
+
+ // See if we're going to run into anything soon
+ ChargeLookAhead();
+
+ // Let our animations simply move us forward. Keep the result
+ // of the movement so we know whether we've hit our target.
+ AIMoveTrace_t moveTrace;
+ if ( AutoMovement( GetEnemy(), &moveTrace ) == false )
+ {
+ // Only stop if we hit the world
+ if ( HandleChargeImpact( moveTrace.vEndPosition, moveTrace.pObstruction ) )
+ {
+ // If we're starting up, this is an error
+ if ( eActivity == ACT_ANTLIONGUARD_CHARGE_START )
+ {
+ TaskFail( "Unable to make initial movement of charge\n" );
+ return;
+ }
+
+ // Crash unless we're trying to stop already
+ if ( eActivity != ACT_ANTLIONGUARD_CHARGE_STOP )
+ {
+ if ( moveTrace.fStatus == AIMR_BLOCKED_WORLD && moveTrace.vHitNormal == vec3_origin )
+ {
+ SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP );
+ }
+ else
+ {
+ SetActivity( ACT_ANTLIONGUARD_CHARGE_CRASH );
+ }
+ }
+ }
+ else if ( moveTrace.pObstruction )
+ {
+ // If we hit an antlion, don't stop, but kill it
+ if ( moveTrace.pObstruction->Classify() == CLASS_ANTLION )
+ {
+ if ( FClassnameIs( moveTrace.pObstruction, "npc_antlionguard" ) )
+ {
+ // Crash unless we're trying to stop already
+ if ( eActivity != ACT_ANTLIONGUARD_CHARGE_STOP )
+ {
+ SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP );
+ }
+ }
+ else
+ {
+ ApplyChargeDamage( this, moveTrace.pObstruction, moveTrace.pObstruction->GetHealth() );
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case TASK_WAIT_FOR_MOVEMENT:
+ {
+ // the cavern antlion can clothesline gordon
+ if ( m_bInCavern )
+ {
+
+ // See if we're going to run into anything soon
+ ChargeLookAhead();
+
+ if ( HasCondition(COND_CAN_MELEE_ATTACK1) )
+ {
+
+
+ CBaseEntity *pEntity = GetEnemy();
+
+ if (pEntity && pEntity->IsPlayer())
+ {
+ EmitSound( "NPC_AntlionGuard.Shove" );
+
+ if ( !IsPlayingGesture( ACT_ANTLIONGUARD_CHARGE_HIT ) )
+ {
+ RestartGesture( ACT_ANTLIONGUARD_CHARGE_HIT );
+ }
+
+ ChargeDamage( pEntity );
+
+ pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 200 ) );
+
+ if ( !pEntity->IsAlive() && GetEnemy() == pEntity )
+ {
+ SetEnemy( NULL );
+ }
+
+ SetNextAttack( gpGlobals->curtime + 2.0f );
+ SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP );
+
+ // We've hit something, so clear our miss count
+ m_iChargeMisses = 0;
+
+ AutoMovement();
+ TaskComplete();
+ return;
+ }
+ }
+ }
+
+ BaseClass::RunTask( pTask );
+
+ }
+ break;
+
+ default:
+ BaseClass::RunTask(pTask);
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Summon antlions around the guard
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::SummonAntlions( void )
+{
+ // We want to spawn them around the guard
+ Vector vecForward, vecRight;
+ AngleVectors( QAngle(0,GetAbsAngles().y,0), &vecForward, &vecRight, NULL );
+
+ // Spawn positions
+ struct spawnpos_t
+ {
+ float flForward;
+ float flRight;
+ };
+
+ spawnpos_t sAntlionSpawnPositions[] =
+ {
+ { 0, 200 },
+ { 0, -200 },
+ { 128, 128 },
+ { 128, -128 },
+ { -128, 128 },
+ { -128, -128 },
+ { 200, 0 },
+ { -200, 0 },
+ };
+
+ // Only spawn up to our max count
+ int iSpawnPoint = 0;
+ for ( int i = 0; (m_iNumLiveAntlions < ANTLIONGUARD_SUMMON_COUNT) && (iSpawnPoint < ARRAYSIZE(sAntlionSpawnPositions)); i++ )
+ {
+ // Determine spawn position for the antlion
+ Vector vecSpawn = GetAbsOrigin() + ( sAntlionSpawnPositions[iSpawnPoint].flForward * vecForward ) + ( sAntlionSpawnPositions[iSpawnPoint].flRight * vecRight );
+ iSpawnPoint++;
+ // Randomise it a little
+ vecSpawn.x += RandomFloat( -64, 64 );
+ vecSpawn.y += RandomFloat( -64, 64 );
+ vecSpawn.z += 64;
+
+ // Make sure it's clear, and make sure we hit something
+ trace_t tr;
+ UTIL_TraceHull( vecSpawn, vecSpawn - Vector(0,0,128), NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), MASK_NPCSOLID, NULL, COLLISION_GROUP_NONE, &tr );
+ if ( tr.startsolid || tr.allsolid || tr.fraction == 1.0 )
+ {
+ if ( g_debug_antlionguard.GetInt() == 2 )
+ {
+ NDebugOverlay::Box( tr.endpos, NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 255, 0, 0, true, 5.0f );
+ }
+ continue;
+ }
+
+ // Ensure it's dirt or sand
+ const surfacedata_t *pdata = physprops->GetSurfaceData( tr.surface.surfaceProps );
+ if ( ( pdata->game.material != CHAR_TEX_DIRT ) && ( pdata->game.material != CHAR_TEX_SAND ) )
+ {
+ if ( g_debug_antlionguard.GetInt() == 2 )
+ {
+ NDebugOverlay::Box( tr.endpos, NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 255, 128, 128, true, 5.0f );
+ }
+ continue;
+ }
+
+ // Make sure the guard can see it
+ trace_t tr_vis;
+ UTIL_TraceLine( WorldSpaceCenter(), tr.endpos, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr_vis );
+ if ( tr_vis.fraction != 1.0 )
+ {
+ if ( g_debug_antlionguard.GetInt() == 2 )
+ {
+ NDebugOverlay::Line( WorldSpaceCenter(), tr.endpos, 255, 0, 0, true, 5.0f );
+ }
+ continue;
+ }
+
+ CAI_BaseNPC *pent = (CAI_BaseNPC*)CreateEntityByName( "npc_antlion" );
+ if ( !pent )
+ break;
+
+ CNPC_Antlion *pAntlion = assert_cast<CNPC_Antlion*>(pent);
+
+ if ( g_debug_antlionguard.GetInt() == 2 )
+ {
+ NDebugOverlay::Box( tr.endpos, NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 0, 255, 0, true, 5.0f );
+ NDebugOverlay::Line( WorldSpaceCenter(), tr.endpos, 0, 255, 0, true, 5.0f );
+ }
+
+ vecSpawn = tr.endpos;
+ pAntlion->SetAbsOrigin( vecSpawn );
+
+ // Start facing our enemy if we have one, otherwise just match the guard.
+ Vector vecFacing = vecForward;
+ if ( GetEnemy() )
+ {
+ vecFacing = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
+ VectorNormalize( vecFacing );
+ }
+ QAngle vecAngles;
+ VectorAngles( vecFacing, vecAngles );
+ pAntlion->SetAbsAngles( vecAngles );
+
+ pAntlion->AddSpawnFlags( SF_NPC_FALL_TO_GROUND );
+ pAntlion->AddSpawnFlags( SF_NPC_FADE_CORPSE );
+
+ // Make the antlion fire my input when he dies
+ pAntlion->KeyValue( "OnDeath", UTIL_VarArgs("%s,SummonedAntlionDied,,0,-1", STRING(GetEntityName())) );
+
+ // Start the antlion burrowed, and tell him to come up
+ pAntlion->m_bStartBurrowed = true;
+ DispatchSpawn( pAntlion );
+ pAntlion->Activate();
+ g_EventQueue.AddEvent( pAntlion, "Unburrow", RandomFloat(0.1, 1.0), this, this );
+
+ // Add it to our squad
+ if ( GetSquad() != NULL )
+ {
+ GetSquad()->AddToSquad( pAntlion );
+ }
+
+ // Set the antlion's enemy to our enemy
+ if ( GetEnemy() )
+ {
+ pAntlion->SetEnemy( GetEnemy() );
+ pAntlion->SetState( NPC_STATE_COMBAT );
+ pAntlion->UpdateEnemyMemory( GetEnemy(), GetEnemy()->GetAbsOrigin() );
+ }
+
+ m_iNumLiveAntlions++;
+ }
+
+ if ( g_debug_antlionguard.GetInt() == 2 )
+ {
+ Msg("Guard summoned antlion count: %d\n", m_iNumLiveAntlions );
+ }
+
+ if ( m_iNumLiveAntlions > 2 )
+ {
+ m_flNextSummonTime = gpGlobals->curtime + RandomFloat( 15,20 );
+ }
+ else
+ {
+ m_flNextSummonTime = gpGlobals->curtime + RandomFloat( 10,15 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::FoundEnemy( void )
+{
+ m_flAngerNoiseTime = gpGlobals->curtime + 2.0f;
+ SetState( NPC_STATE_COMBAT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::LostEnemy( void )
+{
+ m_flSearchNoiseTime = gpGlobals->curtime + 2.0f;
+ SetState( NPC_STATE_ALERT );
+
+ m_OnLostPlayer.FireOutput( this, this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::InputSetShoveTarget( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller );
+
+ if ( pTarget == NULL )
+ {
+ Warning( "**Guard %s cannot find shove target %s\n", GetClassname(), inputdata.value.String() );
+ m_hShoveTarget = NULL;
+ return;
+ }
+
+ m_hShoveTarget = pTarget;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::InputSetChargeTarget( inputdata_t &inputdata )
+{
+ if ( !IsAlive() )
+ return;
+
+ // Pull the target & position out of the string
+ char parseString[255];
+ Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString));
+
+ // Get charge target name
+ char *pszParam = strtok(parseString," ");
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, pszParam, NULL, inputdata.pActivator, inputdata.pCaller );
+ if ( !pTarget )
+ {
+ Warning( "ERROR: Guard %s cannot find charge target '%s'\n", STRING(GetEntityName()), pszParam );
+ return;
+ }
+
+ // Get the charge position name
+ pszParam = strtok(NULL," ");
+ CBaseEntity *pPosition = gEntList.FindEntityByName( NULL, pszParam, NULL, inputdata.pActivator, inputdata.pCaller );
+ if ( !pPosition )
+ {
+ Warning( "ERROR: Guard %s cannot find charge position '%s'\nMake sure you've specified the parameters as [target start]!\n", STRING(GetEntityName()), pszParam );
+ return;
+ }
+
+ // Make sure we don't stack charge targets
+ if ( m_hChargeTarget )
+ {
+ if ( GetEnemy() == m_hChargeTarget )
+ {
+ SetEnemy( NULL );
+ }
+ }
+
+ SetCondition( COND_ANTLIONGUARD_HAS_CHARGE_TARGET );
+ m_hChargeTarget = pTarget;
+ m_hChargeTargetPosition = pPosition;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::InputClearChargeTarget( inputdata_t &inputdata )
+{
+ m_hChargeTarget = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : baseAct -
+// Output : Activity
+//-----------------------------------------------------------------------------
+Activity CNPC_AntlionGuard::NPC_TranslateActivity( Activity baseAct )
+{
+ //See which run to use
+ if ( ( baseAct == ACT_RUN ) && IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) )
+ return (Activity) ACT_ANTLIONGUARD_CHARGE_RUN;
+
+ // Do extra code if we're trying to close on an enemy in a confined space (unless scripted)
+ if ( hl2_episodic.GetBool() && m_bInCavern && baseAct == ACT_RUN && IsInAScript() == false )
+ return (Activity) ACT_ANTLIONGUARD_CHARGE_RUN;
+
+ if ( ( baseAct == ACT_RUN ) && ( m_iHealth <= (m_iMaxHealth/4) ) )
+ return (Activity) ACT_ANTLIONGUARD_RUN_HURT;
+
+ return baseAct;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::ShouldWatchEnemy( void )
+{
+ Activity nActivity = GetActivity();
+
+ if ( ( nActivity == ACT_ANTLIONGUARD_SEARCH ) ||
+ ( nActivity == ACT_ANTLIONGUARD_PEEK_ENTER ) ||
+ ( nActivity == ACT_ANTLIONGUARD_PEEK_EXIT ) ||
+ ( nActivity == ACT_ANTLIONGUARD_PEEK1 ) ||
+ ( nActivity == ACT_ANTLIONGUARD_PEEK_SIGHTED ) ||
+ ( nActivity == ACT_ANTLIONGUARD_SHOVE_PHYSOBJECT ) ||
+ ( nActivity == ACT_ANTLIONGUARD_PHYSHIT_FR ) ||
+ ( nActivity == ACT_ANTLIONGUARD_PHYSHIT_FL ) ||
+ ( nActivity == ACT_ANTLIONGUARD_PHYSHIT_RR ) ||
+ ( nActivity == ACT_ANTLIONGUARD_PHYSHIT_RL ) ||
+ ( nActivity == ACT_ANTLIONGUARD_CHARGE_CRASH ) ||
+ ( nActivity == ACT_ANTLIONGUARD_CHARGE_HIT ) ||
+ ( nActivity == ACT_ANTLIONGUARD_CHARGE_ANTICIPATION ) )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::UpdateHead( void )
+{
+ float yaw = GetPoseParameter( m_poseHead_Yaw );
+ float pitch = GetPoseParameter( m_poseHead_Pitch );
+
+ // If we should be watching our enemy, turn our head
+ if ( ShouldWatchEnemy() && ( GetEnemy() != NULL ) )
+ {
+ Vector enemyDir = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
+ VectorNormalize( enemyDir );
+
+ float angle = VecToYaw( BodyDirection3D() );
+ float angleDiff = VecToYaw( enemyDir );
+ angleDiff = UTIL_AngleDiff( angleDiff, angle + yaw );
+
+ SetPoseParameter( m_poseHead_Yaw, UTIL_Approach( yaw + angleDiff, yaw, 50 ) );
+
+ angle = UTIL_VecToPitch( BodyDirection3D() );
+ angleDiff = UTIL_VecToPitch( enemyDir );
+ angleDiff = UTIL_AngleDiff( angleDiff, angle + pitch );
+
+ SetPoseParameter( m_poseHead_Pitch, UTIL_Approach( pitch + angleDiff, pitch, 50 ) );
+ }
+ else
+ {
+ // Otherwise turn the head back to its normal position
+ SetPoseParameter( m_poseHead_Yaw, UTIL_Approach( 0, yaw, 10 ) );
+ SetPoseParameter( m_poseHead_Pitch, UTIL_Approach( 0, pitch, 10 ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::MaintainPhysicsTarget( void )
+{
+ if ( m_hPhysicsTarget == NULL || GetEnemy() == NULL )
+ return;
+
+ // Update our current target to make sure it's still valid
+ float flTargetDistSqr = ( m_hPhysicsTarget->WorldSpaceCenter() - m_vecPhysicsTargetStartPos ).LengthSqr();
+ bool bTargetMoved = ( flTargetDistSqr > Square(30*12.0f) );
+ bool bEnemyCloser = ( ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() <= flTargetDistSqr );
+
+ // Make sure this hasn't moved too far or that the player is now closer
+ if ( bTargetMoved || bEnemyCloser )
+ {
+ ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET );
+ SetCondition( COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID );
+ m_hPhysicsTarget = NULL;
+ return;
+ }
+ else
+ {
+ SetCondition( COND_ANTLIONGUARD_PHYSICS_TARGET );
+ ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID );
+ }
+
+ if ( g_debug_antlionguard.GetInt() == 3 )
+ {
+ NDebugOverlay::Cross3D( m_hPhysicsTarget->WorldSpaceCenter(), -Vector(32,32,32), Vector(32,32,32), 255, 255, 255, true, 1.0f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::UpdatePhysicsTarget( bool bPreferObjectsAlongTargetVector, float flRadius )
+{
+ if ( GetEnemy() == NULL )
+ return;
+
+ // Already have a target, don't bother looking
+ if ( m_hPhysicsTarget != NULL )
+ return;
+
+ // Too soon to check again
+ if ( m_flPhysicsCheckTime > gpGlobals->curtime )
+ return;
+
+ // Attempt to find a valid shove target
+ PhysicsObjectCriteria_t criteria;
+ criteria.pTarget = GetEnemy();
+ criteria.vecCenter = GetEnemy()->GetAbsOrigin();
+ criteria.flRadius = flRadius;
+ criteria.flTargetCone = ANTLIONGUARD_OBJECTFINDING_FOV;
+ criteria.bPreferObjectsAlongTargetVector = bPreferObjectsAlongTargetVector;
+ criteria.flNearRadius = (20*12); // TODO: It may preferable to disable this for legacy products as well -- jdw
+
+ m_hPhysicsTarget = FindPhysicsObjectTarget( criteria );
+
+ // Found one, so interrupt us if we care
+ if ( m_hPhysicsTarget != NULL )
+ {
+ SetCondition( COND_ANTLIONGUARD_PHYSICS_TARGET );
+ m_vecPhysicsTargetStartPos = m_hPhysicsTarget->WorldSpaceCenter();
+ }
+
+ // Don't search again for another second
+ m_flPhysicsCheckTime = gpGlobals->curtime + 1.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Let the probe know I can run through small debris
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity )
+{
+ if ( m_iszPhysicsPropClass != pEntity->m_iClassname )
+ return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
+
+ if ( m_hPhysicsTarget == pEntity )
+ return false;
+
+ if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ IPhysicsObject *pPhysObj = pEntity->VPhysicsGetObject();
+
+ if( pPhysObj && pPhysObj->GetMass() <= ANTLIONGUARD_MAX_OBJECT_MASS )
+ {
+ return false;
+ }
+ }
+
+ return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+
+ // Don't do anything after death
+ if ( m_NPCState == NPC_STATE_DEAD )
+ return;
+
+ // If we're burrowed, then don't do any of this
+ if ( m_bIsBurrowed )
+ return;
+
+ // Automatically update our physics target when chasing enemies
+ if ( IsCurSchedule( SCHED_ANTLIONGUARD_CHASE_ENEMY ) ||
+ IsCurSchedule( SCHED_ANTLIONGUARD_PATROL_RUN ) ||
+ IsCurSchedule( SCHED_ANTLIONGUARD_CANT_ATTACK ) ||
+ IsCurSchedule( SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE ) )
+ {
+ bool bCheckAlongLine = ( IsCurSchedule( SCHED_ANTLIONGUARD_CHASE_ENEMY ) || IsCurSchedule( SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE ) );
+ UpdatePhysicsTarget( bCheckAlongLine );
+ }
+ else if ( !IsCurSchedule( SCHED_ANTLIONGUARD_PHYSICS_ATTACK ) )
+ {
+ ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET );
+ m_hPhysicsTarget = NULL;
+ }
+
+ UpdateHead();
+
+ if ( ( m_flGroundSpeed <= 0.0f ) )
+ {
+ if ( m_bStopped == false )
+ {
+ StartSounds();
+
+ float duration = random->RandomFloat( 2.0f, 8.0f );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, duration );
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pBreathSound, random->RandomInt( 40, 60 ), duration );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, duration );
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pGrowlIdleSound, random->RandomInt( 120, 140 ), duration );
+
+ m_flBreathTime = gpGlobals->curtime + duration - (duration*0.75f);
+ }
+
+ m_bStopped = true;
+
+ if ( m_flBreathTime < gpGlobals->curtime )
+ {
+ StartSounds();
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, random->RandomFloat( 0.2f, 0.3f ), random->RandomFloat( 0.5f, 1.0f ) );
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pGrowlIdleSound, random->RandomInt( 80, 120 ), random->RandomFloat( 0.5f, 1.0f ) );
+
+ m_flBreathTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 8.0f );
+ }
+ }
+ else
+ {
+ if ( m_bStopped )
+ {
+ StartSounds();
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.6f, random->RandomFloat( 2.0f, 4.0f ) );
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pBreathSound, random->RandomInt( 140, 160 ), random->RandomFloat( 2.0f, 4.0f ) );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 1.0f );
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pGrowlIdleSound, random->RandomInt( 90, 110 ), 0.2f );
+ }
+
+
+ m_bStopped = false;
+ }
+
+ // Put danger sounds out in front of us
+ for ( int i = 0; i < 3; i++ )
+ {
+ CSoundEnt::InsertSound( SOUND_DANGER, WorldSpaceCenter() + ( BodyDirection3D() * 128 * (i+1) ), 128, 0.1f, this );
+ }
+
+#if ANTLIONGUARD_BLOOD_EFFECTS
+ // compute and if necessary transmit the bleeding level for the particle effect
+ m_iBleedingLevel = GetBleedingLevel();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::GatherConditions( void )
+{
+ BaseClass::GatherConditions();
+
+ if ( CanSummon(false) )
+ {
+ SetCondition( COND_ANTLIONGUARD_CAN_SUMMON );
+ }
+ else
+ {
+ ClearCondition( COND_ANTLIONGUARD_CAN_SUMMON );
+ }
+
+ // Make sure our physics target is still valid
+ MaintainPhysicsTarget();
+
+ if( IsCurSchedule(SCHED_ANTLIONGUARD_PHYSICS_ATTACK) )
+ {
+ if( gpGlobals->curtime > m_flWaitFinished )
+ {
+ ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET );
+ SetCondition( COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID );
+ m_hPhysicsTarget = NULL;
+ }
+ }
+
+ // See if we can charge the target
+ if ( GetEnemy() )
+ {
+ if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false ) )
+ {
+ SetCondition( COND_ANTLIONGUARD_CAN_CHARGE );
+ }
+ else
+ {
+ ClearCondition( COND_ANTLIONGUARD_CAN_CHARGE );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::StopLoopingSounds()
+{
+ //Stop all sounds
+ ENVELOPE_CONTROLLER.SoundDestroy( m_pGrowlHighSound );
+ ENVELOPE_CONTROLLER.SoundDestroy( m_pGrowlIdleSound );
+ ENVELOPE_CONTROLLER.SoundDestroy( m_pBreathSound );
+ ENVELOPE_CONTROLLER.SoundDestroy( m_pConfusedSound );
+
+
+ m_pGrowlHighSound = NULL;
+ m_pGrowlIdleSound = NULL;
+ m_pBreathSound = NULL;
+ m_pConfusedSound = NULL;
+
+ BaseClass::StopLoopingSounds();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::InputUnburrow( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ if ( m_bIsBurrowed == false )
+ return;
+
+ m_spawnflags &= ~SF_NPC_GAG;
+
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+ m_takedamage = DAMAGE_YES;
+
+ SetSchedule( SCHED_ANTLIONGUARD_UNBURROW );
+
+ m_bIsBurrowed = false;
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Returns true is entity was remembered as unreachable.
+// After a time delay reachability is checked
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+bool CNPC_AntlionGuard::IsUnreachable(CBaseEntity *pEntity)
+{
+ float UNREACHABLE_DIST_TOLERANCE_SQ = (240 * 240);
+
+ // Note that it's ok to remove elements while I'm iterating
+ // as long as I iterate backwards and remove them using FastRemove
+ for (int i=m_UnreachableEnts.Size()-1;i>=0;i--)
+ {
+ // Remove any dead elements
+ if (m_UnreachableEnts[i].hUnreachableEnt == NULL)
+ {
+ m_UnreachableEnts.FastRemove(i);
+ }
+ else if (pEntity == m_UnreachableEnts[i].hUnreachableEnt)
+ {
+ // Test for reachability on any elements that have timed out
+ if ( gpGlobals->curtime > m_UnreachableEnts[i].fExpireTime ||
+ pEntity->GetAbsOrigin().DistToSqr(m_UnreachableEnts[i].vLocationWhenUnreachable) > UNREACHABLE_DIST_TOLERANCE_SQ)
+ {
+ m_UnreachableEnts.FastRemove(i);
+ return false;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the point at which the guard wants to stand on to knock the physics object at the target entity
+// Input : *pObject - Object to be shoved.
+// *pTarget - Target to be shoved at.
+// *vecTrajectory - Trajectory to our target
+// *flClearDistance - Distance behind the entity we're clear to use
+// Output : Position at which to attempt to strike the object
+//-----------------------------------------------------------------------------
+Vector CNPC_AntlionGuard::GetPhysicsHitPosition( CBaseEntity *pObject, CBaseEntity *pTarget, Vector *vecTrajectory, float *flClearDistance )
+{
+ // Get the trajectory we want to knock the object along
+ Vector vecToTarget = pTarget->WorldSpaceCenter() - pObject->WorldSpaceCenter();
+ VectorNormalize( vecToTarget );
+ vecToTarget.z = 0;
+
+ // Get the distance we want to be from the object when we hit it
+ IPhysicsObject *pPhys = pObject->VPhysicsGetObject();
+ Vector extent = physcollision->CollideGetExtent( pPhys->GetCollide(), pObject->GetAbsOrigin(), pObject->GetAbsAngles(), -vecToTarget );
+ float flDist = ( extent - pObject->WorldSpaceCenter() ).Length() + CollisionProp()->BoundingRadius() + 32.0f;
+
+ if ( vecTrajectory != NULL )
+ {
+ *vecTrajectory = vecToTarget;
+ }
+
+ if ( flClearDistance != NULL )
+ {
+ *flClearDistance = flDist;
+ }
+
+ // Position at which we'd like to be
+ return (pObject->WorldSpaceCenter() + ( vecToTarget * -flDist ));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if we're able to stand on the ground at this point
+// Input : &vecPos - Position to try
+// *pOut - Result position (only valid if we return true)
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+inline bool CNPC_AntlionGuard::CanStandAtPoint( const Vector &vecPos, Vector *pOut )
+{
+ Vector vecStart = vecPos + Vector( 0, 0, (4*12) );
+ Vector vecEnd = vecPos - Vector( 0, 0, (4*12) );
+ trace_t tr;
+ bool bTraceCleared = false;
+
+ // Start high and try to go lower, looking for the ground between here and there
+ // We do this first because it's more likely to succeed in the typical guard arenas (with open terrain)
+ UTIL_TraceHull( vecStart, vecEnd, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.startsolid && !tr.allsolid )
+ {
+ // We started in solid but didn't end up there, see if we can stand where we ended up
+ UTIL_TraceHull( tr.endpos, tr.endpos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ // Must not be in solid
+ bTraceCleared = ( !tr.allsolid && !tr.startsolid );
+ }
+ else
+ {
+ // Must not be in solid and must have found a floor (otherwise we're potentially hanging over a ledge)
+ bTraceCleared = ( tr.allsolid == false && tr.fraction < 1.0f );
+ }
+
+ // Either we're clear or we're still unlucky
+ if ( bTraceCleared == false )
+ {
+ if ( g_debug_antlionguard.GetInt() == 3 )
+ {
+ NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 15.0f );
+ }
+ return false;
+ }
+
+ if ( pOut )
+ {
+ *pOut = tr.endpos;
+ }
+
+ if ( g_debug_antlionguard.GetInt() == 3 )
+ {
+ NDebugOverlay::Box( (*pOut), GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 15.0f );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines whether or not the guard can stand in a position to strike a specified object
+// Input : *pShoveObject - Object being shoved
+// *pTarget - Target we're shoving the object at
+// *pOut - The position we decide to stand at
+// Output : Returns true if the guard can stand and deliver.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::CanStandAtShoveTarget( CBaseEntity *pShoveObject, CBaseEntity *pTarget, Vector *pOut )
+{
+ // Get the position we want to be at to swing at the object
+ float flClearDistance;
+ Vector vecTrajectory;
+ Vector vecHitPosition = GetPhysicsHitPosition( pShoveObject, pTarget, &vecTrajectory, &flClearDistance );
+ Vector vecStandPosition;
+
+ if ( g_debug_antlionguard.GetInt() == 3 )
+ {
+ NDebugOverlay::HorzArrow( pShoveObject->WorldSpaceCenter(), pShoveObject->WorldSpaceCenter() + vecTrajectory * 64.0f, 16.0f, 255, 255, 0, 16, true, 15.0f );
+ }
+
+ // If we failed, try around the sides
+ if ( CanStandAtPoint( vecHitPosition, &vecStandPosition ) == false )
+ {
+ // Get the angle (in reverse) to the target
+ float flRad = atan2( -vecTrajectory.y, -vecTrajectory.x );
+ float flRadOffset = DEG2RAD( 45.0f );
+
+ // Build an offset vector, rotating around the base
+ Vector vecSkewTrajectory;
+ SinCos( flRad + flRadOffset, &vecSkewTrajectory.y, &vecSkewTrajectory.x );
+ vecSkewTrajectory.z = 0.0f;
+
+ // Try to the side
+ if ( CanStandAtPoint( ( pShoveObject->WorldSpaceCenter() + ( vecSkewTrajectory * flClearDistance ) ), &vecStandPosition ) == false )
+ {
+ // Try the other side
+ SinCos( flRad - flRadOffset, &vecSkewTrajectory.y, &vecSkewTrajectory.x );
+ vecSkewTrajectory.z = 0.0f;
+ if ( CanStandAtPoint( ( pShoveObject->WorldSpaceCenter() + ( vecSkewTrajectory * flClearDistance ) ), &vecStandPosition ) == false )
+ return false;
+ }
+ }
+
+ // Found it, report it
+ if ( pOut != NULL )
+ {
+ *pOut = vecStandPosition;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Iterate through a number of lists depending on our criteria
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_AntlionGuard::GetNextShoveTarget( CBaseEntity *pLastEntity, AISightIter_t &iter )
+{
+ // Try to find scripted items first
+ if ( m_strShoveTargets != NULL_STRING )
+ {
+ CBaseEntity *pFound = gEntList.FindEntityByName( pLastEntity, m_strShoveTargets );
+ if ( pFound )
+ return pFound;
+ }
+
+ // Failing that, use our senses
+ if ( iter != (AISightIter_t)(-1) )
+ return GetSenses()->GetNextSeenEntity( &iter );
+
+ return GetSenses()->GetFirstSeenEntity( &iter, SEEN_MISC );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Search for a physics item to swat at the player
+// Output : Returns the object we're going to swat.
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_AntlionGuard::FindPhysicsObjectTarget( const PhysicsObjectCriteria_t &criteria )
+{
+ // Must have a valid target entity
+ if ( criteria.pTarget == NULL )
+ return NULL;
+
+ if ( g_debug_antlionguard.GetInt() == 3 )
+ {
+ NDebugOverlay::Circle( GetAbsOrigin(), QAngle( -90, 0, 0 ), criteria.flRadius, 255, 0, 0, 8, true, 2.0f );
+ }
+
+ // Get the vector to our target, from ourself
+ Vector vecDirToTarget = criteria.pTarget->GetAbsOrigin() - GetAbsOrigin();
+ VectorNormalize( vecDirToTarget );
+ vecDirToTarget.z = 0;
+
+ // Cost is determined by distance to the object, modified by how "in line" it is with our target direction of travel
+ // Use the distance to the player as the base cost for throwing an object (avoids pushing things too close to the player)
+ float flLeastCost = ( criteria.bPreferObjectsAlongTargetVector ) ? ( criteria.pTarget->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() : Square( criteria.flRadius );
+ float flCost;
+
+ AISightIter_t iter = (AISightIter_t)(-1);
+ CBaseEntity *pObject = NULL;
+ CBaseEntity *pNearest = NULL;
+ Vector vecBestHitPosition = vec3_origin;
+
+ // Look through the list of sensed objects for possible targets
+ while( ( pObject = GetNextShoveTarget( pObject, iter ) ) != NULL )
+ {
+ // If we couldn't shove this object last time, don't try again
+ if ( m_FailedPhysicsTargets.Find( pObject ) != m_FailedPhysicsTargets.InvalidIndex() )
+ continue;
+
+ // Ignore things less than half a foot in diameter
+ if ( pObject->CollisionProp()->BoundingRadius() < 6.0f )
+ continue;
+
+ IPhysicsObject *pPhysObj = pObject->VPhysicsGetObject();
+ if ( pPhysObj == NULL )
+ continue;
+
+ // Ignore motion disabled props
+ if ( pPhysObj->IsMoveable() == false )
+ continue;
+
+ // Ignore things lighter than 5kg
+ if ( pPhysObj->GetMass() < 5.0f )
+ continue;
+
+ // Ignore objects moving too quickly (they'll be too hard to catch otherwise)
+ Vector velocity;
+ pPhysObj->GetVelocity( &velocity, NULL );
+ if ( velocity.LengthSqr() > (16*16) )
+ continue;
+
+ // Get the direction from us to the physics object
+ Vector vecDirToObject = pObject->WorldSpaceCenter() - GetAbsOrigin();
+ VectorNormalize( vecDirToObject );
+ vecDirToObject.z = 0;
+
+ Vector vecObjCenter = pObject->WorldSpaceCenter();
+ float flDistSqr = 0.0f;
+ float flDot = 0.0f;
+
+ // If we want to find things along the vector to the target, do so
+ if ( criteria.bPreferObjectsAlongTargetVector )
+ {
+ // Object must be closer than our target
+ if ( ( GetAbsOrigin() - vecObjCenter ).LengthSqr() > ( GetAbsOrigin() - criteria.pTarget->GetAbsOrigin() ).LengthSqr() )
+ continue;
+
+ // Calculate a "cost" to this object
+ flDistSqr = ( GetAbsOrigin() - vecObjCenter ).LengthSqr();
+ flDot = DotProduct( vecDirToTarget, vecDirToObject );
+
+ // Ignore things outside our allowed cone
+ if ( flDot < criteria.flTargetCone )
+ continue;
+
+ // The more perpendicular we are, the higher the cost
+ float flCostScale = RemapValClamped( flDot, 1.0f, criteria.flTargetCone, 1.0f, 4.0f );
+ flCost = flDistSqr * flCostScale;
+ }
+ else
+ {
+ // Straight distance cost
+ flCost = ( criteria.vecCenter - vecObjCenter ).LengthSqr();
+ }
+
+ // This must be a less costly object to use
+ if ( flCost >= flLeastCost )
+ {
+ if ( g_debug_antlionguard.GetInt() == 3 )
+ {
+ NDebugOverlay::Box( vecObjCenter, -Vector(16,16,16), Vector(16,16,16), 255, 0, 0, 0, 2.0f );
+ }
+
+ continue;
+ }
+
+ // Check for a (roughly) clear trajectory path from the object to target
+ trace_t tr;
+ UTIL_TraceLine( vecObjCenter, criteria.pTarget->BodyTarget( vecObjCenter ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ // See how close to our target we got (we still look good hurling things that won't necessarily hit)
+ if ( ( tr.endpos - criteria.pTarget->WorldSpaceCenter() ).LengthSqr() > Square(criteria.flNearRadius) )
+ continue;
+
+ // Must be able to stand at a position to hit the object
+ Vector vecHitPosition;
+ if ( CanStandAtShoveTarget( pObject, criteria.pTarget, &vecHitPosition ) == false )
+ {
+ if ( g_debug_antlionguard.GetInt() == 3 )
+ {
+ NDebugOverlay::HorzArrow( GetAbsOrigin(), pObject->WorldSpaceCenter(), 32.0f, 255, 0, 0, 64, true, 2.0f );
+ }
+ continue;
+ }
+
+ // Take this as the best object so far
+ pNearest = pObject;
+ flLeastCost = flCost;
+ vecBestHitPosition = vecHitPosition;
+
+ if ( g_debug_antlionguard.GetInt() == 3 )
+ {
+ NDebugOverlay::HorzArrow( GetAbsOrigin(), pObject->WorldSpaceCenter(), 16.0f, 255, 255, 0, 0, true, 2.0f );
+ }
+ }
+
+ // Set extra info if we've succeeded
+ if ( pNearest != NULL )
+ {
+ m_vecPhysicsHitPosition = vecBestHitPosition;
+
+ if ( g_debug_antlionguard.GetInt() == 3 )
+ {
+ NDebugOverlay::HorzArrow( GetAbsOrigin(), pNearest->WorldSpaceCenter(), 32.0f, 0, 255, 0, 128, true, 2.0f );
+ }
+ }
+
+ return pNearest;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows for modification of the interrupt mask for the current schedule.
+// In the most cases the base implementation should be called first.
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::BuildScheduleTestBits( void )
+{
+ BaseClass::BuildScheduleTestBits();
+
+ // Interrupt if we can shove something
+ if ( IsCurSchedule( SCHED_ANTLIONGUARD_CHASE_ENEMY ) )
+ {
+ SetCustomInterruptCondition( COND_ANTLIONGUARD_PHYSICS_TARGET );
+ SetCustomInterruptCondition( COND_ANTLIONGUARD_CAN_SUMMON );
+ }
+
+ // Interrupt if we've been given a charge target
+ if ( IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) == false )
+ {
+ SetCustomInterruptCondition( COND_ANTLIONGUARD_HAS_CHARGE_TARGET );
+ }
+
+ // Once we commit to doing this, just do it!
+ if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) )
+ {
+ ClearCustomInterruptCondition( COND_ENEMY_OCCLUDED );
+ }
+
+ // Always take heavy damage
+ SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &origin -
+// radius -
+// magnitude -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::ImpactShock( const Vector &origin, float radius, float magnitude, CBaseEntity *pIgnored )
+{
+ // Also do a local physics explosion to push objects away
+ float adjustedDamage, flDist;
+ Vector vecSpot;
+ float falloff = 1.0f / 2.5f;
+
+ CBaseEntity *pEntity = NULL;
+
+ // Find anything within our radius
+ while ( ( pEntity = gEntList.FindEntityInSphere( pEntity, origin, radius ) ) != NULL )
+ {
+ // Don't affect the ignored target
+ if ( pEntity == pIgnored )
+ continue;
+ if ( pEntity == this )
+ continue;
+
+ // UNDONE: Ask the object if it should get force if it's not MOVETYPE_VPHYSICS?
+ if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS || ( pEntity->VPhysicsGetObject() && pEntity->IsPlayer() == false ) )
+ {
+ vecSpot = pEntity->BodyTarget( GetAbsOrigin() );
+
+ // decrease damage for an ent that's farther from the bomb.
+ flDist = ( GetAbsOrigin() - vecSpot ).Length();
+
+ if ( radius == 0 || flDist <= radius )
+ {
+ adjustedDamage = flDist * falloff;
+ adjustedDamage = magnitude - adjustedDamage;
+
+ if ( adjustedDamage < 1 )
+ {
+ adjustedDamage = 1;
+ }
+
+ CTakeDamageInfo info( this, this, adjustedDamage, DMG_BLAST );
+ CalculateExplosiveDamageForce( &info, (vecSpot - GetAbsOrigin()), GetAbsOrigin() );
+
+ pEntity->VPhysicsTakeDamage( info );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTarget -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::ChargeDamage( CBaseEntity *pTarget )
+{
+ if ( pTarget == NULL )
+ return;
+
+ CBasePlayer *pPlayer = ToBasePlayer( pTarget );
+
+ if ( pPlayer != NULL )
+ {
+ //Kick the player angles
+ pPlayer->ViewPunch( QAngle( 20, 20, -30 ) );
+
+ Vector dir = pPlayer->WorldSpaceCenter() - WorldSpaceCenter();
+ VectorNormalize( dir );
+ dir.z = 0.0f;
+
+ Vector vecNewVelocity = dir * 250.0f;
+ vecNewVelocity[2] += 128.0f;
+ pPlayer->SetAbsVelocity( vecNewVelocity );
+
+ color32 red = {128,0,0,128};
+ UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
+ }
+
+ // Player takes less damage
+ float flDamage = ( pPlayer == NULL ) ? 250 : sk_antlionguard_dmg_charge.GetFloat();
+
+ // If it's being held by the player, break that bond
+ Pickup_ForcePlayerToDropThisObject( pTarget );
+
+ // Calculate the physics force
+ ApplyChargeDamage( this, pTarget, flDamage );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::InputRagdoll( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ //Set us to nearly dead so the velocity from death is minimal
+ SetHealth( 1 );
+
+ CTakeDamageInfo info( this, this, GetHealth(), DMG_CRUSH );
+ BaseClass::TakeDamage( info );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: make m_bPreferPhysicsAttack true
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::InputEnablePreferPhysicsAttack( inputdata_t &inputdata )
+{
+ m_bPreferPhysicsAttack = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: make m_bPreferPhysicsAttack false
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::InputDisablePreferPhysicsAttack( inputdata_t &inputdata )
+{
+ m_bPreferPhysicsAttack = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_AntlionGuard::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
+{
+ // Figure out what to do next
+ if ( failedSchedule == SCHED_ANTLIONGUARD_CHASE_ENEMY && HasCondition( COND_ENEMY_UNREACHABLE ) )
+ return SelectUnreachableSchedule();
+
+ return BaseClass::SelectFailSchedule( failedSchedule,failedTask, taskFailCode );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : scheduleType -
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_AntlionGuard::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_CHASE_ENEMY:
+ return SCHED_ANTLIONGUARD_CHASE_ENEMY;
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::StartSounds( void )
+{
+ //Initialize the additive sound channels
+ CPASAttenuationFilter filter( this );
+
+ if ( m_pGrowlHighSound == NULL )
+ {
+ m_pGrowlHighSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_VOICE, "NPC_AntlionGuard.GrowlHigh", ATTN_NORM );
+
+ if ( m_pGrowlHighSound )
+ {
+ ENVELOPE_CONTROLLER.Play( m_pGrowlHighSound,0.0f, 100 );
+ }
+ }
+
+ if ( m_pGrowlIdleSound == NULL )
+ {
+ m_pGrowlIdleSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_STATIC, "NPC_AntlionGuard.GrowlIdle", ATTN_NORM );
+
+ if ( m_pGrowlIdleSound )
+ {
+ ENVELOPE_CONTROLLER.Play( m_pGrowlIdleSound,0.0f, 100 );
+ }
+ }
+
+ if ( m_pBreathSound == NULL )
+ {
+ m_pBreathSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_ITEM, "NPC_AntlionGuard.BreathSound", ATTN_NORM );
+
+ if ( m_pBreathSound )
+ {
+ ENVELOPE_CONTROLLER.Play( m_pBreathSound, 0.0f, 100 );
+ }
+ }
+
+ if ( m_pConfusedSound == NULL )
+ {
+ m_pConfusedSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_WEAPON,"NPC_AntlionGuard.Confused", ATTN_NORM );
+
+ if ( m_pConfusedSound )
+ {
+ ENVELOPE_CONTROLLER.Play( m_pConfusedSound, 0.0f, 100 );
+ }
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::InputEnableBark( inputdata_t &inputdata )
+{
+ m_bBarkEnabled = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::InputDisableBark( inputdata_t &inputdata )
+{
+ m_bBarkEnabled = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::DeathSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_AntlionGuard.Die" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::Event_Killed( const CTakeDamageInfo &info )
+{
+ BaseClass::Event_Killed( info );
+
+ // Tell all of my antlions to burrow away, 'cos they fear the Freeman
+ if ( m_iNumLiveAntlions )
+ {
+ CBaseEntity *pSearch = NULL;
+
+ // Iterate through all antlions and see if there are any orphans
+ while ( ( pSearch = gEntList.FindEntityByClassname( pSearch, "npc_antlion" ) ) != NULL )
+ {
+ CNPC_Antlion *pAntlion = assert_cast<CNPC_Antlion *>(pSearch);
+
+ // See if it's a live orphan
+ if ( pAntlion && pAntlion->GetOwnerEntity() == NULL && pAntlion->IsAlive() )
+ {
+ g_EventQueue.AddEvent( pAntlion, "BurrowAway", RandomFloat(0.1, 2.0), this, this );
+ }
+ }
+ }
+
+ DestroyGlows();
+
+ // If I'm bleeding, stop due to decreased pressure of hemolymph after
+ // cessation of aortic contraction
+#if ANTLIONGUARD_BLOOD_EFFECTS
+ m_iBleedingLevel = 0;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Don't become a ragdoll until we've finished our death anim
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::CanBecomeRagdoll( void )
+{
+ if ( IsCurSchedule( SCHED_DIE ) )
+ return true;
+
+ return hl2_episodic.GetBool();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &force -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::BecomeRagdollOnClient( const Vector &force )
+{
+ if ( !CanBecomeRagdoll() )
+ return false;
+
+ EmitSound( "NPC_AntlionGuard.Fallover" );
+
+ // Become server-side ragdoll if we're flagged to do it
+ if ( m_spawnflags & SF_ANTLIONGUARD_SERVERSIDE_RAGDOLL )
+ {
+ CTakeDamageInfo info;
+
+ // Fake the info
+ info.SetDamageType( DMG_GENERIC );
+ info.SetDamageForce( force );
+ info.SetDamagePosition( WorldSpaceCenter() );
+
+ CBaseEntity *pRagdoll = CreateServerRagdoll( this, 0, info, COLLISION_GROUP_NONE );
+
+ // Transfer our name to the new ragdoll
+ pRagdoll->SetName( GetEntityName() );
+ pRagdoll->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+
+ // Get rid of our old body
+ UTIL_Remove(this);
+
+ return true;
+ }
+
+ return BaseClass::BecomeRagdollOnClient( force );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override how we face our target as we move
+// Output :
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
+{
+ Vector vecFacePosition = vec3_origin;
+ CBaseEntity *pFaceTarget = NULL;
+ bool bFaceTarget = false;
+
+ // FIXME: this will break scripted sequences that walk when they have an enemy
+ if ( m_hChargeTarget )
+ {
+ vecFacePosition = m_hChargeTarget->GetAbsOrigin();
+ pFaceTarget = m_hChargeTarget;
+ bFaceTarget = true;
+ }
+#ifdef HL2_EPISODIC
+ else if ( GetEnemy() && IsCurSchedule( SCHED_ANTLIONGUARD_CANT_ATTACK ) )
+ {
+ // Always face our enemy when randomly patrolling around
+ vecFacePosition = GetEnemy()->EyePosition();
+ pFaceTarget = GetEnemy();
+ bFaceTarget = true;
+ }
+#endif // HL2_EPISODIC
+ else if ( GetEnemy() && GetNavigator()->GetMovementActivity() == ACT_RUN )
+ {
+ Vector vecEnemyLKP = GetEnemyLKP();
+
+ // Only start facing when we're close enough
+ if ( ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < 512 ) || IsCurSchedule( SCHED_ANTLIONGUARD_PATROL_RUN ) )
+ {
+ vecFacePosition = vecEnemyLKP;
+ pFaceTarget = GetEnemy();
+ bFaceTarget = true;
+ }
+ }
+
+ // Face
+ if ( bFaceTarget )
+ {
+ AddFacingTarget( pFaceTarget, vecFacePosition, 1.0, 0.2 );
+ }
+
+ return BaseClass::OverrideMoveFacing( move, flInterval );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::IsHeavyDamage( const CTakeDamageInfo &info )
+{
+ // Struck by blast
+ if ( info.GetDamageType() & DMG_BLAST )
+ {
+ if ( info.GetDamage() > MIN_BLAST_DAMAGE )
+ return true;
+ }
+
+ // Struck by large object
+ if ( info.GetDamageType() & DMG_CRUSH )
+ {
+ IPhysicsObject *pPhysObject = info.GetInflictor()->VPhysicsGetObject();
+
+ if ( ( pPhysObject != NULL ) && ( pPhysObject->GetGameFlags() & FVPHYSICS_WAS_THROWN ) )
+ {
+ // Always take hits from a combine ball
+ if ( UTIL_IsAR2CombineBall( info.GetInflictor() ) )
+ return true;
+
+ // If we're under half health, stop being interrupted by heavy damage
+ if ( GetHealth() < (GetMaxHealth() * 0.25) )
+ return false;
+
+ // Ignore physics damages that don't do much damage
+ if ( info.GetDamage() < MIN_CRUSH_DAMAGE )
+ return false;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::IsLightDamage( const CTakeDamageInfo &info )
+{
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pChild -
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::InputSummonedAntlionDied( inputdata_t &inputdata )
+{
+ m_iNumLiveAntlions--;
+ Assert( m_iNumLiveAntlions >= 0 );
+
+ if ( g_debug_antlionguard.GetInt() == 2 )
+ {
+ Msg("Guard summoned antlion count: %d\n", m_iNumLiveAntlions );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Filter out sounds we don't care about
+// Input : *pSound - sound to test against
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::QueryHearSound( CSound *pSound )
+{
+ // Don't bother with danger sounds from antlions or other guards
+ if ( pSound->SoundType() == SOUND_DANGER && ( pSound->m_hOwner != NULL && pSound->m_hOwner->Classify() == CLASS_ANTLION ) )
+ return false;
+
+ return BaseClass::QueryHearSound( pSound );
+}
+
+
+#if HL2_EPISODIC
+//---------------------------------------------------------
+// Prevent the cavern guard from using stopping paths, as it occasionally forces him off the navmesh.
+//---------------------------------------------------------
+bool CNPC_AntlionGuard::CNavigator::GetStoppingPath( CAI_WaypointList *pClippedWaypoints )
+{
+ if (GetOuter()->m_bInCavern)
+ {
+ return false;
+ }
+ else
+ {
+ return BaseClass::GetStoppingPath( pClippedWaypoints );
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTarget -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::RememberFailedPhysicsTarget( CBaseEntity *pTarget )
+{
+ // Already in the list?
+ if ( m_FailedPhysicsTargets.Find( pTarget ) != m_FailedPhysicsTargets.InvalidIndex() )
+ return true;
+
+ // We're not holding on to any more
+ if ( ( m_FailedPhysicsTargets.Count() + 1 ) > MAX_FAILED_PHYSOBJECTS )
+ return false;
+
+ m_FailedPhysicsTargets.AddToTail( pTarget );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle squad or NPC interactions
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AntlionGuard::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sender )
+{
+ // Don't chase targets that other guards in our squad may be going after!
+ if ( interactionType == g_interactionAntlionGuardFoundPhysicsObject )
+ {
+ RememberFailedPhysicsTarget( (CBaseEntity *) data );
+ return true;
+ }
+
+ // Mark a shoved object as being free to pursue again
+ if ( interactionType == g_interactionAntlionGuardShovedPhysicsObject )
+ {
+ CBaseEntity *pObject = (CBaseEntity *) data;
+ m_FailedPhysicsTargets.FindAndRemove( pObject );
+ return true;
+ }
+
+ return BaseClass::HandleInteraction( interactionType, data, sender );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Cache whatever pose parameters we intend to use
+//-----------------------------------------------------------------------------
+void CNPC_AntlionGuard::PopulatePoseParameters( void )
+{
+ m_poseThrow = LookupPoseParameter("throw");
+ m_poseHead_Pitch = LookupPoseParameter("head_pitch");
+ m_poseHead_Yaw = LookupPoseParameter("head_yaw" );
+
+ BaseClass::PopulatePoseParameters();
+}
+
+#if ANTLIONGUARD_BLOOD_EFFECTS
+//-----------------------------------------------------------------------------
+// Purpose: Return desired level for the continuous bleeding effect (not the
+// individual blood spurts you see per bullet hit)
+// Return 0 for don't bleed,
+// 1 for mild bleeding
+// 2 for severe bleeding
+//-----------------------------------------------------------------------------
+unsigned char CNPC_AntlionGuard::GetBleedingLevel( void ) const
+{
+ if ( m_iHealth > ( m_iMaxHealth >> 1 ) )
+ { // greater than 50%
+ return 0;
+ }
+ else if ( m_iHealth > ( m_iMaxHealth >> 2 ) )
+ { // less than 50% but greater than 25%
+ return 1;
+ }
+ else
+ {
+ return 2;
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( npc_antlionguard, CNPC_AntlionGuard )
+
+ // Interactions
+ DECLARE_INTERACTION( g_interactionAntlionGuardFoundPhysicsObject )
+ DECLARE_INTERACTION( g_interactionAntlionGuardShovedPhysicsObject )
+
+ // Squadslots
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ANTLIONGUARD_CHARGE )
+
+ //Tasks
+ DECLARE_TASK( TASK_ANTLIONGUARD_CHARGE )
+ DECLARE_TASK( TASK_ANTLIONGUARD_GET_PATH_TO_PHYSOBJECT )
+ DECLARE_TASK( TASK_ANTLIONGUARD_SHOVE_PHYSOBJECT )
+ DECLARE_TASK( TASK_ANTLIONGUARD_SUMMON )
+ DECLARE_TASK( TASK_ANTLIONGUARD_SET_FLINCH_ACTIVITY )
+ DECLARE_TASK( TASK_ANTLIONGUARD_GET_PATH_TO_CHARGE_POSITION )
+ DECLARE_TASK( TASK_ANTLIONGUARD_GET_PATH_TO_NEAREST_NODE )
+ DECLARE_TASK( TASK_ANTLIONGUARD_GET_CHASE_PATH_ENEMY_TOLERANCE )
+ DECLARE_TASK( TASK_ANTLIONGUARD_OPPORTUNITY_THROW )
+ DECLARE_TASK( TASK_ANTLIONGUARD_FIND_PHYSOBJECT )
+
+ //Activities
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_SEARCH )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PEEK_FLINCH )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PEEK_ENTER )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PEEK_EXIT )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PEEK1 )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_BARK )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PEEK_SIGHTED )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_START )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_CANCEL )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_RUN )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_CRASH )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_STOP )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_HIT )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_ANTICIPATION )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_SHOVE_PHYSOBJECT )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_FLINCH_LIGHT )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_UNBURROW )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_ROAR )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_RUN_HURT )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_FR )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_FL )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_RR )
+ DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_RL )
+
+ //Adrian: events go here
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_CHARGE_HIT )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_SHOVE_PHYSOBJECT )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_SHOVE )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_FOOTSTEP_LIGHT )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_FOOTSTEP_HEAVY )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_CHARGE_EARLYOUT )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_GROWL )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_BARK )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_PAIN )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_SQUEEZE )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_SCRATCH )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_GRUNT )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_BURROW_OUT )
+ DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_ROAR )
+
+ DECLARE_CONDITION( COND_ANTLIONGUARD_PHYSICS_TARGET )
+ DECLARE_CONDITION( COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID )
+ DECLARE_CONDITION( COND_ANTLIONGUARD_HAS_CHARGE_TARGET )
+ DECLARE_CONDITION( COND_ANTLIONGUARD_CAN_SUMMON )
+ DECLARE_CONDITION( COND_ANTLIONGUARD_CAN_CHARGE )
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_SUMMON
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_SUMMON,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLIONGUARD_BARK"
+ " TASK_ANTLIONGUARD_SUMMON 0"
+ " TASK_ANTLIONGUARD_OPPORTUNITY_THROW 0"
+ " "
+ " Interrupts"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_CHARGE
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_CHARGE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_CHASE_ENEMY"
+ " TASK_FACE_ENEMY 0"
+ " TASK_ANTLIONGUARD_CHARGE 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_HEAVY_DAMAGE"
+
+ // These are deliberately left out so they can be detected during the
+ // charge Task and correctly play the charge stop animation.
+ //" COND_NEW_ENEMY"
+ //" COND_ENEMY_DEAD"
+ //" COND_LOST_ENEMY"
+ )
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_CHARGE_TARGET
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_CHARGE_TARGET,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_ANTLIONGUARD_CHARGE 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_ENEMY_DEAD"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_CHARGE_SMASH
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_CHARGE_CRASH,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLIONGUARD_CHARGE_CRASH"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_PHYSICS_ATTACK
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_PHYSICS_ATTACK,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_CHASE_ENEMY"
+ " TASK_ANTLIONGUARD_GET_PATH_TO_PHYSOBJECT 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_ANTLIONGUARD_SHOVE_PHYSOBJECT 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_ENEMY_DEAD"
+ " COND_LOST_ENEMY"
+ " COND_NEW_ENEMY"
+ " COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //==================================================
+ // SCHED_FORCE_ANTLIONGUARD_PHYSICS_ATTACK
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_FORCE_ANTLIONGUARD_PHYSICS_ATTACK,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_CANT_ATTACK"
+ " TASK_ANTLIONGUARD_FIND_PHYSOBJECT 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_PHYSICS_ATTACK"
+ ""
+ " Interrupts"
+ " COND_ANTLIONGUARD_PHYSICS_TARGET"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_CANT_ATTACK
+ // If we're here, the guard can't chase enemy, can't find a physobject to attack with, and can't summon
+ //==================================================
+
+#ifdef HL2_EPISODIC
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_CANT_ATTACK,
+
+ " Tasks"
+ " TASK_SET_ROUTE_SEARCH_TIME 2" // Spend 5 seconds trying to build a path if stuck
+ " TASK_GET_PATH_TO_RANDOM_NODE 1024"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_WAIT_PVS 0"
+ ""
+ " Interrupts"
+ " COND_GIVE_WAY"
+ " COND_NEW_ENEMY"
+ " COND_ANTLIONGUARD_PHYSICS_TARGET"
+ " COND_HEAVY_DAMAGE"
+ )
+
+#else
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_CANT_ATTACK,
+
+ " Tasks"
+ " TASK_WAIT 5"
+ ""
+ " Interrupts"
+ )
+
+#endif
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_PHYSICS_DAMAGE_HEAVY
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_PHYSICS_DAMAGE_HEAVY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_RESET_ACTIVITY 0"
+ " TASK_ANTLIONGUARD_SET_FLINCH_ACTIVITY 0"
+ ""
+ " Interrupts"
+ )
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_UNBURROW
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_UNBURROW,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLIONGUARD_UNBURROW"
+ ""
+ " Interrupts"
+ )
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_CHARGE_CANCEL
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_CHARGE_CANCEL,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLIONGUARD_CHARGE_CANCEL"
+ ""
+ " Interrupts"
+ )
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_FIND_CHARGE_POSITION
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_FIND_CHARGE_POSITION,
+
+ " Tasks"
+ " TASK_ANTLIONGUARD_GET_PATH_TO_CHARGE_POSITION 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " "
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_GIVE_WAY"
+ " COND_TASK_FAILED"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // > SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_PATROL_RUN"
+ " TASK_ANTLIONGUARD_GET_PATH_TO_NEAREST_NODE 500"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_GIVE_WAY"
+ " COND_NEW_ENEMY"
+ " COND_ANTLIONGUARD_CAN_SUMMON"
+ " COND_ANTLIONGUARD_PHYSICS_TARGET"
+ " COND_HEAVY_DAMAGE"
+ " COND_ANTLIONGUARD_CAN_CHARGE"
+ );
+
+ //=========================================================
+ // > PATROL_RUN
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_PATROL_RUN,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_CANT_ATTACK"
+ " TASK_SET_ROUTE_SEARCH_TIME 3" // Spend 3 seconds trying to build a path if stuck
+ " TASK_ANTLIONGUARD_GET_PATH_TO_NEAREST_NODE 500"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_GIVE_WAY"
+ " COND_NEW_ENEMY"
+ " COND_ANTLIONGUARD_PHYSICS_TARGET"
+ " COND_ANTLIONGUARD_CAN_SUMMON"
+ " COND_HEAVY_DAMAGE"
+ " COND_ANTLIONGUARD_CAN_CHARGE"
+ );
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_ROAR
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_ROAR,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLIONGUARD_ROAR"
+ " "
+ " Interrupts"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //==================================================
+ // SCHED_ANTLIONGUARD_TAKE_COVER_FROM_ENEMY
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_TAKE_COVER_FROM_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_CANT_ATTACK"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ANTLIONGUARD_PHYSICS_TARGET"
+ " COND_ANTLIONGUARD_CAN_SUMMON"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // SCHED_ANTLIONGUARD_CHASE_ENEMY
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLIONGUARD_CHASE_ENEMY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_GET_CHASE_PATH_TO_ENEMY 300"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_UNREACHABLE"
+ " COND_CAN_RANGE_ATTACK1"
+ // " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_TOO_CLOSE_TO_ATTACK"
+ " COND_TASK_FAILED"
+ " COND_LOST_ENEMY"
+ " COND_HEAVY_DAMAGE"
+ " COND_ANTLIONGUARD_CAN_CHARGE"
+ )
+
+AI_END_CUSTOM_NPC()