aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_headcrab.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mp/src/game/server/hl2/npc_headcrab.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_headcrab.cpp7982
1 files changed, 3991 insertions, 3991 deletions
diff --git a/mp/src/game/server/hl2/npc_headcrab.cpp b/mp/src/game/server/hl2/npc_headcrab.cpp
index ab0cc05b..6e7d3e71 100644
--- a/mp/src/game/server/hl2/npc_headcrab.cpp
+++ b/mp/src/game/server/hl2/npc_headcrab.cpp
@@ -1,3991 +1,3991 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Implements the headcrab, a tiny, jumpy alien parasite.
-//
-// TODO: make poison headcrab hop in response to nearby bullet impacts?
-//
-//=============================================================================//
-
-#include "cbase.h"
-#include "game.h"
-#include "antlion_dust.h"
-#include "ai_default.h"
-#include "ai_schedule.h"
-#include "ai_hint.h"
-#include "ai_hull.h"
-#include "ai_navigator.h"
-#include "ai_moveprobe.h"
-#include "ai_memory.h"
-#include "bitstring.h"
-#include "hl2_shareddefs.h"
-#include "npcevent.h"
-#include "soundent.h"
-#include "npc_headcrab.h"
-#include "gib.h"
-#include "ai_interactions.h"
-#include "ndebugoverlay.h"
-#include "vstdlib/random.h"
-#include "engine/IEngineSound.h"
-#include "movevars_shared.h"
-#include "world.h"
-#include "npc_bullseye.h"
-#include "physics_npc_solver.h"
-#include "hl2_gamerules.h"
-#include "decals.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-#define CRAB_ATTN_IDLE (float)1.5
-#define HEADCRAB_GUTS_GIB_COUNT 1
-#define HEADCRAB_LEGS_GIB_COUNT 3
-#define HEADCRAB_ALL_GIB_COUNT 5
-
-#define HEADCRAB_RUNMODE_ACCELERATE 1
-#define HEADCRAB_RUNMODE_IDLE 2
-#define HEADCRAB_RUNMODE_DECELERATE 3
-#define HEADCRAB_RUNMODE_FULLSPEED 4
-#define HEADCRAB_RUNMODE_PAUSE 5
-
-#define HEADCRAB_RUN_MINSPEED 0.5
-#define HEADCRAB_RUN_MAXSPEED 1.0
-
-const float HEADCRAB_BURROWED_FOV = -1.0f;
-const float HEADCRAB_UNBURROWED_FOV = 0.5f;
-
-#define HEADCRAB_IGNORE_WORLD_COLLISION_TIME 0.5
-
-const int HEADCRAB_MIN_JUMP_DIST = 48;
-const int HEADCRAB_MAX_JUMP_DIST = 256;
-
-#define HEADCRAB_BURROW_POINT_SEARCH_RADIUS 256.0
-
-// Debugging
-#define HEADCRAB_DEBUG_HIDING 1
-
-#define HEADCRAB_BURN_SOUND_FREQUENCY 10
-
-ConVar g_debug_headcrab( "g_debug_headcrab", "0", FCVAR_CHEAT );
-
-//------------------------------------
-// Spawnflags
-//------------------------------------
-#define SF_HEADCRAB_START_HIDDEN (1 << 16)
-#define SF_HEADCRAB_START_HANGING (1 << 17)
-
-
-//-----------------------------------------------------------------------------
-// Think contexts.
-//-----------------------------------------------------------------------------
-static const char *s_pPitchContext = "PitchContext";
-
-
-//-----------------------------------------------------------------------------
-// Animation events.
-//-----------------------------------------------------------------------------
-int AE_HEADCRAB_JUMPATTACK;
-int AE_HEADCRAB_JUMP_TELEGRAPH;
-int AE_POISONHEADCRAB_FLINCH_HOP;
-int AE_POISONHEADCRAB_FOOTSTEP;
-int AE_POISONHEADCRAB_THREAT_SOUND;
-int AE_HEADCRAB_BURROW_IN;
-int AE_HEADCRAB_BURROW_IN_FINISH;
-int AE_HEADCRAB_BURROW_OUT;
-int AE_HEADCRAB_CEILING_DETACH;
-
-
-//-----------------------------------------------------------------------------
-// Custom schedules.
-//-----------------------------------------------------------------------------
-enum
-{
- SCHED_HEADCRAB_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE,
- SCHED_HEADCRAB_WAKE_ANGRY,
- SCHED_HEADCRAB_WAKE_ANGRY_NO_DISPLAY,
- SCHED_HEADCRAB_DROWN,
- SCHED_HEADCRAB_FAIL_DROWN,
- SCHED_HEADCRAB_AMBUSH,
- SCHED_HEADCRAB_HOP_RANDOMLY, // get off something you're not supposed to be on.
- SCHED_HEADCRAB_BARNACLED,
- SCHED_HEADCRAB_UNHIDE,
- SCHED_HEADCRAB_HARASS_ENEMY,
- SCHED_HEADCRAB_FALL_TO_GROUND,
- SCHED_HEADCRAB_RUN_TO_BURROW_IN,
- SCHED_HEADCRAB_RUN_TO_SPECIFIC_BURROW,
- SCHED_HEADCRAB_BURROW_IN,
- SCHED_HEADCRAB_BURROW_WAIT,
- SCHED_HEADCRAB_BURROW_OUT,
- SCHED_HEADCRAB_WAIT_FOR_CLEAR_UNBURROW,
- SCHED_HEADCRAB_CRAWL_FROM_CANISTER,
-
- SCHED_FAST_HEADCRAB_RANGE_ATTACK1,
-
- SCHED_HEADCRAB_CEILING_WAIT,
- SCHED_HEADCRAB_CEILING_DROP,
-};
-
-
-//=========================================================
-// tasks
-//=========================================================
-enum
-{
- TASK_HEADCRAB_HOP_ASIDE = LAST_SHARED_TASK,
- TASK_HEADCRAB_HOP_OFF_NPC,
- TASK_HEADCRAB_DROWN,
- TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL,
- TASK_HEADCRAB_UNHIDE,
- TASK_HEADCRAB_HARASS_HOP,
- TASK_HEADCRAB_FIND_BURROW_IN_POINT,
- TASK_HEADCRAB_BURROW,
- TASK_HEADCRAB_UNBURROW,
- TASK_HEADCRAB_BURROW_WAIT,
- TASK_HEADCRAB_CHECK_FOR_UNBURROW,
- TASK_HEADCRAB_JUMP_FROM_CANISTER,
- TASK_HEADCRAB_CLIMB_FROM_CANISTER,
-
- TASK_HEADCRAB_CEILING_WAIT,
- TASK_HEADCRAB_CEILING_POSITION,
- TASK_HEADCRAB_CEILING_DETACH,
- TASK_HEADCRAB_CEILING_FALL,
- TASK_HEADCRAB_CEILING_LAND,
-};
-
-
-//=========================================================
-// conditions
-//=========================================================
-enum
-{
- COND_HEADCRAB_IN_WATER = LAST_SHARED_CONDITION,
- COND_HEADCRAB_ILLEGAL_GROUNDENT,
- COND_HEADCRAB_BARNACLED,
- COND_HEADCRAB_UNHIDE,
-};
-
-//=========================================================
-// private activities
-//=========================================================
-int ACT_HEADCRAB_THREAT_DISPLAY;
-int ACT_HEADCRAB_HOP_LEFT;
-int ACT_HEADCRAB_HOP_RIGHT;
-int ACT_HEADCRAB_DROWN;
-int ACT_HEADCRAB_BURROW_IN;
-int ACT_HEADCRAB_BURROW_OUT;
-int ACT_HEADCRAB_BURROW_IDLE;
-int ACT_HEADCRAB_CRAWL_FROM_CANISTER_LEFT;
-int ACT_HEADCRAB_CRAWL_FROM_CANISTER_CENTER;
-int ACT_HEADCRAB_CRAWL_FROM_CANISTER_RIGHT;
-int ACT_HEADCRAB_CEILING_IDLE;
-int ACT_HEADCRAB_CEILING_DETACH;
-int ACT_HEADCRAB_CEILING_FALL;
-int ACT_HEADCRAB_CEILING_LAND;
-
-
-//-----------------------------------------------------------------------------
-// Skill settings.
-//-----------------------------------------------------------------------------
-ConVar sk_headcrab_health( "sk_headcrab_health","0");
-ConVar sk_headcrab_fast_health( "sk_headcrab_fast_health","0");
-ConVar sk_headcrab_poison_health( "sk_headcrab_poison_health","0");
-ConVar sk_headcrab_melee_dmg( "sk_headcrab_melee_dmg","0");
-ConVar sk_headcrab_poison_npc_damage( "sk_headcrab_poison_npc_damage", "0" );
-
-BEGIN_DATADESC( CBaseHeadcrab )
-
- // m_nGibCount - don't save
- DEFINE_FIELD( m_bHidden, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flTimeDrown, FIELD_TIME ),
- DEFINE_FIELD( m_bCommittedToJump, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_vecCommittedJumpPos, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_flNextNPCThink, FIELD_TIME ),
- DEFINE_FIELD( m_flIgnoreWorldCollisionTime, FIELD_TIME ),
-
- DEFINE_KEYFIELD( m_bStartBurrowed, FIELD_BOOLEAN, "startburrowed" ),
- DEFINE_FIELD( m_bBurrowed, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flBurrowTime, FIELD_TIME ),
- DEFINE_FIELD( m_nContext, FIELD_INTEGER ),
- DEFINE_FIELD( m_bCrawlFromCanister, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bMidJump, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_nJumpFromCanisterDir, FIELD_INTEGER ),
-
- DEFINE_FIELD( m_bHangingFromCeiling, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flIlluminatedTime, FIELD_TIME ),
-
- DEFINE_INPUTFUNC( FIELD_VOID, "Burrow", InputBurrow ),
- DEFINE_INPUTFUNC( FIELD_VOID, "BurrowImmediate", InputBurrowImmediate ),
- DEFINE_INPUTFUNC( FIELD_VOID, "Unburrow", InputUnburrow ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartHangingFromCeiling", InputStartHangingFromCeiling ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DropFromCeiling", InputDropFromCeiling ),
-
- // Function Pointers
- DEFINE_THINKFUNC( EliminateRollAndPitch ),
- DEFINE_THINKFUNC( ThrowThink ),
- DEFINE_ENTITYFUNC( LeapTouch ),
-
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::Spawn( void )
-{
- //Precache();
- //SetModel( "models/headcrab.mdl" );
- //m_iHealth = sk_headcrab_health.GetFloat();
-
-#ifdef _XBOX
- // Always fade the corpse
- AddSpawnFlags( SF_NPC_FADE_CORPSE );
-#endif // _XBOX
-
- SetHullType(HULL_TINY);
- SetHullSizeNormal();
-
- SetSolid( SOLID_BBOX );
- AddSolidFlags( FSOLID_NOT_STANDABLE );
- SetMoveType( MOVETYPE_STEP );
-
- SetCollisionGroup( HL2COLLISION_GROUP_HEADCRAB );
-
- SetViewOffset( Vector(6, 0, 11) ) ; // Position of the eyes relative to NPC's origin.
-
- SetBloodColor( BLOOD_COLOR_GREEN );
- m_flFieldOfView = 0.5;
- m_NPCState = NPC_STATE_NONE;
- m_nGibCount = HEADCRAB_ALL_GIB_COUNT;
-
- // Are we starting hidden?
- if ( m_spawnflags & SF_HEADCRAB_START_HIDDEN )
- {
- m_bHidden = true;
- AddSolidFlags( FSOLID_NOT_SOLID );
- SetRenderColorA( 0 );
- m_nRenderMode = kRenderTransTexture;
- AddEffects( EF_NODRAW );
- }
- else
- {
- m_bHidden = false;
- }
-
- CapabilitiesClear();
- CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 );
- CapabilitiesAdd( bits_CAP_SQUAD );
-
- // headcrabs get to cheat for 5 seconds (sjb)
- GetEnemies()->SetFreeKnowledgeDuration( 5.0 );
-
- m_bHangingFromCeiling = false;
- m_flIlluminatedTime = -1;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Stuff that must happen after NPCInit is called.
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::HeadcrabInit()
-{
- // See if we're supposed to start burrowed
- if ( m_bStartBurrowed )
- {
- SetBurrowed( true );
- SetSchedule( SCHED_HEADCRAB_BURROW_WAIT );
- }
-
- if ( GetSpawnFlags() & SF_HEADCRAB_START_HANGING )
- {
- SetSchedule( SCHED_HEADCRAB_CEILING_WAIT );
- m_flIlluminatedTime = -1;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Precaches all resources this monster needs.
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::Precache( void )
-{
- BaseClass::Precache();
-}
-
-
-//-----------------------------------------------------------------------------
-// The headcrab will crawl from the cannister, then jump to a burrow point
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::CrawlFromCanister()
-{
- // This is necessary to prevent ground computations, etc. from happening
- // while the crawling animation is occuring
- AddFlag( FL_FLY );
- m_bCrawlFromCanister = true;
- SetNextThink( gpGlobals->curtime );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : NewActivity -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::OnChangeActivity( Activity NewActivity )
-{
- bool fRandomize = false;
- float flRandomRange = 0.0;
-
- // If this crab is starting to walk or idle, pick a random point within
- // the animation to begin. This prevents lots of crabs being in lockstep.
- if ( NewActivity == ACT_IDLE )
- {
- flRandomRange = 0.75;
- fRandomize = true;
- }
- else if ( NewActivity == ACT_RUN )
- {
- flRandomRange = 0.25;
- fRandomize = true;
- }
-
- BaseClass::OnChangeActivity( NewActivity );
-
- if( fRandomize )
- {
- SetCycle( random->RandomFloat( 0.0, flRandomRange ) );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Indicates this monster's place in the relationship table.
-// Output :
-//-----------------------------------------------------------------------------
-Class_T CBaseHeadcrab::Classify( void )
-{
- if( m_bHidden )
- {
- // Effectively invisible to other AI's while hidden.
- return( CLASS_NONE );
- }
- else
- {
- return( CLASS_HEADCRAB );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &posSrc -
-// Output : Vector
-//-----------------------------------------------------------------------------
-Vector CBaseHeadcrab::BodyTarget( const Vector &posSrc, bool bNoisy )
-{
- Vector vecResult;
- vecResult = GetAbsOrigin();
- vecResult.z += 6;
- return vecResult;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-float CBaseHeadcrab::GetAutoAimRadius()
-{
- if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
- {
- return 24.0f;
- }
-
- return 12.0f;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Allows each sequence to have a different turn rate associated with it.
-// Output : float
-//-----------------------------------------------------------------------------
-float CBaseHeadcrab::MaxYawSpeed( void )
-{
- return BaseClass::MaxYawSpeed();
-}
-
-//-----------------------------------------------------------------------------
-// Because the AI code does a tracehull to find the ground under an NPC, headcrabs
-// can often be seen standing with one edge of their box perched on a ledge and
-// 80% or more of their body hanging out over nothing. This is often a case
-// where a headcrab will be unable to pathfind out of its location. This heuristic
-// very crudely tries to determine if this is the case by casting a simple ray
-// down from the center of the headcrab.
-//-----------------------------------------------------------------------------
-#define HEADCRAB_MAX_LEDGE_HEIGHT 12.0f
-bool CBaseHeadcrab::IsFirmlyOnGround()
-{
- if( !(GetFlags()&FL_ONGROUND) )
- return false;
-
- trace_t tr;
- UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, HEADCRAB_MAX_LEDGE_HEIGHT ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
- return tr.fraction != 1.0;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::MoveOrigin( const Vector &vecDelta )
-{
- UTIL_SetOrigin( this, GetLocalOrigin() + vecDelta );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : vecPos -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::ThrowAt( const Vector &vecPos )
-{
- JumpAttack( false, vecPos, true );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : vecPos -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::JumpToBurrowHint( CAI_Hint *pHint )
-{
- Vector vecVel = VecCheckToss( this, GetAbsOrigin(), pHint->GetAbsOrigin(), 0.5f, 1.0f, false, NULL, NULL );
-
- // Undershoot by a little because it looks bad if we overshoot and turn around to burrow.
- vecVel *= 0.9f;
- Leap( vecVel );
-
- GrabHintNode( pHint );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : vecVel -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::Leap( const Vector &vecVel )
-{
- SetTouch( &CBaseHeadcrab::LeapTouch );
-
- SetCondition( COND_FLOATING_OFF_GROUND );
- SetGroundEntity( NULL );
-
- m_flIgnoreWorldCollisionTime = gpGlobals->curtime + HEADCRAB_IGNORE_WORLD_COLLISION_TIME;
-
- if( HasHeadroom() )
- {
- // Take him off ground so engine doesn't instantly reset FL_ONGROUND.
- MoveOrigin( Vector( 0, 0, 1 ) );
- }
-
- SetAbsVelocity( vecVel );
-
- // Think every frame so the player sees the headcrab where he actually is...
- m_bMidJump = true;
- SetThink( &CBaseHeadcrab::ThrowThink );
- SetNextThink( gpGlobals->curtime );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::ThrowThink( void )
-{
- if (gpGlobals->curtime > m_flNextNPCThink)
- {
- NPCThink();
- m_flNextNPCThink = gpGlobals->curtime + 0.1;
- }
-
- if( GetFlags() & FL_ONGROUND )
- {
- SetThink( &CBaseHeadcrab::CallNPCThink );
- SetNextThink( gpGlobals->curtime + 0.1 );
- return;
- }
-
- SetNextThink( gpGlobals->curtime );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Does a jump attack at the given position.
-// Input : bRandomJump - Just hop in a random direction.
-// vecPos - Position to jump at, ignored if bRandom is set to true.
-// bThrown -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::JumpAttack( bool bRandomJump, const Vector &vecPos, bool bThrown )
-{
- Vector vecJumpVel;
- if ( !bRandomJump )
- {
- float gravity = GetCurrentGravity();
- if ( gravity <= 1 )
- {
- gravity = 1;
- }
-
- // How fast does the headcrab need to travel to reach the position given gravity?
- float flActualHeight = vecPos.z - GetAbsOrigin().z;
- float height = flActualHeight;
- if ( height < 16 )
- {
- height = 16;
- }
- else
- {
- float flMaxHeight = bThrown ? 400 : 120;
- if ( height > flMaxHeight )
- {
- height = flMaxHeight;
- }
- }
-
- // overshoot the jump by an additional 8 inches
- // NOTE: This calculation jumps at a position INSIDE the box of the enemy (player)
- // so if you make the additional height too high, the crab can land on top of the
- // enemy's head. If we want to jump high, we'll need to move vecPos to the surface/outside
- // of the enemy's box.
-
- float additionalHeight = 0;
- if ( height < 32 )
- {
- additionalHeight = 8;
- }
-
- height += additionalHeight;
-
- // NOTE: This equation here is from vf^2 = vi^2 + 2*a*d
- float speed = sqrt( 2 * gravity * height );
- float time = speed / gravity;
-
- // add in the time it takes to fall the additional height
- // So the impact takes place on the downward slope at the original height
- time += sqrt( (2 * additionalHeight) / gravity );
-
- // Scale the sideways velocity to get there at the right time
- VectorSubtract( vecPos, GetAbsOrigin(), vecJumpVel );
- vecJumpVel /= time;
-
- // Speed to offset gravity at the desired height.
- vecJumpVel.z = speed;
-
- // Don't jump too far/fast.
- float flJumpSpeed = vecJumpVel.Length();
- float flMaxSpeed = bThrown ? 1000.0f : 650.0f;
- if ( flJumpSpeed > flMaxSpeed )
- {
- vecJumpVel *= flMaxSpeed / flJumpSpeed;
- }
- }
- else
- {
- //
- // Jump hop, don't care where.
- //
- Vector forward, up;
- AngleVectors( GetLocalAngles(), &forward, NULL, &up );
- vecJumpVel = Vector( forward.x, forward.y, up.z ) * 350;
- }
-
- AttackSound();
- Leap( vecJumpVel );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Catches the monster-specific messages that occur when tagged
-// animation frames are played.
-// Input : *pEvent -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::HandleAnimEvent( animevent_t *pEvent )
-{
- if ( pEvent->event == AE_HEADCRAB_JUMPATTACK )
- {
- // Ignore if we're in mid air
- if ( m_bMidJump )
- return;
-
- CBaseEntity *pEnemy = GetEnemy();
-
- if ( pEnemy )
- {
- if ( m_bCommittedToJump )
- {
- JumpAttack( false, m_vecCommittedJumpPos );
- }
- else
- {
- // Jump at my enemy's eyes.
- JumpAttack( false, pEnemy->EyePosition() );
- }
-
- m_bCommittedToJump = false;
-
- }
- else
- {
- // Jump hop, don't care where.
- JumpAttack( true );
- }
-
- return;
- }
-
- if ( pEvent->event == AE_HEADCRAB_CEILING_DETACH )
- {
- SetMoveType( MOVETYPE_STEP );
- RemoveFlag( FL_ONGROUND );
- RemoveFlag( FL_FLY );
-
- SetAbsVelocity( Vector ( 0, 0, -128 ) );
- return;
- }
- if ( pEvent->event == AE_HEADCRAB_JUMP_TELEGRAPH )
- {
- TelegraphSound();
-
- CBaseEntity *pEnemy = GetEnemy();
-
- if ( pEnemy )
- {
- // Once we telegraph, we MUST jump. This is also when commit to what point
- // we jump at. Jump at our enemy's eyes.
- m_vecCommittedJumpPos = pEnemy->EyePosition();
- m_bCommittedToJump = true;
- }
-
- return;
- }
-
- if ( pEvent->event == AE_HEADCRAB_BURROW_IN )
- {
- EmitSound( "NPC_Headcrab.BurrowIn" );
- CreateDust();
-
- return;
- }
-
- if ( pEvent->event == AE_HEADCRAB_BURROW_IN_FINISH )
- {
- SetBurrowed( true );
- return;
- }
-
- if ( pEvent->event == AE_HEADCRAB_BURROW_OUT )
- {
- Assert( m_bBurrowed );
- if ( m_bBurrowed )
- {
- EmitSound( "NPC_Headcrab.BurrowOut" );
- CreateDust();
- SetBurrowed( false );
-
- // We're done with this burrow hint node. It might be NULL here
- // because we may have started burrowed (no hint node in that case).
- GrabHintNode( NULL );
- }
-
- return;
- }
-
- CAI_BaseNPC::HandleAnimEvent( pEvent );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Does all the fixup for going to/from the burrowed state.
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::SetBurrowed( bool bBurrowed )
-{
- if ( bBurrowed )
- {
- AddEffects( EF_NODRAW );
- AddFlag( FL_NOTARGET );
- m_spawnflags |= SF_NPC_GAG;
- AddSolidFlags( FSOLID_NOT_SOLID );
- m_takedamage = DAMAGE_NO;
- m_flFieldOfView = HEADCRAB_BURROWED_FOV;
-
- SetState( NPC_STATE_IDLE );
- SetActivity( (Activity) ACT_HEADCRAB_BURROW_IDLE );
- }
- else
- {
- RemoveEffects( EF_NODRAW );
- RemoveFlag( FL_NOTARGET );
- m_spawnflags &= ~SF_NPC_GAG;
- RemoveSolidFlags( FSOLID_NOT_SOLID );
- m_takedamage = DAMAGE_YES;
- m_flFieldOfView = HEADCRAB_UNBURROWED_FOV;
- }
-
- m_bBurrowed = bBurrowed;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pTask -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::RunTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_HEADCRAB_CLIMB_FROM_CANISTER:
- AutoMovement( );
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
-
- case TASK_HEADCRAB_JUMP_FROM_CANISTER:
- GetMotor()->UpdateYaw();
- if ( FacingIdeal() )
- {
- TaskComplete();
- }
- break;
-
- case TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL:
- if ( m_flNextFlinchTime < gpGlobals->curtime )
- {
- m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f );
- CTakeDamageInfo info;
- PainSound( info );
- }
- break;
-
- case TASK_HEADCRAB_HOP_OFF_NPC:
- if( GetFlags() & FL_ONGROUND )
- {
- TaskComplete();
- }
- else
- {
- // Face the direction I've been forced to jump.
- GetMotor()->SetIdealYawToTargetAndUpdate( GetAbsOrigin() + GetAbsVelocity() );
- }
- break;
-
- case TASK_HEADCRAB_DROWN:
- if( gpGlobals->curtime > m_flTimeDrown )
- {
- OnTakeDamage( CTakeDamageInfo( this, this, m_iHealth * 2, DMG_DROWN ) );
- }
- break;
-
- case TASK_RANGE_ATTACK1:
- case TASK_RANGE_ATTACK2:
- case TASK_HEADCRAB_HARASS_HOP:
- {
- if ( IsActivityFinished() )
- {
- TaskComplete();
- m_bMidJump = false;
- SetTouch( NULL );
- SetThink( &CBaseHeadcrab::CallNPCThink );
- SetIdealActivity( ACT_IDLE );
-
- if ( m_bAttackFailed )
- {
- // our attack failed because we just ran into something solid.
- // delay attacking for a while so we don't just repeatedly leap
- // at the enemy from a bad location.
- m_bAttackFailed = false;
- m_flNextAttack = gpGlobals->curtime + 1.2f;
- }
- }
- break;
- }
-
- case TASK_HEADCRAB_CHECK_FOR_UNBURROW:
- {
- // 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 );
-
- break;
- }
-
- case TASK_HEADCRAB_BURROW_WAIT:
- {
- if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_CAN_RANGE_ATTACK2 ) )
- {
- TaskComplete();
- }
-
- break;
- }
-
- case TASK_HEADCRAB_CEILING_WAIT:
- {
-#ifdef HL2_EPISODIC
- if ( DarknessLightSourceWithinRadius( this, DARKNESS_LIGHTSOURCE_SIZE ) )
- {
- DropFromCeiling();
- }
-#endif
-
- if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_CAN_RANGE_ATTACK2 ) )
- {
- TaskComplete();
- }
-
- break;
- }
-
- case TASK_HEADCRAB_CEILING_DETACH:
- {
- if ( IsActivityFinished() )
- {
- ClearCondition( COND_CAN_RANGE_ATTACK1 );
- RemoveFlag(FL_FLY);
- TaskComplete();
- }
- }
- break;
-
- case TASK_HEADCRAB_CEILING_FALL:
- {
- Vector vecPrPos;
- trace_t tr;
-
- //Figure out where the headcrab is going to be in quarter of a second.
- vecPrPos = GetAbsOrigin() + ( GetAbsVelocity() * 0.25f );
- UTIL_TraceHull( vecPrPos, vecPrPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.startsolid == true || GetFlags() & FL_ONGROUND )
- {
- RemoveSolidFlags( FSOLID_NOT_SOLID );
- TaskComplete();
- }
- }
- break;
-
- case TASK_HEADCRAB_CEILING_LAND:
- {
- if ( IsActivityFinished() )
- {
- RemoveSolidFlags( FSOLID_NOT_SOLID ); //double-dog verify that we're solid.
- TaskComplete();
- m_bHangingFromCeiling = false;
- }
- }
- break;
- default:
- {
- BaseClass::RunTask( pTask );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Before jumping, headcrabs usually use SetOrigin() to lift themselves off the
-// ground. If the headcrab doesn't have the clearance to so, they'll be stuck
-// in the world. So this function makes sure there's headroom first.
-//-----------------------------------------------------------------------------
-bool CBaseHeadcrab::HasHeadroom()
-{
- trace_t tr;
- UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 1 ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
-
-#if 0
- if( tr.fraction == 1.0f )
- {
- Msg("Headroom\n");
- }
- else
- {
- Msg("NO Headroom\n");
- }
-#endif
-
- return (tr.fraction == 1.0);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: LeapTouch - this is the headcrab's touch function when it is in the air.
-// Input : *pOther -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::LeapTouch( CBaseEntity *pOther )
-{
- m_bMidJump = false;
-
- if ( IRelationType( pOther ) == D_HT )
- {
- // Don't hit if back on ground
- if ( !( GetFlags() & FL_ONGROUND ) )
- {
- if ( pOther->m_takedamage != DAMAGE_NO )
- {
- BiteSound();
- TouchDamage( pOther );
-
- // attack succeeded, so don't delay our next attack if we previously thought we failed
- m_bAttackFailed = false;
- }
- else
- {
- ImpactSound();
- }
- }
- else
- {
- ImpactSound();
- }
- }
- else if( !(GetFlags() & FL_ONGROUND) )
- {
- // Still in the air...
- if( !pOther->IsSolid() )
- {
- // Touching a trigger or something.
- return;
- }
-
- // just ran into something solid, so the attack probably failed. make a note of it
- // so that when the attack is done, we'll delay attacking for a while so we don't
- // just repeatedly leap at the enemy from a bad location.
- m_bAttackFailed = true;
-
- if( gpGlobals->curtime < m_flIgnoreWorldCollisionTime )
- {
- // Headcrabs try to ignore the world, static props, and friends for a
- // fraction of a second after they jump. This is because they often brush
- // doorframes or props as they leap, and touching those objects turns off
- // this touch function, which can cause them to hit the player and not bite.
- // A timer probably isn't the best way to fix this, but it's one of our
- // safer options at this point (sjb).
- return;
- }
- }
-
- // Shut off the touch function.
- SetTouch( NULL );
- SetThink ( &CBaseHeadcrab::CallNPCThink );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CBaseHeadcrab::CalcDamageInfo( CTakeDamageInfo *pInfo )
-{
- pInfo->Set( this, this, sk_headcrab_melee_dmg.GetFloat(), DMG_SLASH );
- CalculateMeleeDamageForce( pInfo, GetAbsVelocity(), GetAbsOrigin() );
- return pInfo->GetDamage();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Deal the damage from the headcrab's touch attack.
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::TouchDamage( CBaseEntity *pOther )
-{
- CTakeDamageInfo info;
- CalcDamageInfo( &info );
- pOther->TakeDamage( info );
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CBaseHeadcrab::GatherConditions( void )
-{
- // If we're hidden, just check to see if we should unhide
- if ( m_bHidden )
- {
- // See if there's enough room for our hull to fit here. If so, unhide.
- trace_t tr;
- AI_TraceHull( GetAbsOrigin(), GetAbsOrigin(),GetHullMins(), GetHullMaxs(), MASK_SHOT, this, GetCollisionGroup(), &tr );
- if ( tr.fraction == 1.0 )
- {
- SetCondition( COND_PROVOKED );
- SetCondition( COND_HEADCRAB_UNHIDE );
-
- if ( g_debug_headcrab.GetInt() == HEADCRAB_DEBUG_HIDING )
- {
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0,255,0, true, 1.0 );
- }
- }
- else if ( g_debug_headcrab.GetInt() == HEADCRAB_DEBUG_HIDING )
- {
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255,0,0, true, 0.1 );
- }
-
- // Prevent baseclass thinking, so we don't respond to enemy fire, etc.
- return;
- }
-
- BaseClass::GatherConditions();
-
- if( m_lifeState == LIFE_ALIVE && GetWaterLevel() > 1 )
- {
- // Start Drowning!
- SetCondition( COND_HEADCRAB_IN_WATER );
- }
-
- // See if I've landed on an NPC or player or something else illegal
- ClearCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT );
- CBaseEntity *ground = GetGroundEntity();
- if( (GetFlags() & FL_ONGROUND) && ground && !ground->IsWorld() )
- {
- if ( IsHangingFromCeiling() == false )
- {
- if( ( ground->IsNPC() || ground->IsPlayer() ) )
- {
- SetCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT );
- }
- else if( ground->VPhysicsGetObject() && (ground->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
- {
- SetCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT );
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::PrescheduleThink( void )
-{
- BaseClass::PrescheduleThink();
-
- // Are we fading in after being hidden?
- if ( !m_bHidden && (m_nRenderMode != kRenderNormal) )
- {
- int iNewAlpha = MIN( 255, GetRenderColor().a + 120 );
- if ( iNewAlpha >= 255 )
- {
- m_nRenderMode = kRenderNormal;
- SetRenderColorA( 0 );
- }
- else
- {
- SetRenderColorA( iNewAlpha );
- }
- }
-
- //
- // Make the crab coo a little bit in combat state.
- //
- if (( m_NPCState == NPC_STATE_COMBAT ) && ( random->RandomFloat( 0, 5 ) < 0.1 ))
- {
- IdleSound();
- }
-
- // Make sure we've turned off our burrow state if we're not in it
- Activity eActivity = GetActivity();
- if ( m_bBurrowed &&
- ( eActivity != ACT_HEADCRAB_BURROW_IDLE ) &&
- ( eActivity != ACT_HEADCRAB_BURROW_OUT ) &&
- ( eActivity != ACT_HEADCRAB_BURROW_IN) )
- {
- DevMsg( "Headcrab failed to unburrow properly!\n" );
- Assert( 0 );
- SetBurrowed( false );
- }
-
-}
-
-
-//-----------------------------------------------------------------------------
-// Eliminates roll + pitch from the headcrab
-//-----------------------------------------------------------------------------
-#define HEADCRAB_ROLL_ELIMINATION_TIME 0.3f
-#define HEADCRAB_PITCH_ELIMINATION_TIME 0.3f
-
-//-----------------------------------------------------------------------------
-// Eliminates roll + pitch potentially in the headcrab at canister jump time
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::EliminateRollAndPitch()
-{
- QAngle angles = GetAbsAngles();
- angles.x = AngleNormalize( angles.x );
- angles.z = AngleNormalize( angles.z );
- if ( ( angles.x == 0.0f ) && ( angles.z == 0.0f ) )
- return;
-
- float flPitchRate = 90.0f / HEADCRAB_PITCH_ELIMINATION_TIME;
- float flPitchDelta = flPitchRate * TICK_INTERVAL;
- if ( fabs( angles.x ) <= flPitchDelta )
- {
- angles.x = 0.0f;
- }
- else
- {
- flPitchDelta *= (angles.x > 0.0f) ? -1.0f : 1.0f;
- angles.x += flPitchDelta;
- }
-
- float flRollRate = 180.0f / HEADCRAB_ROLL_ELIMINATION_TIME;
- float flRollDelta = flRollRate * TICK_INTERVAL;
- if ( fabs( angles.z ) <= flRollDelta )
- {
- angles.z = 0.0f;
- }
- else
- {
- flRollDelta *= (angles.z > 0.0f) ? -1.0f : 1.0f;
- angles.z += flRollDelta;
- }
-
- SetAbsAngles( angles );
-
- SetContextThink( &CBaseHeadcrab::EliminateRollAndPitch, gpGlobals->curtime + TICK_INTERVAL, s_pPitchContext );
-}
-
-
-//-----------------------------------------------------------------------------
-// Begins the climb from the canister
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::BeginClimbFromCanister()
-{
- Assert( GetMoveParent() );
- // Compute a desired position or hint
- Vector vecForward, vecActualForward;
- AngleVectors( GetMoveParent()->GetAbsAngles(), &vecActualForward );
- vecForward = vecActualForward;
- vecForward.z = 0.0f;
- VectorNormalize( vecForward );
-
- Vector vecSearchCenter = GetAbsOrigin();
- CAI_Hint *pHint = CAI_HintManager::FindHint( this, HINT_HEADCRAB_BURROW_POINT, 0, HEADCRAB_BURROW_POINT_SEARCH_RADIUS, &vecSearchCenter );
-
- if( !pHint && hl2_episodic.GetBool() )
- {
- // Look for exit points within 10 feet.
- pHint = CAI_HintManager::FindHint( this, HINT_HEADCRAB_EXIT_POD_POINT, 0, 120.0f, &vecSearchCenter );
- }
-
- if ( pHint && ( !pHint->IsLocked() ) )
- {
- // Claim the hint node so other headcrabs don't try to take it!
- GrabHintNode( pHint );
-
- // Compute relative yaw..
- Vector vecDelta;
- VectorSubtract( pHint->GetAbsOrigin(), vecSearchCenter, vecDelta );
- vecDelta.z = 0.0f;
- VectorNormalize( vecDelta );
-
- float flAngle = DotProduct( vecDelta, vecForward );
- if ( flAngle >= 0.707f )
- {
- m_nJumpFromCanisterDir = 1;
- }
- else
- {
- // Check the cross product to see if it's on the left or right.
- // All we care about is the sign of the z component. If it's +, the hint is on the left.
- // If it's -, then the hint is on the right.
- float flCrossZ = vecForward.x * vecDelta.y - vecDelta.x * vecForward.y;
- m_nJumpFromCanisterDir = ( flCrossZ > 0 ) ? 0 : 2;
- }
- }
- else
- {
- // Choose a random direction (forward, left, or right)
- m_nJumpFromCanisterDir = random->RandomInt( 0, 2 );
- }
-
- Activity act;
- switch( m_nJumpFromCanisterDir )
- {
- case 0:
- act = (Activity)ACT_HEADCRAB_CRAWL_FROM_CANISTER_LEFT;
- break;
-
- default:
- case 1:
- act = (Activity)ACT_HEADCRAB_CRAWL_FROM_CANISTER_CENTER;
- break;
-
- case 2:
- act = (Activity)ACT_HEADCRAB_CRAWL_FROM_CANISTER_RIGHT;
- break;
- }
-
- SetIdealActivity( act );
-}
-
-
-//-----------------------------------------------------------------------------
-// Jumps from the canister
-//-----------------------------------------------------------------------------
-#define HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_DIST 250.0f
-#define HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_COSANGLE 0.866f
-
-void CBaseHeadcrab::JumpFromCanister()
-{
- Assert( GetMoveParent() );
-
- Vector vecForward, vecActualForward, vecActualRight;
- AngleVectors( GetMoveParent()->GetAbsAngles(), &vecActualForward, &vecActualRight, NULL );
-
- switch( m_nJumpFromCanisterDir )
- {
- case 0:
- VectorMultiply( vecActualRight, -1.0f, vecForward );
- break;
- case 1:
- vecForward = vecActualForward;
- break;
- case 2:
- vecForward = vecActualRight;
- break;
- }
-
- vecForward.z = 0.0f;
- VectorNormalize( vecForward );
- QAngle headCrabAngles;
- VectorAngles( vecForward, headCrabAngles );
-
- SetActivity( ACT_RANGE_ATTACK1 );
- StudioFrameAdvanceManual( 0.0 );
- SetParent( NULL );
- RemoveFlag( FL_FLY );
- IncrementInterpolationFrame();
-
- GetMotor()->SetIdealYaw( headCrabAngles.y );
-
- // Check to see if the player is within jump range. If so, jump at him!
- bool bJumpedAtEnemy = false;
-
- // FIXME: Can't use GetEnemy() here because enemy only updates during
- // schedules which are interruptible by COND_NEW_ENEMY or COND_LOST_ENEMY
- CBaseEntity *pEnemy = BestEnemy();
- if ( pEnemy )
- {
- Vector vecDirToEnemy;
- VectorSubtract( pEnemy->GetAbsOrigin(), GetAbsOrigin(), vecDirToEnemy );
- vecDirToEnemy.z = 0.0f;
- float flDist = VectorNormalize( vecDirToEnemy );
- if ( ( flDist < HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_DIST ) &&
- ( DotProduct( vecDirToEnemy, vecForward ) >= HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_COSANGLE ) )
- {
- GrabHintNode( NULL );
- JumpAttack( false, pEnemy->EyePosition(), false );
- bJumpedAtEnemy = true;
- }
- }
-
- if ( !bJumpedAtEnemy )
- {
- if ( GetHintNode() )
- {
- JumpToBurrowHint( GetHintNode() );
- }
- else
- {
- vecForward *= 100.0f;
- vecForward += GetAbsOrigin();
- JumpAttack( false, vecForward, false );
- }
- }
-
- EliminateRollAndPitch();
-}
-
-#define HEADCRAB_ILLUMINATED_TIME 0.15f
-
-void CBaseHeadcrab::DropFromCeiling( void )
-{
-#ifdef HL2_EPISODIC
- if ( HL2GameRules()->IsAlyxInDarknessMode() )
- {
- if ( IsHangingFromCeiling() )
- {
- if ( m_flIlluminatedTime == -1 )
- {
- m_flIlluminatedTime = gpGlobals->curtime + HEADCRAB_ILLUMINATED_TIME;
- return;
- }
-
- if ( m_flIlluminatedTime <= gpGlobals->curtime )
- {
- if ( IsCurSchedule( SCHED_HEADCRAB_CEILING_DROP ) == false )
- {
- SetSchedule( SCHED_HEADCRAB_CEILING_DROP );
-
- CBaseEntity *pPlayer = AI_GetSinglePlayer();
-
- if ( pPlayer )
- {
- SetEnemy( pPlayer ); //Is this a bad thing to do?
- UpdateEnemyMemory( pPlayer, pPlayer->GetAbsOrigin());
- }
- }
- }
- }
- }
-#endif // HL2_EPISODIC
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Player has illuminated this NPC with the flashlight
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
-{
- if ( flDot < 0.97387f )
- return;
-
- DropFromCeiling();
-}
-
-bool CBaseHeadcrab::CanBeAnEnemyOf( CBaseEntity *pEnemy )
-{
-#ifdef HL2_EPISODIC
- if ( IsHangingFromCeiling() )
- return false;
-#endif
-
- return BaseClass::CanBeAnEnemyOf( pEnemy );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : pTask -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::StartTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL:
- break;
-
- case TASK_HEADCRAB_BURROW_WAIT:
- break;
-
- case TASK_HEADCRAB_CLIMB_FROM_CANISTER:
- BeginClimbFromCanister();
- break;
-
- case TASK_HEADCRAB_JUMP_FROM_CANISTER:
- JumpFromCanister();
- break;
-
- case TASK_HEADCRAB_CEILING_POSITION:
- {
- trace_t tr;
- UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 512 ), NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
-
- // SetMoveType( MOVETYPE_NONE );
- AddFlag(FL_FLY);
- m_bHangingFromCeiling = true;
-
- //Don't need this anymore
- RemoveSpawnFlags( SF_HEADCRAB_START_HANGING );
-
- SetAbsOrigin( tr.endpos );
-
- TaskComplete();
- }
- break;
-
- case TASK_HEADCRAB_CEILING_WAIT:
- break;
-
- case TASK_HEADCRAB_CEILING_DETACH:
- {
- SetIdealActivity( (Activity)ACT_HEADCRAB_CEILING_DETACH );
- }
- break;
-
- case TASK_HEADCRAB_CEILING_FALL:
- {
- SetIdealActivity( (Activity)ACT_HEADCRAB_CEILING_FALL );
- }
- break;
-
- case TASK_HEADCRAB_CEILING_LAND:
- {
- SetIdealActivity( (Activity)ACT_HEADCRAB_CEILING_LAND );
- }
- break;
-
- case TASK_HEADCRAB_HARASS_HOP:
- {
- // Just pop up into the air like you're trying to get at the
- // enemy, even though it's known you can't reach them.
- if ( GetEnemy() )
- {
- Vector forward, up;
-
- GetVectors( &forward, NULL, &up );
-
- m_vecCommittedJumpPos = GetAbsOrigin();
- m_vecCommittedJumpPos += up * random->RandomFloat( 80, 150 );
- m_vecCommittedJumpPos += forward * random->RandomFloat( 32, 80 );
-
- m_bCommittedToJump = true;
-
- SetIdealActivity( ACT_RANGE_ATTACK1 );
- }
- else
- {
- TaskFail( "No enemy" );
- }
- }
- break;
-
- case TASK_HEADCRAB_HOP_OFF_NPC:
- {
- CBaseEntity *ground = GetGroundEntity();
- if( ground )
- {
- // If jumping off of a physics object that the player is holding, create a
- // solver to prevent the headcrab from colliding with that object for a
- // short time.
- if( ground && ground->VPhysicsGetObject() )
- {
- if( ground->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
- {
- NPCPhysics_CreateSolver( this, ground, true, 0.5 );
- }
- }
-
-
- Vector vecJumpDir;
-
- // Jump in some random direction. This way if the person I'm standing on is
- // against a wall, I'll eventually get away.
-
- 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 );
-
- if( HasHeadroom() )
- {
- // Bump up
- MoveOrigin( Vector( 0, 0, 1 ) );
- }
-
- SetAbsVelocity( vecJumpDir * 200 + Vector( 0, 0, 200 ) );
- }
- else
- {
- // *shrug* I guess they're gone now. Or dead.
- TaskComplete();
- }
- }
- break;
-
- case TASK_HEADCRAB_DROWN:
- {
- // Set the gravity really low here! Sink slowly
- SetGravity( UTIL_ScaleForGravity( 80 ) );
- m_flTimeDrown = gpGlobals->curtime + 4;
- break;
- }
-
- case TASK_RANGE_ATTACK1:
- {
-#ifdef WEDGE_FIX_THIS
- CPASAttenuationFilter filter( this, ATTN_IDLE );
- EmitSound( filter, entindex(), CHAN_WEAPON, pAttackSounds[0], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );
-#endif
- SetIdealActivity( ACT_RANGE_ATTACK1 );
- break;
- }
-
- case TASK_HEADCRAB_UNHIDE:
- {
- m_bHidden = false;
- RemoveSolidFlags( FSOLID_NOT_SOLID );
- RemoveEffects( EF_NODRAW );
-
- TaskComplete();
- break;
- }
-
- case TASK_HEADCRAB_CHECK_FOR_UNBURROW:
- {
- if ( ValidBurrowPoint( GetAbsOrigin() ) )
- {
- m_spawnflags &= ~SF_NPC_GAG;
- RemoveSolidFlags( FSOLID_NOT_SOLID );
- TaskComplete();
- }
- break;
- }
-
- case TASK_HEADCRAB_FIND_BURROW_IN_POINT:
- {
- if ( FindBurrow( GetAbsOrigin(), pTask->flTaskData, true ) == false )
- {
- TaskFail( "TASK_HEADCRAB_FIND_BURROW_IN_POINT: Unable to find burrow in position\n" );
- }
- else
- {
- TaskComplete();
- }
- break;
- }
-
- case TASK_HEADCRAB_BURROW:
- {
- Burrow();
- TaskComplete();
- break;
- }
-
- case TASK_HEADCRAB_UNBURROW:
- {
- Unburrow();
- TaskComplete();
- break;
- }
-
- default:
- {
- BaseClass::StartTask( pTask );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: For innate melee attack
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-float CBaseHeadcrab::InnateRange1MinRange( void )
-{
- return HEADCRAB_MIN_JUMP_DIST;
-}
-
-float CBaseHeadcrab::InnateRange1MaxRange( void )
-{
- return HEADCRAB_MAX_JUMP_DIST;
-}
-
-int CBaseHeadcrab::RangeAttack1Conditions( float flDot, float flDist )
-{
- if ( gpGlobals->curtime < m_flNextAttack )
- return 0;
-
- if ( ( GetFlags() & FL_ONGROUND ) == false )
- return 0;
-
- // When we're burrowed ignore facing, because when we unburrow we'll cheat and face our enemy.
- if ( !m_bBurrowed && ( flDot < 0.65 ) )
- return COND_NOT_FACING_ATTACK;
-
- // This code stops lots of headcrabs swarming you and blocking you
- // whilst jumping up and down in your face over and over. It forces
- // them to back up a bit. If this causes problems, consider using it
- // for the fast headcrabs only, rather than just removing it.(sjb)
- if ( flDist < HEADCRAB_MIN_JUMP_DIST )
- return COND_TOO_CLOSE_TO_ATTACK;
-
- if ( flDist > HEADCRAB_MAX_JUMP_DIST )
- return COND_TOO_FAR_TO_ATTACK;
-
- // Make sure the way is clear!
- CBaseEntity *pEnemy = GetEnemy();
- if( pEnemy )
- {
- bool bEnemyIsBullseye = ( dynamic_cast<CNPC_Bullseye *>(pEnemy) != NULL );
-
- trace_t tr;
- AI_TraceLine( EyePosition(), pEnemy->EyePosition(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.m_pEnt != GetEnemy() )
- {
- if ( !bEnemyIsBullseye || tr.m_pEnt != NULL )
- return COND_NONE;
- }
-
- if( GetEnemy()->EyePosition().z - 36.0f > GetAbsOrigin().z )
- {
- // Only run this test if trying to jump at a player who is higher up than me, else this
- // code will always prevent a headcrab from jumping down at an enemy, and sometimes prevent it
- // jumping just slightly up at an enemy.
- Vector vStartHullTrace = GetAbsOrigin();
- vStartHullTrace.z += 1.0;
-
- Vector vEndHullTrace = GetEnemy()->EyePosition() - GetAbsOrigin();
- vEndHullTrace.NormalizeInPlace();
- vEndHullTrace *= 8.0;
- vEndHullTrace += GetAbsOrigin();
-
- AI_TraceHull( vStartHullTrace, vEndHullTrace,GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
-
- if ( tr.m_pEnt != NULL && tr.m_pEnt != GetEnemy() )
- {
- return COND_TOO_CLOSE_TO_ATTACK;
- }
- }
- }
-
- return COND_CAN_RANGE_ATTACK1;
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose: Override to do headcrab specific gibs
-// Output :
-//------------------------------------------------------------------------------
-bool CBaseHeadcrab::CorpseGib( const CTakeDamageInfo &info )
-{
- EmitSound( "NPC_HeadCrab.Gib" );
-
- return BaseClass::CorpseGib( info );
-}
-
-//------------------------------------------------------------------------------
-// Purpose:
-// Input :
-//------------------------------------------------------------------------------
-void CBaseHeadcrab::Touch( CBaseEntity *pOther )
-{
- // If someone has smacked me into a wall then gib!
- if (m_NPCState == NPC_STATE_DEAD)
- {
- if (GetAbsVelocity().Length() > 250)
- {
- trace_t tr;
- Vector vecDir = GetAbsVelocity();
- VectorNormalize(vecDir);
- AI_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vecDir * 100,
- MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
- float dotPr = DotProduct(vecDir,tr.plane.normal);
- if ((tr.fraction != 1.0) &&
- (dotPr < -0.8) )
- {
- CTakeDamageInfo info( GetWorldEntity(), GetWorldEntity(), 100.0f, DMG_CRUSH );
-
- info.SetDamagePosition( tr.endpos );
-
- Event_Gibbed( info );
- }
-
- }
- }
- BaseClass::Touch(pOther);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : pevInflictor -
-// pevAttacker -
-// flDamage -
-// bitsDamageType -
-// Output :
-//-----------------------------------------------------------------------------
-int CBaseHeadcrab::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
-{
- CTakeDamageInfo info = inputInfo;
-
- //
- // Don't take any acid damage.
- //
- if ( info.GetDamageType() & DMG_ACID )
- {
- info.SetDamage( 0 );
- }
-
- //
- // Certain death from melee bludgeon weapons!
- //
- if ( info.GetDamageType() & DMG_CLUB )
- {
- info.SetDamage( m_iHealth );
- }
-
- if( info.GetDamageType() & DMG_BLAST )
- {
- if( random->RandomInt( 0 , 1 ) == 0 )
- {
- // Catch on fire randomly if damaged in a blast.
- Ignite( 30 );
- }
- }
-
- if( info.GetDamageType() & DMG_BURN )
- {
- // Slow down burn damage so that headcrabs live longer while on fire.
- info.ScaleDamage( 0.25 );
-
-#define HEADCRAB_SCORCH_RATE 5
-#define HEADCRAB_SCORCH_FLOOR 30
-
- if( IsOnFire() )
- {
- Scorch( HEADCRAB_SCORCH_RATE, HEADCRAB_SCORCH_FLOOR );
-
- if( m_iHealth <= 1 && (entindex() % 2) )
- {
- // Some headcrabs leap at you with their dying breath
- if( !IsCurSchedule( SCHED_HEADCRAB_RANGE_ATTACK1 ) && !IsRunningDynamicInteraction() )
- {
- SetSchedule( SCHED_HEADCRAB_RANGE_ATTACK1 );
- }
- }
- }
-
- Ignite( 30 );
- }
-
- return CAI_BaseNPC::OnTakeDamage_Alive( info );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::ClampRagdollForce( const Vector &vecForceIn, Vector *vecForceOut )
-{
- // Assumes the headcrab mass is 5kg (100 feet per second)
- float MAX_HEADCRAB_RAGDOLL_SPEED = 100.0f * 12.0f * 5.0f;
-
- Vector vecClampedForce;
- BaseClass::ClampRagdollForce( vecForceIn, &vecClampedForce );
-
- // Copy the force to vecForceOut, in case we don't change it.
- *vecForceOut = vecClampedForce;
-
- float speed = VectorNormalize( vecClampedForce );
- if( speed > MAX_HEADCRAB_RAGDOLL_SPEED )
- {
- // Don't let the ragdoll go as fast as it was going to.
- vecClampedForce *= MAX_HEADCRAB_RAGDOLL_SPEED;
- *vecForceOut = vecClampedForce;
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::Event_Killed( const CTakeDamageInfo &info )
-{
- // Create a little decal underneath the headcrab
- // This type of damage combination happens from dynamic scripted sequences
- if ( info.GetDamageType() & (DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE) )
- {
- trace_t tr;
- AI_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
-
- UTIL_DecalTrace( &tr, "YellowBlood" );
- }
-
- BaseClass::Event_Killed( info );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : Type -
-//-----------------------------------------------------------------------------
-int CBaseHeadcrab::TranslateSchedule( int scheduleType )
-{
- switch( scheduleType )
- {
- case SCHED_FALL_TO_GROUND:
- return SCHED_HEADCRAB_FALL_TO_GROUND;
-
- case SCHED_WAKE_ANGRY:
- {
- if ( HaveSequenceForActivity((Activity)ACT_HEADCRAB_THREAT_DISPLAY) )
- return SCHED_HEADCRAB_WAKE_ANGRY;
- else
- return SCHED_HEADCRAB_WAKE_ANGRY_NO_DISPLAY;
- }
-
- case SCHED_RANGE_ATTACK1:
- return SCHED_HEADCRAB_RANGE_ATTACK1;
-
- case SCHED_FAIL_TAKE_COVER:
- return SCHED_ALERT_FACE;
-
- case SCHED_CHASE_ENEMY_FAILED:
- {
- if( !GetEnemy() )
- break;
-
- if( !HasCondition( COND_SEE_ENEMY ) )
- break;
-
- float flZDiff;
- flZDiff = GetEnemy()->GetAbsOrigin().z - GetAbsOrigin().z;
-
- // Make sure the enemy isn't so high above me that this would look silly.
- if( flZDiff < 128.0f || flZDiff > 512.0f )
- return SCHED_COMBAT_PATROL;
-
- float flDist;
- flDist = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length2D();
-
- // Maybe a patrol will bring me closer.
- if( flDist > 384.0f )
- {
- return SCHED_COMBAT_PATROL;
- }
-
- return SCHED_HEADCRAB_HARASS_ENEMY;
- }
- break;
- }
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int CBaseHeadcrab::SelectSchedule( void )
-{
- if ( m_bCrawlFromCanister )
- {
- m_bCrawlFromCanister = false;
- return SCHED_HEADCRAB_CRAWL_FROM_CANISTER;
- }
-
- // If we're hidden or waiting until seen, don't do much at all
- if ( m_bHidden || HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN) )
- {
- if( HasCondition( COND_HEADCRAB_UNHIDE ) )
- {
- // We've decided to unhide
- return SCHED_HEADCRAB_UNHIDE;
- }
-
- return m_bBurrowed ? ( int )SCHED_HEADCRAB_BURROW_WAIT : ( int )SCHED_IDLE_STAND;
- }
-
- if ( GetSpawnFlags() & SF_HEADCRAB_START_HANGING && IsHangingFromCeiling() == false )
- {
- return SCHED_HEADCRAB_CEILING_WAIT;
- }
-
- if ( IsHangingFromCeiling() )
- {
- bool bIsAlyxInDarknessMode = false;
-#ifdef HL2_EPISODIC
- bIsAlyxInDarknessMode = HL2GameRules()->IsAlyxInDarknessMode();
-#endif // HL2_EPISODIC
-
- if ( bIsAlyxInDarknessMode == false && ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_NEW_ENEMY ) ) )
- return SCHED_HEADCRAB_CEILING_DROP;
-
- if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
- return SCHED_HEADCRAB_CEILING_DROP;
-
- return SCHED_HEADCRAB_CEILING_WAIT;
- }
-
- if ( m_bBurrowed )
- {
- if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
- return SCHED_HEADCRAB_BURROW_OUT;
-
- return SCHED_HEADCRAB_BURROW_WAIT;
- }
-
- if( HasCondition( COND_HEADCRAB_IN_WATER ) )
- {
- // No matter what, drown in water
- return SCHED_HEADCRAB_DROWN;
- }
-
- if( HasCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT ) )
- {
- // You're on an NPC's head. Get off.
- return SCHED_HEADCRAB_HOP_RANDOMLY;
- }
-
- if ( HasCondition( COND_HEADCRAB_BARNACLED ) )
- {
- // Caught by a barnacle!
- return SCHED_HEADCRAB_BARNACLED;
- }
-
- switch ( m_NPCState )
- {
- case NPC_STATE_ALERT:
- {
- if (HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ))
- {
- if ( fabs( GetMotor()->DeltaIdealYaw() ) < ( 1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction
- {
- return SCHED_TAKE_COVER_FROM_ORIGIN;
- }
- else if ( SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 )
- {
- m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1, 3 );
- return SCHED_SMALL_FLINCH;
- }
- }
- else if (HasCondition( COND_HEAR_DANGER ) ||
- HasCondition( COND_HEAR_PLAYER ) ||
- HasCondition( COND_HEAR_WORLD ) ||
- HasCondition( COND_HEAR_COMBAT ))
- {
- return SCHED_ALERT_FACE_BESTSOUND;
- }
- else
- {
- return SCHED_PATROL_WALK;
- }
- break;
- }
- }
-
- if ( HasCondition( COND_FLOATING_OFF_GROUND ) )
- {
- SetGravity( 1.0 );
- SetGroundEntity( NULL );
- return SCHED_FALL_TO_GROUND;
- }
-
- if ( GetHintNode() && GetHintNode()->HintType() == HINT_HEADCRAB_BURROW_POINT )
- {
- // Only burrow if we're not within leap attack distance of our enemy.
- if ( ( GetEnemy() == NULL ) || ( ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length() > HEADCRAB_MAX_JUMP_DIST ) )
- {
- return SCHED_HEADCRAB_RUN_TO_SPECIFIC_BURROW;
- }
- else
- {
- // Forget about burrowing, we've got folks to leap at!
- GrabHintNode( NULL );
- }
- }
-
- int nSchedule = BaseClass::SelectSchedule();
- if ( nSchedule == SCHED_SMALL_FLINCH )
- {
- m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1, 3 );
- }
-
- return nSchedule;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CBaseHeadcrab::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
-{
- if ( failedSchedule == SCHED_BACK_AWAY_FROM_ENEMY && failedTask == TASK_FIND_BACKAWAY_FROM_SAVEPOSITION )
- {
- if ( HasCondition( COND_SEE_ENEMY ) )
- {
- return SCHED_RANGE_ATTACK1;
- }
- }
-
- if ( failedSchedule == SCHED_BACK_AWAY_FROM_ENEMY || failedSchedule == SCHED_PATROL_WALK || failedSchedule == SCHED_COMBAT_PATROL )
- {
- if( !IsFirmlyOnGround() )
- {
- return SCHED_HEADCRAB_HOP_RANDOMLY;
- }
- }
-
- return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &info -
-// &vecDir -
-// *ptr -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
-{
- CTakeDamageInfo newInfo = info;
-
- // Ignore if we're in a dynamic scripted sequence
- if ( info.GetDamageType() & DMG_PHYSGUN && !IsRunningDynamicInteraction() )
- {
- Vector puntDir = ( info.GetDamageForce() * 1000.0f );
-
- newInfo.SetDamage( m_iMaxHealth / 3.0f );
-
- if( info.GetDamage() >= GetHealth() )
- {
- // This blow will be fatal, so scale the damage force
- // (it's a unit vector) so that the ragdoll will be
- // affected.
- newInfo.SetDamageForce( info.GetDamageForce() * 3000.0f );
- }
-
- PainSound( newInfo );
- SetGroundEntity( NULL );
- ApplyAbsVelocityImpulse( puntDir );
- }
-
- BaseClass::TraceAttack( newInfo, vecDir, ptr, pAccumulator );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
-{
- // Can't start on fire if we're burrowed
- if ( m_bBurrowed )
- return;
-
- bool bWasOnFire = IsOnFire();
-
-#ifdef HL2_EPISODIC
- if( GetHealth() > flFlameLifetime )
- {
- // Add some burn time to very healthy headcrabs to fix a bug where
- // black headcrabs would sometimes spontaneously extinguish (and survive)
- flFlameLifetime += 10.0f;
- }
-#endif// HL2_EPISODIC
-
- BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
-
- if( !bWasOnFire )
- {
-#ifdef HL2_EPISODIC
- if ( HL2GameRules()->IsAlyxInDarknessMode() == true )
- {
- GetEffectEntity()->AddEffects( EF_DIMLIGHT );
- }
-#endif // HL2_EPISODIC
-
- // For the poison headcrab, who runs around when ignited
- SetActivity( TranslateActivity(GetIdealActivity()) );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: This is a generic function (to be implemented by sub-classes) to
-// handle specific interactions between different types of characters
-// (For example the barnacle grabbing an NPC)
-// Input : Constant for the type of interaction
-// Output : true - if sub-class has a response for the interaction
-// false - if sub-class has no response
-//-----------------------------------------------------------------------------
-bool CBaseHeadcrab::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
-{
- if (interactionType == g_interactionBarnacleVictimDangle)
- {
- // Die instantly
- return false;
- }
- else if (interactionType == g_interactionVortigauntStomp)
- {
- SetIdealState( NPC_STATE_PRONE );
- return true;
- }
- else if (interactionType == g_interactionVortigauntStompFail)
- {
- SetIdealState( NPC_STATE_COMBAT );
- return true;
- }
- else if (interactionType == g_interactionVortigauntStompHit)
- {
- // Gib the existing guy, but only with legs and guts
- m_nGibCount = HEADCRAB_LEGS_GIB_COUNT;
- OnTakeDamage ( CTakeDamageInfo( sourceEnt, sourceEnt, m_iHealth, DMG_CRUSH|DMG_ALWAYSGIB ) );
-
- // Create dead headcrab in its place
- CBaseHeadcrab *pEntity = (CBaseHeadcrab*) CreateEntityByName( "npc_headcrab" );
- pEntity->Spawn();
- pEntity->SetLocalOrigin( GetLocalOrigin() );
- pEntity->SetLocalAngles( GetLocalAngles() );
- pEntity->m_NPCState = NPC_STATE_DEAD;
- return true;
- }
- else if ( interactionType == g_interactionVortigauntKick
- /* || (interactionType == g_interactionBullsquidThrow) */
- )
- {
- SetIdealState( NPC_STATE_PRONE );
-
- if( HasHeadroom() )
- {
- MoveOrigin( Vector( 0, 0, 1 ) );
- }
-
- Vector vHitDir = GetLocalOrigin() - sourceEnt->GetLocalOrigin();
- VectorNormalize(vHitDir);
-
- CTakeDamageInfo info( sourceEnt, sourceEnt, m_iHealth+1, DMG_CLUB );
- CalculateMeleeDamageForce( &info, vHitDir, GetAbsOrigin() );
-
- TakeDamage( info );
-
- return true;
- }
-
- return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CBaseHeadcrab::FValidateHintType( CAI_Hint *pHint )
-{
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &origin -
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::ClearBurrowPoint( const Vector &origin )
-{
- CBaseEntity *pEntity = NULL;
- float flDist;
- Vector vecSpot, vecCenter, vecForce;
-
- //Cause a ruckus
- UTIL_ScreenShake( origin, 1.0f, 80.0f, 1.0f, 256.0f, SHAKE_START );
-
- //Iterate on all entities in the vicinity.
- for ( CEntitySphereQuery sphere( origin, 128 ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
- {
- 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 );
- }
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// 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 CBaseHeadcrab::ValidBurrowPoint( const Vector &point )
-{
- trace_t tr;
-
- AI_TraceHull( point, point+Vector(0,0,1), GetHullMins(), GetHullMaxs(),
- MASK_NPCSOLID, this, GetCollisionGroup(), &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:
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::GrabHintNode( CAI_Hint *pHint )
-{
- // Free up the node for use
- ClearHintNode();
-
- if ( pHint )
- {
- SetHintNode( pHint );
- pHint->Lock( this );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Finds a point where the headcrab can burrow underground.
-// Input : distance - radius to search for burrow spot in
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CBaseHeadcrab::FindBurrow( const Vector &origin, float distance, bool excludeNear )
-{
- // Attempt to find a burrowing point
- CHintCriteria hintCriteria;
-
- hintCriteria.SetHintType( HINT_HEADCRAB_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;
-
- GrabHintNode( pHint );
-
- // Setup our path and attempt to run there
- Vector vHintPos;
- pHint->GetPosition( this, &vHintPos );
-
- AI_NavGoal_t goal( vHintPos, ACT_RUN );
-
- return GetNavigator()->SetGoal( goal );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::Burrow( void )
-{
- // Stop us from taking damage and being solid
- m_spawnflags |= SF_NPC_GAG;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::Unburrow( void )
-{
- // 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);
-
- GetMotor()->SetIdealYaw( dir );
-
- QAngle angles = GetLocalAngles();
- angles[YAW] = UTIL_VecToYaw( dir );
- SetLocalAngles( angles );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Tells the headcrab to unburrow as soon the space is clear.
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::InputUnburrow( inputdata_t &inputdata )
-{
- if ( IsAlive() == false )
- return;
-
- SetSchedule( SCHED_HEADCRAB_WAIT_FOR_CLEAR_UNBURROW );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Tells the headcrab to run to a nearby burrow point and burrow.
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::InputBurrow( inputdata_t &inputdata )
-{
- if ( IsAlive() == false )
- return;
-
- SetSchedule( SCHED_HEADCRAB_RUN_TO_BURROW_IN );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Tells the headcrab to burrow right where he is.
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::InputBurrowImmediate( inputdata_t &inputdata )
-{
- if ( IsAlive() == false )
- return;
-
- SetSchedule( SCHED_HEADCRAB_BURROW_IN );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::InputStartHangingFromCeiling( inputdata_t &inputdata )
-{
- if ( IsAlive() == false )
- return;
-
- SetSchedule( SCHED_HEADCRAB_CEILING_WAIT );
- m_flIlluminatedTime = -1;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::InputDropFromCeiling( inputdata_t &inputdata )
-{
- if ( IsAlive() == false )
- return;
-
- if ( IsHangingFromCeiling() == false )
- return;
-
- SetSchedule( SCHED_HEADCRAB_CEILING_DROP );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::CreateDust( bool placeDecal )
-{
- trace_t tr;
- AI_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction < 1.0f )
- {
- const surfacedata_t *pdata = physprops->GetSurfaceData( tr.surface.surfaceProps );
-
- if ( ( (char) pdata->game.material == CHAR_TEX_CONCRETE ) || ( (char) pdata->game.material == CHAR_TEX_DIRT ) )
- {
- UTIL_CreateAntlionDust( tr.endpos + Vector(0, 0, 24), GetLocalAngles() );
-
- //CEffectData data;
- //data.m_vOrigin = GetAbsOrigin();
- //data.m_vNormal = tr.plane.normal;
- //DispatchEffect( "headcrabdust", data );
-
- if ( placeDecal )
- {
- UTIL_DecalTrace( &tr, "Headcrab.Unburrow" );
- }
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHeadcrab::Precache( void )
-{
- PrecacheModel( "models/headcrabclassic.mdl" );
-
- PrecacheScriptSound( "NPC_HeadCrab.Gib" );
- PrecacheScriptSound( "NPC_HeadCrab.Idle" );
- PrecacheScriptSound( "NPC_HeadCrab.Alert" );
- PrecacheScriptSound( "NPC_HeadCrab.Pain" );
- PrecacheScriptSound( "NPC_HeadCrab.Die" );
- PrecacheScriptSound( "NPC_HeadCrab.Attack" );
- PrecacheScriptSound( "NPC_HeadCrab.Bite" );
- PrecacheScriptSound( "NPC_Headcrab.BurrowIn" );
- PrecacheScriptSound( "NPC_Headcrab.BurrowOut" );
-
- BaseClass::Precache();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHeadcrab::Spawn( void )
-{
- Precache();
- SetModel( "models/headcrabclassic.mdl" );
-
- BaseClass::Spawn();
-
- m_iHealth = sk_headcrab_health.GetFloat();
- m_flBurrowTime = 0.0f;
- m_bCrawlFromCanister = false;
- m_bMidJump = false;
-
- NPCInit();
- HeadcrabInit();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-Activity CHeadcrab::NPC_TranslateActivity( Activity eNewActivity )
-{
- if ( eNewActivity == ACT_WALK )
- return ACT_RUN;
-
- return BaseClass::NPC_TranslateActivity( eNewActivity );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHeadcrab::IdleSound( void )
-{
- EmitSound( "NPC_HeadCrab.Idle" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHeadcrab::AlertSound( void )
-{
- EmitSound( "NPC_HeadCrab.Alert" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHeadcrab::PainSound( const CTakeDamageInfo &info )
-{
- if( IsOnFire() && random->RandomInt( 0, HEADCRAB_BURN_SOUND_FREQUENCY ) > 0 )
- {
- // Don't squeak every think when burning.
- return;
- }
-
- EmitSound( "NPC_HeadCrab.Pain" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHeadcrab::DeathSound( const CTakeDamageInfo &info )
-{
- EmitSound( "NPC_HeadCrab.Die" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHeadcrab::TelegraphSound( void )
-{
- //FIXME: Need a real one
- EmitSound( "NPC_HeadCrab.Alert" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHeadcrab::AttackSound( void )
-{
- EmitSound( "NPC_Headcrab.Attack" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHeadcrab::BiteSound( void )
-{
- EmitSound( "NPC_HeadCrab.Bite" );
-}
-
-
-//---------------------------------------------------------
-// Save/Restore
-//---------------------------------------------------------
-BEGIN_DATADESC( CFastHeadcrab )
-
- DEFINE_FIELD( m_iRunMode, FIELD_INTEGER ),
- DEFINE_FIELD( m_flRealGroundSpeed, FIELD_FLOAT ),
- DEFINE_FIELD( m_flSlowRunTime, FIELD_TIME ),
- DEFINE_FIELD( m_flPauseTime, FIELD_TIME ),
- DEFINE_FIELD( m_vecJumpVel, FIELD_VECTOR ),
-
-END_DATADESC()
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFastHeadcrab::Precache( void )
-{
- PrecacheModel( "models/headcrab.mdl" );
-
- PrecacheScriptSound( "NPC_FastHeadcrab.Idle" );
- PrecacheScriptSound( "NPC_FastHeadcrab.Alert" );
- PrecacheScriptSound( "NPC_FastHeadcrab.Pain" );
- PrecacheScriptSound( "NPC_FastHeadcrab.Die" );
- PrecacheScriptSound( "NPC_FastHeadcrab.Bite" );
- PrecacheScriptSound( "NPC_FastHeadcrab.Attack" );
-
- BaseClass::Precache();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFastHeadcrab::Spawn( void )
-{
- Precache();
- SetModel( "models/headcrab.mdl" );
-
- BaseClass::Spawn();
-
- m_iHealth = sk_headcrab_health.GetFloat();
-
- m_iRunMode = HEADCRAB_RUNMODE_IDLE;
- m_flPauseTime = 999999;
-
- NPCInit();
- HeadcrabInit();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFastHeadcrab::IdleSound( void )
-{
- EmitSound( "NPC_FastHeadcrab.Idle" );
-}
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFastHeadcrab::AlertSound( void )
-{
- EmitSound( "NPC_FastHeadcrab.Alert" );
-}
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFastHeadcrab::PainSound( const CTakeDamageInfo &info )
-{
- if( IsOnFire() && random->RandomInt( 0, HEADCRAB_BURN_SOUND_FREQUENCY ) > 0 )
- {
- // Don't squeak every think when burning.
- return;
- }
-
- EmitSound( "NPC_FastHeadcrab.Pain" );
-}
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFastHeadcrab::DeathSound( const CTakeDamageInfo &info )
-{
- EmitSound( "NPC_FastHeadcrab.Die" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFastHeadcrab::PrescheduleThink( void )
-{
-#if 1 // #IF 0 this to stop the accelrating/decelerating movement.
-#define HEADCRAB_ACCELERATION 0.1
- if( IsAlive() && GetNavigator()->IsGoalActive() )
- {
- switch( m_iRunMode )
- {
- case HEADCRAB_RUNMODE_IDLE:
- if ( GetActivity() == ACT_RUN )
- {
- m_flRealGroundSpeed = m_flGroundSpeed;
- m_iRunMode = HEADCRAB_RUNMODE_ACCELERATE;
- m_flPlaybackRate = HEADCRAB_RUN_MINSPEED;
- }
- break;
-
- case HEADCRAB_RUNMODE_FULLSPEED:
- if( gpGlobals->curtime > m_flSlowRunTime )
- {
- m_iRunMode = HEADCRAB_RUNMODE_DECELERATE;
- }
- break;
-
- case HEADCRAB_RUNMODE_ACCELERATE:
- if( m_flPlaybackRate < HEADCRAB_RUN_MAXSPEED )
- {
- m_flPlaybackRate += HEADCRAB_ACCELERATION;
- }
-
- if( m_flPlaybackRate >= HEADCRAB_RUN_MAXSPEED )
- {
- m_flPlaybackRate = HEADCRAB_RUN_MAXSPEED;
- m_iRunMode = HEADCRAB_RUNMODE_FULLSPEED;
-
- m_flSlowRunTime = gpGlobals->curtime + random->RandomFloat( 0.1, 1.0 );
- }
- break;
-
- case HEADCRAB_RUNMODE_DECELERATE:
- m_flPlaybackRate -= HEADCRAB_ACCELERATION;
-
- if( m_flPlaybackRate <= HEADCRAB_RUN_MINSPEED )
- {
- m_flPlaybackRate = HEADCRAB_RUN_MINSPEED;
-
- // Now stop the crab.
- m_iRunMode = HEADCRAB_RUNMODE_PAUSE;
- SetActivity( ACT_IDLE );
- GetNavigator()->SetMovementActivity(ACT_IDLE);
- m_flPauseTime = gpGlobals->curtime + random->RandomFloat( 0.2, 0.5 );
- m_flRealGroundSpeed = 0.0;
- }
- break;
-
- case HEADCRAB_RUNMODE_PAUSE:
- {
- if( gpGlobals->curtime > m_flPauseTime )
- {
- m_iRunMode = HEADCRAB_RUNMODE_IDLE;
- SetActivity( ACT_RUN );
- GetNavigator()->SetMovementActivity(ACT_RUN);
- m_flPauseTime = gpGlobals->curtime - 1;
- m_flRealGroundSpeed = m_flGroundSpeed;
- }
- }
- break;
-
- default:
- Warning( "BIG TIME HEADCRAB ERROR\n" );
- break;
- }
-
- m_flGroundSpeed = m_flRealGroundSpeed * m_flPlaybackRate;
- }
- else
- {
- m_flPauseTime = gpGlobals->curtime - 1;
- }
-#endif
-
- BaseClass::PrescheduleThink();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : scheduleType -
-//-----------------------------------------------------------------------------
-int CFastHeadcrab::SelectSchedule( void )
-{
- if ( HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN) )
- {
- return SCHED_IDLE_STAND;
- }
-
- if ( HasCondition(COND_CAN_RANGE_ATTACK1) && IsHangingFromCeiling() == false )
- {
- if ( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- return SCHED_RANGE_ATTACK1;
- ClearCondition(COND_CAN_RANGE_ATTACK1);
- }
-
- return BaseClass::SelectSchedule();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : scheduleType -
-//-----------------------------------------------------------------------------
-int CFastHeadcrab::TranslateSchedule( int scheduleType )
-{
- switch( scheduleType )
- {
- case SCHED_IDLE_STAND:
- return SCHED_PATROL_WALK;
- break;
-
- case SCHED_RANGE_ATTACK1:
- return SCHED_FAST_HEADCRAB_RANGE_ATTACK1;
- break;
-
- case SCHED_CHASE_ENEMY:
- if ( !OccupyStrategySlotRange( SQUAD_SLOT_ENGAGE1, SQUAD_SLOT_ENGAGE4 ) )
- return SCHED_PATROL_WALK;
- break;
- }
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pTask -
-//-----------------------------------------------------------------------------
-void CFastHeadcrab::RunTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_RANGE_ATTACK1:
- case TASK_RANGE_ATTACK2:
-
- if ( GetEnemy() )
- // Fast headcrab faces the target in flight.
- GetMotor()->SetIdealYawAndUpdate( GetEnemy()->GetAbsOrigin() - GetAbsOrigin(), AI_KEEP_YAW_SPEED );
-
- // Call back up into base headcrab for collision.
- BaseClass::RunTask( pTask );
- break;
-
- case TASK_HEADCRAB_HOP_ASIDE:
- if ( GetEnemy() )
- GetMotor()->SetIdealYawAndUpdate( GetEnemy()->GetAbsOrigin() - GetAbsOrigin(), AI_KEEP_YAW_SPEED );
-
- if( GetFlags() & FL_ONGROUND )
- {
- SetGravity(1.0);
- SetMoveType( MOVETYPE_STEP );
-
- if( GetEnemy() && ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length() > HEADCRAB_MAX_JUMP_DIST )
- {
- TaskFail( "");
- }
-
- TaskComplete();
- }
- break;
-
- default:
- BaseClass::RunTask( pTask );
- break;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : pTask -
-//-----------------------------------------------------------------------------
-void CFastHeadcrab::StartTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_HEADCRAB_HOP_ASIDE:
- {
- Vector vecDir, vecForward, vecRight;
- bool fJumpIsLeft;
- trace_t tr;
-
- GetVectors( &vecForward, &vecRight, NULL );
-
- fJumpIsLeft = false;
- if( random->RandomInt( 0, 100 ) < 50 )
- {
- fJumpIsLeft = true;
- vecRight.Negate();
- }
-
- vecDir = ( vecRight + ( vecForward * 2 ) );
- VectorNormalize( vecDir );
- vecDir *= 150.0;
-
- // This could be a problem. Since I'm adjusting the headcrab's gravity for flight, this check actually
- // checks farther ahead than the crab will actually jump. (sjb)
- AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + vecDir,GetHullMins(), GetHullMaxs(), MASK_SHOT, this, GetCollisionGroup(), &tr );
-
- //NDebugOverlay::Line( tr.startpos, tr.endpos, 0, 255, 0, false, 1.0 );
-
- if( tr.fraction == 1.0 )
- {
- AIMoveTrace_t moveTrace;
- GetMoveProbe()->MoveLimit( NAV_JUMP, GetAbsOrigin(), tr.endpos, MASK_NPCSOLID, GetEnemy(), &moveTrace );
-
- // FIXME: Where should this happen?
- m_vecJumpVel = moveTrace.vJumpVelocity;
-
- if( !IsMoveBlocked( moveTrace ) )
- {
- SetAbsVelocity( m_vecJumpVel );// + 0.5f * Vector(0,0,GetCurrentGravity()) * flInterval;
- SetGravity( UTIL_ScaleForGravity( 1600 ) );
- SetGroundEntity( NULL );
- SetNavType( NAV_JUMP );
-
- if( fJumpIsLeft )
- {
- SetIdealActivity( (Activity)ACT_HEADCRAB_HOP_LEFT );
- GetNavigator()->SetMovementActivity( (Activity) ACT_HEADCRAB_HOP_LEFT );
- }
- else
- {
- SetIdealActivity( (Activity)ACT_HEADCRAB_HOP_RIGHT );
- GetNavigator()->SetMovementActivity( (Activity) ACT_HEADCRAB_HOP_RIGHT );
- }
- }
- else
- {
- // Can't jump, just fall through.
- TaskComplete();
- }
- }
- else
- {
- // Can't jump, just fall through.
- TaskComplete();
- }
- }
- break;
-
- default:
- {
- BaseClass::StartTask( pTask );
- }
- }
-}
-
-
-LINK_ENTITY_TO_CLASS( npc_headcrab, CHeadcrab );
-LINK_ENTITY_TO_CLASS( npc_headcrab_fast, CFastHeadcrab );
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Make the sound of this headcrab chomping a target.
-// Input :
-//-----------------------------------------------------------------------------
-void CFastHeadcrab::BiteSound( void )
-{
- EmitSound( "NPC_FastHeadcrab.Bite" );
-}
-
-void CFastHeadcrab::AttackSound( void )
-{
- EmitSound( "NPC_FastHeadcrab.Attack" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-float CHeadcrab::MaxYawSpeed ( void )
-{
- switch ( GetActivity() )
- {
- case ACT_IDLE:
- return 30;
-
- case ACT_RUN:
- case ACT_WALK:
- return 20;
-
- case ACT_TURN_LEFT:
- case ACT_TURN_RIGHT:
- return 15;
-
- case ACT_RANGE_ATTACK1:
- {
- const Task_t *pCurTask = GetTask();
- if ( pCurTask && pCurTask->iTask == TASK_HEADCRAB_JUMP_FROM_CANISTER )
- return 15;
- }
- return 30;
-
- default:
- return 30;
- }
-
- return BaseClass::MaxYawSpeed();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Allows for modification of the interrupt mask for the current schedule.
-// In the most cases the base implementation should be called first.
-//-----------------------------------------------------------------------------
-void CBaseHeadcrab::BuildScheduleTestBits( void )
-{
- if ( !IsCurSchedule(SCHED_HEADCRAB_DROWN) )
- {
- // Interrupt any schedule unless already drowning.
- SetCustomInterruptCondition( COND_HEADCRAB_IN_WATER );
- }
- else
- {
- // Don't stop drowning just because you're in water!
- ClearCustomInterruptCondition( COND_HEADCRAB_IN_WATER );
- }
-
- if( !IsCurSchedule(SCHED_HEADCRAB_HOP_RANDOMLY) )
- {
- SetCustomInterruptCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT );
- }
- else
- {
- ClearCustomInterruptCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-float CFastHeadcrab::MaxYawSpeed( void )
-{
- switch ( GetActivity() )
- {
- case ACT_IDLE:
- {
- return( 120 );
- }
-
- case ACT_RUN:
- case ACT_WALK:
- {
- return( 150 );
- }
-
- case ACT_TURN_LEFT:
- case ACT_TURN_RIGHT:
- {
- return( 120 );
- }
-
- case ACT_RANGE_ATTACK1:
- {
- return( 120 );
- }
-
- default:
- {
- return( 120 );
- }
- }
-}
-
-
-bool CFastHeadcrab::QuerySeeEntity(CBaseEntity *pSightEnt, bool bOnlyHateOrFearIfNPC )
-{
- if ( IsHangingFromCeiling() == true )
- return BaseClass::QuerySeeEntity(pSightEnt, bOnlyHateOrFearIfNPC);
-
- if( m_NPCState != NPC_STATE_COMBAT )
- {
- if( fabs( pSightEnt->GetAbsOrigin().z - GetAbsOrigin().z ) >= 150 )
- {
- // Don't see things much higher or lower than me unless
- // I'm already pissed.
- return false;
- }
- }
-
- return BaseClass::QuerySeeEntity(pSightEnt, bOnlyHateOrFearIfNPC);
-}
-
-
-//-----------------------------------------------------------------------------
-// Black headcrab stuff
-//-----------------------------------------------------------------------------
-int ACT_BLACKHEADCRAB_RUN_PANIC;
-
-BEGIN_DATADESC( CBlackHeadcrab )
-
- DEFINE_FIELD( m_bPanicState, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flPanicStopTime, FIELD_TIME ),
- DEFINE_FIELD( m_flNextHopTime, FIELD_TIME ),
-
- DEFINE_ENTITYFUNC( EjectTouch ),
-
-END_DATADESC()
-
-
-LINK_ENTITY_TO_CLASS( npc_headcrab_black, CBlackHeadcrab );
-LINK_ENTITY_TO_CLASS( npc_headcrab_poison, CBlackHeadcrab );
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Make the sound of this headcrab chomping a target.
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::BiteSound( void )
-{
- EmitSound( "NPC_BlackHeadcrab.Bite" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: The sound we make when leaping at our enemy.
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::AttackSound( void )
-{
- EmitSound( "NPC_BlackHeadcrab.Attack" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::TelegraphSound( void )
-{
- EmitSound( "NPC_BlackHeadcrab.Telegraph" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::Spawn( void )
-{
- Precache();
- SetModel( "models/headcrabblack.mdl" );
-
- BaseClass::Spawn();
-
- m_bPanicState = false;
- m_iHealth = sk_headcrab_poison_health.GetFloat();
-
- NPCInit();
- HeadcrabInit();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::Precache( void )
-{
- PrecacheModel( "models/headcrabblack.mdl" );
-
- PrecacheScriptSound( "NPC_BlackHeadcrab.Telegraph" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.Attack" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.Bite" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.Threat" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.Alert" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.Idle" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.Talk" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.AlertVoice" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.Pain" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.Die" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.Impact" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.ImpactAngry" );
-
- PrecacheScriptSound( "NPC_BlackHeadcrab.FootstepWalk" );
- PrecacheScriptSound( "NPC_BlackHeadcrab.Footstep" );
-
- BaseClass::Precache();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns the max yaw speed for the current activity.
-//-----------------------------------------------------------------------------
-float CBlackHeadcrab::MaxYawSpeed( void )
-{
- // Not a constant, can't be in a switch statement.
- if ( GetActivity() == ACT_BLACKHEADCRAB_RUN_PANIC )
- {
- return 30;
- }
-
- switch ( GetActivity() )
- {
- case ACT_WALK:
- case ACT_RUN:
- {
- return 10;
- }
-
- case ACT_TURN_LEFT:
- case ACT_TURN_RIGHT:
- {
- return( 30 );
- }
-
- case ACT_RANGE_ATTACK1:
- {
- return( 30 );
- }
-
- default:
- {
- return( 30 );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-Activity CBlackHeadcrab::NPC_TranslateActivity( Activity eNewActivity )
-{
- if ( eNewActivity == ACT_RUN || eNewActivity == ACT_WALK )
- {
- if( m_bPanicState || IsOnFire() )
- {
- return ( Activity )ACT_BLACKHEADCRAB_RUN_PANIC;
- }
- }
-
- return BaseClass::NPC_TranslateActivity( eNewActivity );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::PrescheduleThink( void )
-{
- BaseClass::PrescheduleThink();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int CBlackHeadcrab::TranslateSchedule( int scheduleType )
-{
- switch ( scheduleType )
- {
- // Keep trying to take cover for at least a few seconds.
- case SCHED_FAIL_TAKE_COVER:
- {
- if ( ( m_bPanicState ) && ( gpGlobals->curtime > m_flPanicStopTime ) )
- {
- //DevMsg( "I'm sick of panicking\n" );
- m_bPanicState = false;
- return SCHED_CHASE_ENEMY;
- }
-
- break;
- }
- }
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Allows for modification of the interrupt mask for the current schedule.
-// In the most cases the base implementation should be called first.
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::BuildScheduleTestBits( void )
-{
- // Ignore damage if we're attacking or are fleeing and recently flinched.
- if ( IsCurSchedule( SCHED_HEADCRAB_CRAWL_FROM_CANISTER ) || IsCurSchedule( SCHED_RANGE_ATTACK1 ) || ( IsCurSchedule( SCHED_TAKE_COVER_FROM_ENEMY ) && HasMemory( bits_MEMORY_FLINCHED ) ) )
- {
- ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
- ClearCustomInterruptCondition( COND_HEAVY_DAMAGE );
- }
- else
- {
- SetCustomInterruptCondition( COND_LIGHT_DAMAGE );
- SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
- }
-
- // If we're committed to jump, carry on even if our enemy hides behind a crate. Or a barrel.
- if ( IsCurSchedule( SCHED_RANGE_ATTACK1 ) && m_bCommittedToJump )
- {
- ClearCustomInterruptCondition( COND_ENEMY_OCCLUDED );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output :
-//-----------------------------------------------------------------------------
-int CBlackHeadcrab::SelectSchedule( void )
-{
- // don't override inherited behavior when hanging from ceiling
- if ( !IsHangingFromCeiling() )
- {
- if ( HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN) )
- {
- return SCHED_IDLE_STAND;
- }
-
- if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
- {
- if ( ( gpGlobals->curtime >= m_flNextHopTime ) && SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 )
- {
- m_flNextHopTime = gpGlobals->curtime + random->RandomFloat( 1, 3 );
- return SCHED_SMALL_FLINCH;
- }
- }
-
- if ( m_bPanicState )
- {
- // We're looking for a place to hide, and we've found one. Lurk!
- if ( HasMemory( bits_MEMORY_INCOVER ) )
- {
- m_bPanicState = false;
- m_flPanicStopTime = gpGlobals->curtime;
-
- return SCHED_HEADCRAB_AMBUSH;
- }
-
- return SCHED_TAKE_COVER_FROM_ENEMY;
- }
- }
-
- return BaseClass::SelectSchedule();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Black headcrab's touch attack damage. Evil!
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::TouchDamage( CBaseEntity *pOther )
-{
- if ( pOther->m_iHealth > 1 )
- {
- CTakeDamageInfo info;
- if ( CalcDamageInfo( &info ) >= pOther->m_iHealth )
- info.SetDamage( pOther->m_iHealth - 1 );
-
- pOther->TakeDamage( info );
-
- if ( pOther->IsAlive() && pOther->m_iHealth > 1)
- {
- // Episodic change to avoid NPCs dying too quickly from poison bites
- if ( hl2_episodic.GetBool() )
- {
- if ( pOther->IsPlayer() )
- {
- // That didn't finish them. Take them down to one point with poison damage. It'll heal.
- pOther->TakeDamage( CTakeDamageInfo( this, this, pOther->m_iHealth - 1, DMG_POISON ) );
- }
- else
- {
- // Just take some amount of slash damage instead
- pOther->TakeDamage( CTakeDamageInfo( this, this, sk_headcrab_poison_npc_damage.GetFloat(), DMG_SLASH ) );
- }
- }
- else
- {
- // That didn't finish them. Take them down to one point with poison damage. It'll heal.
- pOther->TakeDamage( CTakeDamageInfo( this, this, pOther->m_iHealth - 1, DMG_POISON ) );
- }
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Bails out of our host zombie, either because he died or was blown
-// into two pieces by an explosion.
-// Input : vecAngles - The yaw direction we should face.
-// flVelocityScale - A multiplier for our ejection velocity.
-// pEnemy - Who we should acquire as our enemy. Usually our zombie host's enemy.
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::Eject( const QAngle &vecAngles, float flVelocityScale, CBaseEntity *pEnemy )
-{
- SetGroundEntity( NULL );
- m_spawnflags |= SF_NPC_FALL_TO_GROUND;
-
- SetIdealState( NPC_STATE_ALERT );
-
- if ( pEnemy )
- {
- SetEnemy( pEnemy );
- UpdateEnemyMemory(pEnemy, pEnemy->GetAbsOrigin());
- }
-
- SetActivity( ACT_RANGE_ATTACK1 );
-
- SetNextThink( gpGlobals->curtime );
- PhysicsSimulate();
-
- GetMotor()->SetIdealYaw( vecAngles.y );
-
- SetAbsVelocity( flVelocityScale * random->RandomInt( 20, 50 ) *
- Vector( random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( 0.5, 1.0 ) ) );
-
- m_bMidJump = false;
- SetTouch( &CBlackHeadcrab::EjectTouch );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Touch function for when we are ejected from the poison zombie.
-// Panic when we hit the ground.
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::EjectTouch( CBaseEntity *pOther )
-{
- LeapTouch( pOther );
- if ( GetFlags() & FL_ONGROUND )
- {
- // Keep trying to take cover for at least a few seconds.
- Panic( random->RandomFloat( 2, 8 ) );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Puts us in a state in which we just want to hide. We'll stop
-// hiding after the given duration.
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::Panic( float flDuration )
-{
- m_flPanicStopTime = gpGlobals->curtime + flDuration;
- m_bPanicState = true;
-}
-
-
-#if HL2_EPISODIC
-//-----------------------------------------------------------------------------
-// Purpose: Black headcrabs have 360-degree vision when they are in the ambush
-// schedule. This is because they ignore sounds when in ambush, and
-// you could walk up behind them without having them attack you.
-// This vision extends only 24 feet.
-//-----------------------------------------------------------------------------
-#define CRAB_360_VIEW_DIST_SQR (12 * 12 * 24 * 24)
-bool CBlackHeadcrab::FInViewCone( CBaseEntity *pEntity )
-{
- if( IsCurSchedule( SCHED_HEADCRAB_AMBUSH ) &&
- (( pEntity->IsNPC() || pEntity->IsPlayer() ) && pEntity->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= CRAB_360_VIEW_DIST_SQR ) )
- {
- // Only see players and NPC's with 360 cone
- // For instance, DON'T tell the eyeball/head tracking code that you can see an object that is behind you!
- return true;
- }
- else
- {
- return BaseClass::FInViewCone( pEntity );
- }
-}
-#endif
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Does a spastic hop in a random or provided direction.
-// Input : pvecDir - 2D direction to hop, NULL picks a random direction.
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::JumpFlinch( const Vector *pvecDir )
-{
- SetGroundEntity( NULL );
-
- //
- // Take him off ground so engine doesn't instantly reset FL_ONGROUND.
- //
- if( HasHeadroom() )
- {
- MoveOrigin( Vector( 0, 0, 1 ) );
- }
-
- //
- // Jump in a random direction.
- //
- Vector up;
- AngleVectors( GetLocalAngles(), NULL, NULL, &up );
-
- if (pvecDir)
- {
- SetAbsVelocity( Vector( pvecDir->x * 4, pvecDir->y * 4, up.z ) * random->RandomFloat( 40, 80 ) );
- }
- else
- {
- SetAbsVelocity( Vector( random->RandomFloat( -4, 4 ), random->RandomFloat( -4, 4 ), up.z ) * random->RandomFloat( 40, 80 ) );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Catches the monster-specific messages that occur when tagged
-// animation frames are played.
-// Input : pEvent -
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::HandleAnimEvent( animevent_t *pEvent )
-{
- if ( pEvent->event == AE_POISONHEADCRAB_FOOTSTEP )
- {
- bool walk = ( GetActivity() == ACT_WALK ); // ? 1.0 : 0.6; !!cgreen! old code had bug
-
- if ( walk )
- {
- EmitSound( "NPC_BlackHeadcrab.FootstepWalk" );
- }
- else
- {
- EmitSound( "NPC_BlackHeadcrab.Footstep" );
- }
-
- return;
- }
-
- if ( pEvent->event == AE_HEADCRAB_JUMP_TELEGRAPH )
- {
- EmitSound( "NPC_BlackHeadcrab.Telegraph" );
-
- CBaseEntity *pEnemy = GetEnemy();
-
- if ( pEnemy )
- {
- // Once we telegraph, we MUST jump. This is also when commit to what point
- // we jump at. Jump at our enemy's eyes.
- m_vecCommittedJumpPos = pEnemy->EyePosition();
- m_bCommittedToJump = true;
- }
-
- return;
- }
-
- if ( pEvent->event == AE_POISONHEADCRAB_THREAT_SOUND )
- {
- EmitSound( "NPC_BlackHeadcrab.Threat" );
- EmitSound( "NPC_BlackHeadcrab.Alert" );
-
- return;
- }
-
- if ( pEvent->event == AE_POISONHEADCRAB_FLINCH_HOP )
- {
- //
- // Hop in a random direction, then run and hide. If we're already running
- // to hide, jump forward -- hopefully that will take us closer to a hiding spot.
- //
- if (m_bPanicState)
- {
- Vector vecForward;
- AngleVectors( GetLocalAngles(), &vecForward );
- JumpFlinch( &vecForward );
- }
- else
- {
- JumpFlinch( NULL );
- }
-
- Panic( random->RandomFloat( 2, 5 ) );
-
- return;
- }
-
- BaseClass::HandleAnimEvent( pEvent );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CBlackHeadcrab::IsHeavyDamage( const CTakeDamageInfo &info )
-{
- if ( !HasMemory(bits_MEMORY_FLINCHED) && info.GetDamage() > 1.0f )
- {
- // If I haven't flinched lately, any amount of damage is interpreted as heavy.
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::IdleSound( void )
-{
- // TODO: hook up "Marco" / "Polo" talking with nearby buddies
- if ( m_NPCState == NPC_STATE_IDLE )
- {
- EmitSound( "NPC_BlackHeadcrab.Idle" );
- }
- else if ( m_NPCState == NPC_STATE_ALERT )
- {
- EmitSound( "NPC_BlackHeadcrab.Talk" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::AlertSound( void )
-{
- EmitSound( "NPC_BlackHeadcrab.AlertVoice" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::PainSound( const CTakeDamageInfo &info )
-{
- if( IsOnFire() && random->RandomInt( 0, HEADCRAB_BURN_SOUND_FREQUENCY ) > 0 )
- {
- // Don't squeak every think when burning.
- return;
- }
-
- EmitSound( "NPC_BlackHeadcrab.Pain" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::DeathSound( const CTakeDamageInfo &info )
-{
- EmitSound( "NPC_BlackHeadcrab.Die" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Played when we jump and hit something that we can't bite.
-//-----------------------------------------------------------------------------
-void CBlackHeadcrab::ImpactSound( void )
-{
- EmitSound( "NPC_BlackHeadcrab.Impact" );
-
- if ( !( GetFlags() & FL_ONGROUND ) )
- {
- // Hit a wall - make a pissed off sound.
- EmitSound( "NPC_BlackHeadcrab.ImpactAngry" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//
-// Schedules
-//
-//-----------------------------------------------------------------------------
-
-AI_BEGIN_CUSTOM_NPC( npc_headcrab, CBaseHeadcrab )
-
- DECLARE_TASK( TASK_HEADCRAB_HOP_ASIDE )
- DECLARE_TASK( TASK_HEADCRAB_DROWN )
- DECLARE_TASK( TASK_HEADCRAB_HOP_OFF_NPC )
- DECLARE_TASK( TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL )
- DECLARE_TASK( TASK_HEADCRAB_UNHIDE )
- DECLARE_TASK( TASK_HEADCRAB_HARASS_HOP )
- DECLARE_TASK( TASK_HEADCRAB_BURROW )
- DECLARE_TASK( TASK_HEADCRAB_UNBURROW )
- DECLARE_TASK( TASK_HEADCRAB_FIND_BURROW_IN_POINT )
- DECLARE_TASK( TASK_HEADCRAB_BURROW_WAIT )
- DECLARE_TASK( TASK_HEADCRAB_CHECK_FOR_UNBURROW )
- DECLARE_TASK( TASK_HEADCRAB_JUMP_FROM_CANISTER )
- DECLARE_TASK( TASK_HEADCRAB_CLIMB_FROM_CANISTER )
-
- DECLARE_TASK( TASK_HEADCRAB_CEILING_POSITION )
- DECLARE_TASK( TASK_HEADCRAB_CEILING_WAIT )
- DECLARE_TASK( TASK_HEADCRAB_CEILING_DETACH )
- DECLARE_TASK( TASK_HEADCRAB_CEILING_FALL )
- DECLARE_TASK( TASK_HEADCRAB_CEILING_LAND )
-
- DECLARE_ACTIVITY( ACT_HEADCRAB_THREAT_DISPLAY )
- DECLARE_ACTIVITY( ACT_HEADCRAB_HOP_LEFT )
- DECLARE_ACTIVITY( ACT_HEADCRAB_HOP_RIGHT )
- DECLARE_ACTIVITY( ACT_HEADCRAB_DROWN )
- DECLARE_ACTIVITY( ACT_HEADCRAB_BURROW_IN )
- DECLARE_ACTIVITY( ACT_HEADCRAB_BURROW_OUT )
- DECLARE_ACTIVITY( ACT_HEADCRAB_BURROW_IDLE )
- DECLARE_ACTIVITY( ACT_HEADCRAB_CRAWL_FROM_CANISTER_LEFT )
- DECLARE_ACTIVITY( ACT_HEADCRAB_CRAWL_FROM_CANISTER_CENTER )
- DECLARE_ACTIVITY( ACT_HEADCRAB_CRAWL_FROM_CANISTER_RIGHT )
- DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_FALL )
-
- DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_IDLE )
- DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_DETACH )
- DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_LAND )
-
- DECLARE_CONDITION( COND_HEADCRAB_IN_WATER )
- DECLARE_CONDITION( COND_HEADCRAB_ILLEGAL_GROUNDENT )
- DECLARE_CONDITION( COND_HEADCRAB_BARNACLED )
- DECLARE_CONDITION( COND_HEADCRAB_UNHIDE )
-
- //Adrian: events go here
- DECLARE_ANIMEVENT( AE_HEADCRAB_JUMPATTACK )
- DECLARE_ANIMEVENT( AE_HEADCRAB_JUMP_TELEGRAPH )
- DECLARE_ANIMEVENT( AE_HEADCRAB_BURROW_IN )
- DECLARE_ANIMEVENT( AE_HEADCRAB_BURROW_IN_FINISH )
- DECLARE_ANIMEVENT( AE_HEADCRAB_BURROW_OUT )
- DECLARE_ANIMEVENT( AE_HEADCRAB_CEILING_DETACH )
-
- //=========================================================
- // > SCHED_HEADCRAB_RANGE_ATTACK1
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_RANGE_ATTACK1,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_RANGE_ATTACK1 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_IDEAL 0"
- " TASK_WAIT_RANDOM 0.5"
- ""
- " Interrupts"
- " COND_ENEMY_OCCLUDED"
- " COND_NO_PRIMARY_AMMO"
- )
-
- //=========================================================
- //
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_WAKE_ANGRY,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE "
- " TASK_FACE_IDEAL 0"
- " TASK_SOUND_WAKE 0"
- " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HEADCRAB_THREAT_DISPLAY"
- ""
- " Interrupts"
- )
-
- //=========================================================
- //
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_WAKE_ANGRY_NO_DISPLAY,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE "
- " TASK_FACE_IDEAL 0"
- " TASK_SOUND_WAKE 0"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // > SCHED_FAST_HEADCRAB_RANGE_ATTACK1
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_FAST_HEADCRAB_RANGE_ATTACK1,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_RANGE_ATTACK1 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_IDEAL 0"
- " TASK_WAIT_RANDOM 0.5"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // The irreversible process of drowning
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_DROWN,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_FAIL_DROWN"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_DROWN"
- " TASK_HEADCRAB_DROWN 0"
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_FAIL_DROWN,
-
- " Tasks"
- " TASK_HEADCRAB_DROWN 0"
- ""
- " Interrupts"
- )
-
-
- //=========================================================
- // Headcrab lurks in place and waits for a chance to jump on
- // some unfortunate soul.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_AMBUSH,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT_INDEFINITE 0"
-
- " Interrupts"
- " COND_SEE_ENEMY"
- " COND_SEE_HATE"
- " COND_CAN_RANGE_ATTACK1"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_PROVOKED"
- )
-
- //=========================================================
- // Headcrab has landed atop another NPC or has landed on
- // a ledge. Get down!
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_HOP_RANDOMLY,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_HEADCRAB_HOP_OFF_NPC 0"
-
- " Interrupts"
- )
-
- //=========================================================
- // Headcrab is in the clutches of a barnacle
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_BARNACLED,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_DROWN"
- " TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL 0"
-
- " Interrupts"
- )
-
- //=========================================================
- // Headcrab is unhiding
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_UNHIDE,
-
- " Tasks"
- " TASK_HEADCRAB_UNHIDE 0"
-
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_HARASS_ENEMY,
-
- " Tasks"
- " TASK_FACE_ENEMY 0"
- " TASK_HEADCRAB_HARASS_HOP 0"
- " TASK_WAIT_FACE_ENEMY 1"
- " TASK_SET_ROUTE_SEARCH_TIME 2" // Spend 2 seconds trying to build a path if stuck
- " TASK_GET_PATH_TO_RANDOM_NODE 300"
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " Interrupts"
- " COND_NEW_ENEMY"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_FALL_TO_GROUND,
-
- " Tasks"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_DROWN"
- " TASK_FALL_TO_GROUND 0"
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_CRAWL_FROM_CANISTER,
- " Tasks"
- " TASK_HEADCRAB_CLIMB_FROM_CANISTER 0"
- " TASK_HEADCRAB_JUMP_FROM_CANISTER 0"
- ""
- " Interrupts"
- )
-
- //==================================================
- // Burrow In
- //==================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_BURROW_IN,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
- " TASK_HEADCRAB_BURROW 0"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_HEADCRAB_BURROW_IN"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_HEADCRAB_BURROW_IDLE"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- )
-
- //==================================================
- // Run to a nearby burrow hint and burrow there
- //==================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_RUN_TO_BURROW_IN,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
- " TASK_HEADCRAB_FIND_BURROW_IN_POINT 512"
- " TASK_SET_TOLERANCE_DISTANCE 8"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_IN"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- " COND_GIVE_WAY"
- " COND_CAN_RANGE_ATTACK1"
- )
-
- //==================================================
- // Run to m_pHintNode and burrow there
- //==================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_RUN_TO_SPECIFIC_BURROW,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
- " TASK_SET_TOLERANCE_DISTANCE 8"
- " TASK_GET_PATH_TO_HINTNODE 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_IN"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- " COND_GIVE_WAY"
- )
-
- //==================================================
- // Wait until we can unburrow and attack something
- //==================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_BURROW_WAIT,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT"
- " TASK_HEADCRAB_BURROW_WAIT 1"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- " COND_NEW_ENEMY" // HACK: We don't actually choose a new schedule on new enemy, but
- // we need this interrupt so that the headcrab actually acquires
- // new enemies while burrowed. (look in ai_basenpc.cpp for "DO NOT mess")
- " COND_CAN_RANGE_ATTACK1"
- )
-
- //==================================================
- // Burrow Out
- //==================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_BURROW_OUT,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT"
- " TASK_HEADCRAB_UNBURROW 0"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_HEADCRAB_BURROW_OUT"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- )
-
- //==================================================
- // Wait for it to be clear for unburrowing
- //==================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_WAIT_FOR_CLEAR_UNBURROW,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT"
- " TASK_HEADCRAB_CHECK_FOR_UNBURROW 1"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_OUT"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- )
-
- //==================================================
- // Wait until we can drop.
- //==================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_CEILING_WAIT,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_CEILING_DROP"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_CEILING_IDLE"
- " TASK_HEADCRAB_CEILING_POSITION 0"
- " TASK_HEADCRAB_CEILING_WAIT 1"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- " COND_NEW_ENEMY"
- " COND_CAN_RANGE_ATTACK1"
- )
-
- //==================================================
- // Deatch from ceiling.
- //==================================================
- DEFINE_SCHEDULE
- (
- SCHED_HEADCRAB_CEILING_DROP,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_CEILING_WAIT"
- " TASK_HEADCRAB_CEILING_DETACH 0"
- " TASK_HEADCRAB_CEILING_FALL 0"
- " TASK_HEADCRAB_CEILING_LAND 0"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- )
-
-AI_END_CUSTOM_NPC()
-
-//-----------------------------------------------------------------------------
-
-AI_BEGIN_CUSTOM_NPC( npc_headcrab_poison, CBlackHeadcrab )
-
- DECLARE_ACTIVITY( ACT_BLACKHEADCRAB_RUN_PANIC )
-
- //Adrian: events go here
- DECLARE_ANIMEVENT( AE_POISONHEADCRAB_FLINCH_HOP )
- DECLARE_ANIMEVENT( AE_POISONHEADCRAB_FOOTSTEP )
- DECLARE_ANIMEVENT( AE_POISONHEADCRAB_THREAT_SOUND )
-
-AI_END_CUSTOM_NPC()
-
-
-AI_BEGIN_CUSTOM_NPC( npc_headcrab_fast, CFastHeadcrab )
- DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE1 )
- DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE2 )
- DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE3 )
- DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE4 )
-AI_END_CUSTOM_NPC()
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Implements the headcrab, a tiny, jumpy alien parasite.
+//
+// TODO: make poison headcrab hop in response to nearby bullet impacts?
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "game.h"
+#include "antlion_dust.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hint.h"
+#include "ai_hull.h"
+#include "ai_navigator.h"
+#include "ai_moveprobe.h"
+#include "ai_memory.h"
+#include "bitstring.h"
+#include "hl2_shareddefs.h"
+#include "npcevent.h"
+#include "soundent.h"
+#include "npc_headcrab.h"
+#include "gib.h"
+#include "ai_interactions.h"
+#include "ndebugoverlay.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "movevars_shared.h"
+#include "world.h"
+#include "npc_bullseye.h"
+#include "physics_npc_solver.h"
+#include "hl2_gamerules.h"
+#include "decals.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define CRAB_ATTN_IDLE (float)1.5
+#define HEADCRAB_GUTS_GIB_COUNT 1
+#define HEADCRAB_LEGS_GIB_COUNT 3
+#define HEADCRAB_ALL_GIB_COUNT 5
+
+#define HEADCRAB_RUNMODE_ACCELERATE 1
+#define HEADCRAB_RUNMODE_IDLE 2
+#define HEADCRAB_RUNMODE_DECELERATE 3
+#define HEADCRAB_RUNMODE_FULLSPEED 4
+#define HEADCRAB_RUNMODE_PAUSE 5
+
+#define HEADCRAB_RUN_MINSPEED 0.5
+#define HEADCRAB_RUN_MAXSPEED 1.0
+
+const float HEADCRAB_BURROWED_FOV = -1.0f;
+const float HEADCRAB_UNBURROWED_FOV = 0.5f;
+
+#define HEADCRAB_IGNORE_WORLD_COLLISION_TIME 0.5
+
+const int HEADCRAB_MIN_JUMP_DIST = 48;
+const int HEADCRAB_MAX_JUMP_DIST = 256;
+
+#define HEADCRAB_BURROW_POINT_SEARCH_RADIUS 256.0
+
+// Debugging
+#define HEADCRAB_DEBUG_HIDING 1
+
+#define HEADCRAB_BURN_SOUND_FREQUENCY 10
+
+ConVar g_debug_headcrab( "g_debug_headcrab", "0", FCVAR_CHEAT );
+
+//------------------------------------
+// Spawnflags
+//------------------------------------
+#define SF_HEADCRAB_START_HIDDEN (1 << 16)
+#define SF_HEADCRAB_START_HANGING (1 << 17)
+
+
+//-----------------------------------------------------------------------------
+// Think contexts.
+//-----------------------------------------------------------------------------
+static const char *s_pPitchContext = "PitchContext";
+
+
+//-----------------------------------------------------------------------------
+// Animation events.
+//-----------------------------------------------------------------------------
+int AE_HEADCRAB_JUMPATTACK;
+int AE_HEADCRAB_JUMP_TELEGRAPH;
+int AE_POISONHEADCRAB_FLINCH_HOP;
+int AE_POISONHEADCRAB_FOOTSTEP;
+int AE_POISONHEADCRAB_THREAT_SOUND;
+int AE_HEADCRAB_BURROW_IN;
+int AE_HEADCRAB_BURROW_IN_FINISH;
+int AE_HEADCRAB_BURROW_OUT;
+int AE_HEADCRAB_CEILING_DETACH;
+
+
+//-----------------------------------------------------------------------------
+// Custom schedules.
+//-----------------------------------------------------------------------------
+enum
+{
+ SCHED_HEADCRAB_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE,
+ SCHED_HEADCRAB_WAKE_ANGRY,
+ SCHED_HEADCRAB_WAKE_ANGRY_NO_DISPLAY,
+ SCHED_HEADCRAB_DROWN,
+ SCHED_HEADCRAB_FAIL_DROWN,
+ SCHED_HEADCRAB_AMBUSH,
+ SCHED_HEADCRAB_HOP_RANDOMLY, // get off something you're not supposed to be on.
+ SCHED_HEADCRAB_BARNACLED,
+ SCHED_HEADCRAB_UNHIDE,
+ SCHED_HEADCRAB_HARASS_ENEMY,
+ SCHED_HEADCRAB_FALL_TO_GROUND,
+ SCHED_HEADCRAB_RUN_TO_BURROW_IN,
+ SCHED_HEADCRAB_RUN_TO_SPECIFIC_BURROW,
+ SCHED_HEADCRAB_BURROW_IN,
+ SCHED_HEADCRAB_BURROW_WAIT,
+ SCHED_HEADCRAB_BURROW_OUT,
+ SCHED_HEADCRAB_WAIT_FOR_CLEAR_UNBURROW,
+ SCHED_HEADCRAB_CRAWL_FROM_CANISTER,
+
+ SCHED_FAST_HEADCRAB_RANGE_ATTACK1,
+
+ SCHED_HEADCRAB_CEILING_WAIT,
+ SCHED_HEADCRAB_CEILING_DROP,
+};
+
+
+//=========================================================
+// tasks
+//=========================================================
+enum
+{
+ TASK_HEADCRAB_HOP_ASIDE = LAST_SHARED_TASK,
+ TASK_HEADCRAB_HOP_OFF_NPC,
+ TASK_HEADCRAB_DROWN,
+ TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL,
+ TASK_HEADCRAB_UNHIDE,
+ TASK_HEADCRAB_HARASS_HOP,
+ TASK_HEADCRAB_FIND_BURROW_IN_POINT,
+ TASK_HEADCRAB_BURROW,
+ TASK_HEADCRAB_UNBURROW,
+ TASK_HEADCRAB_BURROW_WAIT,
+ TASK_HEADCRAB_CHECK_FOR_UNBURROW,
+ TASK_HEADCRAB_JUMP_FROM_CANISTER,
+ TASK_HEADCRAB_CLIMB_FROM_CANISTER,
+
+ TASK_HEADCRAB_CEILING_WAIT,
+ TASK_HEADCRAB_CEILING_POSITION,
+ TASK_HEADCRAB_CEILING_DETACH,
+ TASK_HEADCRAB_CEILING_FALL,
+ TASK_HEADCRAB_CEILING_LAND,
+};
+
+
+//=========================================================
+// conditions
+//=========================================================
+enum
+{
+ COND_HEADCRAB_IN_WATER = LAST_SHARED_CONDITION,
+ COND_HEADCRAB_ILLEGAL_GROUNDENT,
+ COND_HEADCRAB_BARNACLED,
+ COND_HEADCRAB_UNHIDE,
+};
+
+//=========================================================
+// private activities
+//=========================================================
+int ACT_HEADCRAB_THREAT_DISPLAY;
+int ACT_HEADCRAB_HOP_LEFT;
+int ACT_HEADCRAB_HOP_RIGHT;
+int ACT_HEADCRAB_DROWN;
+int ACT_HEADCRAB_BURROW_IN;
+int ACT_HEADCRAB_BURROW_OUT;
+int ACT_HEADCRAB_BURROW_IDLE;
+int ACT_HEADCRAB_CRAWL_FROM_CANISTER_LEFT;
+int ACT_HEADCRAB_CRAWL_FROM_CANISTER_CENTER;
+int ACT_HEADCRAB_CRAWL_FROM_CANISTER_RIGHT;
+int ACT_HEADCRAB_CEILING_IDLE;
+int ACT_HEADCRAB_CEILING_DETACH;
+int ACT_HEADCRAB_CEILING_FALL;
+int ACT_HEADCRAB_CEILING_LAND;
+
+
+//-----------------------------------------------------------------------------
+// Skill settings.
+//-----------------------------------------------------------------------------
+ConVar sk_headcrab_health( "sk_headcrab_health","0");
+ConVar sk_headcrab_fast_health( "sk_headcrab_fast_health","0");
+ConVar sk_headcrab_poison_health( "sk_headcrab_poison_health","0");
+ConVar sk_headcrab_melee_dmg( "sk_headcrab_melee_dmg","0");
+ConVar sk_headcrab_poison_npc_damage( "sk_headcrab_poison_npc_damage", "0" );
+
+BEGIN_DATADESC( CBaseHeadcrab )
+
+ // m_nGibCount - don't save
+ DEFINE_FIELD( m_bHidden, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flTimeDrown, FIELD_TIME ),
+ DEFINE_FIELD( m_bCommittedToJump, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_vecCommittedJumpPos, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_flNextNPCThink, FIELD_TIME ),
+ DEFINE_FIELD( m_flIgnoreWorldCollisionTime, FIELD_TIME ),
+
+ DEFINE_KEYFIELD( m_bStartBurrowed, FIELD_BOOLEAN, "startburrowed" ),
+ DEFINE_FIELD( m_bBurrowed, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flBurrowTime, FIELD_TIME ),
+ DEFINE_FIELD( m_nContext, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bCrawlFromCanister, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bMidJump, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_nJumpFromCanisterDir, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_bHangingFromCeiling, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flIlluminatedTime, FIELD_TIME ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Burrow", InputBurrow ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "BurrowImmediate", InputBurrowImmediate ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Unburrow", InputUnburrow ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartHangingFromCeiling", InputStartHangingFromCeiling ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DropFromCeiling", InputDropFromCeiling ),
+
+ // Function Pointers
+ DEFINE_THINKFUNC( EliminateRollAndPitch ),
+ DEFINE_THINKFUNC( ThrowThink ),
+ DEFINE_ENTITYFUNC( LeapTouch ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::Spawn( void )
+{
+ //Precache();
+ //SetModel( "models/headcrab.mdl" );
+ //m_iHealth = sk_headcrab_health.GetFloat();
+
+#ifdef _XBOX
+ // Always fade the corpse
+ AddSpawnFlags( SF_NPC_FADE_CORPSE );
+#endif // _XBOX
+
+ SetHullType(HULL_TINY);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+
+ SetCollisionGroup( HL2COLLISION_GROUP_HEADCRAB );
+
+ SetViewOffset( Vector(6, 0, 11) ) ; // Position of the eyes relative to NPC's origin.
+
+ SetBloodColor( BLOOD_COLOR_GREEN );
+ m_flFieldOfView = 0.5;
+ m_NPCState = NPC_STATE_NONE;
+ m_nGibCount = HEADCRAB_ALL_GIB_COUNT;
+
+ // Are we starting hidden?
+ if ( m_spawnflags & SF_HEADCRAB_START_HIDDEN )
+ {
+ m_bHidden = true;
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetRenderColorA( 0 );
+ m_nRenderMode = kRenderTransTexture;
+ AddEffects( EF_NODRAW );
+ }
+ else
+ {
+ m_bHidden = false;
+ }
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 );
+ CapabilitiesAdd( bits_CAP_SQUAD );
+
+ // headcrabs get to cheat for 5 seconds (sjb)
+ GetEnemies()->SetFreeKnowledgeDuration( 5.0 );
+
+ m_bHangingFromCeiling = false;
+ m_flIlluminatedTime = -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stuff that must happen after NPCInit is called.
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::HeadcrabInit()
+{
+ // See if we're supposed to start burrowed
+ if ( m_bStartBurrowed )
+ {
+ SetBurrowed( true );
+ SetSchedule( SCHED_HEADCRAB_BURROW_WAIT );
+ }
+
+ if ( GetSpawnFlags() & SF_HEADCRAB_START_HANGING )
+ {
+ SetSchedule( SCHED_HEADCRAB_CEILING_WAIT );
+ m_flIlluminatedTime = -1;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Precaches all resources this monster needs.
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::Precache( void )
+{
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// The headcrab will crawl from the cannister, then jump to a burrow point
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::CrawlFromCanister()
+{
+ // This is necessary to prevent ground computations, etc. from happening
+ // while the crawling animation is occuring
+ AddFlag( FL_FLY );
+ m_bCrawlFromCanister = true;
+ SetNextThink( gpGlobals->curtime );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : NewActivity -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::OnChangeActivity( Activity NewActivity )
+{
+ bool fRandomize = false;
+ float flRandomRange = 0.0;
+
+ // If this crab is starting to walk or idle, pick a random point within
+ // the animation to begin. This prevents lots of crabs being in lockstep.
+ if ( NewActivity == ACT_IDLE )
+ {
+ flRandomRange = 0.75;
+ fRandomize = true;
+ }
+ else if ( NewActivity == ACT_RUN )
+ {
+ flRandomRange = 0.25;
+ fRandomize = true;
+ }
+
+ BaseClass::OnChangeActivity( NewActivity );
+
+ if( fRandomize )
+ {
+ SetCycle( random->RandomFloat( 0.0, flRandomRange ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Indicates this monster's place in the relationship table.
+// Output :
+//-----------------------------------------------------------------------------
+Class_T CBaseHeadcrab::Classify( void )
+{
+ if( m_bHidden )
+ {
+ // Effectively invisible to other AI's while hidden.
+ return( CLASS_NONE );
+ }
+ else
+ {
+ return( CLASS_HEADCRAB );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &posSrc -
+// Output : Vector
+//-----------------------------------------------------------------------------
+Vector CBaseHeadcrab::BodyTarget( const Vector &posSrc, bool bNoisy )
+{
+ Vector vecResult;
+ vecResult = GetAbsOrigin();
+ vecResult.z += 6;
+ return vecResult;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+float CBaseHeadcrab::GetAutoAimRadius()
+{
+ if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
+ {
+ return 24.0f;
+ }
+
+ return 12.0f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows each sequence to have a different turn rate associated with it.
+// Output : float
+//-----------------------------------------------------------------------------
+float CBaseHeadcrab::MaxYawSpeed( void )
+{
+ return BaseClass::MaxYawSpeed();
+}
+
+//-----------------------------------------------------------------------------
+// Because the AI code does a tracehull to find the ground under an NPC, headcrabs
+// can often be seen standing with one edge of their box perched on a ledge and
+// 80% or more of their body hanging out over nothing. This is often a case
+// where a headcrab will be unable to pathfind out of its location. This heuristic
+// very crudely tries to determine if this is the case by casting a simple ray
+// down from the center of the headcrab.
+//-----------------------------------------------------------------------------
+#define HEADCRAB_MAX_LEDGE_HEIGHT 12.0f
+bool CBaseHeadcrab::IsFirmlyOnGround()
+{
+ if( !(GetFlags()&FL_ONGROUND) )
+ return false;
+
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, HEADCRAB_MAX_LEDGE_HEIGHT ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
+ return tr.fraction != 1.0;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::MoveOrigin( const Vector &vecDelta )
+{
+ UTIL_SetOrigin( this, GetLocalOrigin() + vecDelta );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : vecPos -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::ThrowAt( const Vector &vecPos )
+{
+ JumpAttack( false, vecPos, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : vecPos -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::JumpToBurrowHint( CAI_Hint *pHint )
+{
+ Vector vecVel = VecCheckToss( this, GetAbsOrigin(), pHint->GetAbsOrigin(), 0.5f, 1.0f, false, NULL, NULL );
+
+ // Undershoot by a little because it looks bad if we overshoot and turn around to burrow.
+ vecVel *= 0.9f;
+ Leap( vecVel );
+
+ GrabHintNode( pHint );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : vecVel -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::Leap( const Vector &vecVel )
+{
+ SetTouch( &CBaseHeadcrab::LeapTouch );
+
+ SetCondition( COND_FLOATING_OFF_GROUND );
+ SetGroundEntity( NULL );
+
+ m_flIgnoreWorldCollisionTime = gpGlobals->curtime + HEADCRAB_IGNORE_WORLD_COLLISION_TIME;
+
+ if( HasHeadroom() )
+ {
+ // Take him off ground so engine doesn't instantly reset FL_ONGROUND.
+ MoveOrigin( Vector( 0, 0, 1 ) );
+ }
+
+ SetAbsVelocity( vecVel );
+
+ // Think every frame so the player sees the headcrab where he actually is...
+ m_bMidJump = true;
+ SetThink( &CBaseHeadcrab::ThrowThink );
+ SetNextThink( gpGlobals->curtime );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::ThrowThink( void )
+{
+ if (gpGlobals->curtime > m_flNextNPCThink)
+ {
+ NPCThink();
+ m_flNextNPCThink = gpGlobals->curtime + 0.1;
+ }
+
+ if( GetFlags() & FL_ONGROUND )
+ {
+ SetThink( &CBaseHeadcrab::CallNPCThink );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ return;
+ }
+
+ SetNextThink( gpGlobals->curtime );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Does a jump attack at the given position.
+// Input : bRandomJump - Just hop in a random direction.
+// vecPos - Position to jump at, ignored if bRandom is set to true.
+// bThrown -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::JumpAttack( bool bRandomJump, const Vector &vecPos, bool bThrown )
+{
+ Vector vecJumpVel;
+ if ( !bRandomJump )
+ {
+ float gravity = GetCurrentGravity();
+ if ( gravity <= 1 )
+ {
+ gravity = 1;
+ }
+
+ // How fast does the headcrab need to travel to reach the position given gravity?
+ float flActualHeight = vecPos.z - GetAbsOrigin().z;
+ float height = flActualHeight;
+ if ( height < 16 )
+ {
+ height = 16;
+ }
+ else
+ {
+ float flMaxHeight = bThrown ? 400 : 120;
+ if ( height > flMaxHeight )
+ {
+ height = flMaxHeight;
+ }
+ }
+
+ // overshoot the jump by an additional 8 inches
+ // NOTE: This calculation jumps at a position INSIDE the box of the enemy (player)
+ // so if you make the additional height too high, the crab can land on top of the
+ // enemy's head. If we want to jump high, we'll need to move vecPos to the surface/outside
+ // of the enemy's box.
+
+ float additionalHeight = 0;
+ if ( height < 32 )
+ {
+ additionalHeight = 8;
+ }
+
+ height += additionalHeight;
+
+ // NOTE: This equation here is from vf^2 = vi^2 + 2*a*d
+ float speed = sqrt( 2 * gravity * height );
+ float time = speed / gravity;
+
+ // add in the time it takes to fall the additional height
+ // So the impact takes place on the downward slope at the original height
+ time += sqrt( (2 * additionalHeight) / gravity );
+
+ // Scale the sideways velocity to get there at the right time
+ VectorSubtract( vecPos, GetAbsOrigin(), vecJumpVel );
+ vecJumpVel /= time;
+
+ // Speed to offset gravity at the desired height.
+ vecJumpVel.z = speed;
+
+ // Don't jump too far/fast.
+ float flJumpSpeed = vecJumpVel.Length();
+ float flMaxSpeed = bThrown ? 1000.0f : 650.0f;
+ if ( flJumpSpeed > flMaxSpeed )
+ {
+ vecJumpVel *= flMaxSpeed / flJumpSpeed;
+ }
+ }
+ else
+ {
+ //
+ // Jump hop, don't care where.
+ //
+ Vector forward, up;
+ AngleVectors( GetLocalAngles(), &forward, NULL, &up );
+ vecJumpVel = Vector( forward.x, forward.y, up.z ) * 350;
+ }
+
+ AttackSound();
+ Leap( vecJumpVel );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Catches the monster-specific messages that occur when tagged
+// animation frames are played.
+// Input : *pEvent -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::HandleAnimEvent( animevent_t *pEvent )
+{
+ if ( pEvent->event == AE_HEADCRAB_JUMPATTACK )
+ {
+ // Ignore if we're in mid air
+ if ( m_bMidJump )
+ return;
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if ( pEnemy )
+ {
+ if ( m_bCommittedToJump )
+ {
+ JumpAttack( false, m_vecCommittedJumpPos );
+ }
+ else
+ {
+ // Jump at my enemy's eyes.
+ JumpAttack( false, pEnemy->EyePosition() );
+ }
+
+ m_bCommittedToJump = false;
+
+ }
+ else
+ {
+ // Jump hop, don't care where.
+ JumpAttack( true );
+ }
+
+ return;
+ }
+
+ if ( pEvent->event == AE_HEADCRAB_CEILING_DETACH )
+ {
+ SetMoveType( MOVETYPE_STEP );
+ RemoveFlag( FL_ONGROUND );
+ RemoveFlag( FL_FLY );
+
+ SetAbsVelocity( Vector ( 0, 0, -128 ) );
+ return;
+ }
+ if ( pEvent->event == AE_HEADCRAB_JUMP_TELEGRAPH )
+ {
+ TelegraphSound();
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if ( pEnemy )
+ {
+ // Once we telegraph, we MUST jump. This is also when commit to what point
+ // we jump at. Jump at our enemy's eyes.
+ m_vecCommittedJumpPos = pEnemy->EyePosition();
+ m_bCommittedToJump = true;
+ }
+
+ return;
+ }
+
+ if ( pEvent->event == AE_HEADCRAB_BURROW_IN )
+ {
+ EmitSound( "NPC_Headcrab.BurrowIn" );
+ CreateDust();
+
+ return;
+ }
+
+ if ( pEvent->event == AE_HEADCRAB_BURROW_IN_FINISH )
+ {
+ SetBurrowed( true );
+ return;
+ }
+
+ if ( pEvent->event == AE_HEADCRAB_BURROW_OUT )
+ {
+ Assert( m_bBurrowed );
+ if ( m_bBurrowed )
+ {
+ EmitSound( "NPC_Headcrab.BurrowOut" );
+ CreateDust();
+ SetBurrowed( false );
+
+ // We're done with this burrow hint node. It might be NULL here
+ // because we may have started burrowed (no hint node in that case).
+ GrabHintNode( NULL );
+ }
+
+ return;
+ }
+
+ CAI_BaseNPC::HandleAnimEvent( pEvent );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Does all the fixup for going to/from the burrowed state.
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::SetBurrowed( bool bBurrowed )
+{
+ if ( bBurrowed )
+ {
+ AddEffects( EF_NODRAW );
+ AddFlag( FL_NOTARGET );
+ m_spawnflags |= SF_NPC_GAG;
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_NO;
+ m_flFieldOfView = HEADCRAB_BURROWED_FOV;
+
+ SetState( NPC_STATE_IDLE );
+ SetActivity( (Activity) ACT_HEADCRAB_BURROW_IDLE );
+ }
+ else
+ {
+ RemoveEffects( EF_NODRAW );
+ RemoveFlag( FL_NOTARGET );
+ m_spawnflags &= ~SF_NPC_GAG;
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_YES;
+ m_flFieldOfView = HEADCRAB_UNBURROWED_FOV;
+ }
+
+ m_bBurrowed = bBurrowed;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_HEADCRAB_CLIMB_FROM_CANISTER:
+ AutoMovement( );
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+
+ case TASK_HEADCRAB_JUMP_FROM_CANISTER:
+ GetMotor()->UpdateYaw();
+ if ( FacingIdeal() )
+ {
+ TaskComplete();
+ }
+ break;
+
+ case TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL:
+ if ( m_flNextFlinchTime < gpGlobals->curtime )
+ {
+ m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f );
+ CTakeDamageInfo info;
+ PainSound( info );
+ }
+ break;
+
+ case TASK_HEADCRAB_HOP_OFF_NPC:
+ if( GetFlags() & FL_ONGROUND )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ // Face the direction I've been forced to jump.
+ GetMotor()->SetIdealYawToTargetAndUpdate( GetAbsOrigin() + GetAbsVelocity() );
+ }
+ break;
+
+ case TASK_HEADCRAB_DROWN:
+ if( gpGlobals->curtime > m_flTimeDrown )
+ {
+ OnTakeDamage( CTakeDamageInfo( this, this, m_iHealth * 2, DMG_DROWN ) );
+ }
+ break;
+
+ case TASK_RANGE_ATTACK1:
+ case TASK_RANGE_ATTACK2:
+ case TASK_HEADCRAB_HARASS_HOP:
+ {
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ m_bMidJump = false;
+ SetTouch( NULL );
+ SetThink( &CBaseHeadcrab::CallNPCThink );
+ SetIdealActivity( ACT_IDLE );
+
+ if ( m_bAttackFailed )
+ {
+ // our attack failed because we just ran into something solid.
+ // delay attacking for a while so we don't just repeatedly leap
+ // at the enemy from a bad location.
+ m_bAttackFailed = false;
+ m_flNextAttack = gpGlobals->curtime + 1.2f;
+ }
+ }
+ break;
+ }
+
+ case TASK_HEADCRAB_CHECK_FOR_UNBURROW:
+ {
+ // 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 );
+
+ break;
+ }
+
+ case TASK_HEADCRAB_BURROW_WAIT:
+ {
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ TaskComplete();
+ }
+
+ break;
+ }
+
+ case TASK_HEADCRAB_CEILING_WAIT:
+ {
+#ifdef HL2_EPISODIC
+ if ( DarknessLightSourceWithinRadius( this, DARKNESS_LIGHTSOURCE_SIZE ) )
+ {
+ DropFromCeiling();
+ }
+#endif
+
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ TaskComplete();
+ }
+
+ break;
+ }
+
+ case TASK_HEADCRAB_CEILING_DETACH:
+ {
+ if ( IsActivityFinished() )
+ {
+ ClearCondition( COND_CAN_RANGE_ATTACK1 );
+ RemoveFlag(FL_FLY);
+ TaskComplete();
+ }
+ }
+ break;
+
+ case TASK_HEADCRAB_CEILING_FALL:
+ {
+ Vector vecPrPos;
+ trace_t tr;
+
+ //Figure out where the headcrab is going to be in quarter of a second.
+ vecPrPos = GetAbsOrigin() + ( GetAbsVelocity() * 0.25f );
+ UTIL_TraceHull( vecPrPos, vecPrPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.startsolid == true || GetFlags() & FL_ONGROUND )
+ {
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ TaskComplete();
+ }
+ }
+ break;
+
+ case TASK_HEADCRAB_CEILING_LAND:
+ {
+ if ( IsActivityFinished() )
+ {
+ RemoveSolidFlags( FSOLID_NOT_SOLID ); //double-dog verify that we're solid.
+ TaskComplete();
+ m_bHangingFromCeiling = false;
+ }
+ }
+ break;
+ default:
+ {
+ BaseClass::RunTask( pTask );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Before jumping, headcrabs usually use SetOrigin() to lift themselves off the
+// ground. If the headcrab doesn't have the clearance to so, they'll be stuck
+// in the world. So this function makes sure there's headroom first.
+//-----------------------------------------------------------------------------
+bool CBaseHeadcrab::HasHeadroom()
+{
+ trace_t tr;
+ UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 1 ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
+
+#if 0
+ if( tr.fraction == 1.0f )
+ {
+ Msg("Headroom\n");
+ }
+ else
+ {
+ Msg("NO Headroom\n");
+ }
+#endif
+
+ return (tr.fraction == 1.0);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: LeapTouch - this is the headcrab's touch function when it is in the air.
+// Input : *pOther -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::LeapTouch( CBaseEntity *pOther )
+{
+ m_bMidJump = false;
+
+ if ( IRelationType( pOther ) == D_HT )
+ {
+ // Don't hit if back on ground
+ if ( !( GetFlags() & FL_ONGROUND ) )
+ {
+ if ( pOther->m_takedamage != DAMAGE_NO )
+ {
+ BiteSound();
+ TouchDamage( pOther );
+
+ // attack succeeded, so don't delay our next attack if we previously thought we failed
+ m_bAttackFailed = false;
+ }
+ else
+ {
+ ImpactSound();
+ }
+ }
+ else
+ {
+ ImpactSound();
+ }
+ }
+ else if( !(GetFlags() & FL_ONGROUND) )
+ {
+ // Still in the air...
+ if( !pOther->IsSolid() )
+ {
+ // Touching a trigger or something.
+ return;
+ }
+
+ // just ran into something solid, so the attack probably failed. make a note of it
+ // so that when the attack is done, we'll delay attacking for a while so we don't
+ // just repeatedly leap at the enemy from a bad location.
+ m_bAttackFailed = true;
+
+ if( gpGlobals->curtime < m_flIgnoreWorldCollisionTime )
+ {
+ // Headcrabs try to ignore the world, static props, and friends for a
+ // fraction of a second after they jump. This is because they often brush
+ // doorframes or props as they leap, and touching those objects turns off
+ // this touch function, which can cause them to hit the player and not bite.
+ // A timer probably isn't the best way to fix this, but it's one of our
+ // safer options at this point (sjb).
+ return;
+ }
+ }
+
+ // Shut off the touch function.
+ SetTouch( NULL );
+ SetThink ( &CBaseHeadcrab::CallNPCThink );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CBaseHeadcrab::CalcDamageInfo( CTakeDamageInfo *pInfo )
+{
+ pInfo->Set( this, this, sk_headcrab_melee_dmg.GetFloat(), DMG_SLASH );
+ CalculateMeleeDamageForce( pInfo, GetAbsVelocity(), GetAbsOrigin() );
+ return pInfo->GetDamage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Deal the damage from the headcrab's touch attack.
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::TouchDamage( CBaseEntity *pOther )
+{
+ CTakeDamageInfo info;
+ CalcDamageInfo( &info );
+ pOther->TakeDamage( info );
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBaseHeadcrab::GatherConditions( void )
+{
+ // If we're hidden, just check to see if we should unhide
+ if ( m_bHidden )
+ {
+ // See if there's enough room for our hull to fit here. If so, unhide.
+ trace_t tr;
+ AI_TraceHull( GetAbsOrigin(), GetAbsOrigin(),GetHullMins(), GetHullMaxs(), MASK_SHOT, this, GetCollisionGroup(), &tr );
+ if ( tr.fraction == 1.0 )
+ {
+ SetCondition( COND_PROVOKED );
+ SetCondition( COND_HEADCRAB_UNHIDE );
+
+ if ( g_debug_headcrab.GetInt() == HEADCRAB_DEBUG_HIDING )
+ {
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0,255,0, true, 1.0 );
+ }
+ }
+ else if ( g_debug_headcrab.GetInt() == HEADCRAB_DEBUG_HIDING )
+ {
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255,0,0, true, 0.1 );
+ }
+
+ // Prevent baseclass thinking, so we don't respond to enemy fire, etc.
+ return;
+ }
+
+ BaseClass::GatherConditions();
+
+ if( m_lifeState == LIFE_ALIVE && GetWaterLevel() > 1 )
+ {
+ // Start Drowning!
+ SetCondition( COND_HEADCRAB_IN_WATER );
+ }
+
+ // See if I've landed on an NPC or player or something else illegal
+ ClearCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT );
+ CBaseEntity *ground = GetGroundEntity();
+ if( (GetFlags() & FL_ONGROUND) && ground && !ground->IsWorld() )
+ {
+ if ( IsHangingFromCeiling() == false )
+ {
+ if( ( ground->IsNPC() || ground->IsPlayer() ) )
+ {
+ SetCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT );
+ }
+ else if( ground->VPhysicsGetObject() && (ground->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
+ {
+ SetCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+
+ // Are we fading in after being hidden?
+ if ( !m_bHidden && (m_nRenderMode != kRenderNormal) )
+ {
+ int iNewAlpha = MIN( 255, GetRenderColor().a + 120 );
+ if ( iNewAlpha >= 255 )
+ {
+ m_nRenderMode = kRenderNormal;
+ SetRenderColorA( 0 );
+ }
+ else
+ {
+ SetRenderColorA( iNewAlpha );
+ }
+ }
+
+ //
+ // Make the crab coo a little bit in combat state.
+ //
+ if (( m_NPCState == NPC_STATE_COMBAT ) && ( random->RandomFloat( 0, 5 ) < 0.1 ))
+ {
+ IdleSound();
+ }
+
+ // Make sure we've turned off our burrow state if we're not in it
+ Activity eActivity = GetActivity();
+ if ( m_bBurrowed &&
+ ( eActivity != ACT_HEADCRAB_BURROW_IDLE ) &&
+ ( eActivity != ACT_HEADCRAB_BURROW_OUT ) &&
+ ( eActivity != ACT_HEADCRAB_BURROW_IN) )
+ {
+ DevMsg( "Headcrab failed to unburrow properly!\n" );
+ Assert( 0 );
+ SetBurrowed( false );
+ }
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Eliminates roll + pitch from the headcrab
+//-----------------------------------------------------------------------------
+#define HEADCRAB_ROLL_ELIMINATION_TIME 0.3f
+#define HEADCRAB_PITCH_ELIMINATION_TIME 0.3f
+
+//-----------------------------------------------------------------------------
+// Eliminates roll + pitch potentially in the headcrab at canister jump time
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::EliminateRollAndPitch()
+{
+ QAngle angles = GetAbsAngles();
+ angles.x = AngleNormalize( angles.x );
+ angles.z = AngleNormalize( angles.z );
+ if ( ( angles.x == 0.0f ) && ( angles.z == 0.0f ) )
+ return;
+
+ float flPitchRate = 90.0f / HEADCRAB_PITCH_ELIMINATION_TIME;
+ float flPitchDelta = flPitchRate * TICK_INTERVAL;
+ if ( fabs( angles.x ) <= flPitchDelta )
+ {
+ angles.x = 0.0f;
+ }
+ else
+ {
+ flPitchDelta *= (angles.x > 0.0f) ? -1.0f : 1.0f;
+ angles.x += flPitchDelta;
+ }
+
+ float flRollRate = 180.0f / HEADCRAB_ROLL_ELIMINATION_TIME;
+ float flRollDelta = flRollRate * TICK_INTERVAL;
+ if ( fabs( angles.z ) <= flRollDelta )
+ {
+ angles.z = 0.0f;
+ }
+ else
+ {
+ flRollDelta *= (angles.z > 0.0f) ? -1.0f : 1.0f;
+ angles.z += flRollDelta;
+ }
+
+ SetAbsAngles( angles );
+
+ SetContextThink( &CBaseHeadcrab::EliminateRollAndPitch, gpGlobals->curtime + TICK_INTERVAL, s_pPitchContext );
+}
+
+
+//-----------------------------------------------------------------------------
+// Begins the climb from the canister
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::BeginClimbFromCanister()
+{
+ Assert( GetMoveParent() );
+ // Compute a desired position or hint
+ Vector vecForward, vecActualForward;
+ AngleVectors( GetMoveParent()->GetAbsAngles(), &vecActualForward );
+ vecForward = vecActualForward;
+ vecForward.z = 0.0f;
+ VectorNormalize( vecForward );
+
+ Vector vecSearchCenter = GetAbsOrigin();
+ CAI_Hint *pHint = CAI_HintManager::FindHint( this, HINT_HEADCRAB_BURROW_POINT, 0, HEADCRAB_BURROW_POINT_SEARCH_RADIUS, &vecSearchCenter );
+
+ if( !pHint && hl2_episodic.GetBool() )
+ {
+ // Look for exit points within 10 feet.
+ pHint = CAI_HintManager::FindHint( this, HINT_HEADCRAB_EXIT_POD_POINT, 0, 120.0f, &vecSearchCenter );
+ }
+
+ if ( pHint && ( !pHint->IsLocked() ) )
+ {
+ // Claim the hint node so other headcrabs don't try to take it!
+ GrabHintNode( pHint );
+
+ // Compute relative yaw..
+ Vector vecDelta;
+ VectorSubtract( pHint->GetAbsOrigin(), vecSearchCenter, vecDelta );
+ vecDelta.z = 0.0f;
+ VectorNormalize( vecDelta );
+
+ float flAngle = DotProduct( vecDelta, vecForward );
+ if ( flAngle >= 0.707f )
+ {
+ m_nJumpFromCanisterDir = 1;
+ }
+ else
+ {
+ // Check the cross product to see if it's on the left or right.
+ // All we care about is the sign of the z component. If it's +, the hint is on the left.
+ // If it's -, then the hint is on the right.
+ float flCrossZ = vecForward.x * vecDelta.y - vecDelta.x * vecForward.y;
+ m_nJumpFromCanisterDir = ( flCrossZ > 0 ) ? 0 : 2;
+ }
+ }
+ else
+ {
+ // Choose a random direction (forward, left, or right)
+ m_nJumpFromCanisterDir = random->RandomInt( 0, 2 );
+ }
+
+ Activity act;
+ switch( m_nJumpFromCanisterDir )
+ {
+ case 0:
+ act = (Activity)ACT_HEADCRAB_CRAWL_FROM_CANISTER_LEFT;
+ break;
+
+ default:
+ case 1:
+ act = (Activity)ACT_HEADCRAB_CRAWL_FROM_CANISTER_CENTER;
+ break;
+
+ case 2:
+ act = (Activity)ACT_HEADCRAB_CRAWL_FROM_CANISTER_RIGHT;
+ break;
+ }
+
+ SetIdealActivity( act );
+}
+
+
+//-----------------------------------------------------------------------------
+// Jumps from the canister
+//-----------------------------------------------------------------------------
+#define HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_DIST 250.0f
+#define HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_COSANGLE 0.866f
+
+void CBaseHeadcrab::JumpFromCanister()
+{
+ Assert( GetMoveParent() );
+
+ Vector vecForward, vecActualForward, vecActualRight;
+ AngleVectors( GetMoveParent()->GetAbsAngles(), &vecActualForward, &vecActualRight, NULL );
+
+ switch( m_nJumpFromCanisterDir )
+ {
+ case 0:
+ VectorMultiply( vecActualRight, -1.0f, vecForward );
+ break;
+ case 1:
+ vecForward = vecActualForward;
+ break;
+ case 2:
+ vecForward = vecActualRight;
+ break;
+ }
+
+ vecForward.z = 0.0f;
+ VectorNormalize( vecForward );
+ QAngle headCrabAngles;
+ VectorAngles( vecForward, headCrabAngles );
+
+ SetActivity( ACT_RANGE_ATTACK1 );
+ StudioFrameAdvanceManual( 0.0 );
+ SetParent( NULL );
+ RemoveFlag( FL_FLY );
+ IncrementInterpolationFrame();
+
+ GetMotor()->SetIdealYaw( headCrabAngles.y );
+
+ // Check to see if the player is within jump range. If so, jump at him!
+ bool bJumpedAtEnemy = false;
+
+ // FIXME: Can't use GetEnemy() here because enemy only updates during
+ // schedules which are interruptible by COND_NEW_ENEMY or COND_LOST_ENEMY
+ CBaseEntity *pEnemy = BestEnemy();
+ if ( pEnemy )
+ {
+ Vector vecDirToEnemy;
+ VectorSubtract( pEnemy->GetAbsOrigin(), GetAbsOrigin(), vecDirToEnemy );
+ vecDirToEnemy.z = 0.0f;
+ float flDist = VectorNormalize( vecDirToEnemy );
+ if ( ( flDist < HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_DIST ) &&
+ ( DotProduct( vecDirToEnemy, vecForward ) >= HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_COSANGLE ) )
+ {
+ GrabHintNode( NULL );
+ JumpAttack( false, pEnemy->EyePosition(), false );
+ bJumpedAtEnemy = true;
+ }
+ }
+
+ if ( !bJumpedAtEnemy )
+ {
+ if ( GetHintNode() )
+ {
+ JumpToBurrowHint( GetHintNode() );
+ }
+ else
+ {
+ vecForward *= 100.0f;
+ vecForward += GetAbsOrigin();
+ JumpAttack( false, vecForward, false );
+ }
+ }
+
+ EliminateRollAndPitch();
+}
+
+#define HEADCRAB_ILLUMINATED_TIME 0.15f
+
+void CBaseHeadcrab::DropFromCeiling( void )
+{
+#ifdef HL2_EPISODIC
+ if ( HL2GameRules()->IsAlyxInDarknessMode() )
+ {
+ if ( IsHangingFromCeiling() )
+ {
+ if ( m_flIlluminatedTime == -1 )
+ {
+ m_flIlluminatedTime = gpGlobals->curtime + HEADCRAB_ILLUMINATED_TIME;
+ return;
+ }
+
+ if ( m_flIlluminatedTime <= gpGlobals->curtime )
+ {
+ if ( IsCurSchedule( SCHED_HEADCRAB_CEILING_DROP ) == false )
+ {
+ SetSchedule( SCHED_HEADCRAB_CEILING_DROP );
+
+ CBaseEntity *pPlayer = AI_GetSinglePlayer();
+
+ if ( pPlayer )
+ {
+ SetEnemy( pPlayer ); //Is this a bad thing to do?
+ UpdateEnemyMemory( pPlayer, pPlayer->GetAbsOrigin());
+ }
+ }
+ }
+ }
+ }
+#endif // HL2_EPISODIC
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Player has illuminated this NPC with the flashlight
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
+{
+ if ( flDot < 0.97387f )
+ return;
+
+ DropFromCeiling();
+}
+
+bool CBaseHeadcrab::CanBeAnEnemyOf( CBaseEntity *pEnemy )
+{
+#ifdef HL2_EPISODIC
+ if ( IsHangingFromCeiling() )
+ return false;
+#endif
+
+ return BaseClass::CanBeAnEnemyOf( pEnemy );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pTask -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL:
+ break;
+
+ case TASK_HEADCRAB_BURROW_WAIT:
+ break;
+
+ case TASK_HEADCRAB_CLIMB_FROM_CANISTER:
+ BeginClimbFromCanister();
+ break;
+
+ case TASK_HEADCRAB_JUMP_FROM_CANISTER:
+ JumpFromCanister();
+ break;
+
+ case TASK_HEADCRAB_CEILING_POSITION:
+ {
+ trace_t tr;
+ UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 512 ), NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ // SetMoveType( MOVETYPE_NONE );
+ AddFlag(FL_FLY);
+ m_bHangingFromCeiling = true;
+
+ //Don't need this anymore
+ RemoveSpawnFlags( SF_HEADCRAB_START_HANGING );
+
+ SetAbsOrigin( tr.endpos );
+
+ TaskComplete();
+ }
+ break;
+
+ case TASK_HEADCRAB_CEILING_WAIT:
+ break;
+
+ case TASK_HEADCRAB_CEILING_DETACH:
+ {
+ SetIdealActivity( (Activity)ACT_HEADCRAB_CEILING_DETACH );
+ }
+ break;
+
+ case TASK_HEADCRAB_CEILING_FALL:
+ {
+ SetIdealActivity( (Activity)ACT_HEADCRAB_CEILING_FALL );
+ }
+ break;
+
+ case TASK_HEADCRAB_CEILING_LAND:
+ {
+ SetIdealActivity( (Activity)ACT_HEADCRAB_CEILING_LAND );
+ }
+ break;
+
+ case TASK_HEADCRAB_HARASS_HOP:
+ {
+ // Just pop up into the air like you're trying to get at the
+ // enemy, even though it's known you can't reach them.
+ if ( GetEnemy() )
+ {
+ Vector forward, up;
+
+ GetVectors( &forward, NULL, &up );
+
+ m_vecCommittedJumpPos = GetAbsOrigin();
+ m_vecCommittedJumpPos += up * random->RandomFloat( 80, 150 );
+ m_vecCommittedJumpPos += forward * random->RandomFloat( 32, 80 );
+
+ m_bCommittedToJump = true;
+
+ SetIdealActivity( ACT_RANGE_ATTACK1 );
+ }
+ else
+ {
+ TaskFail( "No enemy" );
+ }
+ }
+ break;
+
+ case TASK_HEADCRAB_HOP_OFF_NPC:
+ {
+ CBaseEntity *ground = GetGroundEntity();
+ if( ground )
+ {
+ // If jumping off of a physics object that the player is holding, create a
+ // solver to prevent the headcrab from colliding with that object for a
+ // short time.
+ if( ground && ground->VPhysicsGetObject() )
+ {
+ if( ground->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
+ {
+ NPCPhysics_CreateSolver( this, ground, true, 0.5 );
+ }
+ }
+
+
+ Vector vecJumpDir;
+
+ // Jump in some random direction. This way if the person I'm standing on is
+ // against a wall, I'll eventually get away.
+
+ 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 );
+
+ if( HasHeadroom() )
+ {
+ // Bump up
+ MoveOrigin( Vector( 0, 0, 1 ) );
+ }
+
+ SetAbsVelocity( vecJumpDir * 200 + Vector( 0, 0, 200 ) );
+ }
+ else
+ {
+ // *shrug* I guess they're gone now. Or dead.
+ TaskComplete();
+ }
+ }
+ break;
+
+ case TASK_HEADCRAB_DROWN:
+ {
+ // Set the gravity really low here! Sink slowly
+ SetGravity( UTIL_ScaleForGravity( 80 ) );
+ m_flTimeDrown = gpGlobals->curtime + 4;
+ break;
+ }
+
+ case TASK_RANGE_ATTACK1:
+ {
+#ifdef WEDGE_FIX_THIS
+ CPASAttenuationFilter filter( this, ATTN_IDLE );
+ EmitSound( filter, entindex(), CHAN_WEAPON, pAttackSounds[0], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );
+#endif
+ SetIdealActivity( ACT_RANGE_ATTACK1 );
+ break;
+ }
+
+ case TASK_HEADCRAB_UNHIDE:
+ {
+ m_bHidden = false;
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ RemoveEffects( EF_NODRAW );
+
+ TaskComplete();
+ break;
+ }
+
+ case TASK_HEADCRAB_CHECK_FOR_UNBURROW:
+ {
+ if ( ValidBurrowPoint( GetAbsOrigin() ) )
+ {
+ m_spawnflags &= ~SF_NPC_GAG;
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_HEADCRAB_FIND_BURROW_IN_POINT:
+ {
+ if ( FindBurrow( GetAbsOrigin(), pTask->flTaskData, true ) == false )
+ {
+ TaskFail( "TASK_HEADCRAB_FIND_BURROW_IN_POINT: Unable to find burrow in position\n" );
+ }
+ else
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_HEADCRAB_BURROW:
+ {
+ Burrow();
+ TaskComplete();
+ break;
+ }
+
+ case TASK_HEADCRAB_UNBURROW:
+ {
+ Unburrow();
+ TaskComplete();
+ break;
+ }
+
+ default:
+ {
+ BaseClass::StartTask( pTask );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: For innate melee attack
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+float CBaseHeadcrab::InnateRange1MinRange( void )
+{
+ return HEADCRAB_MIN_JUMP_DIST;
+}
+
+float CBaseHeadcrab::InnateRange1MaxRange( void )
+{
+ return HEADCRAB_MAX_JUMP_DIST;
+}
+
+int CBaseHeadcrab::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( gpGlobals->curtime < m_flNextAttack )
+ return 0;
+
+ if ( ( GetFlags() & FL_ONGROUND ) == false )
+ return 0;
+
+ // When we're burrowed ignore facing, because when we unburrow we'll cheat and face our enemy.
+ if ( !m_bBurrowed && ( flDot < 0.65 ) )
+ return COND_NOT_FACING_ATTACK;
+
+ // This code stops lots of headcrabs swarming you and blocking you
+ // whilst jumping up and down in your face over and over. It forces
+ // them to back up a bit. If this causes problems, consider using it
+ // for the fast headcrabs only, rather than just removing it.(sjb)
+ if ( flDist < HEADCRAB_MIN_JUMP_DIST )
+ return COND_TOO_CLOSE_TO_ATTACK;
+
+ if ( flDist > HEADCRAB_MAX_JUMP_DIST )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ // Make sure the way is clear!
+ CBaseEntity *pEnemy = GetEnemy();
+ if( pEnemy )
+ {
+ bool bEnemyIsBullseye = ( dynamic_cast<CNPC_Bullseye *>(pEnemy) != NULL );
+
+ trace_t tr;
+ AI_TraceLine( EyePosition(), pEnemy->EyePosition(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.m_pEnt != GetEnemy() )
+ {
+ if ( !bEnemyIsBullseye || tr.m_pEnt != NULL )
+ return COND_NONE;
+ }
+
+ if( GetEnemy()->EyePosition().z - 36.0f > GetAbsOrigin().z )
+ {
+ // Only run this test if trying to jump at a player who is higher up than me, else this
+ // code will always prevent a headcrab from jumping down at an enemy, and sometimes prevent it
+ // jumping just slightly up at an enemy.
+ Vector vStartHullTrace = GetAbsOrigin();
+ vStartHullTrace.z += 1.0;
+
+ Vector vEndHullTrace = GetEnemy()->EyePosition() - GetAbsOrigin();
+ vEndHullTrace.NormalizeInPlace();
+ vEndHullTrace *= 8.0;
+ vEndHullTrace += GetAbsOrigin();
+
+ AI_TraceHull( vStartHullTrace, vEndHullTrace,GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
+
+ if ( tr.m_pEnt != NULL && tr.m_pEnt != GetEnemy() )
+ {
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+ }
+ }
+
+ return COND_CAN_RANGE_ATTACK1;
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose: Override to do headcrab specific gibs
+// Output :
+//------------------------------------------------------------------------------
+bool CBaseHeadcrab::CorpseGib( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_HeadCrab.Gib" );
+
+ return BaseClass::CorpseGib( info );
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+// Input :
+//------------------------------------------------------------------------------
+void CBaseHeadcrab::Touch( CBaseEntity *pOther )
+{
+ // If someone has smacked me into a wall then gib!
+ if (m_NPCState == NPC_STATE_DEAD)
+ {
+ if (GetAbsVelocity().Length() > 250)
+ {
+ trace_t tr;
+ Vector vecDir = GetAbsVelocity();
+ VectorNormalize(vecDir);
+ AI_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vecDir * 100,
+ MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+ float dotPr = DotProduct(vecDir,tr.plane.normal);
+ if ((tr.fraction != 1.0) &&
+ (dotPr < -0.8) )
+ {
+ CTakeDamageInfo info( GetWorldEntity(), GetWorldEntity(), 100.0f, DMG_CRUSH );
+
+ info.SetDamagePosition( tr.endpos );
+
+ Event_Gibbed( info );
+ }
+
+ }
+ }
+ BaseClass::Touch(pOther);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pevInflictor -
+// pevAttacker -
+// flDamage -
+// bitsDamageType -
+// Output :
+//-----------------------------------------------------------------------------
+int CBaseHeadcrab::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ //
+ // Don't take any acid damage.
+ //
+ if ( info.GetDamageType() & DMG_ACID )
+ {
+ info.SetDamage( 0 );
+ }
+
+ //
+ // Certain death from melee bludgeon weapons!
+ //
+ if ( info.GetDamageType() & DMG_CLUB )
+ {
+ info.SetDamage( m_iHealth );
+ }
+
+ if( info.GetDamageType() & DMG_BLAST )
+ {
+ if( random->RandomInt( 0 , 1 ) == 0 )
+ {
+ // Catch on fire randomly if damaged in a blast.
+ Ignite( 30 );
+ }
+ }
+
+ if( info.GetDamageType() & DMG_BURN )
+ {
+ // Slow down burn damage so that headcrabs live longer while on fire.
+ info.ScaleDamage( 0.25 );
+
+#define HEADCRAB_SCORCH_RATE 5
+#define HEADCRAB_SCORCH_FLOOR 30
+
+ if( IsOnFire() )
+ {
+ Scorch( HEADCRAB_SCORCH_RATE, HEADCRAB_SCORCH_FLOOR );
+
+ if( m_iHealth <= 1 && (entindex() % 2) )
+ {
+ // Some headcrabs leap at you with their dying breath
+ if( !IsCurSchedule( SCHED_HEADCRAB_RANGE_ATTACK1 ) && !IsRunningDynamicInteraction() )
+ {
+ SetSchedule( SCHED_HEADCRAB_RANGE_ATTACK1 );
+ }
+ }
+ }
+
+ Ignite( 30 );
+ }
+
+ return CAI_BaseNPC::OnTakeDamage_Alive( info );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::ClampRagdollForce( const Vector &vecForceIn, Vector *vecForceOut )
+{
+ // Assumes the headcrab mass is 5kg (100 feet per second)
+ float MAX_HEADCRAB_RAGDOLL_SPEED = 100.0f * 12.0f * 5.0f;
+
+ Vector vecClampedForce;
+ BaseClass::ClampRagdollForce( vecForceIn, &vecClampedForce );
+
+ // Copy the force to vecForceOut, in case we don't change it.
+ *vecForceOut = vecClampedForce;
+
+ float speed = VectorNormalize( vecClampedForce );
+ if( speed > MAX_HEADCRAB_RAGDOLL_SPEED )
+ {
+ // Don't let the ragdoll go as fast as it was going to.
+ vecClampedForce *= MAX_HEADCRAB_RAGDOLL_SPEED;
+ *vecForceOut = vecClampedForce;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::Event_Killed( const CTakeDamageInfo &info )
+{
+ // Create a little decal underneath the headcrab
+ // This type of damage combination happens from dynamic scripted sequences
+ if ( info.GetDamageType() & (DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE) )
+ {
+ trace_t tr;
+ AI_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ UTIL_DecalTrace( &tr, "YellowBlood" );
+ }
+
+ BaseClass::Event_Killed( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : Type -
+//-----------------------------------------------------------------------------
+int CBaseHeadcrab::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_FALL_TO_GROUND:
+ return SCHED_HEADCRAB_FALL_TO_GROUND;
+
+ case SCHED_WAKE_ANGRY:
+ {
+ if ( HaveSequenceForActivity((Activity)ACT_HEADCRAB_THREAT_DISPLAY) )
+ return SCHED_HEADCRAB_WAKE_ANGRY;
+ else
+ return SCHED_HEADCRAB_WAKE_ANGRY_NO_DISPLAY;
+ }
+
+ case SCHED_RANGE_ATTACK1:
+ return SCHED_HEADCRAB_RANGE_ATTACK1;
+
+ case SCHED_FAIL_TAKE_COVER:
+ return SCHED_ALERT_FACE;
+
+ case SCHED_CHASE_ENEMY_FAILED:
+ {
+ if( !GetEnemy() )
+ break;
+
+ if( !HasCondition( COND_SEE_ENEMY ) )
+ break;
+
+ float flZDiff;
+ flZDiff = GetEnemy()->GetAbsOrigin().z - GetAbsOrigin().z;
+
+ // Make sure the enemy isn't so high above me that this would look silly.
+ if( flZDiff < 128.0f || flZDiff > 512.0f )
+ return SCHED_COMBAT_PATROL;
+
+ float flDist;
+ flDist = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length2D();
+
+ // Maybe a patrol will bring me closer.
+ if( flDist > 384.0f )
+ {
+ return SCHED_COMBAT_PATROL;
+ }
+
+ return SCHED_HEADCRAB_HARASS_ENEMY;
+ }
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseHeadcrab::SelectSchedule( void )
+{
+ if ( m_bCrawlFromCanister )
+ {
+ m_bCrawlFromCanister = false;
+ return SCHED_HEADCRAB_CRAWL_FROM_CANISTER;
+ }
+
+ // If we're hidden or waiting until seen, don't do much at all
+ if ( m_bHidden || HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN) )
+ {
+ if( HasCondition( COND_HEADCRAB_UNHIDE ) )
+ {
+ // We've decided to unhide
+ return SCHED_HEADCRAB_UNHIDE;
+ }
+
+ return m_bBurrowed ? ( int )SCHED_HEADCRAB_BURROW_WAIT : ( int )SCHED_IDLE_STAND;
+ }
+
+ if ( GetSpawnFlags() & SF_HEADCRAB_START_HANGING && IsHangingFromCeiling() == false )
+ {
+ return SCHED_HEADCRAB_CEILING_WAIT;
+ }
+
+ if ( IsHangingFromCeiling() )
+ {
+ bool bIsAlyxInDarknessMode = false;
+#ifdef HL2_EPISODIC
+ bIsAlyxInDarknessMode = HL2GameRules()->IsAlyxInDarknessMode();
+#endif // HL2_EPISODIC
+
+ if ( bIsAlyxInDarknessMode == false && ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_NEW_ENEMY ) ) )
+ return SCHED_HEADCRAB_CEILING_DROP;
+
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ return SCHED_HEADCRAB_CEILING_DROP;
+
+ return SCHED_HEADCRAB_CEILING_WAIT;
+ }
+
+ if ( m_bBurrowed )
+ {
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ return SCHED_HEADCRAB_BURROW_OUT;
+
+ return SCHED_HEADCRAB_BURROW_WAIT;
+ }
+
+ if( HasCondition( COND_HEADCRAB_IN_WATER ) )
+ {
+ // No matter what, drown in water
+ return SCHED_HEADCRAB_DROWN;
+ }
+
+ if( HasCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT ) )
+ {
+ // You're on an NPC's head. Get off.
+ return SCHED_HEADCRAB_HOP_RANDOMLY;
+ }
+
+ if ( HasCondition( COND_HEADCRAB_BARNACLED ) )
+ {
+ // Caught by a barnacle!
+ return SCHED_HEADCRAB_BARNACLED;
+ }
+
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_ALERT:
+ {
+ if (HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ))
+ {
+ if ( fabs( GetMotor()->DeltaIdealYaw() ) < ( 1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction
+ {
+ return SCHED_TAKE_COVER_FROM_ORIGIN;
+ }
+ else if ( SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 )
+ {
+ m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1, 3 );
+ return SCHED_SMALL_FLINCH;
+ }
+ }
+ else if (HasCondition( COND_HEAR_DANGER ) ||
+ HasCondition( COND_HEAR_PLAYER ) ||
+ HasCondition( COND_HEAR_WORLD ) ||
+ HasCondition( COND_HEAR_COMBAT ))
+ {
+ return SCHED_ALERT_FACE_BESTSOUND;
+ }
+ else
+ {
+ return SCHED_PATROL_WALK;
+ }
+ break;
+ }
+ }
+
+ if ( HasCondition( COND_FLOATING_OFF_GROUND ) )
+ {
+ SetGravity( 1.0 );
+ SetGroundEntity( NULL );
+ return SCHED_FALL_TO_GROUND;
+ }
+
+ if ( GetHintNode() && GetHintNode()->HintType() == HINT_HEADCRAB_BURROW_POINT )
+ {
+ // Only burrow if we're not within leap attack distance of our enemy.
+ if ( ( GetEnemy() == NULL ) || ( ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length() > HEADCRAB_MAX_JUMP_DIST ) )
+ {
+ return SCHED_HEADCRAB_RUN_TO_SPECIFIC_BURROW;
+ }
+ else
+ {
+ // Forget about burrowing, we've got folks to leap at!
+ GrabHintNode( NULL );
+ }
+ }
+
+ int nSchedule = BaseClass::SelectSchedule();
+ if ( nSchedule == SCHED_SMALL_FLINCH )
+ {
+ m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1, 3 );
+ }
+
+ return nSchedule;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CBaseHeadcrab::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
+{
+ if ( failedSchedule == SCHED_BACK_AWAY_FROM_ENEMY && failedTask == TASK_FIND_BACKAWAY_FROM_SAVEPOSITION )
+ {
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ return SCHED_RANGE_ATTACK1;
+ }
+ }
+
+ if ( failedSchedule == SCHED_BACK_AWAY_FROM_ENEMY || failedSchedule == SCHED_PATROL_WALK || failedSchedule == SCHED_COMBAT_PATROL )
+ {
+ if( !IsFirmlyOnGround() )
+ {
+ return SCHED_HEADCRAB_HOP_RANDOMLY;
+ }
+ }
+
+ return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+// &vecDir -
+// *ptr -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo newInfo = info;
+
+ // Ignore if we're in a dynamic scripted sequence
+ if ( info.GetDamageType() & DMG_PHYSGUN && !IsRunningDynamicInteraction() )
+ {
+ Vector puntDir = ( info.GetDamageForce() * 1000.0f );
+
+ newInfo.SetDamage( m_iMaxHealth / 3.0f );
+
+ if( info.GetDamage() >= GetHealth() )
+ {
+ // This blow will be fatal, so scale the damage force
+ // (it's a unit vector) so that the ragdoll will be
+ // affected.
+ newInfo.SetDamageForce( info.GetDamageForce() * 3000.0f );
+ }
+
+ PainSound( newInfo );
+ SetGroundEntity( NULL );
+ ApplyAbsVelocityImpulse( puntDir );
+ }
+
+ BaseClass::TraceAttack( newInfo, vecDir, ptr, pAccumulator );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
+{
+ // Can't start on fire if we're burrowed
+ if ( m_bBurrowed )
+ return;
+
+ bool bWasOnFire = IsOnFire();
+
+#ifdef HL2_EPISODIC
+ if( GetHealth() > flFlameLifetime )
+ {
+ // Add some burn time to very healthy headcrabs to fix a bug where
+ // black headcrabs would sometimes spontaneously extinguish (and survive)
+ flFlameLifetime += 10.0f;
+ }
+#endif// HL2_EPISODIC
+
+ BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
+
+ if( !bWasOnFire )
+ {
+#ifdef HL2_EPISODIC
+ if ( HL2GameRules()->IsAlyxInDarknessMode() == true )
+ {
+ GetEffectEntity()->AddEffects( EF_DIMLIGHT );
+ }
+#endif // HL2_EPISODIC
+
+ // For the poison headcrab, who runs around when ignited
+ SetActivity( TranslateActivity(GetIdealActivity()) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: This is a generic function (to be implemented by sub-classes) to
+// handle specific interactions between different types of characters
+// (For example the barnacle grabbing an NPC)
+// Input : Constant for the type of interaction
+// Output : true - if sub-class has a response for the interaction
+// false - if sub-class has no response
+//-----------------------------------------------------------------------------
+bool CBaseHeadcrab::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
+{
+ if (interactionType == g_interactionBarnacleVictimDangle)
+ {
+ // Die instantly
+ return false;
+ }
+ else if (interactionType == g_interactionVortigauntStomp)
+ {
+ SetIdealState( NPC_STATE_PRONE );
+ return true;
+ }
+ else if (interactionType == g_interactionVortigauntStompFail)
+ {
+ SetIdealState( NPC_STATE_COMBAT );
+ return true;
+ }
+ else if (interactionType == g_interactionVortigauntStompHit)
+ {
+ // Gib the existing guy, but only with legs and guts
+ m_nGibCount = HEADCRAB_LEGS_GIB_COUNT;
+ OnTakeDamage ( CTakeDamageInfo( sourceEnt, sourceEnt, m_iHealth, DMG_CRUSH|DMG_ALWAYSGIB ) );
+
+ // Create dead headcrab in its place
+ CBaseHeadcrab *pEntity = (CBaseHeadcrab*) CreateEntityByName( "npc_headcrab" );
+ pEntity->Spawn();
+ pEntity->SetLocalOrigin( GetLocalOrigin() );
+ pEntity->SetLocalAngles( GetLocalAngles() );
+ pEntity->m_NPCState = NPC_STATE_DEAD;
+ return true;
+ }
+ else if ( interactionType == g_interactionVortigauntKick
+ /* || (interactionType == g_interactionBullsquidThrow) */
+ )
+ {
+ SetIdealState( NPC_STATE_PRONE );
+
+ if( HasHeadroom() )
+ {
+ MoveOrigin( Vector( 0, 0, 1 ) );
+ }
+
+ Vector vHitDir = GetLocalOrigin() - sourceEnt->GetLocalOrigin();
+ VectorNormalize(vHitDir);
+
+ CTakeDamageInfo info( sourceEnt, sourceEnt, m_iHealth+1, DMG_CLUB );
+ CalculateMeleeDamageForce( &info, vHitDir, GetAbsOrigin() );
+
+ TakeDamage( info );
+
+ return true;
+ }
+
+ return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseHeadcrab::FValidateHintType( CAI_Hint *pHint )
+{
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &origin -
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::ClearBurrowPoint( const Vector &origin )
+{
+ CBaseEntity *pEntity = NULL;
+ float flDist;
+ Vector vecSpot, vecCenter, vecForce;
+
+ //Cause a ruckus
+ UTIL_ScreenShake( origin, 1.0f, 80.0f, 1.0f, 256.0f, SHAKE_START );
+
+ //Iterate on all entities in the vicinity.
+ for ( CEntitySphereQuery sphere( origin, 128 ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
+ {
+ 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 );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 CBaseHeadcrab::ValidBurrowPoint( const Vector &point )
+{
+ trace_t tr;
+
+ AI_TraceHull( point, point+Vector(0,0,1), GetHullMins(), GetHullMaxs(),
+ MASK_NPCSOLID, this, GetCollisionGroup(), &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:
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::GrabHintNode( CAI_Hint *pHint )
+{
+ // Free up the node for use
+ ClearHintNode();
+
+ if ( pHint )
+ {
+ SetHintNode( pHint );
+ pHint->Lock( this );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds a point where the headcrab can burrow underground.
+// Input : distance - radius to search for burrow spot in
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseHeadcrab::FindBurrow( const Vector &origin, float distance, bool excludeNear )
+{
+ // Attempt to find a burrowing point
+ CHintCriteria hintCriteria;
+
+ hintCriteria.SetHintType( HINT_HEADCRAB_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;
+
+ GrabHintNode( pHint );
+
+ // Setup our path and attempt to run there
+ Vector vHintPos;
+ pHint->GetPosition( this, &vHintPos );
+
+ AI_NavGoal_t goal( vHintPos, ACT_RUN );
+
+ return GetNavigator()->SetGoal( goal );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::Burrow( void )
+{
+ // Stop us from taking damage and being solid
+ m_spawnflags |= SF_NPC_GAG;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::Unburrow( void )
+{
+ // 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);
+
+ GetMotor()->SetIdealYaw( dir );
+
+ QAngle angles = GetLocalAngles();
+ angles[YAW] = UTIL_VecToYaw( dir );
+ SetLocalAngles( angles );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tells the headcrab to unburrow as soon the space is clear.
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::InputUnburrow( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ SetSchedule( SCHED_HEADCRAB_WAIT_FOR_CLEAR_UNBURROW );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tells the headcrab to run to a nearby burrow point and burrow.
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::InputBurrow( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ SetSchedule( SCHED_HEADCRAB_RUN_TO_BURROW_IN );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tells the headcrab to burrow right where he is.
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::InputBurrowImmediate( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ SetSchedule( SCHED_HEADCRAB_BURROW_IN );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::InputStartHangingFromCeiling( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ SetSchedule( SCHED_HEADCRAB_CEILING_WAIT );
+ m_flIlluminatedTime = -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::InputDropFromCeiling( inputdata_t &inputdata )
+{
+ if ( IsAlive() == false )
+ return;
+
+ if ( IsHangingFromCeiling() == false )
+ return;
+
+ SetSchedule( SCHED_HEADCRAB_CEILING_DROP );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::CreateDust( bool placeDecal )
+{
+ trace_t tr;
+ AI_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0f )
+ {
+ const surfacedata_t *pdata = physprops->GetSurfaceData( tr.surface.surfaceProps );
+
+ if ( ( (char) pdata->game.material == CHAR_TEX_CONCRETE ) || ( (char) pdata->game.material == CHAR_TEX_DIRT ) )
+ {
+ UTIL_CreateAntlionDust( tr.endpos + Vector(0, 0, 24), GetLocalAngles() );
+
+ //CEffectData data;
+ //data.m_vOrigin = GetAbsOrigin();
+ //data.m_vNormal = tr.plane.normal;
+ //DispatchEffect( "headcrabdust", data );
+
+ if ( placeDecal )
+ {
+ UTIL_DecalTrace( &tr, "Headcrab.Unburrow" );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHeadcrab::Precache( void )
+{
+ PrecacheModel( "models/headcrabclassic.mdl" );
+
+ PrecacheScriptSound( "NPC_HeadCrab.Gib" );
+ PrecacheScriptSound( "NPC_HeadCrab.Idle" );
+ PrecacheScriptSound( "NPC_HeadCrab.Alert" );
+ PrecacheScriptSound( "NPC_HeadCrab.Pain" );
+ PrecacheScriptSound( "NPC_HeadCrab.Die" );
+ PrecacheScriptSound( "NPC_HeadCrab.Attack" );
+ PrecacheScriptSound( "NPC_HeadCrab.Bite" );
+ PrecacheScriptSound( "NPC_Headcrab.BurrowIn" );
+ PrecacheScriptSound( "NPC_Headcrab.BurrowOut" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHeadcrab::Spawn( void )
+{
+ Precache();
+ SetModel( "models/headcrabclassic.mdl" );
+
+ BaseClass::Spawn();
+
+ m_iHealth = sk_headcrab_health.GetFloat();
+ m_flBurrowTime = 0.0f;
+ m_bCrawlFromCanister = false;
+ m_bMidJump = false;
+
+ NPCInit();
+ HeadcrabInit();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Activity CHeadcrab::NPC_TranslateActivity( Activity eNewActivity )
+{
+ if ( eNewActivity == ACT_WALK )
+ return ACT_RUN;
+
+ return BaseClass::NPC_TranslateActivity( eNewActivity );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHeadcrab::IdleSound( void )
+{
+ EmitSound( "NPC_HeadCrab.Idle" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHeadcrab::AlertSound( void )
+{
+ EmitSound( "NPC_HeadCrab.Alert" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHeadcrab::PainSound( const CTakeDamageInfo &info )
+{
+ if( IsOnFire() && random->RandomInt( 0, HEADCRAB_BURN_SOUND_FREQUENCY ) > 0 )
+ {
+ // Don't squeak every think when burning.
+ return;
+ }
+
+ EmitSound( "NPC_HeadCrab.Pain" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHeadcrab::DeathSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_HeadCrab.Die" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHeadcrab::TelegraphSound( void )
+{
+ //FIXME: Need a real one
+ EmitSound( "NPC_HeadCrab.Alert" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHeadcrab::AttackSound( void )
+{
+ EmitSound( "NPC_Headcrab.Attack" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHeadcrab::BiteSound( void )
+{
+ EmitSound( "NPC_HeadCrab.Bite" );
+}
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CFastHeadcrab )
+
+ DEFINE_FIELD( m_iRunMode, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flRealGroundSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flSlowRunTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flPauseTime, FIELD_TIME ),
+ DEFINE_FIELD( m_vecJumpVel, FIELD_VECTOR ),
+
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFastHeadcrab::Precache( void )
+{
+ PrecacheModel( "models/headcrab.mdl" );
+
+ PrecacheScriptSound( "NPC_FastHeadcrab.Idle" );
+ PrecacheScriptSound( "NPC_FastHeadcrab.Alert" );
+ PrecacheScriptSound( "NPC_FastHeadcrab.Pain" );
+ PrecacheScriptSound( "NPC_FastHeadcrab.Die" );
+ PrecacheScriptSound( "NPC_FastHeadcrab.Bite" );
+ PrecacheScriptSound( "NPC_FastHeadcrab.Attack" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFastHeadcrab::Spawn( void )
+{
+ Precache();
+ SetModel( "models/headcrab.mdl" );
+
+ BaseClass::Spawn();
+
+ m_iHealth = sk_headcrab_health.GetFloat();
+
+ m_iRunMode = HEADCRAB_RUNMODE_IDLE;
+ m_flPauseTime = 999999;
+
+ NPCInit();
+ HeadcrabInit();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFastHeadcrab::IdleSound( void )
+{
+ EmitSound( "NPC_FastHeadcrab.Idle" );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFastHeadcrab::AlertSound( void )
+{
+ EmitSound( "NPC_FastHeadcrab.Alert" );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFastHeadcrab::PainSound( const CTakeDamageInfo &info )
+{
+ if( IsOnFire() && random->RandomInt( 0, HEADCRAB_BURN_SOUND_FREQUENCY ) > 0 )
+ {
+ // Don't squeak every think when burning.
+ return;
+ }
+
+ EmitSound( "NPC_FastHeadcrab.Pain" );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFastHeadcrab::DeathSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_FastHeadcrab.Die" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFastHeadcrab::PrescheduleThink( void )
+{
+#if 1 // #IF 0 this to stop the accelrating/decelerating movement.
+#define HEADCRAB_ACCELERATION 0.1
+ if( IsAlive() && GetNavigator()->IsGoalActive() )
+ {
+ switch( m_iRunMode )
+ {
+ case HEADCRAB_RUNMODE_IDLE:
+ if ( GetActivity() == ACT_RUN )
+ {
+ m_flRealGroundSpeed = m_flGroundSpeed;
+ m_iRunMode = HEADCRAB_RUNMODE_ACCELERATE;
+ m_flPlaybackRate = HEADCRAB_RUN_MINSPEED;
+ }
+ break;
+
+ case HEADCRAB_RUNMODE_FULLSPEED:
+ if( gpGlobals->curtime > m_flSlowRunTime )
+ {
+ m_iRunMode = HEADCRAB_RUNMODE_DECELERATE;
+ }
+ break;
+
+ case HEADCRAB_RUNMODE_ACCELERATE:
+ if( m_flPlaybackRate < HEADCRAB_RUN_MAXSPEED )
+ {
+ m_flPlaybackRate += HEADCRAB_ACCELERATION;
+ }
+
+ if( m_flPlaybackRate >= HEADCRAB_RUN_MAXSPEED )
+ {
+ m_flPlaybackRate = HEADCRAB_RUN_MAXSPEED;
+ m_iRunMode = HEADCRAB_RUNMODE_FULLSPEED;
+
+ m_flSlowRunTime = gpGlobals->curtime + random->RandomFloat( 0.1, 1.0 );
+ }
+ break;
+
+ case HEADCRAB_RUNMODE_DECELERATE:
+ m_flPlaybackRate -= HEADCRAB_ACCELERATION;
+
+ if( m_flPlaybackRate <= HEADCRAB_RUN_MINSPEED )
+ {
+ m_flPlaybackRate = HEADCRAB_RUN_MINSPEED;
+
+ // Now stop the crab.
+ m_iRunMode = HEADCRAB_RUNMODE_PAUSE;
+ SetActivity( ACT_IDLE );
+ GetNavigator()->SetMovementActivity(ACT_IDLE);
+ m_flPauseTime = gpGlobals->curtime + random->RandomFloat( 0.2, 0.5 );
+ m_flRealGroundSpeed = 0.0;
+ }
+ break;
+
+ case HEADCRAB_RUNMODE_PAUSE:
+ {
+ if( gpGlobals->curtime > m_flPauseTime )
+ {
+ m_iRunMode = HEADCRAB_RUNMODE_IDLE;
+ SetActivity( ACT_RUN );
+ GetNavigator()->SetMovementActivity(ACT_RUN);
+ m_flPauseTime = gpGlobals->curtime - 1;
+ m_flRealGroundSpeed = m_flGroundSpeed;
+ }
+ }
+ break;
+
+ default:
+ Warning( "BIG TIME HEADCRAB ERROR\n" );
+ break;
+ }
+
+ m_flGroundSpeed = m_flRealGroundSpeed * m_flPlaybackRate;
+ }
+ else
+ {
+ m_flPauseTime = gpGlobals->curtime - 1;
+ }
+#endif
+
+ BaseClass::PrescheduleThink();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : scheduleType -
+//-----------------------------------------------------------------------------
+int CFastHeadcrab::SelectSchedule( void )
+{
+ if ( HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN) )
+ {
+ return SCHED_IDLE_STAND;
+ }
+
+ if ( HasCondition(COND_CAN_RANGE_ATTACK1) && IsHangingFromCeiling() == false )
+ {
+ if ( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
+ return SCHED_RANGE_ATTACK1;
+ ClearCondition(COND_CAN_RANGE_ATTACK1);
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : scheduleType -
+//-----------------------------------------------------------------------------
+int CFastHeadcrab::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_IDLE_STAND:
+ return SCHED_PATROL_WALK;
+ break;
+
+ case SCHED_RANGE_ATTACK1:
+ return SCHED_FAST_HEADCRAB_RANGE_ATTACK1;
+ break;
+
+ case SCHED_CHASE_ENEMY:
+ if ( !OccupyStrategySlotRange( SQUAD_SLOT_ENGAGE1, SQUAD_SLOT_ENGAGE4 ) )
+ return SCHED_PATROL_WALK;
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CFastHeadcrab::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_RANGE_ATTACK1:
+ case TASK_RANGE_ATTACK2:
+
+ if ( GetEnemy() )
+ // Fast headcrab faces the target in flight.
+ GetMotor()->SetIdealYawAndUpdate( GetEnemy()->GetAbsOrigin() - GetAbsOrigin(), AI_KEEP_YAW_SPEED );
+
+ // Call back up into base headcrab for collision.
+ BaseClass::RunTask( pTask );
+ break;
+
+ case TASK_HEADCRAB_HOP_ASIDE:
+ if ( GetEnemy() )
+ GetMotor()->SetIdealYawAndUpdate( GetEnemy()->GetAbsOrigin() - GetAbsOrigin(), AI_KEEP_YAW_SPEED );
+
+ if( GetFlags() & FL_ONGROUND )
+ {
+ SetGravity(1.0);
+ SetMoveType( MOVETYPE_STEP );
+
+ if( GetEnemy() && ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length() > HEADCRAB_MAX_JUMP_DIST )
+ {
+ TaskFail( "");
+ }
+
+ TaskComplete();
+ }
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pTask -
+//-----------------------------------------------------------------------------
+void CFastHeadcrab::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_HEADCRAB_HOP_ASIDE:
+ {
+ Vector vecDir, vecForward, vecRight;
+ bool fJumpIsLeft;
+ trace_t tr;
+
+ GetVectors( &vecForward, &vecRight, NULL );
+
+ fJumpIsLeft = false;
+ if( random->RandomInt( 0, 100 ) < 50 )
+ {
+ fJumpIsLeft = true;
+ vecRight.Negate();
+ }
+
+ vecDir = ( vecRight + ( vecForward * 2 ) );
+ VectorNormalize( vecDir );
+ vecDir *= 150.0;
+
+ // This could be a problem. Since I'm adjusting the headcrab's gravity for flight, this check actually
+ // checks farther ahead than the crab will actually jump. (sjb)
+ AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + vecDir,GetHullMins(), GetHullMaxs(), MASK_SHOT, this, GetCollisionGroup(), &tr );
+
+ //NDebugOverlay::Line( tr.startpos, tr.endpos, 0, 255, 0, false, 1.0 );
+
+ if( tr.fraction == 1.0 )
+ {
+ AIMoveTrace_t moveTrace;
+ GetMoveProbe()->MoveLimit( NAV_JUMP, GetAbsOrigin(), tr.endpos, MASK_NPCSOLID, GetEnemy(), &moveTrace );
+
+ // FIXME: Where should this happen?
+ m_vecJumpVel = moveTrace.vJumpVelocity;
+
+ if( !IsMoveBlocked( moveTrace ) )
+ {
+ SetAbsVelocity( m_vecJumpVel );// + 0.5f * Vector(0,0,GetCurrentGravity()) * flInterval;
+ SetGravity( UTIL_ScaleForGravity( 1600 ) );
+ SetGroundEntity( NULL );
+ SetNavType( NAV_JUMP );
+
+ if( fJumpIsLeft )
+ {
+ SetIdealActivity( (Activity)ACT_HEADCRAB_HOP_LEFT );
+ GetNavigator()->SetMovementActivity( (Activity) ACT_HEADCRAB_HOP_LEFT );
+ }
+ else
+ {
+ SetIdealActivity( (Activity)ACT_HEADCRAB_HOP_RIGHT );
+ GetNavigator()->SetMovementActivity( (Activity) ACT_HEADCRAB_HOP_RIGHT );
+ }
+ }
+ else
+ {
+ // Can't jump, just fall through.
+ TaskComplete();
+ }
+ }
+ else
+ {
+ // Can't jump, just fall through.
+ TaskComplete();
+ }
+ }
+ break;
+
+ default:
+ {
+ BaseClass::StartTask( pTask );
+ }
+ }
+}
+
+
+LINK_ENTITY_TO_CLASS( npc_headcrab, CHeadcrab );
+LINK_ENTITY_TO_CLASS( npc_headcrab_fast, CFastHeadcrab );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Make the sound of this headcrab chomping a target.
+// Input :
+//-----------------------------------------------------------------------------
+void CFastHeadcrab::BiteSound( void )
+{
+ EmitSound( "NPC_FastHeadcrab.Bite" );
+}
+
+void CFastHeadcrab::AttackSound( void )
+{
+ EmitSound( "NPC_FastHeadcrab.Attack" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+float CHeadcrab::MaxYawSpeed ( void )
+{
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ return 30;
+
+ case ACT_RUN:
+ case ACT_WALK:
+ return 20;
+
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ return 15;
+
+ case ACT_RANGE_ATTACK1:
+ {
+ const Task_t *pCurTask = GetTask();
+ if ( pCurTask && pCurTask->iTask == TASK_HEADCRAB_JUMP_FROM_CANISTER )
+ return 15;
+ }
+ return 30;
+
+ default:
+ return 30;
+ }
+
+ return BaseClass::MaxYawSpeed();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows for modification of the interrupt mask for the current schedule.
+// In the most cases the base implementation should be called first.
+//-----------------------------------------------------------------------------
+void CBaseHeadcrab::BuildScheduleTestBits( void )
+{
+ if ( !IsCurSchedule(SCHED_HEADCRAB_DROWN) )
+ {
+ // Interrupt any schedule unless already drowning.
+ SetCustomInterruptCondition( COND_HEADCRAB_IN_WATER );
+ }
+ else
+ {
+ // Don't stop drowning just because you're in water!
+ ClearCustomInterruptCondition( COND_HEADCRAB_IN_WATER );
+ }
+
+ if( !IsCurSchedule(SCHED_HEADCRAB_HOP_RANDOMLY) )
+ {
+ SetCustomInterruptCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT );
+ }
+ else
+ {
+ ClearCustomInterruptCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CFastHeadcrab::MaxYawSpeed( void )
+{
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ {
+ return( 120 );
+ }
+
+ case ACT_RUN:
+ case ACT_WALK:
+ {
+ return( 150 );
+ }
+
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ {
+ return( 120 );
+ }
+
+ case ACT_RANGE_ATTACK1:
+ {
+ return( 120 );
+ }
+
+ default:
+ {
+ return( 120 );
+ }
+ }
+}
+
+
+bool CFastHeadcrab::QuerySeeEntity(CBaseEntity *pSightEnt, bool bOnlyHateOrFearIfNPC )
+{
+ if ( IsHangingFromCeiling() == true )
+ return BaseClass::QuerySeeEntity(pSightEnt, bOnlyHateOrFearIfNPC);
+
+ if( m_NPCState != NPC_STATE_COMBAT )
+ {
+ if( fabs( pSightEnt->GetAbsOrigin().z - GetAbsOrigin().z ) >= 150 )
+ {
+ // Don't see things much higher or lower than me unless
+ // I'm already pissed.
+ return false;
+ }
+ }
+
+ return BaseClass::QuerySeeEntity(pSightEnt, bOnlyHateOrFearIfNPC);
+}
+
+
+//-----------------------------------------------------------------------------
+// Black headcrab stuff
+//-----------------------------------------------------------------------------
+int ACT_BLACKHEADCRAB_RUN_PANIC;
+
+BEGIN_DATADESC( CBlackHeadcrab )
+
+ DEFINE_FIELD( m_bPanicState, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flPanicStopTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextHopTime, FIELD_TIME ),
+
+ DEFINE_ENTITYFUNC( EjectTouch ),
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( npc_headcrab_black, CBlackHeadcrab );
+LINK_ENTITY_TO_CLASS( npc_headcrab_poison, CBlackHeadcrab );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Make the sound of this headcrab chomping a target.
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::BiteSound( void )
+{
+ EmitSound( "NPC_BlackHeadcrab.Bite" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: The sound we make when leaping at our enemy.
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::AttackSound( void )
+{
+ EmitSound( "NPC_BlackHeadcrab.Attack" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::TelegraphSound( void )
+{
+ EmitSound( "NPC_BlackHeadcrab.Telegraph" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::Spawn( void )
+{
+ Precache();
+ SetModel( "models/headcrabblack.mdl" );
+
+ BaseClass::Spawn();
+
+ m_bPanicState = false;
+ m_iHealth = sk_headcrab_poison_health.GetFloat();
+
+ NPCInit();
+ HeadcrabInit();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::Precache( void )
+{
+ PrecacheModel( "models/headcrabblack.mdl" );
+
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Telegraph" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Attack" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Bite" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Threat" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Alert" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Idle" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Talk" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.AlertVoice" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Pain" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Die" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Impact" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.ImpactAngry" );
+
+ PrecacheScriptSound( "NPC_BlackHeadcrab.FootstepWalk" );
+ PrecacheScriptSound( "NPC_BlackHeadcrab.Footstep" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the max yaw speed for the current activity.
+//-----------------------------------------------------------------------------
+float CBlackHeadcrab::MaxYawSpeed( void )
+{
+ // Not a constant, can't be in a switch statement.
+ if ( GetActivity() == ACT_BLACKHEADCRAB_RUN_PANIC )
+ {
+ return 30;
+ }
+
+ switch ( GetActivity() )
+ {
+ case ACT_WALK:
+ case ACT_RUN:
+ {
+ return 10;
+ }
+
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ {
+ return( 30 );
+ }
+
+ case ACT_RANGE_ATTACK1:
+ {
+ return( 30 );
+ }
+
+ default:
+ {
+ return( 30 );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Activity CBlackHeadcrab::NPC_TranslateActivity( Activity eNewActivity )
+{
+ if ( eNewActivity == ACT_RUN || eNewActivity == ACT_WALK )
+ {
+ if( m_bPanicState || IsOnFire() )
+ {
+ return ( Activity )ACT_BLACKHEADCRAB_RUN_PANIC;
+ }
+ }
+
+ return BaseClass::NPC_TranslateActivity( eNewActivity );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBlackHeadcrab::TranslateSchedule( int scheduleType )
+{
+ switch ( scheduleType )
+ {
+ // Keep trying to take cover for at least a few seconds.
+ case SCHED_FAIL_TAKE_COVER:
+ {
+ if ( ( m_bPanicState ) && ( gpGlobals->curtime > m_flPanicStopTime ) )
+ {
+ //DevMsg( "I'm sick of panicking\n" );
+ m_bPanicState = false;
+ return SCHED_CHASE_ENEMY;
+ }
+
+ break;
+ }
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows for modification of the interrupt mask for the current schedule.
+// In the most cases the base implementation should be called first.
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::BuildScheduleTestBits( void )
+{
+ // Ignore damage if we're attacking or are fleeing and recently flinched.
+ if ( IsCurSchedule( SCHED_HEADCRAB_CRAWL_FROM_CANISTER ) || IsCurSchedule( SCHED_RANGE_ATTACK1 ) || ( IsCurSchedule( SCHED_TAKE_COVER_FROM_ENEMY ) && HasMemory( bits_MEMORY_FLINCHED ) ) )
+ {
+ ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
+ ClearCustomInterruptCondition( COND_HEAVY_DAMAGE );
+ }
+ else
+ {
+ SetCustomInterruptCondition( COND_LIGHT_DAMAGE );
+ SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
+ }
+
+ // If we're committed to jump, carry on even if our enemy hides behind a crate. Or a barrel.
+ if ( IsCurSchedule( SCHED_RANGE_ATTACK1 ) && m_bCommittedToJump )
+ {
+ ClearCustomInterruptCondition( COND_ENEMY_OCCLUDED );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output :
+//-----------------------------------------------------------------------------
+int CBlackHeadcrab::SelectSchedule( void )
+{
+ // don't override inherited behavior when hanging from ceiling
+ if ( !IsHangingFromCeiling() )
+ {
+ if ( HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN) )
+ {
+ return SCHED_IDLE_STAND;
+ }
+
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ if ( ( gpGlobals->curtime >= m_flNextHopTime ) && SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 )
+ {
+ m_flNextHopTime = gpGlobals->curtime + random->RandomFloat( 1, 3 );
+ return SCHED_SMALL_FLINCH;
+ }
+ }
+
+ if ( m_bPanicState )
+ {
+ // We're looking for a place to hide, and we've found one. Lurk!
+ if ( HasMemory( bits_MEMORY_INCOVER ) )
+ {
+ m_bPanicState = false;
+ m_flPanicStopTime = gpGlobals->curtime;
+
+ return SCHED_HEADCRAB_AMBUSH;
+ }
+
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Black headcrab's touch attack damage. Evil!
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::TouchDamage( CBaseEntity *pOther )
+{
+ if ( pOther->m_iHealth > 1 )
+ {
+ CTakeDamageInfo info;
+ if ( CalcDamageInfo( &info ) >= pOther->m_iHealth )
+ info.SetDamage( pOther->m_iHealth - 1 );
+
+ pOther->TakeDamage( info );
+
+ if ( pOther->IsAlive() && pOther->m_iHealth > 1)
+ {
+ // Episodic change to avoid NPCs dying too quickly from poison bites
+ if ( hl2_episodic.GetBool() )
+ {
+ if ( pOther->IsPlayer() )
+ {
+ // That didn't finish them. Take them down to one point with poison damage. It'll heal.
+ pOther->TakeDamage( CTakeDamageInfo( this, this, pOther->m_iHealth - 1, DMG_POISON ) );
+ }
+ else
+ {
+ // Just take some amount of slash damage instead
+ pOther->TakeDamage( CTakeDamageInfo( this, this, sk_headcrab_poison_npc_damage.GetFloat(), DMG_SLASH ) );
+ }
+ }
+ else
+ {
+ // That didn't finish them. Take them down to one point with poison damage. It'll heal.
+ pOther->TakeDamage( CTakeDamageInfo( this, this, pOther->m_iHealth - 1, DMG_POISON ) );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Bails out of our host zombie, either because he died or was blown
+// into two pieces by an explosion.
+// Input : vecAngles - The yaw direction we should face.
+// flVelocityScale - A multiplier for our ejection velocity.
+// pEnemy - Who we should acquire as our enemy. Usually our zombie host's enemy.
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::Eject( const QAngle &vecAngles, float flVelocityScale, CBaseEntity *pEnemy )
+{
+ SetGroundEntity( NULL );
+ m_spawnflags |= SF_NPC_FALL_TO_GROUND;
+
+ SetIdealState( NPC_STATE_ALERT );
+
+ if ( pEnemy )
+ {
+ SetEnemy( pEnemy );
+ UpdateEnemyMemory(pEnemy, pEnemy->GetAbsOrigin());
+ }
+
+ SetActivity( ACT_RANGE_ATTACK1 );
+
+ SetNextThink( gpGlobals->curtime );
+ PhysicsSimulate();
+
+ GetMotor()->SetIdealYaw( vecAngles.y );
+
+ SetAbsVelocity( flVelocityScale * random->RandomInt( 20, 50 ) *
+ Vector( random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( 0.5, 1.0 ) ) );
+
+ m_bMidJump = false;
+ SetTouch( &CBlackHeadcrab::EjectTouch );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Touch function for when we are ejected from the poison zombie.
+// Panic when we hit the ground.
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::EjectTouch( CBaseEntity *pOther )
+{
+ LeapTouch( pOther );
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ // Keep trying to take cover for at least a few seconds.
+ Panic( random->RandomFloat( 2, 8 ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Puts us in a state in which we just want to hide. We'll stop
+// hiding after the given duration.
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::Panic( float flDuration )
+{
+ m_flPanicStopTime = gpGlobals->curtime + flDuration;
+ m_bPanicState = true;
+}
+
+
+#if HL2_EPISODIC
+//-----------------------------------------------------------------------------
+// Purpose: Black headcrabs have 360-degree vision when they are in the ambush
+// schedule. This is because they ignore sounds when in ambush, and
+// you could walk up behind them without having them attack you.
+// This vision extends only 24 feet.
+//-----------------------------------------------------------------------------
+#define CRAB_360_VIEW_DIST_SQR (12 * 12 * 24 * 24)
+bool CBlackHeadcrab::FInViewCone( CBaseEntity *pEntity )
+{
+ if( IsCurSchedule( SCHED_HEADCRAB_AMBUSH ) &&
+ (( pEntity->IsNPC() || pEntity->IsPlayer() ) && pEntity->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= CRAB_360_VIEW_DIST_SQR ) )
+ {
+ // Only see players and NPC's with 360 cone
+ // For instance, DON'T tell the eyeball/head tracking code that you can see an object that is behind you!
+ return true;
+ }
+ else
+ {
+ return BaseClass::FInViewCone( pEntity );
+ }
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Does a spastic hop in a random or provided direction.
+// Input : pvecDir - 2D direction to hop, NULL picks a random direction.
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::JumpFlinch( const Vector *pvecDir )
+{
+ SetGroundEntity( NULL );
+
+ //
+ // Take him off ground so engine doesn't instantly reset FL_ONGROUND.
+ //
+ if( HasHeadroom() )
+ {
+ MoveOrigin( Vector( 0, 0, 1 ) );
+ }
+
+ //
+ // Jump in a random direction.
+ //
+ Vector up;
+ AngleVectors( GetLocalAngles(), NULL, NULL, &up );
+
+ if (pvecDir)
+ {
+ SetAbsVelocity( Vector( pvecDir->x * 4, pvecDir->y * 4, up.z ) * random->RandomFloat( 40, 80 ) );
+ }
+ else
+ {
+ SetAbsVelocity( Vector( random->RandomFloat( -4, 4 ), random->RandomFloat( -4, 4 ), up.z ) * random->RandomFloat( 40, 80 ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Catches the monster-specific messages that occur when tagged
+// animation frames are played.
+// Input : pEvent -
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::HandleAnimEvent( animevent_t *pEvent )
+{
+ if ( pEvent->event == AE_POISONHEADCRAB_FOOTSTEP )
+ {
+ bool walk = ( GetActivity() == ACT_WALK ); // ? 1.0 : 0.6; !!cgreen! old code had bug
+
+ if ( walk )
+ {
+ EmitSound( "NPC_BlackHeadcrab.FootstepWalk" );
+ }
+ else
+ {
+ EmitSound( "NPC_BlackHeadcrab.Footstep" );
+ }
+
+ return;
+ }
+
+ if ( pEvent->event == AE_HEADCRAB_JUMP_TELEGRAPH )
+ {
+ EmitSound( "NPC_BlackHeadcrab.Telegraph" );
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if ( pEnemy )
+ {
+ // Once we telegraph, we MUST jump. This is also when commit to what point
+ // we jump at. Jump at our enemy's eyes.
+ m_vecCommittedJumpPos = pEnemy->EyePosition();
+ m_bCommittedToJump = true;
+ }
+
+ return;
+ }
+
+ if ( pEvent->event == AE_POISONHEADCRAB_THREAT_SOUND )
+ {
+ EmitSound( "NPC_BlackHeadcrab.Threat" );
+ EmitSound( "NPC_BlackHeadcrab.Alert" );
+
+ return;
+ }
+
+ if ( pEvent->event == AE_POISONHEADCRAB_FLINCH_HOP )
+ {
+ //
+ // Hop in a random direction, then run and hide. If we're already running
+ // to hide, jump forward -- hopefully that will take us closer to a hiding spot.
+ //
+ if (m_bPanicState)
+ {
+ Vector vecForward;
+ AngleVectors( GetLocalAngles(), &vecForward );
+ JumpFlinch( &vecForward );
+ }
+ else
+ {
+ JumpFlinch( NULL );
+ }
+
+ Panic( random->RandomFloat( 2, 5 ) );
+
+ return;
+ }
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CBlackHeadcrab::IsHeavyDamage( const CTakeDamageInfo &info )
+{
+ if ( !HasMemory(bits_MEMORY_FLINCHED) && info.GetDamage() > 1.0f )
+ {
+ // If I haven't flinched lately, any amount of damage is interpreted as heavy.
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::IdleSound( void )
+{
+ // TODO: hook up "Marco" / "Polo" talking with nearby buddies
+ if ( m_NPCState == NPC_STATE_IDLE )
+ {
+ EmitSound( "NPC_BlackHeadcrab.Idle" );
+ }
+ else if ( m_NPCState == NPC_STATE_ALERT )
+ {
+ EmitSound( "NPC_BlackHeadcrab.Talk" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::AlertSound( void )
+{
+ EmitSound( "NPC_BlackHeadcrab.AlertVoice" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::PainSound( const CTakeDamageInfo &info )
+{
+ if( IsOnFire() && random->RandomInt( 0, HEADCRAB_BURN_SOUND_FREQUENCY ) > 0 )
+ {
+ // Don't squeak every think when burning.
+ return;
+ }
+
+ EmitSound( "NPC_BlackHeadcrab.Pain" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::DeathSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_BlackHeadcrab.Die" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Played when we jump and hit something that we can't bite.
+//-----------------------------------------------------------------------------
+void CBlackHeadcrab::ImpactSound( void )
+{
+ EmitSound( "NPC_BlackHeadcrab.Impact" );
+
+ if ( !( GetFlags() & FL_ONGROUND ) )
+ {
+ // Hit a wall - make a pissed off sound.
+ EmitSound( "NPC_BlackHeadcrab.ImpactAngry" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( npc_headcrab, CBaseHeadcrab )
+
+ DECLARE_TASK( TASK_HEADCRAB_HOP_ASIDE )
+ DECLARE_TASK( TASK_HEADCRAB_DROWN )
+ DECLARE_TASK( TASK_HEADCRAB_HOP_OFF_NPC )
+ DECLARE_TASK( TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL )
+ DECLARE_TASK( TASK_HEADCRAB_UNHIDE )
+ DECLARE_TASK( TASK_HEADCRAB_HARASS_HOP )
+ DECLARE_TASK( TASK_HEADCRAB_BURROW )
+ DECLARE_TASK( TASK_HEADCRAB_UNBURROW )
+ DECLARE_TASK( TASK_HEADCRAB_FIND_BURROW_IN_POINT )
+ DECLARE_TASK( TASK_HEADCRAB_BURROW_WAIT )
+ DECLARE_TASK( TASK_HEADCRAB_CHECK_FOR_UNBURROW )
+ DECLARE_TASK( TASK_HEADCRAB_JUMP_FROM_CANISTER )
+ DECLARE_TASK( TASK_HEADCRAB_CLIMB_FROM_CANISTER )
+
+ DECLARE_TASK( TASK_HEADCRAB_CEILING_POSITION )
+ DECLARE_TASK( TASK_HEADCRAB_CEILING_WAIT )
+ DECLARE_TASK( TASK_HEADCRAB_CEILING_DETACH )
+ DECLARE_TASK( TASK_HEADCRAB_CEILING_FALL )
+ DECLARE_TASK( TASK_HEADCRAB_CEILING_LAND )
+
+ DECLARE_ACTIVITY( ACT_HEADCRAB_THREAT_DISPLAY )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_HOP_LEFT )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_HOP_RIGHT )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_DROWN )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_BURROW_IN )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_BURROW_OUT )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_BURROW_IDLE )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_CRAWL_FROM_CANISTER_LEFT )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_CRAWL_FROM_CANISTER_CENTER )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_CRAWL_FROM_CANISTER_RIGHT )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_FALL )
+
+ DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_IDLE )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_DETACH )
+ DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_LAND )
+
+ DECLARE_CONDITION( COND_HEADCRAB_IN_WATER )
+ DECLARE_CONDITION( COND_HEADCRAB_ILLEGAL_GROUNDENT )
+ DECLARE_CONDITION( COND_HEADCRAB_BARNACLED )
+ DECLARE_CONDITION( COND_HEADCRAB_UNHIDE )
+
+ //Adrian: events go here
+ DECLARE_ANIMEVENT( AE_HEADCRAB_JUMPATTACK )
+ DECLARE_ANIMEVENT( AE_HEADCRAB_JUMP_TELEGRAPH )
+ DECLARE_ANIMEVENT( AE_HEADCRAB_BURROW_IN )
+ DECLARE_ANIMEVENT( AE_HEADCRAB_BURROW_IN_FINISH )
+ DECLARE_ANIMEVENT( AE_HEADCRAB_BURROW_OUT )
+ DECLARE_ANIMEVENT( AE_HEADCRAB_CEILING_DETACH )
+
+ //=========================================================
+ // > SCHED_HEADCRAB_RANGE_ATTACK1
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_IDEAL 0"
+ " TASK_WAIT_RANDOM 0.5"
+ ""
+ " Interrupts"
+ " COND_ENEMY_OCCLUDED"
+ " COND_NO_PRIMARY_AMMO"
+ )
+
+ //=========================================================
+ //
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_WAKE_ANGRY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE "
+ " TASK_FACE_IDEAL 0"
+ " TASK_SOUND_WAKE 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HEADCRAB_THREAT_DISPLAY"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ //
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_WAKE_ANGRY_NO_DISPLAY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE "
+ " TASK_FACE_IDEAL 0"
+ " TASK_SOUND_WAKE 0"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_FAST_HEADCRAB_RANGE_ATTACK1
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FAST_HEADCRAB_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_IDEAL 0"
+ " TASK_WAIT_RANDOM 0.5"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // The irreversible process of drowning
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_DROWN,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_FAIL_DROWN"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_DROWN"
+ " TASK_HEADCRAB_DROWN 0"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_FAIL_DROWN,
+
+ " Tasks"
+ " TASK_HEADCRAB_DROWN 0"
+ ""
+ " Interrupts"
+ )
+
+
+ //=========================================================
+ // Headcrab lurks in place and waits for a chance to jump on
+ // some unfortunate soul.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_AMBUSH,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_INDEFINITE 0"
+
+ " Interrupts"
+ " COND_SEE_ENEMY"
+ " COND_SEE_HATE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_PROVOKED"
+ )
+
+ //=========================================================
+ // Headcrab has landed atop another NPC or has landed on
+ // a ledge. Get down!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_HOP_RANDOMLY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HEADCRAB_HOP_OFF_NPC 0"
+
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Headcrab is in the clutches of a barnacle
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_BARNACLED,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_DROWN"
+ " TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL 0"
+
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Headcrab is unhiding
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_UNHIDE,
+
+ " Tasks"
+ " TASK_HEADCRAB_UNHIDE 0"
+
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_HARASS_ENEMY,
+
+ " Tasks"
+ " TASK_FACE_ENEMY 0"
+ " TASK_HEADCRAB_HARASS_HOP 0"
+ " TASK_WAIT_FACE_ENEMY 1"
+ " TASK_SET_ROUTE_SEARCH_TIME 2" // Spend 2 seconds trying to build a path if stuck
+ " TASK_GET_PATH_TO_RANDOM_NODE 300"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_FALL_TO_GROUND,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_DROWN"
+ " TASK_FALL_TO_GROUND 0"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_CRAWL_FROM_CANISTER,
+ " Tasks"
+ " TASK_HEADCRAB_CLIMB_FROM_CANISTER 0"
+ " TASK_HEADCRAB_JUMP_FROM_CANISTER 0"
+ ""
+ " Interrupts"
+ )
+
+ //==================================================
+ // Burrow In
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_BURROW_IN,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
+ " TASK_HEADCRAB_BURROW 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_HEADCRAB_BURROW_IN"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_HEADCRAB_BURROW_IDLE"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // Run to a nearby burrow hint and burrow there
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_RUN_TO_BURROW_IN,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
+ " TASK_HEADCRAB_FIND_BURROW_IN_POINT 512"
+ " TASK_SET_TOLERANCE_DISTANCE 8"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_IN"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_GIVE_WAY"
+ " COND_CAN_RANGE_ATTACK1"
+ )
+
+ //==================================================
+ // Run to m_pHintNode and burrow there
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_RUN_TO_SPECIFIC_BURROW,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
+ " TASK_SET_TOLERANCE_DISTANCE 8"
+ " TASK_GET_PATH_TO_HINTNODE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_IN"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_GIVE_WAY"
+ )
+
+ //==================================================
+ // Wait until we can unburrow and attack something
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_BURROW_WAIT,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT"
+ " TASK_HEADCRAB_BURROW_WAIT 1"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_NEW_ENEMY" // HACK: We don't actually choose a new schedule on new enemy, but
+ // we need this interrupt so that the headcrab actually acquires
+ // new enemies while burrowed. (look in ai_basenpc.cpp for "DO NOT mess")
+ " COND_CAN_RANGE_ATTACK1"
+ )
+
+ //==================================================
+ // Burrow Out
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_BURROW_OUT,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT"
+ " TASK_HEADCRAB_UNBURROW 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_HEADCRAB_BURROW_OUT"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // Wait for it to be clear for unburrowing
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_WAIT_FOR_CLEAR_UNBURROW,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT"
+ " TASK_HEADCRAB_CHECK_FOR_UNBURROW 1"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_OUT"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //==================================================
+ // Wait until we can drop.
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_CEILING_WAIT,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_CEILING_DROP"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_CEILING_IDLE"
+ " TASK_HEADCRAB_CEILING_POSITION 0"
+ " TASK_HEADCRAB_CEILING_WAIT 1"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_NEW_ENEMY"
+ " COND_CAN_RANGE_ATTACK1"
+ )
+
+ //==================================================
+ // Deatch from ceiling.
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_CEILING_DROP,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_CEILING_WAIT"
+ " TASK_HEADCRAB_CEILING_DETACH 0"
+ " TASK_HEADCRAB_CEILING_FALL 0"
+ " TASK_HEADCRAB_CEILING_LAND 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+AI_END_CUSTOM_NPC()
+
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( npc_headcrab_poison, CBlackHeadcrab )
+
+ DECLARE_ACTIVITY( ACT_BLACKHEADCRAB_RUN_PANIC )
+
+ //Adrian: events go here
+ DECLARE_ANIMEVENT( AE_POISONHEADCRAB_FLINCH_HOP )
+ DECLARE_ANIMEVENT( AE_POISONHEADCRAB_FOOTSTEP )
+ DECLARE_ANIMEVENT( AE_POISONHEADCRAB_THREAT_SOUND )
+
+AI_END_CUSTOM_NPC()
+
+
+AI_BEGIN_CUSTOM_NPC( npc_headcrab_fast, CFastHeadcrab )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE1 )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE2 )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE3 )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE4 )
+AI_END_CUSTOM_NPC()