diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /sp/src/game/server/hl2/npc_antlion.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'sp/src/game/server/hl2/npc_antlion.cpp')
| -rw-r--r-- | sp/src/game/server/hl2/npc_antlion.cpp | 5103 |
1 files changed, 5103 insertions, 0 deletions
diff --git a/sp/src/game/server/hl2/npc_antlion.cpp b/sp/src/game/server/hl2/npc_antlion.cpp new file mode 100644 index 00000000..f78e4d2f --- /dev/null +++ b/sp/src/game/server/hl2/npc_antlion.cpp @@ -0,0 +1,5103 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Antlion - nasty bug
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_hint.h"
+#include "ai_squad.h"
+#include "ai_moveprobe.h"
+#include "ai_route.h"
+#include "npcevent.h"
+#include "gib.h"
+#include "entitylist.h"
+#include "ndebugoverlay.h"
+#include "antlion_dust.h"
+#include "engine/IEngineSound.h"
+#include "globalstate.h"
+#include "movevars_shared.h"
+#include "te_effect_dispatch.h"
+#include "vehicle_base.h"
+#include "mapentities.h"
+#include "antlion_maker.h"
+#include "npc_antlion.h"
+#include "decals.h"
+#include "hl2_shareddefs.h"
+#include "explode.h"
+#include "weapon_physcannon.h"
+#include "baseparticleentity.h"
+#include "props.h"
+#include "particle_parse.h"
+#include "ai_tacticalservices.h"
+
+#ifdef HL2_EPISODIC
+#include "grenade_spit.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//Debug visualization
+ConVar g_debug_antlion( "g_debug_antlion", "0" );
+
+// base antlion stuff
+ConVar sk_antlion_health( "sk_antlion_health", "0" );
+ConVar sk_antlion_swipe_damage( "sk_antlion_swipe_damage", "0" );
+ConVar sk_antlion_jump_damage( "sk_antlion_jump_damage", "0" );
+ConVar sk_antlion_air_attack_dmg( "sk_antlion_air_attack_dmg", "0" );
+
+
+#ifdef HL2_EPISODIC
+
+// workers
+#define ANTLION_WORKERS_BURST() (true)
+#define ANTLION_WORKER_BURST_IS_POISONOUS() (true)
+
+ConVar sk_antlion_worker_burst_damage( "sk_antlion_worker_burst_damage", "50", FCVAR_NONE, "How much damage is inflicted by an antlion worker's death explosion." );
+ConVar sk_antlion_worker_health( "sk_antlion_worker_health", "0", FCVAR_NONE, "Hitpoints of an antlion worker. If 0, will use base antlion hitpoints." );
+ConVar sk_antlion_worker_spit_speed( "sk_antlion_worker_spit_speed", "0", FCVAR_NONE, "Speed at which an antlion spit grenade travels." );
+
+// This must agree with the AntlionWorkerBurstRadius() function!
+ConVar sk_antlion_worker_burst_radius( "sk_antlion_worker_burst_radius", "160", FCVAR_NONE, "Effect radius of an antlion worker's death explosion." );
+
+#endif
+
+ConVar g_test_new_antlion_jump( "g_test_new_antlion_jump", "1", FCVAR_ARCHIVE );
+ConVar antlion_easycrush( "antlion_easycrush", "1" );
+ConVar g_antlion_cascade_push( "g_antlion_cascade_push", "1", FCVAR_ARCHIVE );
+
+ConVar g_debug_antlion_worker( "g_debug_antlion_worker", "0" );
+
+extern ConVar bugbait_radius;
+
+int AE_ANTLION_WALK_FOOTSTEP;
+int AE_ANTLION_MELEE_HIT1;
+int AE_ANTLION_MELEE_HIT2;
+int AE_ANTLION_MELEE_POUNCE;
+int AE_ANTLION_FOOTSTEP_SOFT;
+int AE_ANTLION_FOOTSTEP_HEAVY;
+int AE_ANTLION_START_JUMP;
+int AE_ANTLION_BURROW_IN;
+int AE_ANTLION_BURROW_OUT;
+int AE_ANTLION_VANISH;
+int AE_ANTLION_OPEN_WINGS;
+int AE_ANTLION_CLOSE_WINGS;
+int AE_ANTLION_MELEE1_SOUND;
+int AE_ANTLION_MELEE2_SOUND;
+int AE_ANTLION_WORKER_EXPLODE_SCREAM;
+int AE_ANTLION_WORKER_EXPLODE_WARN;
+int AE_ANTLION_WORKER_EXPLODE;
+int AE_ANTLION_WORKER_SPIT;
+int AE_ANTLION_WORKER_DONT_EXPLODE;
+
+
+//Attack range definitions
+#define ANTLION_MELEE1_RANGE 100.0f
+#define ANTLION_MELEE2_RANGE 64.0f
+#define ANTLION_MELEE2_RANGE_MAX 175.0f
+#define ANTLION_MELEE2_RANGE_MIN 64.0f
+#define ANTLION_JUMP_MIN 128.0f
+
+#define ANTLION_JUMP_MAX_RISE 512.0f
+#define ANTLION_JUMP_MAX 1024.0f
+
+#define ANTLION_MIN_BUGBAIT_GOAL_TARGET_RADIUS 512
+
+//Interaction IDs
+int g_interactionAntlionFoundTarget = 0;
+int g_interactionAntlionFiredAtTarget = 0;
+
+#define ANTLION_MODEL "models/antlion.mdl"
+#define ANTLION_WORKER_MODEL "models/antlion_worker.mdl"
+
+#define ANTLION_BURROW_IN 0
+#define ANTLION_BURROW_OUT 1
+
+#define ANTLION_BUGBAIT_NAV_TOLERANCE 200
+
+#define ANTLION_OBEY_FOLLOW_TIME 5.0f
+
+
+//==================================================
+// AntlionSquadSlots
+//==================================================
+
+enum
+{
+ SQUAD_SLOT_ANTLION_JUMP = LAST_SHARED_SQUADSLOT,
+ SQUAD_SLOT_ANTLION_WORKER_FIRE,
+};
+
+//==================================================
+// Antlion Activities
+//==================================================
+
+int ACT_ANTLION_JUMP_START;
+int ACT_ANTLION_DISTRACT;
+int ACT_ANTLION_DISTRACT_ARRIVED;
+int ACT_ANTLION_BURROW_IN;
+int ACT_ANTLION_BURROW_OUT;
+int ACT_ANTLION_BURROW_IDLE;
+int ACT_ANTLION_RUN_AGITATED;
+int ACT_ANTLION_FLIP;
+int ACT_ANTLION_ZAP_FLIP;
+int ACT_ANTLION_POUNCE;
+int ACT_ANTLION_POUNCE_MOVING;
+int ACT_ANTLION_DROWN;
+int ACT_ANTLION_LAND;
+int ACT_ANTLION_WORKER_EXPLODE;
+
+
+//==================================================
+// CNPC_Antlion
+//==================================================
+
+CNPC_Antlion::CNPC_Antlion( void )
+{
+ m_flIdleDelay = 0.0f;
+ m_flBurrowTime = 0.0f;
+ m_flJumpTime = 0.0f;
+ m_flPounceTime = 0.0f;
+ m_flObeyFollowTime = 0.0f;
+ m_iUnBurrowAttempts = 0;
+
+ m_flAlertRadius = 256.0f;
+ m_flFieldOfView = -0.5f;
+
+ m_bStartBurrowed = false;
+ m_bAgitatedSound = false;
+ m_bWingsOpen = false;
+
+ m_flIgnoreSoundTime = 0.0f;
+ m_bHasHeardSound = false;
+
+ m_flNextAcknowledgeTime = 0.0f;
+ m_flNextJumpPushTime = 0.0f;
+
+ m_vecLastJumpAttempt.Init();
+ m_vecSavedJump.Init();
+
+ m_hFightGoalTarget = NULL;
+ m_hFollowTarget = NULL;
+ m_bLoopingStarted = false;
+
+ m_bForcedStuckJump = false;
+ m_nBodyBone = -1;
+ m_bSuppressUnburrowEffects = false;
+}
+
+LINK_ENTITY_TO_CLASS( npc_antlion, CNPC_Antlion );
+
+//==================================================
+// CNPC_Antlion::m_DataDesc
+//==================================================
+
+BEGIN_DATADESC( CNPC_Antlion )
+
+ DEFINE_KEYFIELD( m_bStartBurrowed, FIELD_BOOLEAN, "startburrowed" ),
+ DEFINE_KEYFIELD( m_bIgnoreBugbait, FIELD_BOOLEAN, "ignorebugbait" ),
+ DEFINE_KEYFIELD( m_flAlertRadius, FIELD_FLOAT, "radius" ),
+ DEFINE_KEYFIELD( m_flEludeDistance, FIELD_FLOAT, "eludedist" ),
+ DEFINE_KEYFIELD( m_bSuppressUnburrowEffects, FIELD_BOOLEAN, "unburroweffects" ),
+
+ DEFINE_FIELD( m_vecSaveSpitVelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flIdleDelay, FIELD_TIME ),
+ DEFINE_FIELD( m_flBurrowTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flJumpTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flPounceTime, FIELD_TIME ),
+ DEFINE_FIELD( m_iUnBurrowAttempts, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iContext, FIELD_INTEGER ),
+ DEFINE_FIELD( m_vecSavedJump, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecLastJumpAttempt, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flIgnoreSoundTime, FIELD_TIME ),
+ DEFINE_FIELD( m_vecHeardSound, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_bHasHeardSound, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bAgitatedSound, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bWingsOpen, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flNextAcknowledgeTime, FIELD_TIME ),
+ DEFINE_FIELD( m_hFollowTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hFightGoalTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_strParentSpawner, FIELD_STRING ),
+ DEFINE_FIELD( m_flSuppressFollowTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_MoveState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flObeyFollowTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bLeapAttack, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bDisableJump, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flTimeDrown, FIELD_TIME ),
+ DEFINE_FIELD( m_flTimeDrownSplash, FIELD_TIME ),
+ DEFINE_FIELD( m_bDontExplode, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flNextJumpPushTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bForcedStuckJump, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flZapDuration, FIELD_TIME ),
+#if HL2_EPISODIC
+ DEFINE_FIELD( m_bHasDoneAirAttack, FIELD_BOOLEAN ),
+#endif
+ // DEFINE_FIELD( m_bLoopingStarted, FIELD_BOOLEAN ),
+ // m_FollowBehavior
+ // m_AssaultBehavior
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Unburrow", InputUnburrow ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Burrow", InputBurrow ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "BurrowAway", InputBurrowAway ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "FightToPosition", InputFightToPosition ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "StopFightToPosition", InputStopFightToPosition ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableJump", InputEnableJump ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableJump", InputDisableJump ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "IgnoreBugbait", InputIgnoreBugbait ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "HearBugbait", InputHearBugbait ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "JumpAtTarget", InputJumpAtTarget ),
+
+ DEFINE_OUTPUT( m_OnReachFightGoal, "OnReachedFightGoal" ),
+ DEFINE_OUTPUT( m_OnUnBurrowed, "OnUnBurrowed" ),
+
+ // Function Pointers
+ DEFINE_ENTITYFUNC( Touch ),
+ DEFINE_USEFUNC( BurrowUse ),
+ DEFINE_THINKFUNC( ZapThink ),
+
+ // DEFINE_FIELD( FIELD_SHORT, m_hFootstep ),
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::Spawn( void )
+{
+ Precache();
+
+#ifdef _XBOX
+ // Always fade the corpse
+ AddSpawnFlags( SF_NPC_FADE_CORPSE );
+#endif // _XBOX
+
+#ifdef HL2_EPISODIC
+ if ( IsWorker() )
+ {
+ SetModel( ANTLION_WORKER_MODEL );
+ AddSpawnFlags( SF_NPC_LONG_RANGE );
+ SetBloodColor( BLOOD_COLOR_ANTLION_WORKER );
+ }
+ else
+ {
+ SetModel( ANTLION_MODEL );
+ SetBloodColor( BLOOD_COLOR_ANTLION );
+ }
+#else
+ SetModel( ANTLION_MODEL );
+ SetBloodColor( BLOOD_COLOR_YELLOW );
+#endif // HL2_EPISODIC
+
+ SetHullType(HULL_MEDIUM);
+ SetHullSizeNormal();
+ SetDefaultEyeOffset();
+
+ SetNavType( NAV_GROUND );
+
+ m_NPCState = NPC_STATE_NONE;
+
+#if HL2_EPISODIC
+ m_iHealth = ( IsWorker() ) ? sk_antlion_worker_health.GetFloat() : sk_antlion_health.GetFloat();
+#else
+ m_iHealth = sk_antlion_health.GetFloat();
+#endif // _DEBUG
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+
+ SetMoveType( MOVETYPE_STEP );
+
+ //Only do this if a squadname appears in the entity
+ if ( m_SquadName != NULL_STRING )
+ {
+ CapabilitiesAdd( bits_CAP_SQUAD );
+ }
+
+ SetCollisionGroup( HL2COLLISION_GROUP_ANTLION );
+
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_MOVE_JUMP | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 );
+
+ // Workers shoot projectiles
+ if ( IsWorker() )
+ {
+ CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 );
+ // CapabilitiesRemove( bits_CAP_INNATE_MELEE_ATTACK2 );
+ }
+
+ // JAY: Optimize these out for now
+ if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) == false )
+ CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
+
+ NPCInit();
+
+ if ( IsWorker() )
+ {
+ // Bump up the worker's eye position a bit
+ SetViewOffset( Vector( 0, 0, 32 ) );
+ }
+
+ // Antlions will always pursue
+ m_flDistTooFar = FLT_MAX;
+
+ m_bDisableJump = false;
+
+ //See if we're supposed to start burrowed
+ if ( m_bStartBurrowed )
+ {
+ AddEffects( EF_NODRAW );
+ AddFlag( FL_NOTARGET );
+ m_spawnflags |= SF_NPC_GAG;
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_NO;
+
+ SetState( NPC_STATE_IDLE );
+ SetActivity( (Activity) ACT_ANTLION_BURROW_IDLE );
+ SetSchedule( SCHED_ANTLION_WAIT_FOR_UNBORROW_TRIGGER );
+
+ SetUse( &CNPC_Antlion::BurrowUse );
+ }
+
+ BaseClass::Spawn();
+
+ m_nSkin = random->RandomInt( 0, ANTLION_SKIN_COUNT-1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::Activate( void )
+{
+ // If we're friendly to the player, setup a relationship to reflect it
+ if ( IsAllied() )
+ {
+ // Handle all clients
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+
+ if ( pPlayer != NULL )
+ {
+ AddEntityRelationship( pPlayer, D_LI, 99 );
+ }
+ }
+ }
+
+ BaseClass::Activate();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: override this to simplify the physics shadow of the antlions
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::CreateVPhysics()
+{
+ bool bRet = BaseClass::CreateVPhysics();
+ return bRet;
+}
+
+// Use all the gibs
+#define NUM_ANTLION_GIBS_UNIQUE 3
+const char *pszAntlionGibs_Unique[NUM_ANTLION_GIBS_UNIQUE] = {
+ "models/gibs/antlion_gib_large_1.mdl",
+ "models/gibs/antlion_gib_large_2.mdl",
+ "models/gibs/antlion_gib_large_3.mdl"
+};
+
+#define NUM_ANTLION_GIBS_MEDIUM 3
+const char *pszAntlionGibs_Medium[NUM_ANTLION_GIBS_MEDIUM] = {
+ "models/gibs/antlion_gib_medium_1.mdl",
+ "models/gibs/antlion_gib_medium_2.mdl",
+ "models/gibs/antlion_gib_medium_3.mdl"
+};
+
+// XBox doesn't use the smaller gibs, so don't cache them
+#define NUM_ANTLION_GIBS_SMALL 3
+const char *pszAntlionGibs_Small[NUM_ANTLION_GIBS_SMALL] = {
+ "models/gibs/antlion_gib_small_1.mdl",
+ "models/gibs/antlion_gib_small_2.mdl",
+ "models/gibs/antlion_gib_small_3.mdl"
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::Precache( void )
+{
+#ifdef HL2_EPISODIC
+ if ( IsWorker() )
+ {
+ PrecacheModel( ANTLION_WORKER_MODEL );
+ PropBreakablePrecacheAll( MAKE_STRING( ANTLION_WORKER_MODEL ) );
+ UTIL_PrecacheOther( "grenade_spit" );
+ PrecacheParticleSystem( "blood_impact_antlion_worker_01" );
+ PrecacheParticleSystem( "antlion_gib_02" );
+ PrecacheParticleSystem( "blood_impact_yellow_01" );
+ }
+ else
+#endif // HL2_EPISODIC
+ {
+ PrecacheModel( ANTLION_MODEL );
+ PropBreakablePrecacheAll( MAKE_STRING( ANTLION_MODEL ) );
+ PrecacheParticleSystem( "blood_impact_antlion_01" );
+ PrecacheParticleSystem( "AntlionGib" );
+ }
+
+ for ( int i = 0; i < NUM_ANTLION_GIBS_UNIQUE; ++i )
+ {
+ PrecacheModel( pszAntlionGibs_Unique[ i ] );
+ }
+ for ( int i = 0; i < NUM_ANTLION_GIBS_MEDIUM; ++i )
+ {
+ PrecacheModel( pszAntlionGibs_Medium[ i ] );
+ }
+ for ( int i = 0; i < NUM_ANTLION_GIBS_SMALL; ++i )
+ {
+ PrecacheModel( pszAntlionGibs_Small[ i ] );
+ }
+
+ PrecacheScriptSound( "NPC_Antlion.RunOverByVehicle" );
+ PrecacheScriptSound( "NPC_Antlion.MeleeAttack" );
+ m_hFootstep = PrecacheScriptSound( "NPC_Antlion.Footstep" );
+ PrecacheScriptSound( "NPC_Antlion.BurrowIn" );
+ PrecacheScriptSound( "NPC_Antlion.BurrowOut" );
+ PrecacheScriptSound( "NPC_Antlion.FootstepSoft" );
+ PrecacheScriptSound( "NPC_Antlion.FootstepHeavy" );
+ PrecacheScriptSound( "NPC_Antlion.MeleeAttackSingle" );
+ PrecacheScriptSound( "NPC_Antlion.MeleeAttackDouble" );
+ PrecacheScriptSound( "NPC_Antlion.Distracted" );
+ PrecacheScriptSound( "NPC_Antlion.Idle" );
+ PrecacheScriptSound( "NPC_Antlion.Pain" );
+ PrecacheScriptSound( "NPC_Antlion.Land" );
+ PrecacheScriptSound( "NPC_Antlion.WingsOpen" );
+ PrecacheScriptSound( "NPC_Antlion.LoopingAgitated" );
+ PrecacheScriptSound( "NPC_Antlion.Distracted" );
+
+#ifdef HL2_EPISODIC
+ PrecacheScriptSound( "NPC_Antlion.PoisonBurstScream" );
+ PrecacheScriptSound( "NPC_Antlion.PoisonBurstScreamSubmerged" );
+ PrecacheScriptSound( "NPC_Antlion.PoisonBurstExplode" );
+ PrecacheScriptSound( "NPC_Antlion.MeleeAttack_Muffled" );
+ PrecacheScriptSound( "NPC_Antlion.TrappedMetal" );
+ PrecacheScriptSound( "NPC_Antlion.ZappedFlip" );
+ PrecacheScriptSound( "NPC_Antlion.PoisonShoot" );
+ PrecacheScriptSound( "NPC_Antlion.PoisonBall" );
+#endif
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+inline CBaseEntity *CNPC_Antlion::EntityToWatch( void )
+{
+ return ( m_hFollowTarget != NULL ) ? m_hFollowTarget.Get() : GetEnemy();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Cache whatever pose parameters we intend to use
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::PopulatePoseParameters( void )
+{
+ m_poseHead_Pitch = LookupPoseParameter("head_pitch");
+ m_poseHead_Yaw = LookupPoseParameter("head_yaw" );
+
+ BaseClass::PopulatePoseParameters();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::UpdateHead( void )
+{
+ float yaw = GetPoseParameter( m_poseHead_Yaw );
+ float pitch = GetPoseParameter( m_poseHead_Pitch );
+
+ CBaseEntity *pTarget = EntityToWatch();
+
+ if ( pTarget != NULL )
+ {
+ Vector enemyDir = pTarget->WorldSpaceCenter() - WorldSpaceCenter();
+ VectorNormalize( enemyDir );
+
+ if ( DotProduct( enemyDir, BodyDirection3D() ) < 0.0f )
+ {
+ SetPoseParameter( m_poseHead_Yaw, UTIL_Approach( 0, yaw, 10 ) );
+ SetPoseParameter( m_poseHead_Pitch, UTIL_Approach( 0, pitch, 10 ) );
+
+ return;
+ }
+
+ float facingYaw = VecToYaw( BodyDirection3D() );
+ float yawDiff = VecToYaw( enemyDir );
+ yawDiff = UTIL_AngleDiff( yawDiff, facingYaw + yaw );
+
+ float facingPitch = UTIL_VecToPitch( BodyDirection3D() );
+ float pitchDiff = UTIL_VecToPitch( enemyDir );
+ pitchDiff = UTIL_AngleDiff( pitchDiff, facingPitch + pitch );
+
+ SetPoseParameter( m_poseHead_Yaw, UTIL_Approach( yaw + yawDiff, yaw, 50 ) );
+ SetPoseParameter( m_poseHead_Pitch, UTIL_Approach( pitch + pitchDiff, pitch, 50 ) );
+ }
+ else
+ {
+ SetPoseParameter( m_poseHead_Yaw, UTIL_Approach( 0, yaw, 10 ) );
+ SetPoseParameter( m_poseHead_Pitch, UTIL_Approach( 0, pitch, 10 ) );
+ }
+}
+
+#define ANTLION_VIEW_FIELD_NARROW 0.85f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEntity -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::FInViewCone( CBaseEntity *pEntity )
+{
+ m_flFieldOfView = ( GetEnemy() != NULL ) ? ANTLION_VIEW_FIELD_NARROW : VIEW_FIELD_WIDE;
+
+ return BaseClass::FInViewCone( pEntity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &vecSpot -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::FInViewCone( const Vector &vecSpot )
+{
+ m_flFieldOfView = ( GetEnemy() != NULL ) ? ANTLION_VIEW_FIELD_NARROW : VIEW_FIELD_WIDE;
+
+ return BaseClass::FInViewCone( vecSpot );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::CanBecomeRagdoll()
+{
+ // This prevents us from dying in the regular way. It forces a schedule selection
+ // that will select SCHED_DIE, where we can do our poison burst thing.
+#ifdef HL2_EPISODIC
+ if ( IsWorker() && ANTLION_WORKERS_BURST() )
+ {
+ // If we're in a script, we're allowed to ragdoll. This lets the vort's dynamic
+ // interaction ragdoll us.
+ return ( m_NPCState == NPC_STATE_SCRIPT || m_bDontExplode );
+ }
+#endif
+ return BaseClass::CanBecomeRagdoll();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pVictim -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::Event_Killed( const CTakeDamageInfo &info )
+{
+ //Turn off wings
+ SetWings( false );
+ VacateStrategySlot();
+
+ if ( IsCurSchedule(SCHED_ANTLION_BURROW_IN) || IsCurSchedule(SCHED_ANTLION_BURROW_OUT) )
+ {
+ AddEFlags( EF_NOSHADOW );
+ }
+
+ if ( info.GetDamageType() & DMG_CRUSH )
+ {
+ CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, GetAbsOrigin(), 256, 0.5f, this );
+ }
+
+ BaseClass::Event_Killed( info );
+
+ CBaseEntity *pAttacker = info.GetInflictor();
+
+ if ( pAttacker && pAttacker->GetServerVehicle() && ShouldGib( info ) == true )
+ {
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin() + Vector( 0, 0, 64 ), pAttacker->GetAbsOrigin(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ UTIL_DecalTrace( &tr, "Antlion.Splat" );
+
+ SpawnBlood( GetAbsOrigin(), g_vecAttackDir, BloodColor(), info.GetDamage() );
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NPC_Antlion.RunOverByVehicle" );
+ }
+
+ // Stop our zap effect!
+ SetContextThink( NULL, gpGlobals->curtime, "ZapThink" );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::MeleeAttack( float distance, float damage, QAngle &viewPunch, Vector &shove )
+{
+ Vector vecForceDir;
+
+ // Always hurt bullseyes for now
+ if ( ( GetEnemy() != NULL ) && ( GetEnemy()->Classify() == CLASS_BULLSEYE ) )
+ {
+ vecForceDir = (GetEnemy()->GetAbsOrigin() - GetAbsOrigin());
+ CTakeDamageInfo info( this, this, damage, DMG_SLASH );
+ CalculateMeleeDamageForce( &info, vecForceDir, GetEnemy()->GetAbsOrigin() );
+ GetEnemy()->TakeDamage( info );
+ return;
+ }
+
+ CBaseEntity *pHurt = CheckTraceHullAttack( distance, -Vector(16,16,32), Vector(16,16,32), damage, DMG_SLASH, 5.0f );
+
+ if ( pHurt )
+ {
+ vecForceDir = ( pHurt->WorldSpaceCenter() - WorldSpaceCenter() );
+
+ //FIXME: Until the interaction is setup, kill combine soldiers in one hit -- jdw
+ if ( FClassnameIs( pHurt, "npc_combine_s" ) )
+ {
+ CTakeDamageInfo dmgInfo( this, this, pHurt->m_iHealth+25, DMG_SLASH );
+ CalculateMeleeDamageForce( &dmgInfo, vecForceDir, pHurt->GetAbsOrigin() );
+ pHurt->TakeDamage( dmgInfo );
+ return;
+ }
+
+ CBasePlayer *pPlayer = ToBasePlayer( pHurt );
+
+ if ( pPlayer != NULL )
+ {
+ //Kick the player angles
+ if ( !(pPlayer->GetFlags() & FL_GODMODE ) && pPlayer->GetMoveType() != MOVETYPE_NOCLIP )
+ {
+ pPlayer->ViewPunch( viewPunch );
+
+ Vector dir = pHurt->GetAbsOrigin() - GetAbsOrigin();
+ VectorNormalize(dir);
+
+ QAngle angles;
+ VectorAngles( dir, angles );
+ Vector forward, right;
+ AngleVectors( angles, &forward, &right, NULL );
+
+ //Push the target back
+ pHurt->ApplyAbsVelocityImpulse( - right * shove[1] - forward * shove[0] );
+ }
+ }
+
+ // Play a random attack hit sound
+ EmitSound( "NPC_Antlion.MeleeAttack" );
+ }
+}
+
+// Number of times the antlions will attempt to generate a random chase position
+#define NUM_CHASE_POSITION_ATTEMPTS 3
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &targetPos -
+// &result -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::FindChasePosition( const Vector &targetPos, Vector &result )
+{
+ if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) == true )
+ {
+ result = targetPos;
+ return true;
+ }
+
+ Vector runDir = ( targetPos - GetAbsOrigin() );
+ VectorNormalize( runDir );
+
+ Vector vRight, vUp;
+ VectorVectors( runDir, vRight, vUp );
+
+ for ( int i = 0; i < NUM_CHASE_POSITION_ATTEMPTS; i++ )
+ {
+ result = targetPos;
+ result += -runDir * random->RandomInt( 64, 128 );
+ result += vRight * random->RandomInt( -128, 128 );
+
+ //FIXME: We need to do a more robust search here
+ // Find a ground position and try to get there
+ if ( GetGroundPosition( result, result ) )
+ return true;
+ }
+
+ //TODO: If we're making multiple inquiries to this, make sure it's evenly spread
+
+ if ( g_debug_antlion.GetInt() == 1 )
+ {
+ NDebugOverlay::Cross3D( result, -Vector(32,32,32), Vector(32,32,32), 255, 255, 0, true, 2.0f );
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &testPos -
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::GetGroundPosition( const Vector &testPos, Vector &result )
+{
+ // Trace up to clear the ground
+ trace_t tr;
+ AI_TraceHull( testPos, testPos + Vector( 0, 0, 64 ), NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ // If we're stuck in solid, this can't be valid
+ if ( tr.allsolid )
+ {
+ if ( g_debug_antlion.GetInt() == 3 )
+ {
+ NDebugOverlay::BoxDirection( testPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ) + Vector( 0, 0, 128 ), Vector( 0, 0, 1 ), 255, 0, 0, true, 2.0f );
+ }
+
+ return false;
+ }
+
+ if ( g_debug_antlion.GetInt() == 3 )
+ {
+ NDebugOverlay::BoxDirection( testPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ) + Vector( 0, 0, 128 ), Vector( 0, 0, 1 ), 0, 255, 0, true, 2.0f );
+ }
+
+ // Trace down to find the ground
+ AI_TraceHull( tr.endpos, tr.endpos - Vector( 0, 0, 128 ), NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( g_debug_antlion.GetInt() == 3 )
+ {
+ NDebugOverlay::BoxDirection( tr.endpos, NAI_Hull::Mins( GetHullType() ) - Vector( 0, 0, 256 ), NAI_Hull::Maxs( GetHullType() ), Vector( 0, 0, 1 ), 255, 255, 0, true, 2.0f );
+ }
+
+ // We must end up on the floor with this trace
+ if ( tr.fraction < 1.0f )
+ {
+ if ( g_debug_antlion.GetInt() == 3 )
+ {
+ NDebugOverlay::Cross3D( tr.endpos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), 255, 0, 0, true, 2.0f );
+ }
+
+ result = tr.endpos;
+ return true;
+ }
+
+ // Ended up in open space
+ return false;
+}
+void CNPC_Antlion::ManageFleeCapabilities( bool bEnable )
+{
+ if ( bEnable == false )
+ {
+ //Remove the jump capabilty when we build our route.
+ //We'll enable it back again after the route has been built.
+ CapabilitiesRemove( bits_CAP_MOVE_JUMP );
+
+ if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) == false )
+ CapabilitiesRemove( bits_CAP_SKIP_NAV_GROUND_CHECK );
+ }
+ else
+ {
+ if ( m_bDisableJump == false )
+ CapabilitiesAdd( bits_CAP_MOVE_JUMP );
+
+ if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) == false )
+ CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : soundType -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::GetPathToSoundFleePoint( int soundType )
+{
+ CSound *pSound = GetLoudestSoundOfType( soundType );
+
+ if ( pSound == NULL )
+ {
+ //NOTENOTE: If you're here, there's a disparity between Listen() and GetLoudestSoundOfType() - jdw
+ TaskFail( "Unable to find thumper sound!" );
+ return false;
+ }
+
+ ManageFleeCapabilities( false );
+
+ //Try and find a hint-node first
+ CHintCriteria hintCriteria;
+
+ hintCriteria.SetHintType( HINT_ANTLION_THUMPER_FLEE_POINT );
+ hintCriteria.SetFlag( bits_HINT_NODE_NEAREST );
+ hintCriteria.AddIncludePosition( WorldSpaceCenter(), 2500 );
+
+ CAI_Hint *pHint = CAI_HintManager::FindHint( WorldSpaceCenter(), hintCriteria );
+
+ Vector vecFleeGoal;
+ Vector vecSoundPos = pSound->GetSoundOrigin();
+
+ // Put the sound location on the same plane as the antlion.
+ vecSoundPos.z = GetAbsOrigin().z;
+
+ Vector vecFleeDir = GetAbsOrigin() - vecSoundPos;
+ VectorNormalize( vecFleeDir );
+
+ if ( pHint != NULL )
+ {
+ // Get our goal position
+ pHint->GetPosition( this, &vecFleeGoal );
+
+ // Find a route to that position
+ AI_NavGoal_t goal( vecFleeGoal, (Activity) ACT_ANTLION_RUN_AGITATED, 128, AIN_DEF_FLAGS );
+
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ pHint->Lock( this );
+ pHint->Unlock( 2.0f );
+
+ GetNavigator()->SetArrivalActivity( (Activity) ACT_ANTLION_DISTRACT_ARRIVED );
+ GetNavigator()->SetArrivalDirection( -vecFleeDir );
+
+ ManageFleeCapabilities( true );
+ return true;
+ }
+ }
+
+ //Make us offset this a little at least
+ float flFleeYaw = VecToYaw( vecFleeDir ) + random->RandomInt( -20, 20 );
+
+ vecFleeDir = UTIL_YawToVector( flFleeYaw );
+
+ // Move us to the outer radius of the noise (with some randomness)
+ vecFleeGoal = vecSoundPos + vecFleeDir * ( pSound->Volume() + random->RandomInt( 32, 64 ) );
+
+ // Find a route to that position
+ AI_NavGoal_t goal( vecFleeGoal + Vector( 0, 0, 8 ), (Activity) ACT_ANTLION_RUN_AGITATED, 512, AIN_DEF_FLAGS );
+
+ if ( GetNavigator()->SetGoal( goal ) )
+ {
+ GetNavigator()->SetArrivalActivity( (Activity) ACT_ANTLION_DISTRACT_ARRIVED );
+ GetNavigator()->SetArrivalDirection( -vecFleeDir );
+
+ ManageFleeCapabilities( true );
+ return true;
+ }
+
+ ManageFleeCapabilities( true );
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether the enemy has been seen within the time period supplied
+// Input : flTime - Timespan we consider
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::SeenEnemyWithinTime( float flTime )
+{
+ float flLastSeenTime = GetEnemies()->LastTimeSeen( GetEnemy() );
+ return ( flLastSeenTime != 0.0f && ( gpGlobals->curtime - flLastSeenTime ) < flTime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Test whether this antlion can hit the target
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::InnateWeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
+{
+ if ( GetNextAttack() > gpGlobals->curtime )
+ return false;
+
+ // If we can see the enemy, or we've seen them in the last few seconds just try to lob in there
+ if ( SeenEnemyWithinTime( 3.0f ) )
+ {
+ Vector vSpitPos;
+ GetAttachment( "mouth", vSpitPos );
+
+ return GetSpitVector( vSpitPos, targetPos, &m_vecSaveSpitVelocity );
+ }
+
+ return BaseClass::InnateWeaponLOSCondition( ownerPos, targetPos, bSetConditions );
+}
+
+//
+// FIXME: Create this in a better fashion!
+//
+
+Vector VecCheckThrowTolerance( CBaseEntity *pEdict, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flTolerance )
+{
+ flSpeed = MAX( 1.0f, flSpeed );
+
+ float flGravity = GetCurrentGravity();
+
+ Vector vecGrenadeVel = (vecSpot2 - vecSpot1);
+
+ // throw at a constant time
+ float time = vecGrenadeVel.Length( ) / flSpeed;
+ vecGrenadeVel = vecGrenadeVel * (1.0 / time);
+
+ // adjust upward toss to compensate for gravity loss
+ vecGrenadeVel.z += flGravity * time * 0.5;
+
+ Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5;
+ vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5);
+
+
+ trace_t tr;
+ UTIL_TraceLine( vecSpot1, vecApex, MASK_SOLID, pEdict, COLLISION_GROUP_NONE, &tr );
+ if (tr.fraction != 1.0)
+ {
+ // fail!
+ if ( g_debug_antlion_worker.GetBool() )
+ {
+ NDebugOverlay::Line( vecSpot1, vecApex, 255, 0, 0, true, 5.0 );
+ }
+
+ return vec3_origin;
+ }
+
+ if ( g_debug_antlion_worker.GetBool() )
+ {
+ NDebugOverlay::Line( vecSpot1, vecApex, 0, 255, 0, true, 5.0 );
+ }
+
+ UTIL_TraceLine( vecApex, vecSpot2, MASK_SOLID_BRUSHONLY, pEdict, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction != 1.0 )
+ {
+ bool bFail = true;
+
+ // Didn't make it all the way there, but check if we're within our tolerance range
+ if ( flTolerance > 0.0f )
+ {
+ float flNearness = ( tr.endpos - vecSpot2 ).LengthSqr();
+ if ( flNearness < Square( flTolerance ) )
+ {
+ if ( g_debug_antlion_worker.GetBool() )
+ {
+ NDebugOverlay::Sphere( tr.endpos, vec3_angle, flTolerance, 0, 255, 0, 0, true, 5.0 );
+ }
+
+ bFail = false;
+ }
+ }
+
+ if ( bFail )
+ {
+ if ( g_debug_antlion_worker.GetBool() )
+ {
+ NDebugOverlay::Line( vecApex, vecSpot2, 255, 0, 0, true, 5.0 );
+ NDebugOverlay::Sphere( tr.endpos, vec3_angle, flTolerance, 255, 0, 0, 0, true, 5.0 );
+ }
+ return vec3_origin;
+ }
+ }
+
+ if ( g_debug_antlion_worker.GetBool() )
+ {
+ NDebugOverlay::Line( vecApex, vecSpot2, 0, 255, 0, true, 5.0 );
+ }
+
+ return vecGrenadeVel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get a toss direction that will properly lob spit to hit a target
+// Input : &vecStartPos - Where the spit will start from
+// &vecTarget - Where the spit is meant to land
+// *vecOut - The resulting vector to lob the spit
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::GetSpitVector( const Vector &vecStartPos, const Vector &vecTarget, Vector *vecOut )
+{
+ // antlion workers exist only in episodic.
+#if HL2_EPISODIC
+ // Try the most direct route
+ Vector vecToss = VecCheckThrowTolerance( this, vecStartPos, vecTarget, sk_antlion_worker_spit_speed.GetFloat(), (10.0f*12.0f) );
+
+ // If this failed then try a little faster (flattens the arc)
+ if ( vecToss == vec3_origin )
+ {
+ vecToss = VecCheckThrowTolerance( this, vecStartPos, vecTarget, sk_antlion_worker_spit_speed.GetFloat() * 1.5f, (10.0f*12.0f) );
+ if ( vecToss == vec3_origin )
+ return false;
+ }
+
+ // Save out the result
+ if ( vecOut )
+ {
+ *vecOut = vecToss;
+ }
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flDuration -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::DelaySquadAttack( float flDuration )
+{
+ if ( GetSquad() )
+ {
+ // Reduce the duration by as much as 50% of the total time to make this less robotic
+ float flAdjDuration = flDuration - random->RandomFloat( 0.0f, (flDuration*0.5f) );
+ GetSquad()->BroadcastInteraction( g_interactionAntlionFiredAtTarget, (void *)&flAdjDuration, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEvent -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::HandleAnimEvent( animevent_t *pEvent )
+{
+#ifdef HL2_EPISODIC
+ // Handle the spit event
+ if ( pEvent->event == AE_ANTLION_WORKER_SPIT )
+ {
+ if ( GetEnemy() )
+ {
+ Vector vSpitPos;
+ GetAttachment( "mouth", vSpitPos );
+
+ Vector vTarget;
+
+ // If our enemy is looking at us and far enough away, lead him
+ if ( HasCondition( COND_ENEMY_FACING_ME ) && UTIL_DistApprox( GetAbsOrigin(), GetEnemy()->GetAbsOrigin() ) > (40*12) )
+ {
+ UTIL_PredictedPosition( GetEnemy(), 0.5f, &vTarget );
+ vTarget.z = GetEnemy()->GetAbsOrigin().z;
+ }
+ else
+ {
+ // Otherwise he can't see us and he won't be able to dodge
+ vTarget = GetEnemy()->BodyTarget( vSpitPos, true );
+ }
+
+ vTarget[2] += random->RandomFloat( 0.0f, 32.0f );
+
+ // Try and spit at our target
+ Vector vecToss;
+ if ( GetSpitVector( vSpitPos, vTarget, &vecToss ) == false )
+ {
+ // Now try where they were
+ if ( GetSpitVector( vSpitPos, m_vSavePosition, &vecToss ) == false )
+ {
+ // Failing that, just shoot with the old velocity we calculated initially!
+ vecToss = m_vecSaveSpitVelocity;
+ }
+ }
+
+ // Find what our vertical theta is to estimate the time we'll impact the ground
+ Vector vecToTarget = ( vTarget - vSpitPos );
+ VectorNormalize( vecToTarget );
+ float flVelocity = VectorNormalize( vecToss );
+ float flCosTheta = DotProduct( vecToTarget, vecToss );
+ float flTime = (vSpitPos-vTarget).Length2D() / ( flVelocity * flCosTheta );
+
+ // Emit a sound where this is going to hit so that targets get a chance to act correctly
+ CSoundEnt::InsertSound( SOUND_DANGER, vTarget, (15*12), flTime, this );
+
+ // Don't fire again until this volley would have hit the ground (with some lag behind it)
+ SetNextAttack( gpGlobals->curtime + flTime + random->RandomFloat( 0.5f, 2.0f ) );
+
+ // Tell any squadmates not to fire for some portion of the time this volley will be in the air (except on hard)
+ if ( g_pGameRules->IsSkillLevel( SKILL_HARD ) == false )
+ DelaySquadAttack( flTime );
+
+ for ( int i = 0; i < 6; i++ )
+ {
+ CGrenadeSpit *pGrenade = (CGrenadeSpit*) CreateEntityByName( "grenade_spit" );
+ pGrenade->SetAbsOrigin( vSpitPos );
+ pGrenade->SetAbsAngles( vec3_angle );
+ DispatchSpawn( pGrenade );
+ pGrenade->SetThrower( this );
+ pGrenade->SetOwnerEntity( this );
+
+ if ( i == 0 )
+ {
+ pGrenade->SetSpitSize( SPIT_LARGE );
+ pGrenade->SetAbsVelocity( vecToss * flVelocity );
+ }
+ else
+ {
+ pGrenade->SetAbsVelocity( ( vecToss + RandomVector( -0.035f, 0.035f ) ) * flVelocity );
+ pGrenade->SetSpitSize( random->RandomInt( SPIT_SMALL, SPIT_MEDIUM ) );
+ }
+
+ // Tumble through the air
+ pGrenade->SetLocalAngularVelocity(
+ QAngle( random->RandomFloat( -250, -500 ),
+ random->RandomFloat( -250, -500 ),
+ random->RandomFloat( -250, -500 ) ) );
+ }
+
+ for ( int i = 0; i < 8; i++ )
+ {
+ DispatchParticleEffect( "blood_impact_yellow_01", vSpitPos + RandomVector( -12.0f, 12.0f ), RandomAngle( 0, 360 ) );
+ }
+
+ EmitSound( "NPC_Antlion.PoisonShoot" );
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_WORKER_DONT_EXPLODE )
+ {
+ m_bDontExplode = true;
+ return;
+ }
+
+#endif // HL2_EPISODIC
+
+ if ( pEvent->event == AE_ANTLION_WALK_FOOTSTEP )
+ {
+ MakeAIFootstepSound( 240.0f );
+ EmitSound( "NPC_Antlion.Footstep", m_hFootstep, pEvent->eventtime );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_MELEE_HIT1 )
+ {
+ QAngle qa( 20.0f, 0.0f, -12.0f );
+ Vector vec( -250.0f, 1.0f, 1.0f );
+ MeleeAttack( ANTLION_MELEE1_RANGE, sk_antlion_swipe_damage.GetFloat(), qa, vec );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_MELEE_HIT2 )
+ {
+ QAngle qa( 20.0f, 0.0f, 0.0f );
+ Vector vec( -350.0f, 1.0f, 1.0f );
+ MeleeAttack( ANTLION_MELEE1_RANGE, sk_antlion_swipe_damage.GetFloat(), qa, vec );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_MELEE_POUNCE )
+ {
+ QAngle qa( 4.0f, 0.0f, 0.0f );
+ Vector vec( -250.0f, 1.0f, 1.0f );
+ MeleeAttack( ANTLION_MELEE2_RANGE, sk_antlion_swipe_damage.GetFloat(), qa, vec );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_OPEN_WINGS )
+ {
+ SetWings( true );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_CLOSE_WINGS )
+ {
+ SetWings( false );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_VANISH )
+ {
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_NO;
+ AddEffects( EF_NODRAW );
+ SetWings( false );
+
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_BURROW_IN )
+ {
+ //Burrowing sound
+ EmitSound( "NPC_Antlion.BurrowIn" );
+
+ //Shake the screen
+ UTIL_ScreenShake( GetAbsOrigin(), 0.5f, 80.0f, 1.0f, 256.0f, SHAKE_START );
+
+ //Throw dust up
+ CreateDust();
+
+ if ( GetHintNode() )
+ {
+ GetHintNode()->Unlock( 2.0f );
+ }
+
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_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
+ CreateDust();
+
+ RemoveEffects( EF_NODRAW );
+ RemoveFlag( FL_NOTARGET );
+
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_FOOTSTEP_SOFT )
+ {
+ EmitSound( "NPC_Antlion.FootstepSoft", pEvent->eventtime );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_FOOTSTEP_HEAVY )
+ {
+ EmitSound( "NPC_Antlion.FootstepHeavy", pEvent->eventtime );
+ return;
+ }
+
+
+ if ( pEvent->event == AE_ANTLION_MELEE1_SOUND )
+ {
+ EmitSound( "NPC_Antlion.MeleeAttackSingle" );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_MELEE2_SOUND )
+ {
+ EmitSound( "NPC_Antlion.MeleeAttackDouble" );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_START_JUMP )
+ {
+ StartJump();
+ return;
+ }
+
+ // antlion worker events
+#if HL2_EPISODIC
+ if ( pEvent->event == AE_ANTLION_WORKER_EXPLODE_SCREAM )
+ {
+ if ( GetWaterLevel() < 2 )
+ {
+ EmitSound( "NPC_Antlion.PoisonBurstScream" );
+ }
+ else
+ {
+ EmitSound( "NPC_Antlion.PoisonBurstScreamSubmerged" );
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_WORKER_EXPLODE_WARN )
+ {
+ CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, GetAbsOrigin(), sk_antlion_worker_burst_radius.GetFloat(), 0.5f, this );
+ return;
+ }
+
+ if ( pEvent->event == AE_ANTLION_WORKER_EXPLODE )
+ {
+ CTakeDamageInfo info( this, this, sk_antlion_worker_burst_damage.GetFloat(), DMG_BLAST_SURFACE | ( ANTLION_WORKER_BURST_IS_POISONOUS() ? DMG_POISON : DMG_ACID ) );
+ Event_Gibbed( info );
+ return;
+ }
+#endif
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+bool CNPC_Antlion::IsUnusableNode(int iNodeID, CAI_Hint *pHint)
+{
+ bool iBaseReturn = BaseClass::IsUnusableNode( iNodeID, pHint );
+
+ if ( g_test_new_antlion_jump.GetBool() == 0 )
+ return iBaseReturn;
+
+ CAI_Node *pNode = GetNavigator()->GetNetwork()->GetNode( iNodeID );
+
+ if ( pNode )
+ {
+ if ( pNode->IsLocked() )
+ return true;
+ }
+
+ return iBaseReturn;
+}
+
+void CNPC_Antlion::LockJumpNode( void )
+{
+ if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) == false )
+ return;
+
+ if ( GetNavigator()->GetPath() == NULL )
+ return;
+
+ if ( g_test_new_antlion_jump.GetBool() == false )
+ return;
+
+ AI_Waypoint_t *pWaypoint = GetNavigator()->GetPath()->GetCurWaypoint();
+
+ while ( pWaypoint )
+ {
+ AI_Waypoint_t *pNextWaypoint = pWaypoint->GetNext();
+ if ( pNextWaypoint && pNextWaypoint->NavType() == NAV_JUMP && pWaypoint->iNodeID != NO_NODE )
+ {
+ CAI_Node *pNode = GetNavigator()->GetNetwork()->GetNode( pWaypoint->iNodeID );
+
+ if ( pNode )
+ {
+ //NDebugOverlay::Box( pNode->GetOrigin(), Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 255, 0, 0, 0, 2 );
+ pNode->Lock( 0.5f );
+ break;
+ }
+ }
+ else
+ {
+ pWaypoint = pWaypoint->GetNext();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult )
+{
+ bool iBaseReturn = BaseClass::OnObstructionPreSteer( pMoveGoal, distClear, pResult );
+
+ if ( g_test_new_antlion_jump.GetBool() == false )
+ return iBaseReturn;
+
+ if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) == false )
+ return iBaseReturn;
+
+ CAI_BaseNPC *pBlocker = pMoveGoal->directTrace.pObstruction->MyNPCPointer();
+
+ if ( pBlocker && pBlocker->Classify() == CLASS_ANTLION )
+ {
+ // HACKHACK
+ CNPC_Antlion *pAntlion = dynamic_cast< CNPC_Antlion * > ( pBlocker );
+
+ if ( pAntlion )
+ {
+ if ( pAntlion->AllowedToBePushed() == true && GetEnemy() == NULL )
+ {
+ //NDebugOverlay::Box( pAntlion->GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 2 );
+ pAntlion->GetMotor()->SetIdealYawToTarget( WorldSpaceCenter() );
+ pAntlion->SetSchedule( SCHED_MOVE_AWAY );
+ pAntlion->m_flNextJumpPushTime = gpGlobals->curtime + 2.0f;
+ }
+ }
+ }
+
+ return iBaseReturn;
+}
+
+bool NPC_Antlion_IsAntlion( CBaseEntity *pEntity )
+{
+ CNPC_Antlion *pAntlion = dynamic_cast<CNPC_Antlion *>(pEntity);
+
+ return pAntlion ? true : false;
+}
+
+class CTraceFilterAntlion : public CTraceFilterEntitiesOnly
+{
+public:
+ CTraceFilterAntlion( const CBaseEntity *pEntity ) { m_pIgnore = pEntity; }
+
+ virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+ {
+ CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
+
+ if ( m_pIgnore == pEntity )
+ return false;
+
+ if ( pEntity->IsNPC() == false )
+ return false;
+
+ if ( NPC_Antlion_IsAntlion( pEntity ) )
+ return true;
+
+ return false;
+ }
+private:
+
+ const CBaseEntity *m_pIgnore;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_ANTLION_FIND_COVER_FROM_SAVEPOSITION:
+ {
+ Vector coverPos;
+
+ if ( GetTacticalServices()->FindCoverPos( m_vSavePosition, EyePosition(), 0, CoverRadius(), &coverPos ) )
+ {
+ AI_NavGoal_t goal(coverPos, ACT_RUN, AIN_HULL_TOLERANCE);
+ GetNavigator()->SetGoal( goal );
+
+ m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
+ }
+ else
+ {
+ // no coverwhatsoever.
+ TaskFail(FAIL_NO_COVER);
+ }
+ }
+ break;
+
+ case TASK_ANNOUNCE_ATTACK:
+ {
+ EmitSound( "NPC_Antlion.MeleeAttackSingle" );
+ TaskComplete();
+ break;
+ }
+
+ case TASK_ANTLION_FACE_JUMP:
+ break;
+
+ case TASK_ANTLION_DROWN:
+ {
+ // Set the gravity really low here! Sink slowly
+ SetGravity( 0 );
+ SetAbsVelocity( vec3_origin );
+ m_flTimeDrownSplash = gpGlobals->curtime + random->RandomFloat( 0, 0.5 );
+ m_flTimeDrown = gpGlobals->curtime + 4;
+ break;
+ }
+
+ case TASK_ANTLION_REACH_FIGHT_GOAL:
+
+ m_OnReachFightGoal.FireOutput( this, this );
+ TaskComplete();
+ break;
+
+ case TASK_ANTLION_DISMOUNT_NPC:
+ {
+ CBaseEntity *pGroundEnt = GetGroundEntity();
+
+ if( pGroundEnt != NULL )
+ {
+ trace_t trace;
+ CTraceFilterAntlion traceFilter( this );
+ AI_TraceHull( GetAbsOrigin(), GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), MASK_SOLID, &traceFilter, &trace );
+
+ if ( trace.m_pEnt )
+ {
+ m_bDontExplode = true;
+ OnTakeDamage( CTakeDamageInfo( this, this, m_iHealth+1, DMG_GENERIC ) );
+ return;
+ }
+
+ // Jump behind the other NPC so I don't block their path.
+ Vector vecJumpDir;
+
+ pGroundEnt->GetVectors( &vecJumpDir, NULL, NULL );
+
+ SetGroundEntity( NULL );
+
+ // Bump up
+ UTIL_SetOrigin( this, GetAbsOrigin() + Vector( 0, 0 , 1 ) );
+
+ SetAbsVelocity( vecJumpDir * -200 + Vector( 0, 0, 100 ) );
+
+ // Doing ACT_RESET first assures they play the animation, even when in transition
+ ResetActivity();
+ SetActivity( (Activity) ACT_ANTLION_FLIP );
+ }
+ else
+ {
+ // Dead or gone now
+ TaskComplete();
+ }
+ }
+
+ break;
+
+ case TASK_ANTLION_FACE_BUGBAIT:
+
+ //Must have a saved sound
+ //FIXME: This isn't assured to be still pointing to the right place, need to protect this
+ if ( !m_bHasHeardSound )
+ {
+ TaskFail( "No remembered bug bait sound to run to!" );
+ return;
+ }
+
+ GetMotor()->SetIdealYawToTargetAndUpdate( m_vecHeardSound );
+ SetTurnActivity();
+
+ break;
+
+ case TASK_ANTLION_GET_PATH_TO_BUGBAIT:
+ {
+ //Must have a saved sound
+ //FIXME: This isn't assured to be still pointing to the right place, need to protect this
+ if ( !m_bHasHeardSound )
+ {
+ TaskFail( "No remembered bug bait sound to run to!" );
+ return;
+ }
+
+ Vector goalPos;
+
+ // Find the position to chase to
+ if ( FindChasePosition( m_vecHeardSound, goalPos ) )
+ {
+ AI_NavGoal_t goal( goalPos, (Activity) ACT_ANTLION_RUN_AGITATED, ANTLION_BUGBAIT_NAV_TOLERANCE );
+
+ //Try to run directly there
+ if ( GetNavigator()->SetGoal( goal, AIN_DISCARD_IF_FAIL ) == false )
+ {
+ //Try and get as close as possible otherwise
+ AI_NavGoal_t nearGoal( GOALTYPE_LOCATION_NEAREST_NODE, goalPos, (Activity) ACT_ANTLION_RUN_AGITATED, ANTLION_BUGBAIT_NAV_TOLERANCE );
+
+ if ( GetNavigator()->SetGoal( nearGoal, AIN_CLEAR_PREVIOUS_STATE ) )
+ {
+ //FIXME: HACK! The internal pathfinding is setting this without our consent, so override it!
+ ClearCondition( COND_TASK_FAILED );
+
+ LockJumpNode();
+ TaskComplete();
+ return;
+ }
+ else
+ {
+ TaskFail( "Antlion failed to find path to bugbait position\n" );
+ return;
+ }
+ }
+ else
+ {
+ LockJumpNode();
+ TaskComplete();
+ return;
+ }
+ }
+
+ TaskFail( "Antlion failed to find path to bugbait position\n" );
+ break;
+ }
+
+ case TASK_ANTLION_WAIT_FOR_TRIGGER:
+ m_flIdleDelay = gpGlobals->curtime + 1.0f;
+
+ break;
+
+ case TASK_ANTLION_JUMP:
+
+ if ( CheckLanding() )
+ {
+ TaskComplete();
+ }
+
+ break;
+
+ case TASK_ANTLION_CHECK_FOR_UNBORROW:
+
+ m_iUnBurrowAttempts = 0;
+
+ if ( ValidBurrowPoint( GetAbsOrigin() ) )
+ {
+ m_spawnflags &= ~SF_NPC_GAG;
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ TaskComplete();
+ }
+
+ break;
+
+ case TASK_ANTLION_BURROW_WAIT:
+
+ if ( pTask->flTaskData == 1.0f )
+ {
+ //Set our next burrow time
+ m_flBurrowTime = gpGlobals->curtime + random->RandomFloat( 1, 6 );
+ }
+
+ break;
+
+ case TASK_ANTLION_FIND_BURROW_IN_POINT:
+
+ if ( FindBurrow( GetAbsOrigin(), pTask->flTaskData, ANTLION_BURROW_IN ) == false )
+ {
+ TaskFail( "TASK_ANTLION_FIND_BURROW_IN_POINT: Unable to find burrow in position\n" );
+ }
+ else
+ {
+ TaskComplete();
+ }
+
+ break;
+
+ case TASK_ANTLION_FIND_BURROW_OUT_POINT:
+
+ if ( FindBurrow( GetAbsOrigin(), pTask->flTaskData, ANTLION_BURROW_OUT ) == false )
+ {
+ TaskFail( "TASK_ANTLION_FIND_BURROW_OUT_POINT: Unable to find burrow out position\n" );
+ }
+ else
+ {
+ TaskComplete();
+ }
+
+ break;
+
+ case TASK_ANTLION_BURROW:
+ Burrow();
+ TaskComplete();
+
+ break;
+
+ case TASK_ANTLION_UNBURROW:
+ Unburrow();
+ TaskComplete();
+
+ break;
+
+ case TASK_ANTLION_VANISH:
+ AddEffects( EF_NODRAW );
+ AddFlag( FL_NOTARGET );
+ m_spawnflags |= SF_NPC_GAG;
+
+ // If the task parameter is non-zero, remove us when we vanish
+ if ( pTask->flTaskData )
+ {
+ CBaseEntity *pOwner = GetOwnerEntity();
+
+ if( pOwner != NULL )
+ {
+ pOwner->DeathNotice( this );
+ SetOwnerEntity( NULL );
+ }
+
+ // NOTE: We can't UTIL_Remove here, because we're in the middle of running our AI, and
+ // we'll crash later in the bowels of the AI. Remove ourselves next frame.
+ SetThink( &CNPC_Antlion::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ }
+
+ TaskComplete();
+
+ break;
+
+ case TASK_ANTLION_GET_THUMPER_ESCAPE_PATH:
+ {
+ if ( GetPathToSoundFleePoint( SOUND_THUMPER ) )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail( FAIL_NO_REACHABLE_NODE );
+ }
+ }
+
+ break;
+
+ case TASK_ANTLION_GET_PHYSICS_DANGER_ESCAPE_PATH:
+ {
+ if ( GetPathToSoundFleePoint( SOUND_PHYSICS_DANGER ) )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail( FAIL_NO_REACHABLE_NODE );
+ }
+ }
+
+ break;
+
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::RunTask( const Task_t *pTask )
+{
+ // some state that needs be set each frame
+#if HL2_EPISODIC
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ m_bHasDoneAirAttack = false;
+ }
+#endif
+
+ switch ( pTask->iTask )
+ {
+ case TASK_ANTLION_FACE_JUMP:
+ {
+ Vector jumpDir = m_vecSavedJump;
+ VectorNormalize( jumpDir );
+
+ QAngle jumpAngles;
+ VectorAngles( jumpDir, jumpAngles );
+
+ GetMotor()->SetIdealYawAndUpdate( jumpAngles[YAW], AI_KEEP_YAW_SPEED );
+ SetTurnActivity();
+
+ if ( GetMotor()->DeltaIdealYaw() < 2 )
+ {
+ TaskComplete();
+ }
+ }
+
+ break;
+
+ case TASK_ANTLION_DROWN:
+ {
+ if ( gpGlobals->curtime > m_flTimeDrownSplash )
+ {
+ float flWaterZ = UTIL_FindWaterSurface( GetAbsOrigin(), GetAbsOrigin().z, GetAbsOrigin().z + NAI_Hull::Maxs( GetHullType() ).z );
+
+ CEffectData data;
+ data.m_fFlags = 0;
+ data.m_vOrigin = GetAbsOrigin();
+ data.m_vOrigin.z = flWaterZ;
+ data.m_vNormal = Vector( 0, 0, 1 );
+ data.m_flScale = random->RandomFloat( 12.0, 16.0 );
+
+ DispatchEffect( "watersplash", data );
+
+ m_flTimeDrownSplash = gpGlobals->curtime + random->RandomFloat( 0.5, 2.5 );
+ }
+
+ if ( gpGlobals->curtime > m_flTimeDrown )
+ {
+ m_bDontExplode = true;
+ OnTakeDamage( CTakeDamageInfo( this, this, m_iHealth+1, DMG_DROWN ) );
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_ANTLION_REACH_FIGHT_GOAL:
+ break;
+
+ case TASK_ANTLION_DISMOUNT_NPC:
+
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ CBaseEntity *pGroundEnt = GetGroundEntity();
+
+ if ( ( pGroundEnt != NULL ) && ( ( pGroundEnt->MyNPCPointer() != NULL ) || pGroundEnt->GetSolidFlags() & FSOLID_NOT_STANDABLE ) )
+ {
+ // Jump behind the other NPC so I don't block their path.
+ Vector vecJumpDir;
+
+ pGroundEnt->GetVectors( &vecJumpDir, NULL, NULL );
+
+ SetGroundEntity( NULL );
+
+ // Bump up
+ UTIL_SetOrigin( this, GetAbsOrigin() + Vector( 0, 0 , 1 ) );
+
+ Vector vecRandom = RandomVector( -250.0f, 250.0f );
+ vecRandom[2] = random->RandomFloat( 100.0f, 200.0f );
+ SetAbsVelocity( vecRandom );
+
+ // Doing ACT_RESET first assures they play the animation, even when in transition
+ ResetActivity();
+ SetActivity( (Activity) ACT_ANTLION_FLIP );
+ }
+ else if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ }
+
+ break;
+
+ case TASK_ANTLION_FACE_BUGBAIT:
+
+ //Must have a saved sound
+ //FIXME: This isn't assured to be still pointing to the right place, need to protect this
+ if ( !m_bHasHeardSound )
+ {
+ TaskFail( "No remembered bug bait sound to run to!" );
+ return;
+ }
+
+ GetMotor()->SetIdealYawToTargetAndUpdate( m_vecHeardSound );
+
+ if ( FacingIdeal() )
+ {
+ TaskComplete();
+ }
+
+ break;
+
+ case TASK_ANTLION_WAIT_FOR_TRIGGER:
+
+ if ( ( m_flIdleDelay > gpGlobals->curtime ) || GetEntityName() != NULL_STRING )
+ return;
+
+ TaskComplete();
+
+ break;
+
+ case TASK_ANTLION_JUMP:
+
+ if ( CheckLanding() )
+ {
+ TaskComplete();
+ }
+
+ break;
+
+ case TASK_ANTLION_CHECK_FOR_UNBORROW:
+
+ //Must wait for our next check time
+ if ( m_flBurrowTime > gpGlobals->curtime )
+ return;
+
+ //See if we can pop up
+ if ( ValidBurrowPoint( GetAbsOrigin() ) )
+ {
+ m_spawnflags &= ~SF_NPC_GAG;
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ TaskComplete();
+ return;
+ }
+
+ //Try again in a couple of seconds
+ m_flBurrowTime = gpGlobals->curtime + random->RandomFloat( 0.5f, 1.0f );
+ m_iUnBurrowAttempts++;
+
+ // Robin: If we fail 10 times, kill ourself.
+ // This deals with issues where the game relies out antlion spawners
+ // firing their OnBlocked output, but the spawner isn't attempting to
+ // spawn because it has multiple live children lying around stuck under
+ // physics props unable to unburrow.
+ if ( m_iUnBurrowAttempts >= 10 )
+ {
+ m_bDontExplode = true;
+ m_takedamage = DAMAGE_YES;
+ OnTakeDamage( CTakeDamageInfo( this, this, m_iHealth+1, DMG_GENERIC ) );
+ }
+
+ break;
+
+ case TASK_ANTLION_BURROW_WAIT:
+
+ //See if enough time has passed
+ if ( m_flBurrowTime < gpGlobals->curtime )
+ {
+ TaskComplete();
+ }
+
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+bool CNPC_Antlion::AllowedToBePushed( void )
+{
+ if ( IsCurSchedule( SCHED_ANTLION_BURROW_WAIT ) ||
+ IsCurSchedule(SCHED_ANTLION_BURROW_IN) ||
+ IsCurSchedule(SCHED_ANTLION_BURROW_OUT) ||
+ IsCurSchedule(SCHED_ANTLION_BURROW_AWAY ) ||
+ IsCurSchedule( SCHED_ANTLION_RUN_TO_FIGHT_GOAL ) )
+ return false;
+
+ if ( IsRunningDynamicInteraction() )
+ return false;
+
+ if ( IsMoving() == false && IsCurSchedule( SCHED_ANTLION_FLIP ) == false
+ && GetNavType() != NAV_JUMP && m_flNextJumpPushTime <= gpGlobals->curtime )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if a reasonable jumping distance
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::IsJumpLegal( const Vector &startPos, const Vector &apex, const Vector &endPos ) const
+{
+ const float MAX_JUMP_RISE = 512;
+ const float MAX_JUMP_DROP = 512;
+ const float MAX_JUMP_DISTANCE = 1024;
+ const float MIN_JUMP_DISTANCE = 128;
+
+ if ( CAntlionRepellant::IsPositionRepellantFree( endPos ) == false )
+ return false;
+
+ //Adrian: Don't try to jump if my destination is right next to me.
+ if ( ( endPos - GetAbsOrigin()).Length() < MIN_JUMP_DISTANCE )
+ return false;
+
+ if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) && g_test_new_antlion_jump.GetBool() == true )
+ {
+ trace_t tr;
+ AI_TraceHull( endPos, endPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.m_pEnt )
+ {
+ CAI_BaseNPC *pBlocker = tr.m_pEnt->MyNPCPointer();
+
+ if ( pBlocker && pBlocker->Classify() == CLASS_ANTLION )
+ {
+ // HACKHACK
+ CNPC_Antlion *pAntlion = dynamic_cast< CNPC_Antlion * > ( pBlocker );
+
+ if ( pAntlion )
+ {
+ if ( pAntlion->AllowedToBePushed() == true )
+ {
+ // NDebugOverlay::Line( GetAbsOrigin(), endPos, 255, 0, 0, 0, 2 );
+ // NDebugOverlay::Box( pAntlion->GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 0, 255, 0, 2 );
+ pAntlion->GetMotor()->SetIdealYawToTarget( endPos );
+ pAntlion->SetSchedule( SCHED_MOVE_AWAY );
+ pAntlion->m_flNextJumpPushTime = gpGlobals->curtime + 2.0f;
+ }
+ }
+ }
+ }
+ }
+
+ return BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE );
+}
+
+bool CNPC_Antlion::IsFirmlyOnGround( void )
+{
+ if( !( GetFlags()&FL_ONGROUND ) )
+ return false;
+
+ trace_t tr;
+
+ float flHeight = fabs( GetHullMaxs().z - GetHullMins().z );
+
+ Vector vOrigin = GetAbsOrigin() + Vector( GetHullMins().x, GetHullMins().y, 0 );
+// NDebugOverlay::Line( vOrigin, vOrigin - Vector( 0, 0, flHeight * 0.5 ), 255, 0, 0, true, 5 );
+ UTIL_TraceLine( vOrigin, vOrigin - Vector( 0, 0, flHeight * 0.5 ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
+
+ if ( tr.fraction != 1.0f )
+ return true;
+
+ vOrigin = GetAbsOrigin() - Vector( GetHullMins().x, GetHullMins().y, 0 );
+// NDebugOverlay::Line( vOrigin, vOrigin - Vector( 0, 0, flHeight * 0.5 ), 255, 0, 0, true, 5 );
+ UTIL_TraceLine( vOrigin, vOrigin - Vector( 0, 0, flHeight * 0.5 ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
+
+ if ( tr.fraction != 1.0f )
+ return true;
+
+ vOrigin = GetAbsOrigin() + Vector( GetHullMins().x, -GetHullMins().y, 0 );
+// NDebugOverlay::Line( vOrigin, vOrigin - Vector( 0, 0, flHeight * 0.5 ), 255, 0, 0, true, 5 );
+ UTIL_TraceLine( vOrigin, vOrigin - Vector( 0, 0, flHeight * 0.5 ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
+
+ if ( tr.fraction != 1.0f )
+ return true;
+
+ vOrigin = GetAbsOrigin() + Vector( -GetHullMins().x, GetHullMins().y, 0 );
+// NDebugOverlay::Line( vOrigin, vOrigin - Vector( 0, 0, flHeight * 0.5 ), 255, 0, 0, true, 5 );
+ UTIL_TraceLine( vOrigin, vOrigin - Vector( 0, 0, flHeight * 0.5 ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
+
+ if ( tr.fraction != 1.0f )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_Antlion::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
+{
+ if ( m_FollowBehavior.GetNumFailedFollowAttempts() >= 2 )
+ {
+ if( IsFirmlyOnGround() == false )
+ {
+ Vector vecJumpDir;
+
+ vecJumpDir.z = 0;
+ vecJumpDir.x = 0;
+ vecJumpDir.y = 0;
+
+ while( vecJumpDir.x == 0 && vecJumpDir.y == 0 )
+ {
+ vecJumpDir.x = random->RandomInt( -1, 1 );
+ vecJumpDir.y = random->RandomInt( -1, 1 );
+ }
+
+ vecJumpDir.NormalizeInPlace();
+
+ SetGroundEntity( NULL );
+
+ m_vecSavedJump = vecJumpDir * 512 + Vector( 0, 0, 256 );
+ m_bForcedStuckJump = true;
+
+ return SCHED_ANTLION_JUMP;
+ }
+ }
+
+ // Catch the LOF failure and choose another route to take
+ if ( failedSchedule == SCHED_ESTABLISH_LINE_OF_FIRE )
+ return SCHED_ANTLION_WORKER_FLANK_RANDOM;
+
+ return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::ShouldJump( void )
+{
+ if ( GetEnemy() == NULL )
+ return false;
+
+ //Too soon to try to jump
+ if ( m_flJumpTime > gpGlobals->curtime )
+ return false;
+
+ // only jump if you're on the ground
+ if (!(GetFlags() & FL_ONGROUND) || GetNavType() == NAV_JUMP )
+ return false;
+
+ // Don't jump if I'm not allowed
+ if ( ( CapabilitiesGet() & bits_CAP_MOVE_JUMP ) == false )
+ return false;
+
+ Vector vEnemyForward, vForward;
+
+ GetEnemy()->GetVectors( &vEnemyForward, NULL, NULL );
+ GetVectors( &vForward, NULL, NULL );
+
+ float flDot = DotProduct( vForward, vEnemyForward );
+
+ if ( flDot < 0.5f )
+ flDot = 0.5f;
+
+ Vector vecPredictedPos;
+
+ //Get our likely position in two seconds
+ UTIL_PredictedPosition( GetEnemy(), flDot * 2.5f, &vecPredictedPos );
+
+ // Don't jump if we're already near the target
+ if ( ( GetAbsOrigin() - vecPredictedPos ).LengthSqr() < (512*512) )
+ return false;
+
+ //Don't retest if the target hasn't moved enough
+ //FIXME: Check your own distance from last attempt as well
+ if ( ( ( m_vecLastJumpAttempt - vecPredictedPos ).LengthSqr() ) < (128*128) )
+ {
+ m_flJumpTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f );
+ return false;
+ }
+
+ Vector targetDir = ( vecPredictedPos - GetAbsOrigin() );
+
+ float flDist = VectorNormalize( targetDir );
+
+ // don't jump at target it it's very close
+ if (flDist < ANTLION_JUMP_MIN)
+ return false;
+
+ Vector targetPos = vecPredictedPos + ( targetDir * (GetHullWidth()*4.0f) );
+
+ if ( CAntlionRepellant::IsPositionRepellantFree( targetPos ) == false )
+ return false;
+
+ // Try the jump
+ AIMoveTrace_t moveTrace;
+ GetMoveProbe()->MoveLimit( NAV_JUMP, GetAbsOrigin(), targetPos, MASK_NPCSOLID, GetNavTargetEntity(), &moveTrace );
+
+ //See if it succeeded
+ if ( IsMoveBlocked( moveTrace.fStatus ) )
+ {
+ if ( g_debug_antlion.GetInt() == 2 )
+ {
+ NDebugOverlay::Box( targetPos, GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 5 );
+ NDebugOverlay::Line( GetAbsOrigin(), targetPos, 255, 0, 0, 0, 5 );
+ }
+
+ m_flJumpTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f );
+ return false;
+ }
+
+ if ( g_debug_antlion.GetInt() == 2 )
+ {
+ NDebugOverlay::Box( targetPos, GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 5 );
+ NDebugOverlay::Line( GetAbsOrigin(), targetPos, 0, 255, 0, 0, 5 );
+ }
+
+ //Save this jump in case the next time fails
+ m_vecSavedJump = moveTrace.vJumpVelocity;
+ m_vecLastJumpAttempt = targetPos;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Antlion::TranslateSchedule( int scheduleType )
+{
+ if ( ( m_hFollowTarget != NULL ) || IsAllied() )
+ {
+ if ( ( scheduleType == SCHED_IDLE_STAND ) || ( scheduleType == SCHED_ALERT_STAND ) )
+ return SCHED_ANTLION_BUGBAIT_IDLE_STAND;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Activity CNPC_Antlion::NPC_TranslateActivity( Activity baseAct )
+{
+ // Workers explode as long as they didn't drown.
+ if ( IsWorker() && ( baseAct == ACT_DIESIMPLE ) && !m_bDontExplode )
+ {
+ return ( Activity )ACT_ANTLION_WORKER_EXPLODE;
+ }
+
+ return BaseClass::NPC_TranslateActivity( baseAct );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Antlion::ChooseMoveSchedule( void )
+{
+ // See if we need to invalidate our fight goal
+ if ( ShouldResumeFollow() )
+ {
+ // Set us back to following
+ SetMoveState( ANTLION_MOVE_FOLLOW );
+
+ // Tell our parent that we've swapped modes
+ CAntlionTemplateMaker *pMaker = dynamic_cast<CAntlionTemplateMaker *>(GetOwnerEntity());
+
+ if ( pMaker != NULL )
+ {
+ pMaker->SetChildMoveState( ANTLION_MOVE_FOLLOW );
+ }
+ }
+
+ // Figure out our move state
+ switch( m_MoveState )
+ {
+ case ANTLION_MOVE_FREE:
+ return SCHED_NONE; // Let the base class handle us
+ break;
+
+ // Fighting to a position
+ case ANTLION_MOVE_FIGHT_TO_GOAL:
+ {
+ if ( m_hFightGoalTarget )
+ {
+ float targetDist = UTIL_DistApprox( WorldSpaceCenter(), m_hFightGoalTarget->GetAbsOrigin() );
+
+ if ( targetDist > 256 )
+ {
+ Vector testPos;
+ Vector targetPos = ( m_hFightGoalTarget ) ? m_hFightGoalTarget->GetAbsOrigin() : m_vSavePosition;
+
+ // Find a suitable chase position
+ if ( FindChasePosition( targetPos, testPos ) )
+ {
+ m_vSavePosition = testPos;
+ return SCHED_ANTLION_RUN_TO_FIGHT_GOAL;
+ }
+ }
+ }
+ }
+ break;
+
+ // Following a goal
+ case ANTLION_MOVE_FOLLOW:
+ {
+ if ( m_FollowBehavior.CanSelectSchedule() )
+ {
+ // See if we should burrow away if our target it too far off
+ if ( ShouldAbandonFollow() )
+ return SCHED_ANTLION_BURROW_AWAY;
+
+ DeferSchedulingToBehavior( &m_FollowBehavior );
+ return BaseClass::SelectSchedule();
+ }
+ }
+ break;
+ }
+
+ return SCHED_NONE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::ZapThink( void )
+{
+ CEffectData data;
+ data.m_nEntIndex = entindex();
+ data.m_flMagnitude = 4;
+ data.m_flScale = random->RandomFloat( 0.25f, 1.0f );
+
+ DispatchEffect( "TeslaHitboxes", data );
+
+ if ( m_flZapDuration > gpGlobals->curtime )
+ {
+ SetContextThink( &CNPC_Antlion::ZapThink, gpGlobals->curtime + random->RandomFloat( 0.05f, 0.25f ), "ZapThink" );
+ }
+ else
+ {
+ SetContextThink( NULL, gpGlobals->curtime, "ZapThink" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Antlion::SelectSchedule( void )
+{
+ // Workers explode when killed unless told otherwise by anim events etc.
+ m_bDontExplode = false;
+
+ // Clear out this condition
+ ClearCondition( COND_ANTLION_RECEIVED_ORDERS );
+
+ // If we're supposed to be burrowed, stay there
+ if ( m_bStartBurrowed )
+ return SCHED_ANTLION_WAIT_FOR_UNBORROW_TRIGGER;
+
+ // See if a friendly player is pushing us away
+ if ( HasCondition( COND_PLAYER_PUSHING ) )
+ return SCHED_MOVE_AWAY;
+
+ //Flipped?
+ if ( HasCondition( COND_ANTLION_FLIPPED ) )
+ {
+ ClearCondition( COND_ANTLION_FLIPPED );
+
+ // See if it's a forced, electrical flip
+ if ( m_flZapDuration > gpGlobals->curtime )
+ {
+ SetContextThink( &CNPC_Antlion::ZapThink, gpGlobals->curtime, "ZapThink" );
+ return SCHED_ANTLION_ZAP_FLIP;
+ }
+
+ // Regular flip
+ return SCHED_ANTLION_FLIP;
+ }
+
+ if( HasCondition( COND_ANTLION_IN_WATER ) )
+ {
+ // No matter what, drown in water
+ return SCHED_ANTLION_DROWN;
+ }
+
+ // If we're flagged to burrow away when eluded, do so
+ if ( ( m_spawnflags & SF_ANTLION_BURROW_ON_ELUDED ) && ( HasCondition( COND_ENEMY_UNREACHABLE ) || HasCondition( COND_ENEMY_TOO_FAR ) ) )
+ return SCHED_ANTLION_BURROW_AWAY;
+
+ //Hear a thumper?
+ if ( HasCondition( COND_HEAR_THUMPER ) )
+ {
+ // Ignore thumpers that aren't visible
+ CSound *pSound = GetLoudestSoundOfType( SOUND_THUMPER );
+
+ if ( pSound )
+ {
+ CTakeDamageInfo info;
+ PainSound( info );
+ ClearCondition( COND_HEAR_THUMPER );
+
+ return SCHED_ANTLION_FLEE_THUMPER;
+ }
+ }
+
+ //Hear a physics danger sound?
+ if( HasCondition( COND_HEAR_PHYSICS_DANGER ) )
+ {
+ CTakeDamageInfo info;
+ PainSound( info );
+ return SCHED_ANTLION_FLEE_PHYSICS_DANGER;
+ }
+
+ //On another NPC's head?
+ if( HasCondition( COND_ANTLION_ON_NPC ) )
+ {
+ // You're on an NPC's head. Get off.
+ return SCHED_ANTLION_DISMOUNT_NPC;
+ }
+
+ // If we're scripted to jump at a target, do so
+ if ( HasCondition( COND_ANTLION_CAN_JUMP_AT_TARGET ) )
+ {
+ // NDebugOverlay::Cross3D( m_vecSavedJump, 32.0f, 255, 0, 0, true, 2.0f );
+ ClearCondition( COND_ANTLION_CAN_JUMP_AT_TARGET );
+ return SCHED_ANTLION_JUMP;
+ }
+
+ //Hear bug bait splattered?
+ if ( HasCondition( COND_HEAR_BUGBAIT ) && ( m_bIgnoreBugbait == false ) )
+ {
+ //Play a special sound
+ if ( m_flNextAcknowledgeTime < gpGlobals->curtime )
+ {
+ EmitSound( "NPC_Antlion.Distracted" );
+ m_flNextAcknowledgeTime = gpGlobals->curtime + 1.0f;
+ }
+
+ m_flIdleDelay = gpGlobals->curtime + 4.0f;
+
+ //If the sound is valid, act upon it
+ if ( m_bHasHeardSound )
+ {
+ //Mark anything in the area as more interesting
+ CBaseEntity *pTarget = NULL;
+ CBaseEntity *pNewEnemy = NULL;
+ Vector soundOrg = m_vecHeardSound;
+
+ //Find all entities within that sphere
+ while ( ( pTarget = gEntList.FindEntityInSphere( pTarget, soundOrg, bugbait_radius.GetInt() ) ) != NULL )
+ {
+ CAI_BaseNPC *pNPC = pTarget->MyNPCPointer();
+
+ if ( pNPC == NULL )
+ continue;
+
+ if ( pNPC->CanBeAnEnemyOf( this ) == false )
+ continue;
+
+ //Check to see if the default relationship is hatred, and if so intensify that
+ if ( ( IRelationType( pNPC ) == D_HT ) && ( pNPC->IsPlayer() == false ) )
+ {
+ AddEntityRelationship( pNPC, D_HT, 99 );
+
+ //Try to spread out the enemy distribution
+ if ( ( pNewEnemy == NULL ) || ( random->RandomInt( 0, 1 ) ) )
+ {
+ pNewEnemy = pNPC;
+ continue;
+ }
+ }
+ }
+
+ // If we have a new enemy, take it
+ if ( pNewEnemy != NULL )
+ {
+ //Setup our ignore info
+ SetEnemy( pNewEnemy );
+ }
+
+ ClearCondition( COND_HEAR_BUGBAIT );
+
+ return SCHED_ANTLION_CHASE_BUGBAIT;
+ }
+ }
+
+ if( m_AssaultBehavior.CanSelectSchedule() )
+ {
+ DeferSchedulingToBehavior( &m_AssaultBehavior );
+ return BaseClass::SelectSchedule();
+ }
+
+ //Otherwise do basic state schedule selection
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_COMBAT:
+ {
+ // Worker-only AI
+ if ( hl2_episodic.GetBool() && IsWorker() )
+ {
+ // Melee attack if we can
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ return SCHED_MELEE_ATTACK1;
+
+ // Pounce if they're too near us
+ if ( HasCondition( COND_CAN_MELEE_ATTACK2 ) )
+ {
+ m_flPounceTime = gpGlobals->curtime + 1.5f;
+
+ if ( m_bLeapAttack == true )
+ return SCHED_ANTLION_POUNCE_MOVING;
+
+ return SCHED_ANTLION_POUNCE;
+ }
+
+ // A squadmate died, so run away!
+ if ( HasCondition( COND_ANTLION_SQUADMATE_KILLED ) )
+ {
+ SetNextAttack( gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f ) );
+ ClearCondition( COND_ANTLION_SQUADMATE_KILLED );
+ return SCHED_ANTLION_TAKE_COVER_FROM_ENEMY;
+ }
+
+ // Flee on heavy damage
+ if ( HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ SetNextAttack( gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f ) );
+ return SCHED_ANTLION_TAKE_COVER_FROM_ENEMY;
+ }
+
+ // Range attack if we're able
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ if ( OccupyStrategySlot( SQUAD_SLOT_ANTLION_WORKER_FIRE ) )
+ {
+ EmitSound( "NPC_Antlion.PoisonBurstScream" );
+ SetNextAttack( gpGlobals->curtime + random->RandomFloat( 0.5f, 2.5f ) );
+ if ( GetEnemy() )
+ {
+ m_vSavePosition = GetEnemy()->BodyTarget( GetAbsOrigin() );
+ }
+
+ return SCHED_ANTLION_WORKER_RANGE_ATTACK1;
+ }
+ }
+
+ // Back up, we're too near an enemy or can't see them
+ if ( HasCondition( COND_TOO_CLOSE_TO_ATTACK ) || HasCondition( COND_ENEMY_OCCLUDED ) )
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+
+ // See if we need to destroy breakable cover
+ if ( HasCondition( COND_WEAPON_SIGHT_OCCLUDED ) )
+ return SCHED_SHOOT_ENEMY_COVER;
+
+ // Run around randomly if our target is looking in our direction
+ if ( HasCondition( COND_BEHIND_ENEMY ) == false )
+ return SCHED_ANTLION_WORKER_FLANK_RANDOM;
+
+ // Face our target and continue to fire
+ return SCHED_COMBAT_FACE;
+ }
+ else
+ {
+ // Lunge at the enemy
+ if ( HasCondition( COND_CAN_MELEE_ATTACK2 ) )
+ {
+ m_flPounceTime = gpGlobals->curtime + 1.5f;
+
+ if ( m_bLeapAttack == true )
+ return SCHED_ANTLION_POUNCE_MOVING;
+ else
+ return SCHED_ANTLION_POUNCE;
+ }
+
+ // Try to jump
+ if ( HasCondition( COND_ANTLION_CAN_JUMP ) )
+ return SCHED_ANTLION_JUMP;
+ }
+ }
+ break;
+
+ default:
+ {
+ int moveSched = ChooseMoveSchedule();
+
+ if ( moveSched != SCHED_NONE )
+ return moveSched;
+
+ if ( GetEnemy() == NULL && ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) )
+ {
+ Vector vecEnemyLKP;
+
+ // Retrieve a memory for the damage taken
+ // Fill in where we're trying to look
+ if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) )
+ {
+ vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY );
+ }
+ else
+ {
+ // Don't have an enemy, so face the direction the last attack came from (don't face north)
+ vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 );
+ }
+
+ // If we're already facing the attack direction, then take cover from it
+ if ( FInViewCone( vecEnemyLKP ) )
+ {
+ // Save this position for our cover search
+ m_vSavePosition = vecEnemyLKP;
+ return SCHED_ANTLION_TAKE_COVER_FROM_SAVEPOSITION;
+ }
+
+ // By default, we'll turn to face the attack
+ }
+ }
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+void CNPC_Antlion::Ignite ( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
+{
+#ifdef HL2_EPISODIC
+ float flDamage = m_iHealth + 1;
+
+ CTakeDamageInfo dmgInfo( this, this, flDamage, DMG_GENERIC );
+ GuessDamageForce( &dmgInfo, Vector( 0, 0, 8 ), GetAbsOrigin() );
+ TakeDamage( dmgInfo );
+#else
+ BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
+#endif
+
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Antlion::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo newInfo = info;
+
+ if( hl2_episodic.GetBool() && antlion_easycrush.GetBool() )
+ {
+ if( newInfo.GetDamageType() & DMG_CRUSH )
+ {
+ if( newInfo.GetInflictor() && newInfo.GetInflictor()->VPhysicsGetObject() )
+ {
+ float flMass = newInfo.GetInflictor()->VPhysicsGetObject()->GetMass();
+
+ if( flMass > 250.0f && newInfo.GetDamage() < GetHealth() )
+ {
+ newInfo.SetDamage( GetHealth() );
+ }
+ }
+ }
+ }
+
+ // If we're being hoisted by a barnacle, we only take damage from that barnacle (otherwise we can die too early!)
+ if ( IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
+ {
+ if ( info.GetAttacker() && info.GetAttacker()->Classify() != CLASS_BARNACLE )
+ return 0;
+ }
+
+ // Find out how much damage we're about to take
+ int nDamageTaken = BaseClass::OnTakeDamage_Alive( newInfo );
+ if ( gpGlobals->curtime - m_flLastDamageTime < 0.5f )
+ {
+ // Accumulate it
+ m_nSustainedDamage += nDamageTaken;
+ }
+ else
+ {
+ // Reset, it's been too long
+ m_nSustainedDamage = nDamageTaken;
+ }
+
+ m_flLastDamageTime = gpGlobals->curtime;
+
+ return nDamageTaken;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Antlion who are flipped will knock over other antlions behind them!
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::CascadePush( const Vector &vecForce )
+{
+ // Controlled via this convar until this is proven worthwhile
+ if ( hl2_episodic.GetBool() == false /*|| g_antlion_cascade_push.GetBool() == false*/ )
+ return;
+
+ Vector vecForceDir = vecForce;
+ float flMagnitude = VectorNormalize( vecForceDir );
+ Vector vecPushBack = GetAbsOrigin() + ( vecForceDir * (flMagnitude*0.1f) );
+
+ // Make antlions flip all around us!
+ CBaseEntity *pEnemySearch[32];
+ int nNumEnemies = UTIL_EntitiesInBox( pEnemySearch, ARRAYSIZE(pEnemySearch), vecPushBack-Vector(48,48,0), vecPushBack+Vector(48,48,64), FL_NPC );
+ for ( int i = 0; i < nNumEnemies; i++ )
+ {
+ // We only care about antlions
+ if ( pEnemySearch[i] == NULL || pEnemySearch[i]->Classify() != CLASS_ANTLION || pEnemySearch[i] == this )
+ continue;
+
+ CNPC_Antlion *pAntlion = dynamic_cast<CNPC_Antlion *>(pEnemySearch[i]);
+ if ( pAntlion != NULL )
+ {
+ Vector vecDir = ( pAntlion->GetAbsOrigin() - GetAbsOrigin() );
+ vecDir[2] = 0.0f;
+ float flDist = VectorNormalize( vecDir );
+ float flFalloff = RemapValClamped( flDist, 0, 256, 1.0f, 0.1f );
+
+ vecDir *= ( flMagnitude * flFalloff );
+ vecDir[2] += ( (flMagnitude*0.25f) * flFalloff );
+
+ pAntlion->ApplyAbsVelocityImpulse( vecDir );
+
+ // Turn them over
+ pAntlion->Flip();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+inline bool CNPC_Antlion::IsFlipped( void )
+{
+ return ( GetActivity() == ACT_ANTLION_FLIP || GetActivity() == ACT_ANTLION_ZAP_FLIP );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo newInfo = info;
+
+ Vector vecShoveDir = vecDir;
+ vecShoveDir.z = 0.0f;
+
+ //Are we already flipped?
+ if ( IsFlipped() )
+ {
+ //If we were hit by physics damage, move with it
+ if ( newInfo.GetDamageType() & (DMG_CRUSH|DMG_PHYSGUN) )
+ {
+ PainSound( newInfo );
+ Vector vecForce = ( vecShoveDir * random->RandomInt( 500.0f, 1000.0f ) ) + Vector(0,0,64.0f);
+ CascadePush( vecForce );
+ ApplyAbsVelocityImpulse( vecForce );
+ SetGroundEntity( NULL );
+ }
+
+ //More vulnerable when flipped
+ newInfo.ScaleDamage( 4.0f );
+ }
+ else if ( newInfo.GetDamageType() & (DMG_PHYSGUN) ||
+ ( newInfo.GetDamageType() & (DMG_BLAST|DMG_CRUSH) && newInfo.GetDamage() >= 25.0f ) )
+ {
+ // Don't do this if we're in an interaction
+ if ( !IsRunningDynamicInteraction() )
+ {
+ //Grenades, physcannons, and physics impacts make us fuh-lip!
+
+ if( hl2_episodic.GetBool() )
+ {
+ PainSound( newInfo );
+
+ if( GetFlags() & FL_ONGROUND )
+ {
+ // Only flip if on the ground.
+ SetCondition( COND_ANTLION_FLIPPED );
+ }
+
+ Vector vecForce = ( vecShoveDir * random->RandomInt( 500.0f, 1000.0f ) ) + Vector(0,0,64.0f);
+
+ CascadePush( vecForce );
+ ApplyAbsVelocityImpulse( vecForce );
+ SetGroundEntity( NULL );
+ }
+ else
+ {
+ //Don't flip off the deck
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ PainSound( newInfo );
+
+ SetCondition( COND_ANTLION_FLIPPED );
+
+ //Get tossed!
+ ApplyAbsVelocityImpulse( ( vecShoveDir * random->RandomInt( 500.0f, 1000.0f ) ) + Vector(0,0,64.0f) );
+ SetGroundEntity( NULL );
+ }
+ }
+ }
+ }
+
+ BaseClass::TraceAttack( newInfo, vecDir, ptr, pAccumulator );
+}
+
+void CNPC_Antlion::StopLoopingSounds( void )
+{
+ if ( m_bLoopingStarted )
+ {
+ StopSound( "NPC_Antlion.WingsOpen" );
+ m_bLoopingStarted = false;
+ }
+ if ( m_bAgitatedSound )
+ {
+ StopSound( "NPC_Antlion.LoopingAgitated" );
+ m_bAgitatedSound = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::IdleSound( void )
+{
+ EmitSound( "NPC_Antlion.Idle" );
+ m_flIdleDelay = gpGlobals->curtime + 4.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::PainSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_Antlion.Pain" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output :
+//-----------------------------------------------------------------------------
+float CNPC_Antlion::GetIdealAccel( void ) const
+{
+ return GetIdealSpeed() * 2.0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float CNPC_Antlion::MaxYawSpeed( void )
+{
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ return 32.0f;
+ break;
+
+ case ACT_WALK:
+ return 16.0f;
+ break;
+
+ default:
+ case ACT_RUN:
+ return 32.0f;
+ break;
+ }
+
+ return 32.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::ShouldPlayIdleSound( void )
+{
+ //Only do idles in the right states
+ if ( ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT ) )
+ return false;
+
+ //Gagged monsters don't talk
+ if ( m_spawnflags & SF_NPC_GAG )
+ return false;
+
+ //Don't cut off another sound or play again too soon
+ if ( m_flIdleDelay > gpGlobals->curtime )
+ return false;
+
+ //Randomize it a bit
+ if ( random->RandomInt( 0, 20 ) )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pFriend -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::NotifyDeadFriend( CBaseEntity *pFriend )
+{
+ SetCondition( COND_ANTLION_SQUADMATE_KILLED );
+ BaseClass::NotifyDeadFriend( pFriend );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Determine whether or not to check our attack conditions
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::FCanCheckAttacks( void )
+{
+ if ( IsWorker() )
+ {
+ // Only do this if we've seen our target recently and our schedule can be interrupted
+ if ( SeenEnemyWithinTime( 3.0f ) && ConditionInterruptsCurSchedule( COND_CAN_RANGE_ATTACK1 ) )
+ return FInViewCone( GetEnemy() );
+ }
+
+ return BaseClass::FCanCheckAttacks();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_Antlion::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( GetNextAttack() > gpGlobals->curtime )
+ return COND_NOT_FACING_ATTACK;
+
+ if ( flDot < DOT_10DEGREE )
+ return COND_NOT_FACING_ATTACK;
+
+ if ( flDist > (150*12) )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ if ( flDist < (20*12) )
+ return COND_TOO_CLOSE_TO_ATTACK;
+
+ return COND_CAN_RANGE_ATTACK1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_Antlion::MeleeAttack1Conditions( float flDot, float flDist )
+{
+#if 1 //NOTENOTE: Use predicted position melee attacks
+
+ //Get our likely position in one half second
+ Vector vecPrPos;
+ UTIL_PredictedPosition( GetEnemy(), 0.5f, &vecPrPos );
+
+ //Get the predicted distance and direction
+ float flPrDist = ( vecPrPos - GetAbsOrigin() ).LengthSqr();
+ if ( flPrDist > Square( ANTLION_MELEE1_RANGE ) )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ // Compare our target direction to our body facing
+ Vector2D vec2DPrDir = ( vecPrPos - GetAbsOrigin() ).AsVector2D();
+ Vector2D vec2DBodyDir = BodyDirection2D().AsVector2D();
+
+ float flPrDot = DotProduct2D ( vec2DPrDir, vec2DBodyDir );
+ if ( flPrDot < 0.5f )
+ return COND_NOT_FACING_ATTACK;
+
+ trace_t tr;
+ AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ // If the hit entity isn't our target and we don't hate it, don't hit it
+ if ( tr.m_pEnt != GetEnemy() && tr.fraction < 1.0f && IRelationType( tr.m_pEnt ) != D_HT )
+ return 0;
+
+#else
+
+ if ( flDot < 0.5f )
+ return COND_NOT_FACING_ATTACK;
+
+ float flAdjustedDist = ANTLION_MELEE1_RANGE;
+
+ if ( GetEnemy() )
+ {
+ // Give us extra space if our enemy is in a vehicle
+ CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
+ if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
+ {
+ flAdjustedDist *= 2.0f;
+ }
+ }
+
+ if ( flDist > flAdjustedDist )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ trace_t tr;
+ AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0f )
+ return 0;
+
+#endif
+
+ return COND_CAN_MELEE_ATTACK1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flDot -
+// flDist -
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Antlion::MeleeAttack2Conditions( float flDot, float flDist )
+{
+ // See if it's too soon to pounce again
+ if ( m_flPounceTime > gpGlobals->curtime )
+ return 0;
+
+ float flPrDist, flPrDot;
+ Vector vecPrPos;
+ Vector2D vec2DPrDir;
+
+ //Get our likely position in one half second
+ UTIL_PredictedPosition( GetEnemy(), 0.25f, &vecPrPos );
+
+ //Get the predicted distance and direction
+ flPrDist = ( vecPrPos - GetAbsOrigin() ).Length();
+ vec2DPrDir = ( vecPrPos - GetAbsOrigin() ).AsVector2D();
+
+ Vector vecBodyDir = BodyDirection2D();
+
+ Vector2D vec2DBodyDir = vecBodyDir.AsVector2D();
+
+ flPrDot = DotProduct2D ( vec2DPrDir, vec2DBodyDir );
+
+ if ( ( flPrDist > ANTLION_MELEE2_RANGE_MAX ) )
+ {
+ m_flPounceTime = gpGlobals->curtime + 0.2f;
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+ else if ( ( flPrDist < ANTLION_MELEE2_RANGE_MIN ) )
+ {
+ m_flPounceTime = gpGlobals->curtime + 0.2f;
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+
+ trace_t tr;
+ AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0f )
+ return 0;
+
+ if ( IsMoving() )
+ m_bLeapAttack = true;
+ else
+ m_bLeapAttack = false;
+
+ return COND_CAN_MELEE_ATTACK2;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : interactionType -
+// *data -
+// *sender -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sender )
+{
+ //Check for a target found while burrowed
+ if ( interactionType == g_interactionAntlionFoundTarget )
+ {
+ CBaseEntity *pOther = (CBaseEntity *) data;
+
+ //Randomly delay
+ m_flBurrowTime = gpGlobals->curtime + random->RandomFloat( 0.5f, 1.0f );
+ BurrowUse( pOther, pOther, USE_ON, 0.0f );
+
+ return true;
+ }
+
+ // fixed for episodic: allow interactions to fall through in the base class. ifdefed away
+ // for mainline in case anything depends on this bug.
+#ifdef HL2_EPISODIC
+
+ if ( interactionType == g_interactionAntlionFiredAtTarget )
+ {
+ // Bump out our attack time
+ if ( IsWorker() )
+ {
+ float flDuration = *((float *)data);
+ SetNextAttack( gpGlobals->curtime + flDuration );
+ }
+ }
+
+ return BaseClass::HandleInteraction( interactionType, data, sender );
+#else
+ return false;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::Alone( void )
+{
+ if ( m_pSquad == NULL )
+ return true;
+
+ if ( m_pSquad->NumMembers() <= 1 )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::StartJump( void )
+{
+ if ( m_bForcedStuckJump == false )
+ {
+ // FIXME: Why must this be true?
+ // Must be jumping at an enemy
+ // if ( GetEnemy() == NULL )
+ // return;
+
+ //Don't jump if we're not on the ground
+ if ( ( GetFlags() & FL_ONGROUND ) == false )
+ return;
+ }
+
+ //Take us off the ground
+ SetGroundEntity( NULL );
+ SetAbsVelocity( m_vecSavedJump );
+
+ m_bForcedStuckJump = false;
+#if HL2_EPISODIC
+ m_bHasDoneAirAttack = false;
+#endif
+
+ //Setup our jump time so that we don't try it again too soon
+ m_flJumpTime = gpGlobals->curtime + random->RandomInt( 2, 6 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : sHint -
+// nNodeNum -
+// Output : bool CAI_BaseNPC::FValidateHintType
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::FValidateHintType( CAI_Hint *pHint )
+{
+ switch ( m_iContext )
+ {
+ case ANTLION_BURROW_OUT:
+ {
+ //See if this is a valid point
+ Vector vHintPos;
+ pHint->GetPosition(this,&vHintPos);
+
+ if ( ValidBurrowPoint( vHintPos ) == false )
+ return false;
+ }
+ break;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &origin -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::ClearBurrowPoint( const Vector &origin )
+{
+ CBaseEntity *pEntity = NULL;
+ float flDist;
+ Vector vecSpot, vecCenter, vecForce;
+
+ bool bPlayerInSphere = false;
+
+ //Iterate on all entities in the vicinity.
+ for ( CEntitySphereQuery sphere( origin, 128 ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
+ {
+ if ( pEntity->Classify() == CLASS_PLAYER )
+ {
+ bPlayerInSphere = true;
+ continue;
+ }
+
+ if ( pEntity->m_takedamage != DAMAGE_NO && pEntity->Classify() != CLASS_PLAYER && pEntity->VPhysicsGetObject() )
+ {
+ vecSpot = pEntity->BodyTarget( origin );
+ vecForce = ( vecSpot - origin ) + Vector( 0, 0, 16 );
+
+ // decrease damage for an ent that's farther from the bomb.
+ flDist = VectorNormalize( vecForce );
+
+ //float mass = pEntity->VPhysicsGetObject()->GetMass();
+ CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1.0f, 1.0f, 1.0f ), &vecCenter );
+
+ if ( flDist <= 128.0f )
+ {
+ pEntity->VPhysicsGetObject()->Wake();
+ pEntity->VPhysicsGetObject()->ApplyForceOffset( vecForce * 250.0f, vecCenter );
+ }
+ }
+ }
+
+ if ( bPlayerInSphere == false )
+ {
+ //Cause a ruckus
+ UTIL_ScreenShake( origin, 1.0f, 80.0f, 1.0f, 256.0f, SHAKE_START );
+ }
+}
+
+bool NPC_CheckBrushExclude( CBaseEntity *pEntity, CBaseEntity *pBrush );
+//-----------------------------------------------------------------------------
+// traceline methods
+//-----------------------------------------------------------------------------
+class CTraceFilterSimpleNPCExclude : public CTraceFilterSimple
+{
+public:
+ DECLARE_CLASS( CTraceFilterSimpleNPCExclude, CTraceFilterSimple );
+
+ CTraceFilterSimpleNPCExclude( const IHandleEntity *passentity, int collisionGroup )
+ : CTraceFilterSimple( passentity, collisionGroup )
+ {
+ }
+
+ bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+ {
+ Assert( dynamic_cast<CBaseEntity*>(pHandleEntity) );
+ CBaseEntity *pTestEntity = static_cast<CBaseEntity*>(pHandleEntity);
+
+ if ( GetPassEntity() )
+ {
+ CBaseEntity *pEnt = gEntList.GetBaseEntity( GetPassEntity()->GetRefEHandle() );
+
+ if ( pEnt->IsNPC() )
+ {
+ if ( NPC_CheckBrushExclude( pEnt, pTestEntity ) == true )
+ return false;
+ }
+ }
+ return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Determine whether a point is valid or not for burrowing up into
+// Input : &point - point to test for validity
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::ValidBurrowPoint( const Vector &point )
+{
+ trace_t tr;
+
+ CTraceFilterSimpleNPCExclude filter( this, COLLISION_GROUP_NONE );
+ AI_TraceHull( point, point+Vector(0,0,1), GetHullMins(), GetHullMaxs(),
+ MASK_NPCSOLID, &filter, &tr );
+
+ //See if we were able to get there
+ if ( ( tr.startsolid ) || ( tr.allsolid ) || ( tr.fraction < 1.0f ) )
+ {
+ CBaseEntity *pEntity = tr.m_pEnt;
+
+ //If it's a physics object, attempt to knock is away, unless it's a car
+ if ( ( pEntity ) && ( pEntity->VPhysicsGetObject() ) && ( pEntity->GetServerVehicle() == NULL ) )
+ {
+ ClearBurrowPoint( point );
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds a burrow point for the antlion
+// Input : distance - radius to search for burrow spot in
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::FindBurrow( const Vector &origin, float distance, int type, bool excludeNear )
+{
+ //Burrowing in?
+ if ( type == ANTLION_BURROW_IN )
+ {
+ //Attempt to find a burrowing point
+ CHintCriteria hintCriteria;
+
+ hintCriteria.SetHintType( HINT_ANTLION_BURROW_POINT );
+ hintCriteria.SetFlag( bits_HINT_NODE_NEAREST );
+
+ hintCriteria.AddIncludePosition( origin, distance );
+
+ if ( excludeNear )
+ {
+ hintCriteria.AddExcludePosition( origin, 128 );
+ }
+
+ CAI_Hint *pHint = CAI_HintManager::FindHint( this, hintCriteria );
+
+ if ( pHint == NULL )
+ return false;
+
+ //Free up the node for use
+ if ( GetHintNode() )
+ {
+ GetHintNode()->Unlock(0);
+ }
+
+ SetHintNode( pHint );
+
+ //Lock the node
+ pHint->Lock(this);
+
+ //Setup our path and attempt to run there
+ Vector vHintPos;
+ GetHintNode()->GetPosition( this, &vHintPos );
+
+ AI_NavGoal_t goal( vHintPos, ACT_RUN );
+
+ return GetNavigator()->SetGoal( goal );
+ }
+
+ //Burrow out
+ m_iContext = ANTLION_BURROW_OUT;
+
+ CHintCriteria hintCriteria;
+
+ hintCriteria.SetHintType( HINT_ANTLION_BURROW_POINT );
+ hintCriteria.SetFlag( bits_HINT_NODE_NEAREST );
+
+ if ( GetEnemy() != NULL )
+ {
+ hintCriteria.AddIncludePosition( GetEnemy()->GetAbsOrigin(), distance );
+ }
+
+ //Attempt to find an open burrow point
+ CAI_Hint *pHint = CAI_HintManager::FindHint( this, hintCriteria );
+
+ m_iContext = -1;
+
+ if ( pHint == NULL )
+ return false;
+
+ //Free up the node for use
+ if (GetHintNode())
+ {
+ GetHintNode()->Unlock(0);
+ }
+
+ SetHintNode( pHint );
+ pHint->Lock(this);
+
+ Vector burrowPoint;
+ pHint->GetPosition(this,&burrowPoint);
+
+ UTIL_SetOrigin( this, burrowPoint );
+
+ //Burrowing out
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Cause the antlion to unborrow
+// Input : *pActivator -
+// *pCaller -
+// useType -
+// value -
+//-----------------------------------------------------------------------------
+
+void CNPC_Antlion::BurrowUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ //Don't allow us to do this again
+ SetUse( NULL );
+
+ //Allow idle sounds again
+ m_spawnflags &= ~SF_NPC_GAG;
+
+ //If the player activated this, then take them as an enemy
+ if ( ( pCaller != NULL ) && ( pCaller->IsPlayer() ) )
+ {
+ SetEnemy( pActivator );
+ }
+
+ //Start trying to surface
+ SetSchedule( SCHED_ANTLION_WAIT_UNBORROW );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Monitor the antlion's jump to play the proper landing sequence
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::CheckLanding( void )
+{
+ trace_t tr;
+ Vector testPos;
+
+ //Amount of time to predict forward
+ const float timeStep = 0.1f;
+
+ //Roughly looks one second into the future
+ testPos = GetAbsOrigin() + ( GetAbsVelocity() * timeStep );
+ testPos[2] -= ( 0.5 * GetCurrentGravity() * GetGravity() * timeStep * timeStep);
+
+ if ( g_debug_antlion.GetInt() == 2 )
+ {
+ NDebugOverlay::Line( GetAbsOrigin(), testPos, 255, 0, 0, 0, 0.5f );
+ NDebugOverlay::Cross3D( m_vecSavedJump, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, true, 0.5f );
+ }
+
+ // Look below
+ AI_TraceHull( GetAbsOrigin(), testPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ //See if we're about to contact, or have already contacted the ground
+ if ( ( tr.fraction != 1.0f ) || ( GetFlags() & FL_ONGROUND ) )
+ {
+ int sequence = SelectWeightedSequence( (Activity)ACT_ANTLION_LAND );
+
+ if ( GetSequence() != sequence )
+ {
+ SetWings( false );
+ VacateStrategySlot();
+ SetIdealActivity( (Activity) ACT_ANTLION_LAND );
+
+ CreateDust( false );
+ EmitSound( "NPC_Antlion.Land" );
+
+ if ( GetEnemy() && GetEnemy()->IsPlayer() )
+ {
+ CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
+
+ if ( pPlayer && pPlayer->IsInAVehicle() == false )
+ {
+ QAngle qa( 4.0f, 0.0f, 0.0f );
+ Vector vec( -250.0f, 1.0f, 1.0f );
+ MeleeAttack( ANTLION_MELEE1_RANGE, sk_antlion_swipe_damage.GetFloat(), qa, vec );
+ }
+ }
+
+ SetAbsVelocity( GetAbsVelocity() * 0.33f );
+ return false;
+ }
+
+ return IsActivityFinished();
+ }
+
+ return false;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEntity -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
+{
+ //If we're under the ground, don't look at enemies
+ if ( IsEffectActive( EF_NODRAW ) )
+ return false;
+
+ return BaseClass::QuerySeeEntity(pEntity, bOnlyHateOrFearIfNPC);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Turns the antlion's wings on or off
+// Input : state - on or off
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::SetWings( bool state )
+{
+ if ( m_bWingsOpen == state )
+ return;
+
+ m_bWingsOpen = state;
+
+ if ( m_bWingsOpen )
+ {
+ CPASAttenuationFilter filter( this, "NPC_Antlion.WingsOpen" );
+ filter.MakeReliable();
+
+ EmitSound( filter, entindex(), "NPC_Antlion.WingsOpen" );
+ SetBodygroup( 1, 1 );
+ m_bLoopingStarted = true;
+ }
+ else
+ {
+ StopSound( "NPC_Antlion.WingsOpen" );
+ SetBodygroup( 1, 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::Burrow( void )
+{
+ SetWings( false );
+
+ //Stop us from taking damage and being solid
+ m_spawnflags |= SF_NPC_GAG;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::Unburrow( void )
+{
+ m_bStartBurrowed = false;
+ SetWings( false );
+
+ //Become solid again and visible
+ m_spawnflags &= ~SF_NPC_GAG;
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_YES;
+
+ SetGroundEntity( NULL );
+
+ //If we have an enemy, come out facing them
+ if ( GetEnemy() )
+ {
+ Vector dir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
+ VectorNormalize(dir);
+
+ QAngle angles = GetAbsAngles();
+ angles[ YAW ] = UTIL_VecToYaw( dir );
+ SetLocalAngles( angles );
+ }
+
+ //fire output upon unburrowing
+ m_OnUnBurrowed.FireOutput( this, this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::InputUnburrow( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ SetSchedule( SCHED_ANTLION_WAIT_UNBORROW );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::InputBurrow( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ SetSchedule( SCHED_ANTLION_BURROW_IN );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::InputBurrowAway( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ SetSchedule( SCHED_ANTLION_BURROW_AWAY );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::CreateDust( bool placeDecal )
+{
+ trace_t tr;
+ AI_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0f )
+ {
+ const surfacedata_t *pdata = physprops->GetSurfaceData( tr.surface.surfaceProps );
+
+ if ( hl2_episodic.GetBool() == true || ( pdata->game.material == CHAR_TEX_CONCRETE ) ||
+ ( pdata->game.material == CHAR_TEX_DIRT ) ||
+ ( pdata->game.material == CHAR_TEX_SAND ) )
+ {
+
+ if ( !m_bSuppressUnburrowEffects )
+ {
+ UTIL_CreateAntlionDust( tr.endpos + Vector(0,0,24), GetAbsAngles() );
+
+ if ( placeDecal )
+ {
+ UTIL_DecalTrace( &tr, "Antlion.Unburrow" );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pSound -
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::QueryHearSound( CSound *pSound )
+{
+ if ( !BaseClass::QueryHearSound( pSound ) )
+ return false;
+
+ if ( pSound->m_iType == SOUND_BUGBAIT )
+ {
+ //Must be more recent than the current
+ if ( pSound->SoundExpirationTime() <= m_flIgnoreSoundTime )
+ return false;
+
+ //If we can hear it, store it
+ m_bHasHeardSound = (pSound != NULL);
+ if ( m_bHasHeardSound )
+ {
+ m_vecHeardSound = pSound->GetSoundOrigin();
+ m_flIgnoreSoundTime = pSound->SoundExpirationTime();
+ }
+ }
+
+ //Do the normal behavior at this point
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// 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_Antlion::BuildScheduleTestBits( void )
+{
+ //Don't allow any modifications when scripted
+ if ( m_NPCState == NPC_STATE_SCRIPT )
+ return;
+
+ // If we're allied with the player, don't be startled by him
+ if ( IsAllied() )
+ {
+ ClearCustomInterruptCondition( COND_HEAR_PLAYER );
+ SetCustomInterruptCondition( COND_PLAYER_PUSHING );
+ }
+
+ //Make sure we interrupt a run schedule if we can jump
+ if ( IsCurSchedule(SCHED_CHASE_ENEMY) )
+ {
+ SetCustomInterruptCondition( COND_ANTLION_CAN_JUMP );
+ SetCustomInterruptCondition( COND_ENEMY_UNREACHABLE );
+ }
+
+ if ( !IsCurSchedule( SCHED_ANTLION_DROWN ) )
+ {
+ // Interrupt any schedule unless already drowning.
+ SetCustomInterruptCondition( COND_ANTLION_IN_WATER );
+ }
+ else
+ {
+ // Don't stop drowning just because you're in water!
+ ClearCustomInterruptCondition( COND_ANTLION_IN_WATER );
+ }
+
+ // Make sure we don't stop in midair
+ /*
+ if ( GetActivity() == ACT_JUMP || GetActivity() == ACT_GLIDE || GetActivity() == ACT_LAND )
+ {
+ ClearCustomInterruptCondition( COND_NEW_ENEMY );
+ }
+ */
+
+ //Interrupt any schedule unless already fleeing, burrowing, burrowed, or unburrowing.
+ if( !IsCurSchedule(SCHED_ANTLION_FLEE_THUMPER) &&
+ !IsCurSchedule(SCHED_ANTLION_FLEE_PHYSICS_DANGER) &&
+ !IsCurSchedule(SCHED_ANTLION_BURROW_IN) &&
+ !IsCurSchedule(SCHED_ANTLION_WAIT_UNBORROW) &&
+ !IsCurSchedule(SCHED_ANTLION_BURROW_OUT) &&
+ !IsCurSchedule(SCHED_ANTLION_BURROW_WAIT) &&
+ !IsCurSchedule(SCHED_ANTLION_WAIT_FOR_UNBORROW_TRIGGER)&&
+ !IsCurSchedule(SCHED_ANTLION_WAIT_FOR_CLEAR_UNBORROW)&&
+ !IsCurSchedule(SCHED_ANTLION_WAIT_UNBORROW) &&
+ !IsCurSchedule(SCHED_ANTLION_JUMP) &&
+ !IsCurSchedule(SCHED_ANTLION_FLIP) &&
+ !IsCurSchedule(SCHED_ANTLION_DISMOUNT_NPC) &&
+ ( GetFlags() & FL_ONGROUND ) )
+ {
+ // Only do these if not jumping as well
+ if (!IsCurSchedule(SCHED_ANTLION_JUMP))
+ {
+ if ( GetEnemy() == NULL )
+ {
+ SetCustomInterruptCondition( COND_HEAR_PHYSICS_DANGER );
+ }
+
+ SetCustomInterruptCondition( COND_HEAR_THUMPER );
+ SetCustomInterruptCondition( COND_HEAR_BUGBAIT );
+ SetCustomInterruptCondition( COND_ANTLION_FLIPPED );
+ SetCustomInterruptCondition( COND_ANTLION_CAN_JUMP_AT_TARGET );
+
+ if ( GetNavType() != NAV_JUMP )
+ SetCustomInterruptCondition( COND_ANTLION_RECEIVED_ORDERS );
+ }
+
+ SetCustomInterruptCondition( COND_ANTLION_ON_NPC );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEnemy -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::IsValidEnemy( CBaseEntity *pEnemy )
+{
+ //See if antlions are friendly to the player in this map
+ if ( IsAllied() && pEnemy->IsPlayer() )
+ return false;
+
+ if ( pEnemy->IsWorld() )
+ return false;
+
+ //If we're chasing bugbait, close to within a certain radius before picking up enemies
+ if ( IsCurSchedule( GetGlobalScheduleId( SCHED_ANTLION_CHASE_BUGBAIT ) ) && ( GetNavigator() != NULL ) )
+ {
+ //If the enemy is without the target radius, then don't allow them
+ if ( ( GetNavigator()->IsGoalActive() ) && ( GetNavigator()->GetGoalPos() - pEnemy->GetAbsOrigin() ).Length() > bugbait_radius.GetFloat() )
+ return false;
+ }
+
+ // If we're following an entity we limit our attack distances
+ if ( m_FollowBehavior.GetFollowTarget() != NULL )
+ {
+ float enemyDist = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
+
+ if ( m_flObeyFollowTime > gpGlobals->curtime )
+ {
+ // Unless we're right next to the enemy, follow our target
+ if ( enemyDist > (128*128) )
+ return false;
+ }
+ else
+ {
+ // Otherwise don't follow if the target is far
+ if ( enemyDist > (2000*2000) )
+ return false;
+ }
+ }
+
+ return BaseClass::IsValidEnemy( pEnemy );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::GatherConditions( void )
+{
+ BaseClass::GatherConditions();
+
+ // See if I've landed on an NPC!
+ CBaseEntity *pGroundEnt = GetGroundEntity();
+
+ if ( ( ( pGroundEnt != NULL ) && ( pGroundEnt->GetSolidFlags() & FSOLID_NOT_STANDABLE ) ) && ( GetFlags() & FL_ONGROUND ) && ( !IsEffectActive( EF_NODRAW ) && !pGroundEnt->IsEffectActive( EF_NODRAW ) ) )
+ {
+ SetCondition( COND_ANTLION_ON_NPC );
+ }
+ else
+ {
+ ClearCondition( COND_ANTLION_ON_NPC );
+ }
+
+ // See if our follow target is too far off
+/* if ( m_hFollowTarget != NULL )
+ {
+ float targetDist = UTIL_DistApprox( WorldSpaceCenter(), m_hFollowTarget->GetAbsOrigin() );
+
+ if ( targetDist > 400 )
+ {
+ SetCondition( COND_ANTLION_FOLLOW_TARGET_TOO_FAR );
+ }
+ else
+ {
+ ClearCondition( COND_ANTLION_FOLLOW_TARGET_TOO_FAR );
+ }
+ }*/
+
+ if ( IsCurSchedule( SCHED_ANTLION_BURROW_WAIT ) == false &&
+ IsCurSchedule(SCHED_ANTLION_BURROW_IN) == false &&
+ IsCurSchedule(SCHED_ANTLION_BURROW_OUT) == false &&
+ IsCurSchedule(SCHED_FALL_TO_GROUND ) == false &&
+ IsEffectActive( EF_NODRAW ) == false )
+ {
+ if( m_lifeState == LIFE_ALIVE && GetWaterLevel() > 1 )
+ {
+ // Start Drowning!
+ SetCondition( COND_ANTLION_IN_WATER );
+ }
+ }
+
+ //Ignore the player pushing me if I'm flipped over!
+ if ( IsCurSchedule( SCHED_ANTLION_FLIP ) )
+ ClearCondition( COND_PLAYER_PUSHING );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::PrescheduleThink( void )
+{
+ UpdateHead();
+
+ Activity eActivity = GetActivity();
+
+ //See if we need to play their agitated sound
+ if ( ( eActivity == ACT_ANTLION_RUN_AGITATED ) && ( m_bAgitatedSound == false ) )
+ {
+ //Start sound
+ CPASAttenuationFilter filter( this, "NPC_Antlion.LoopingAgitated" );
+ filter.MakeReliable();
+
+ EmitSound( filter, entindex(), "NPC_Antlion.LoopingAgitated" );
+ m_bAgitatedSound = true;
+ }
+ else if ( ( eActivity != ACT_ANTLION_RUN_AGITATED ) && ( m_bAgitatedSound == true ) )
+ {
+ //Stop sound
+ StopSound( "NPC_Antlion.LoopingAgitated" );
+ m_bAgitatedSound = false;
+ }
+
+ //See if our wings got interrupted from being turned off
+ if ( ( m_bWingsOpen ) &&
+ ( eActivity != ACT_ANTLION_JUMP_START ) &&
+ ( eActivity != ACT_JUMP ) &&
+ ( eActivity != ACT_GLIDE ) &&
+ ( eActivity != ACT_ANTLION_LAND ) &&
+ ( eActivity != ACT_ANTLION_DISTRACT ))
+ {
+ SetWings( false );
+ }
+
+ // Make sure we've turned off our burrow state if we're not in it
+ if ( IsEffectActive( EF_NODRAW ) &&
+ ( eActivity != ACT_ANTLION_BURROW_IDLE ) &&
+ ( eActivity != ACT_ANTLION_BURROW_OUT ) &&
+ ( eActivity != ACT_ANTLION_BURROW_IN) )
+ {
+ DevMsg( "Antlion failed to unburrow properly!\n" );
+ Assert( 0 );
+ RemoveEffects( EF_NODRAW );
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_YES;
+ RemoveFlag( FL_NOTARGET );
+ m_spawnflags &= ~SF_NPC_GAG;
+ }
+
+ //New Enemy? Try to jump at him.
+ if ( HasCondition( COND_NEW_ENEMY ) )
+ {
+ m_flJumpTime = 0.0f;
+ }
+
+ // See if we should jump because of desirables conditions, or a scripted request
+ if ( ShouldJump() )
+ {
+ SetCondition( COND_ANTLION_CAN_JUMP );
+ }
+ else
+ {
+ ClearCondition( COND_ANTLION_CAN_JUMP );
+ }
+
+ BaseClass::PrescheduleThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flDamage -
+// bitsDamageType -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::IsLightDamage( const CTakeDamageInfo &info )
+{
+ if ( ( random->RandomInt( 0, 1 ) ) && ( info.GetDamage() > 3 ) )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::IsAllied( void )
+{
+ return ( GlobalEntity_GetState( "antlion_allied" ) == GLOBAL_ON );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::ShouldResumeFollow( void )
+{
+ if ( IsAllied() == false )
+ return false;
+
+ if ( m_MoveState == ANTLION_MOVE_FOLLOW || m_hFollowTarget == NULL )
+ return false;
+
+ if ( m_flSuppressFollowTime > gpGlobals->curtime )
+ return false;
+
+ if ( GetEnemy() != NULL )
+ {
+ m_flSuppressFollowTime = gpGlobals->curtime + random->RandomInt( 5, 10 );
+ return false;
+ }
+
+ //TODO: See if the follow target has wandered off too far from where we last followed them to
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::ShouldAbandonFollow( void )
+{
+ // Never give up if we can see the goal
+ if ( m_FollowBehavior.FollowTargetVisible() )
+ return false;
+
+ // Never give up if we're too close
+ float flDistance = UTIL_DistApprox2D( m_FollowBehavior.GetFollowTarget()->WorldSpaceCenter(), WorldSpaceCenter() );
+
+ if ( flDistance < 1500 )
+ return false;
+
+ if ( flDistance > 1500 * 2.0f )
+ return true;
+
+ // If we've failed too many times, give up
+ if ( m_FollowBehavior.GetNumFailedFollowAttempts() )
+ return true;
+
+ // If the target simply isn't reachable to us, give up
+ if ( m_FollowBehavior.TargetIsUnreachable() )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTarget -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::SetFightTarget( CBaseEntity *pTarget )
+{
+ m_hFightGoalTarget = pTarget;
+
+ SetCondition( COND_ANTLION_RECEIVED_ORDERS );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::InputFightToPosition( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller );
+
+ if ( pEntity != NULL )
+ {
+ SetFightTarget( pEntity );
+ SetFollowTarget( NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::InputStopFightToPosition( inputdata_t &inputdata )
+{
+ SetFightTarget( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEnemy -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::GatherEnemyConditions( CBaseEntity *pEnemy )
+{
+ // Do the base class
+ BaseClass::GatherEnemyConditions( pEnemy );
+
+ // Only continue if we burrow when eluded
+ if ( ( m_spawnflags & SF_ANTLION_BURROW_ON_ELUDED ) == false )
+ return;
+
+ // If we're not already too far away, check again
+ //TODO: Check to make sure we don't already have a condition set that removes the need for this
+ if ( HasCondition( COND_ENEMY_UNREACHABLE ) == false )
+ {
+ Vector predPosition;
+ UTIL_PredictedPosition( GetEnemy(), 1.0f, &predPosition );
+
+ Vector predDir = ( predPosition - GetAbsOrigin() );
+ float predLength = VectorNormalize( predDir );
+
+ // See if we'll be outside our effective target range
+ if ( predLength > m_flEludeDistance )
+ {
+ Vector predVelDir = ( predPosition - GetEnemy()->GetAbsOrigin() );
+ float predSpeed = VectorNormalize( predVelDir );
+
+ // See if the enemy is moving mostly away from us
+ if ( ( predSpeed > 512.0f ) && ( DotProduct( predVelDir, predDir ) > 0.0f ) )
+ {
+ // Mark the enemy as eluded and burrow away
+ ClearEnemyMemory();
+ SetEnemy( NULL );
+ SetIdealState( NPC_STATE_ALERT );
+ SetCondition( COND_ENEMY_UNREACHABLE );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::ShouldGib( const CTakeDamageInfo &info )
+{
+ // If we're being hoisted, we only want to gib when the barnacle hurts us with his bite!
+ if ( IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
+ {
+ if ( info.GetAttacker() && info.GetAttacker()->Classify() != CLASS_BARNACLE )
+ return false;
+
+ return true;
+ }
+
+ if ( info.GetDamageType() & (DMG_NEVERGIB|DMG_DISSOLVE) )
+ return false;
+
+#ifdef HL2_EPISODIC
+ if ( IsWorker() && ANTLION_WORKERS_BURST() )
+ return !m_bDontExplode;
+#endif
+
+ if ( info.GetDamageType() & (DMG_ALWAYSGIB|DMG_BLAST) )
+ return true;
+
+ if ( m_iHealth < -20 )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::CorpseGib( const CTakeDamageInfo &info )
+{
+#ifdef HL2_EPISODIC
+
+ if ( IsWorker() )
+ {
+ DoPoisonBurst();
+ }
+ else
+#endif // HL2_EPISODIC
+ {
+ // Use the bone position to handle being moved by an animation (like a dynamic scripted sequence)
+ static int s_nBodyBone = -1;
+ if ( s_nBodyBone == -1 )
+ {
+ s_nBodyBone = LookupBone( "Antlion.Body_Bone" );
+ }
+
+ Vector vecOrigin;
+ QAngle angBone;
+ GetBonePosition( s_nBodyBone, vecOrigin, angBone );
+
+ DispatchParticleEffect( "AntlionGib", vecOrigin, QAngle( 0, 0, 0 ) );
+ }
+
+ Vector velocity = vec3_origin;
+ AngularImpulse angVelocity = RandomAngularImpulse( -150, 150 );
+ breakablepropparams_t params( EyePosition(), GetAbsAngles(), velocity, angVelocity );
+ params.impactEnergyScale = 1.0f;
+ params.defBurstScale = 150.0f;
+ params.defCollisionGroup = COLLISION_GROUP_DEBRIS;
+ PropBreakableCreateAll( GetModelIndex(), NULL, params, this, -1, true, true );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pOther -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::Touch( CBaseEntity *pOther )
+{
+ //See if the touching entity is a vehicle
+ CBasePlayer *pPlayer = ToBasePlayer( AI_GetSinglePlayer() );
+
+ // FIXME: Technically we'll want to check to see if a vehicle has touched us with the player OR NPC driver
+
+ if ( pPlayer && pPlayer->IsInAVehicle() )
+ {
+ IServerVehicle *pVehicle = pPlayer->GetVehicle();
+ CBaseEntity *pVehicleEnt = pVehicle->GetVehicleEnt();
+
+ if ( pVehicleEnt == pOther )
+ {
+ CPropVehicleDriveable *pDrivableVehicle = dynamic_cast<CPropVehicleDriveable *>( pVehicleEnt );
+
+ if ( pDrivableVehicle != NULL )
+ {
+ //Get tossed!
+ Vector vecShoveDir = pOther->GetAbsVelocity();
+ Vector vecTargetDir = GetAbsOrigin() - pOther->GetAbsOrigin();
+
+ VectorNormalize( vecShoveDir );
+ VectorNormalize( vecTargetDir );
+
+ bool bBurrowingOut = IsCurSchedule( SCHED_ANTLION_BURROW_OUT );
+
+ if ( ( ( pDrivableVehicle->m_nRPM > 75 ) && DotProduct( vecShoveDir, vecTargetDir ) <= 0 ) || bBurrowingOut == true )
+ {
+ if ( IsFlipped() || bBurrowingOut == true )
+ {
+ float flDamage = m_iHealth;
+
+ if ( random->RandomInt( 0, 10 ) > 4 )
+ flDamage += 25;
+
+ CTakeDamageInfo dmgInfo( pVehicleEnt, pPlayer, flDamage, DMG_VEHICLE );
+
+ CalculateMeleeDamageForce( &dmgInfo, vecShoveDir, pOther->GetAbsOrigin() );
+ TakeDamage( dmgInfo );
+ }
+ else
+ {
+ // We're being shoved
+ CTakeDamageInfo dmgInfo( pVehicleEnt, pPlayer, 0, DMG_VEHICLE );
+ PainSound( dmgInfo );
+
+ SetCondition( COND_ANTLION_FLIPPED );
+
+ vecTargetDir[2] = 0.0f;
+
+ ApplyAbsVelocityImpulse( ( vecTargetDir * 250.0f ) + Vector(0,0,64.0f) );
+ SetGroundEntity( NULL );
+
+ CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, GetAbsOrigin(), 256, 0.5f, this );
+ }
+ }
+ }
+ }
+ }
+
+ BaseClass::Touch( pOther );
+
+ // in episodic, an antlion colliding with the player in midair does him damage.
+ // pursuant bugs 58590, 56960, this happens only once per glide.
+#ifdef HL2_EPISODIC
+ if ( GetActivity() == ACT_GLIDE && IsValidEnemy( pOther ) && !m_bHasDoneAirAttack )
+ {
+ CTakeDamageInfo dmgInfo( this, this, sk_antlion_air_attack_dmg.GetInt(), DMG_SLASH );
+
+ CalculateMeleeDamageForce( &dmgInfo, Vector( 0, 0, 1 ), GetAbsOrigin() );
+ pOther->TakeDamage( dmgInfo );
+
+ //Kick the player angles
+ bool bIsPlayer = pOther->IsPlayer();
+ if ( bIsPlayer && !(pOther->GetFlags() & FL_GODMODE ) && pOther->GetMoveType() != MOVETYPE_NOCLIP )
+ {
+ pOther->ViewPunch( QAngle( 4.0f, 0.0f, 0.0f ) );
+ }
+
+ // set my "I have already attacked someone" flag
+ if ( bIsPlayer || pOther->IsNPC())
+ {
+ m_bHasDoneAirAttack = true;
+ }
+ }
+#endif
+
+ // Did the player touch me?
+ if ( pOther->IsPlayer() )
+ {
+ // Don't test for this if the pusher isn't friendly
+ if ( IsValidEnemy( pOther ) )
+ return;
+
+ // Ignore if pissed at player
+ if ( m_afMemory & bits_MEMORY_PROVOKED )
+ return;
+
+ if ( !IsCurSchedule( SCHED_MOVE_AWAY ) && !IsCurSchedule( SCHED_ANTLION_BURROW_OUT ) )
+ TestPlayerPushing( pOther );
+ }
+
+ //Adrian: Explode if hit by gunship!
+ //Maybe only do this if hit by the propellers?
+ if ( pOther->IsNPC() )
+ {
+ if ( pOther->Classify() == CLASS_COMBINE_GUNSHIP )
+ {
+ float flDamage = m_iHealth + 25;
+
+ CTakeDamageInfo dmgInfo( pOther, pOther, flDamage, DMG_GENERIC );
+ GuessDamageForce( &dmgInfo, (pOther->GetAbsOrigin() - GetAbsOrigin()), pOther->GetAbsOrigin() );
+ TakeDamage( dmgInfo );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: turn in the direction of movement
+// Output :
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
+{
+ if ( hl2_episodic.GetBool() )
+ {
+ if ( IsWorker() && GetEnemy() )
+ {
+ AddFacingTarget( GetEnemy(), GetEnemy()->WorldSpaceCenter(), 1.0f, 0.2f );
+ return BaseClass::OverrideMoveFacing( move, flInterval );
+ }
+ }
+
+ //Adrian: Make antlions face the thumper while they flee away.
+ if ( IsCurSchedule( SCHED_ANTLION_FLEE_THUMPER ) )
+ {
+ CSound *pSound = GetLoudestSoundOfType( SOUND_THUMPER );
+
+ if ( pSound )
+ {
+ AddFacingTarget( pSound->GetSoundOrigin(), 1.0, 0.5f );
+ }
+ }
+ else if ( GetEnemy() && GetNavigator()->GetMovementActivity() == ACT_RUN )
+ {
+ // FIXME: this will break scripted sequences that walk when they have an enemy
+ Vector vecEnemyLKP = GetEnemyLKP();
+ if ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < 512 )
+ {
+ // Only start facing when we're close enough
+ AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.2 );
+ }
+ }
+
+ return BaseClass::OverrideMoveFacing( move, flInterval );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::InputDisableJump( inputdata_t &inputdata )
+{
+ m_bDisableJump = true;
+ CapabilitiesRemove( bits_CAP_MOVE_JUMP );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::InputEnableJump( inputdata_t &inputdata )
+{
+ m_bDisableJump = false;
+ CapabilitiesAdd( bits_CAP_MOVE_JUMP );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTarget -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::SetFollowTarget( CBaseEntity *pTarget )
+{
+ m_FollowBehavior.SetFollowTarget( pTarget );
+ m_hFollowTarget = pTarget;
+ m_flObeyFollowTime = gpGlobals->curtime + ANTLION_OBEY_FOLLOW_TIME;
+
+ SetCondition( COND_ANTLION_RECEIVED_ORDERS );
+
+ // Play an acknowledgement noise
+ if ( m_flNextAcknowledgeTime < gpGlobals->curtime )
+ {
+ EmitSound( "NPC_Antlion.Distracted" );
+ m_flNextAcknowledgeTime = gpGlobals->curtime + 1.0f;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::CreateBehaviors( void )
+{
+ AddBehavior( &m_FollowBehavior );
+ AddBehavior( &m_AssaultBehavior );
+
+ return BaseClass::CreateBehaviors();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::InputIgnoreBugbait( inputdata_t &inputdata )
+{
+ m_bIgnoreBugbait = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::InputHearBugbait( inputdata_t &inputdata )
+{
+ m_bIgnoreBugbait = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : state -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::SetMoveState( AntlionMoveState_e state )
+{
+ m_MoveState = state;
+
+ switch( m_MoveState )
+ {
+ case ANTLION_MOVE_FOLLOW:
+
+ m_FollowBehavior.SetFollowTarget( m_hFollowTarget );
+
+ // Clear any previous state
+ m_flSuppressFollowTime = 0;
+
+ break;
+
+ case ANTLION_MOVE_FIGHT_TO_GOAL:
+
+ m_FollowBehavior.SetFollowTarget( NULL );
+
+ // Keep the time we started this
+ m_flSuppressFollowTime = gpGlobals->curtime + random->RandomInt( 10, 15 );
+ break;
+
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Special version helps other NPCs hit overturned antlion
+//-----------------------------------------------------------------------------
+Vector CNPC_Antlion::BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ )
+{
+ // Cache the bone away to avoid future lookups
+ if ( m_nBodyBone == -1 )
+ {
+ CBaseAnimating *pAnimating = GetBaseAnimating();
+ m_nBodyBone = pAnimating->LookupBone( "Antlion.Body_Bone" );
+ }
+
+ // Get the exact position in our center of mass (thorax)
+ Vector vecResult;
+ QAngle vecAngle;
+ GetBonePosition( m_nBodyBone, vecResult, vecAngle );
+
+ if ( bNoisy )
+ return vecResult + RandomVector( -8, 8 );
+
+ return vecResult;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Flip the antlion over
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::Flip( bool bZapped /*= false*/ )
+{
+ // We can't flip an already flipped antlion
+ if ( IsFlipped() )
+ return;
+
+ // Must be on the ground
+ if ( ( GetFlags() & FL_ONGROUND ) == false )
+ return;
+
+ // Can't be in a dynamic interation
+ if ( IsRunningDynamicInteraction() )
+ return;
+
+ SetCondition( COND_ANTLION_FLIPPED );
+
+ if ( bZapped )
+ {
+ m_flZapDuration = gpGlobals->curtime + SequenceDuration( SelectWeightedSequence( (Activity) ACT_ANTLION_ZAP_FLIP) ) + 0.1f;
+
+ EmitSound( "NPC_Antlion.ZappedFlip" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::InputJumpAtTarget( inputdata_t &inputdata )
+{
+ CBaseEntity *pJumpTarget = gEntList.FindEntityByName( NULL, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller );
+ if ( pJumpTarget == NULL )
+ {
+ Msg("Unable to find jump target named (%s)\n", inputdata.value.String() );
+ return;
+ }
+
+#if HL2_EPISODIC
+
+ // Try the jump
+ AIMoveTrace_t moveTrace;
+ Vector targetPos = pJumpTarget->GetAbsOrigin();
+
+ // initialize jump state
+ float minJumpHeight = 0.0;
+ float maxHorzVel = 800.0f;
+
+ // initial jump, sets baseline for minJumpHeight
+ Vector vecApex;
+ Vector rawJumpVel = GetMoveProbe()->CalcJumpLaunchVelocity(GetAbsOrigin(), targetPos, GetCurrentGravity() * GetJumpGravity(), &minJumpHeight, maxHorzVel, &vecApex );
+
+ if ( g_debug_antlion.GetInt() == 2 )
+ {
+ NDebugOverlay::Box( targetPos, GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 5 );
+ NDebugOverlay::Line( GetAbsOrigin(), targetPos, 0, 255, 0, 0, 5 );
+ NDebugOverlay::Line( GetAbsOrigin(), rawJumpVel, 255, 255, 0, 0, 5 );
+ }
+
+ m_vecSavedJump = rawJumpVel;
+
+#else
+
+ // Get the direction and speed to our target
+ Vector vecJumpDir = ( pJumpTarget->GetAbsOrigin() - GetAbsOrigin() );
+ VectorNormalize( vecJumpDir );
+ vecJumpDir *= 800.0f; // FIXME: We'd like to pass this in as a parameter, but comma delimited lists are bad
+ m_vecSavedJump = vecJumpDir;
+
+#endif
+
+ SetCondition( COND_ANTLION_CAN_JUMP_AT_TARGET );
+}
+
+#if HL2_EPISODIC
+//-----------------------------------------------------------------------------
+// workers can explode.
+//-----------------------------------------------------------------------------
+void CNPC_Antlion::DoPoisonBurst()
+{
+ if ( GetWaterLevel() < 2 )
+ {
+ CTakeDamageInfo info( this, this, sk_antlion_worker_burst_damage.GetFloat(), DMG_BLAST_SURFACE | ( ANTLION_WORKER_BURST_IS_POISONOUS() ? DMG_POISON : DMG_ACID ) );
+
+ RadiusDamage( info, GetAbsOrigin(), sk_antlion_worker_burst_radius.GetFloat(), CLASS_NONE, this );
+
+ DispatchParticleEffect( "antlion_gib_02", WorldSpaceCenter(), GetAbsAngles() );
+ }
+ else
+ {
+ CEffectData data;
+
+ data.m_vOrigin = WorldSpaceCenter();
+ data.m_flMagnitude = 100;
+ data.m_flScale = 128;
+ data.m_fFlags = ( SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE );
+
+ DispatchEffect( "WaterSurfaceExplosion", data );
+ }
+
+ EmitSound( "NPC_Antlion.PoisonBurstExplode" );
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::IsHeavyDamage( const CTakeDamageInfo &info )
+{
+ if ( hl2_episodic.GetBool() && IsWorker() )
+ {
+ if ( m_nSustainedDamage + info.GetDamage() > 6 )
+ return true;
+ }
+
+ return BaseClass::IsHeavyDamage( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : bForced -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Antlion::CanRunAScriptedNPCInteraction( bool bForced /*= false*/ )
+{
+ // Workers shouldn't do DSS's because they explode
+ if ( IsWorker() )
+ return false;
+
+ return BaseClass::CanRunAScriptedNPCInteraction( bForced );
+}
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CAntlionRepellant )
+ DEFINE_KEYFIELD( m_flRepelRadius, FIELD_FLOAT, "repelradius" ),
+ DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+END_DATADESC()
+
+static CUtlVector< CHandle< CAntlionRepellant > >m_hRepellantList;
+
+
+CAntlionRepellant::~CAntlionRepellant()
+{
+ m_hRepellantList.FindAndRemove( this );
+}
+
+void CAntlionRepellant::Spawn( void )
+{
+ BaseClass::Spawn();
+ m_bEnabled = true;
+
+ m_hRepellantList.AddToTail( this );
+}
+
+void CAntlionRepellant::InputEnable( inputdata_t &inputdata )
+{
+ m_bEnabled = true;
+
+ if ( m_hRepellantList.HasElement( this ) == false )
+ m_hRepellantList.AddToTail( this );
+}
+
+void CAntlionRepellant::InputDisable( inputdata_t &inputdata )
+{
+ m_bEnabled = false;
+ m_hRepellantList.FindAndRemove( this );
+}
+
+float CAntlionRepellant::GetRadius( void )
+{
+ if ( m_bEnabled == false )
+ return 0.0f;
+
+ return m_flRepelRadius;
+}
+
+void CAntlionRepellant::OnRestore( void )
+{
+ BaseClass::OnRestore();
+
+ if ( m_bEnabled == true )
+ {
+ if ( m_hRepellantList.HasElement( this ) == false )
+ m_hRepellantList.AddToTail( this );
+ }
+}
+
+bool CAntlionRepellant::IsPositionRepellantFree( Vector vDesiredPos )
+{
+ for ( int i = 0; i < m_hRepellantList.Count(); i++ )
+ {
+ if ( m_hRepellantList[i] )
+ {
+ CAntlionRepellant *pRep = m_hRepellantList[i].Get();
+
+ if ( pRep )
+ {
+ float flDist = (vDesiredPos - pRep->GetAbsOrigin()).Length();
+
+ if ( flDist <= pRep->GetRadius() )
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+LINK_ENTITY_TO_CLASS( point_antlion_repellant, CAntlionRepellant);
+
+
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( npc_antlion, CNPC_Antlion )
+
+ //Register our interactions
+ DECLARE_INTERACTION( g_interactionAntlionFoundTarget )
+ DECLARE_INTERACTION( g_interactionAntlionFiredAtTarget )
+
+ //Conditions
+ DECLARE_CONDITION( COND_ANTLION_FLIPPED )
+ DECLARE_CONDITION( COND_ANTLION_ON_NPC )
+ DECLARE_CONDITION( COND_ANTLION_CAN_JUMP )
+ DECLARE_CONDITION( COND_ANTLION_FOLLOW_TARGET_TOO_FAR )
+ DECLARE_CONDITION( COND_ANTLION_RECEIVED_ORDERS )
+ DECLARE_CONDITION( COND_ANTLION_IN_WATER )
+ DECLARE_CONDITION( COND_ANTLION_CAN_JUMP_AT_TARGET )
+ DECLARE_CONDITION( COND_ANTLION_SQUADMATE_KILLED )
+
+ //Squad slots
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ANTLION_JUMP )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ANTLION_WORKER_FIRE )
+
+ //Tasks
+ DECLARE_TASK( TASK_ANTLION_SET_CHARGE_GOAL )
+ DECLARE_TASK( TASK_ANTLION_BURROW )
+ DECLARE_TASK( TASK_ANTLION_UNBURROW )
+ DECLARE_TASK( TASK_ANTLION_VANISH )
+ DECLARE_TASK( TASK_ANTLION_FIND_BURROW_IN_POINT )
+ DECLARE_TASK( TASK_ANTLION_FIND_BURROW_OUT_POINT )
+ DECLARE_TASK( TASK_ANTLION_BURROW_WAIT )
+ DECLARE_TASK( TASK_ANTLION_CHECK_FOR_UNBORROW )
+ DECLARE_TASK( TASK_ANTLION_JUMP )
+ DECLARE_TASK( TASK_ANTLION_WAIT_FOR_TRIGGER )
+ DECLARE_TASK( TASK_ANTLION_GET_THUMPER_ESCAPE_PATH )
+ DECLARE_TASK( TASK_ANTLION_GET_PATH_TO_BUGBAIT )
+ DECLARE_TASK( TASK_ANTLION_FACE_BUGBAIT )
+ DECLARE_TASK( TASK_ANTLION_DISMOUNT_NPC )
+ DECLARE_TASK( TASK_ANTLION_REACH_FIGHT_GOAL )
+ DECLARE_TASK( TASK_ANTLION_GET_PHYSICS_DANGER_ESCAPE_PATH )
+ DECLARE_TASK( TASK_ANTLION_FACE_JUMP )
+ DECLARE_TASK( TASK_ANTLION_DROWN )
+ DECLARE_TASK( TASK_ANTLION_GET_PATH_TO_RANDOM_NODE )
+ DECLARE_TASK( TASK_ANTLION_FIND_COVER_FROM_SAVEPOSITION )
+
+ //Activities
+ DECLARE_ACTIVITY( ACT_ANTLION_DISTRACT )
+ DECLARE_ACTIVITY( ACT_ANTLION_DISTRACT_ARRIVED )
+ DECLARE_ACTIVITY( ACT_ANTLION_JUMP_START )
+ DECLARE_ACTIVITY( ACT_ANTLION_BURROW_IN )
+ DECLARE_ACTIVITY( ACT_ANTLION_BURROW_OUT )
+ DECLARE_ACTIVITY( ACT_ANTLION_BURROW_IDLE )
+ DECLARE_ACTIVITY( ACT_ANTLION_RUN_AGITATED )
+ DECLARE_ACTIVITY( ACT_ANTLION_FLIP )
+ DECLARE_ACTIVITY( ACT_ANTLION_POUNCE )
+ DECLARE_ACTIVITY( ACT_ANTLION_POUNCE_MOVING )
+ DECLARE_ACTIVITY( ACT_ANTLION_DROWN )
+ DECLARE_ACTIVITY( ACT_ANTLION_LAND )
+ DECLARE_ACTIVITY( ACT_ANTLION_WORKER_EXPLODE )
+ DECLARE_ACTIVITY( ACT_ANTLION_ZAP_FLIP )
+
+ //Events
+ DECLARE_ANIMEVENT( AE_ANTLION_WALK_FOOTSTEP )
+ DECLARE_ANIMEVENT( AE_ANTLION_MELEE_HIT1 )
+ DECLARE_ANIMEVENT( AE_ANTLION_MELEE_HIT2 )
+ DECLARE_ANIMEVENT( AE_ANTLION_MELEE_POUNCE )
+ DECLARE_ANIMEVENT( AE_ANTLION_FOOTSTEP_SOFT )
+ DECLARE_ANIMEVENT( AE_ANTLION_FOOTSTEP_HEAVY )
+ DECLARE_ANIMEVENT( AE_ANTLION_START_JUMP )
+ DECLARE_ANIMEVENT( AE_ANTLION_BURROW_IN )
+ DECLARE_ANIMEVENT( AE_ANTLION_BURROW_OUT )
+ DECLARE_ANIMEVENT( AE_ANTLION_VANISH )
+ DECLARE_ANIMEVENT( AE_ANTLION_OPEN_WINGS )
+ DECLARE_ANIMEVENT( AE_ANTLION_CLOSE_WINGS )
+ DECLARE_ANIMEVENT( AE_ANTLION_MELEE1_SOUND )
+ DECLARE_ANIMEVENT( AE_ANTLION_MELEE2_SOUND )
+ DECLARE_ANIMEVENT( AE_ANTLION_WORKER_EXPLODE_SCREAM )
+ DECLARE_ANIMEVENT( AE_ANTLION_WORKER_EXPLODE_WARN )
+ DECLARE_ANIMEVENT( AE_ANTLION_WORKER_EXPLODE )
+ DECLARE_ANIMEVENT( AE_ANTLION_WORKER_SPIT )
+ DECLARE_ANIMEVENT( AE_ANTLION_WORKER_DONT_EXPLODE )
+
+ //Schedules
+
+ //==================================================
+ // Jump
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_JUMP,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_ANTLION_FACE_JUMP 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLION_JUMP_START"
+ " TASK_ANTLION_JUMP 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // Wait for unborrow (once burrow has been triggered)
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_WAIT_UNBORROW,
+
+ " Tasks"
+ " TASK_ANTLION_BURROW_WAIT 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_ANTLION_WAIT_FOR_CLEAR_UNBORROW"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // Burrow Wait
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_BURROW_WAIT,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLION_BURROW_WAIT"
+ " TASK_ANTLION_BURROW_WAIT 1"
+ " TASK_ANTLION_FIND_BURROW_OUT_POINT 1024"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_ANTLION_WAIT_FOR_CLEAR_UNBORROW"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // Burrow In
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_BURROW_IN,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
+ " TASK_ANTLION_BURROW 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLION_BURROW_IN"
+ " TASK_ANTLION_VANISH 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_ANTLION_BURROW_WAIT"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // Run to burrow in
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_RUN_TO_BURROW_IN,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
+ " TASK_SET_TOLERANCE_DISTANCE 8"
+ " TASK_ANTLION_FIND_BURROW_IN_POINT 512"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_ANTLION_BURROW_IN"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_GIVE_WAY"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ )
+
+ //==================================================
+ // Burrow Out
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_BURROW_OUT,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLION_BURROW_WAIT"
+ " TASK_ANTLION_UNBURROW 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLION_BURROW_OUT"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // Wait for unborrow (triggered)
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_WAIT_FOR_UNBORROW_TRIGGER,
+
+ " Tasks"
+ " TASK_ANTLION_WAIT_FOR_TRIGGER 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // Wait for clear burrow spot (triggered)
+ //==================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_WAIT_FOR_CLEAR_UNBORROW,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLION_BURROW_WAIT"
+ " TASK_ANTLION_CHECK_FOR_UNBORROW 1"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_ANTLION_BURROW_OUT"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // Run from the sound of a thumper!
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_FLEE_THUMPER,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_IDLE_STAND"
+ " TASK_ANTLION_GET_THUMPER_ESCAPE_PATH 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLION_DISTRACT_ARRIVED"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_ANTLION_FLIPPED"
+ )
+
+ //==================================================
+ // SCHED_ANTLION_CHASE_BUGBAIT
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_CHASE_BUGBAIT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_ANTLION_GET_PATH_TO_BUGBAIT 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_ANTLION_FACE_BUGBAIT 0"
+ ""
+ " Interrupts"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_SEE_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //==================================================
+ // SCHED_ANTLION_ZAP_FLIP
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_ZAP_FLIP,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_RESET_ACTIVITY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLION_ZAP_FLIP"
+
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // SCHED_ANTLION_FLIP
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_FLIP,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_RESET_ACTIVITY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLION_FLIP"
+
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //=========================================================
+ // Headcrab has landed atop another NPC. Get down!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_DISMOUNT_NPC,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_ANTLION_DISMOUNT_NPC 0"
+
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_RUN_TO_FIGHT_GOAL,
+
+ " Tasks"
+ " TASK_SET_TOLERANCE_DISTANCE 128"
+ " TASK_GET_PATH_TO_SAVEPOSITION 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_ANTLION_REACH_FIGHT_GOAL 0"
+
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAVY_DAMAGE"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_ANTLION_CAN_JUMP"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_RUN_TO_FOLLOW_GOAL,
+
+ " Tasks"
+ " TASK_SET_TOLERANCE_DISTANCE 128"
+ " TASK_GET_PATH_TO_SAVEPOSITION 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAVY_DAMAGE"
+ " COND_ANTLION_CAN_JUMP"
+ " COND_ANTLION_FOLLOW_TARGET_TOO_FAR"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_BUGBAIT_IDLE_STAND,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_PLAYER 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 2"
+
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAVY_DAMAGE"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_ANTLION_CAN_JUMP"
+ " COND_ANTLION_FOLLOW_TARGET_TOO_FAR"
+ " COND_GIVE_WAY"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_BURROW_AWAY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_ANTLION_BURROW 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLION_BURROW_IN"
+ " TASK_ANTLION_VANISH 1"
+
+ " Interrupts"
+ )
+
+ //==================================================
+ // Run from the sound of a physics crash
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_FLEE_PHYSICS_DANGER,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
+ " TASK_ANTLION_GET_PHYSICS_DANGER_ESCAPE_PATH 1024"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ // Pounce forward at our enemy
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_POUNCE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_ANNOUNCE_ATTACK 1"
+ " TASK_RESET_ACTIVITY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLION_POUNCE"
+
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+ // Pounce forward at our enemy
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_POUNCE_MOVING,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_ANNOUNCE_ATTACK 1"
+ " TASK_RESET_ACTIVITY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLION_POUNCE_MOVING"
+
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //=========================================================
+ // The irreversible process of drowning
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_DROWN,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_ANTLION_DROWN"
+ " TASK_ANTLION_DROWN 0"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_WORKER_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
+ " TASK_RANGE_ATTACK1 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_WORKER_FLANK_RANDOM,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLION_WORKER_RUN_RANDOM"
+ " TASK_SET_TOLERANCE_DISTANCE 48"
+ " TASK_SET_ROUTE_SEARCH_TIME 1" // Spend 1 second trying to build a path if stuck
+ " TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS 30"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_HEAVY_DAMAGE"
+ " COND_ANTLION_SQUADMATE_KILLED"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_WORKER_RUN_RANDOM,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLION_TAKE_COVER_FROM_ENEMY"
+ " TASK_SET_TOLERANCE_DISTANCE 48"
+ " TASK_SET_ROUTE_SEARCH_TIME 1" // Spend 1 second trying to build a path if stuck
+ " TASK_GET_PATH_TO_RANDOM_NODE 128"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_CAN_RANGE_ATTACK1"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_TAKE_COVER_FROM_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FAIL_TAKE_COVER"
+ " 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"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ANTLION_TAKE_COVER_FROM_SAVEPOSITION,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FAIL_TAKE_COVER"
+ " TASK_ANTLION_FIND_COVER_FROM_SAVEPOSITION 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_NEW_ENEMY"
+ )
+
+AI_END_CUSTOM_NPC()
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Whether or not the target is a worker class of antlion
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool IsAntlionWorker( CBaseEntity *pEntity )
+{
+ // Must at least be valid and an antlion
+ return ( pEntity != NULL &&
+ pEntity->Classify() == CLASS_ANTLION &&
+ pEntity->HasSpawnFlags( SF_ANTLION_WORKER ) &&
+ dynamic_cast<CNPC_Antlion *>(pEntity) != NULL ); // Save this as the last step
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Whether or not the entity is a common antlion
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool IsAntlion( CBaseEntity *pEntity )
+{
+ // Must at least be valid and an antlion
+ return ( pEntity != NULL &&
+ pEntity->Classify() == CLASS_ANTLION &&
+ dynamic_cast<CNPC_Antlion *>(pEntity) != NULL ); // Save this as the last step
+}
+
+#ifdef HL2_EPISODIC
+//-----------------------------------------------------------------------------
+// Purpose: Used by other entities to judge the antlion worker's radius of damage
+//-----------------------------------------------------------------------------
+float AntlionWorkerBurstRadius( void )
+{
+ return sk_antlion_worker_burst_radius.GetFloat();
+}
+#endif // HL2_EPISODIC
|