You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5103 lines
141 KiB
5103 lines
141 KiB
//========= 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
|
|
|