diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/server/hl2/npc_antlionguard.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'sp/src/game/server/hl2/npc_antlionguard.cpp')
| -rw-r--r-- | sp/src/game/server/hl2/npc_antlionguard.cpp | 10096 |
1 files changed, 5048 insertions, 5048 deletions
diff --git a/sp/src/game/server/hl2/npc_antlionguard.cpp b/sp/src/game/server/hl2/npc_antlionguard.cpp index 02c880e9..9ed2eff4 100644 --- a/sp/src/game/server/hl2/npc_antlionguard.cpp +++ b/sp/src/game/server/hl2/npc_antlionguard.cpp @@ -1,5048 +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()
+//========= 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() |