diff options
Diffstat (limited to 'mp/src/game/server/hl2/npc_antlion.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/npc_antlion.cpp | 10206 |
1 files changed, 5103 insertions, 5103 deletions
diff --git a/mp/src/game/server/hl2/npc_antlion.cpp b/mp/src/game/server/hl2/npc_antlion.cpp index f78e4d2f..8cb7f789 100644 --- a/mp/src/game/server/hl2/npc_antlion.cpp +++ b/mp/src/game/server/hl2/npc_antlion.cpp @@ -1,5103 +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
+//========= 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 |