//========= Copyright © 1996-2005, 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 = sv_gravity.GetFloat();
		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 );
	AddEffects( EF_NOINTERP );

	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 )
{
	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 );

//////

	// SO2 - James
	// Fix various NPC-related issues and bugs in multiplayer
	// http://developer.valvesoftware.com/wiki/Fixing_AI_in_multiplayer#Patch

					//CBaseEntity *pPlayer = AI_GetSinglePlayer();

					CBaseEntity *pPlayer = UTIL_GetNearestPlayer( GetAbsOrigin() );

//////

					if ( pPlayer )
					{
						SetEnemy( pPlayer ); //Is this a bad thing to do?
						UpdateEnemyMemory( pPlayer, pPlayer->GetAbsOrigin());
					}
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// 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 ? SCHED_HEADCRAB_BURROW_WAIT : SCHED_IDLE_STAND;
	}

	if ( GetSpawnFlags() & SF_HEADCRAB_START_HANGING && IsHangingFromCeiling() == false )
	{
		return SCHED_HEADCRAB_CEILING_WAIT;
	}

	if ( IsHangingFromCeiling() )
	{
		if ( HL2GameRules()->IsAlyxInDarknessMode() == 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 )
{
	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 );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
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,sv_gravity.GetFloat()) * 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()
