//========= 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(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(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(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(pHandleEntity) ); CBaseEntity *pTestEntity = static_cast(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( 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(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(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