//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose:		Ichthyosaur - buh bum...  buh bum...
//
// $NoKeywords: $
//=============================================================================

#include "cbase.h"
#include "AI_BaseNPC.h"
#include "AI_Task.h"
#include "AI_Default.h"
#include "AI_Schedule.h"
#include "AI_Hull.h"
#include "AI_Interactions.h"
#include "AI_Navigator.h"
#include "AI_Motor.h"
#include "activitylist.h"
#include "game.h"
#include "NPCEvent.h"
#include "Player.h"
#include "EntityList.h"
#include "soundenvelope.h"
#include "shake.h"
#include "ndebugoverlay.h"
#include "splash.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "movevars_shared.h"

ConVar	sk_ichthyosaur_health( "sk_ichthyosaur_health", "0" );
ConVar	sk_ichthyosaur_melee_dmg( "sk_ichthyosaur_melee_dmg", "0" );

#define	ICHTHYOSAUR_MODEL	"models/ichthyosaur.mdl"

#define	ICH_HEIGHT_PREFERENCE	16.0f
#define	ICH_DEPTH_PREFERENCE	8.0f

#define	ICH_WAYPOINT_DISTANCE	64.0f

#define	ICH_AE_BITE				11
#define	ICH_AE_BITE_START		12

#define	ICH_SWIM_SPEED_WALK		150
#define	ICH_SWIM_SPEED_RUN		500

#define	ICH_MIN_TURN_SPEED		4.0f
#define	ICH_MAX_TURN_SPEED		30.0f

#define	ENVELOPE_CONTROLLER		(CSoundEnvelopeController::GetController())

#define	FEELER_COLLISION			0
#define	FEELER_COLLISION_VISUALIZE	(FEELER_COLLISION&&0)

enum IchthyosaurMoveType_t
{
	ICH_MOVETYPE_SEEK = 0,	// Fly through the target without stopping.
	ICH_MOVETYPE_ARRIVE		// Slow down and stop at target.
};

//
// CNPC_Ichthyosaur
//

class CNPC_Ichthyosaur : public CAI_BaseNPC
{
public:
	DECLARE_CLASS( CNPC_Ichthyosaur, CAI_BaseNPC );
	DECLARE_DATADESC();

	CNPC_Ichthyosaur( void ) {}

	int		SelectSchedule( void );
	int		MeleeAttack1Conditions( float flDot, float flDist );
	int		OnTakeDamage_Alive( const CTakeDamageInfo &info );
	int		TranslateSchedule( int type );

	void	Precache( void );
	void	Spawn( void );
	void	MoveFlyExecute( CBaseEntity *pTargetEnt, const Vector & vecDir, float flDistance, float flInterval );
	void	HandleAnimEvent( animevent_t *pEvent );
	void	PrescheduleThink( void );
	bool	OverrideMove( float flInterval );
	void	StartTask( const Task_t *pTask );
	void	RunTask( const Task_t *pTask );
	void	TranslateEnemyChasePosition( CBaseEntity *pEnemy, Vector &chasePosition, float &tolerance );

	float	MaxYawSpeed( void );

	Class_T Classify( void )	{	return CLASS_HOUNDEYE;	}	//FIXME: No classification for various wildlife?

	bool	FVisible( CBaseEntity *pEntity, int traceMask = MASK_OPAQUE, CBaseEntity **ppBlocker = NULL );

private:

	bool	SteerAvoidObstacles( Vector &Steer, const Vector &Velocity, const Vector &Forward, const Vector &Right, const Vector &Up );
	bool	Beached( void );

	void	DoMovement( float flInterval, const Vector &MoveTarget, int eMoveType );
	void	SteerArrive( Vector &Steer, const Vector &Target );
	void	SteerSeek( Vector &Steer, const Vector &Target );
	void	ClampSteer( Vector &SteerAbs, Vector &SteerRel, Vector &forward, Vector &right, Vector &up );
	void	AddSwimNoise( Vector *velocity );

	void	Bite( void );
	void	EnsnareVictim( CBaseEntity *pVictim );
	void	ReleaseVictim( void );
	void	DragVictim( float moveDist );

	void	SetPoses( Vector moveRel, float speed );

	//void	IchTouch( CBaseEntity *pOther );

	float	GetGroundSpeed( void );

#if FEELER_COLLISION
	Vector	DoProbe( const Vector &Probe );
	Vector	m_LastSteer;
#endif

	CBaseEntity	*m_pVictim;

	static const Vector	m_vecAccelerationMax;
	static const Vector	m_vecAccelerationMin;

	Vector	m_vecLastMoveTarget;
		
	float	m_flNextBiteTime;
	float	m_flHoldTime;
	float	m_flSwimSpeed;
	float	m_flTailYaw;
	float	m_flTailPitch;

	float	m_flNextPingTime;
	float	m_flNextGrowlTime;

	bool	m_bHasMoveTarget;
	bool	m_bIgnoreSurface;

	//CSoundPatch	*m_pSwimSound;
	//CSoundPatch	*m_pVoiceSound;
	
	DEFINE_CUSTOM_AI;
};

//Acceleration definitions
const Vector CNPC_Ichthyosaur::m_vecAccelerationMax	= Vector(  256,  1024,  512 );
const Vector CNPC_Ichthyosaur::m_vecAccelerationMin	= Vector( -256, -1024, -512 );

//Data description
BEGIN_DATADESC( CNPC_Ichthyosaur )

// Silence classcheck
//	DEFINE_FIELD( CNPC_Ichthyosaur, m_LastSteer, FIELD_VECTOR ),

	DEFINE_FIELD( CNPC_Ichthyosaur, m_pVictim,				FIELD_CLASSPTR ),
	DEFINE_FIELD( CNPC_Ichthyosaur, m_vecLastMoveTarget,	FIELD_VECTOR ),
	DEFINE_FIELD( CNPC_Ichthyosaur, m_flNextBiteTime,		FIELD_FLOAT ),
	DEFINE_FIELD( CNPC_Ichthyosaur, m_flHoldTime,			FIELD_FLOAT ),
	DEFINE_FIELD( CNPC_Ichthyosaur, m_flSwimSpeed,			FIELD_FLOAT ),
	DEFINE_FIELD( CNPC_Ichthyosaur, m_flTailYaw,			FIELD_FLOAT ),
	DEFINE_FIELD( CNPC_Ichthyosaur, m_flTailPitch,			FIELD_FLOAT ),
	DEFINE_FIELD( CNPC_Ichthyosaur, m_flNextPingTime,		FIELD_FLOAT ),
	DEFINE_FIELD( CNPC_Ichthyosaur, m_flNextGrowlTime,		FIELD_FLOAT ),
	DEFINE_FIELD( CNPC_Ichthyosaur, m_bHasMoveTarget,		FIELD_BOOLEAN ),
	DEFINE_FIELD( CNPC_Ichthyosaur, m_bIgnoreSurface,		FIELD_BOOLEAN ),

	//DEFINE_FUNCTION( CNPC_Ichthyosaur, IchTouch ),

END_DATADESC()

//Schedules
enum IchSchedules
{
	SCHED_ICH_CHASE_ENEMY = LAST_SHARED_SCHEDULE,
	SCHED_ICH_PATROL_RUN,
	SCHED_ICH_PATROL_WALK,
	SCHED_ICH_DROWN_VICTIM,
	SCHED_ICH_MELEE_ATTACK1,
	SCHED_ICH_THRASH,
};

//Tasks
enum IchTasks
{
	TASK_ICH_GET_PATH_TO_RANDOM_NODE = LAST_SHARED_TASK,
	TASK_ICH_GET_PATH_TO_DROWN_NODE,
	TASK_ICH_THRASH_PATH,
};

//Activities
int	ACT_ICH_THRASH;
int	ACT_ICH_BITE_HIT;
int	ACT_ICH_BITE_MISS;

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::InitCustomSchedules( void ) 
{
	INIT_CUSTOM_AI( CNPC_Ichthyosaur );

	//Interaction	REGISTER_INTERACTION( g_interactionAntlionAttacked );

	//Schedules
	ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_CHASE_ENEMY );
	ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_PATROL_RUN );
	ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_PATROL_WALK );
	ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_DROWN_VICTIM );
	ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_MELEE_ATTACK1 );
	ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_THRASH );
	
	//Tasks
	ADD_CUSTOM_TASK( CNPC_Ichthyosaur,		TASK_ICH_GET_PATH_TO_RANDOM_NODE );
	ADD_CUSTOM_TASK( CNPC_Ichthyosaur,		TASK_ICH_GET_PATH_TO_DROWN_NODE );
	ADD_CUSTOM_TASK( CNPC_Ichthyosaur,		TASK_ICH_THRASH_PATH );

	//Conditions	ADD_CUSTOM_CONDITION( CNPC_CombineGuard,	COND_ANTLIONGRUB_HEARD_SQUEAL );

	//Activities
	ADD_CUSTOM_ACTIVITY( CNPC_Ichthyosaur,	ACT_ICH_THRASH );
	ADD_CUSTOM_ACTIVITY( CNPC_Ichthyosaur,	ACT_ICH_BITE_HIT );
	ADD_CUSTOM_ACTIVITY( CNPC_Ichthyosaur,	ACT_ICH_BITE_MISS );

	AI_LOAD_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_CHASE_ENEMY );
	AI_LOAD_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_PATROL_RUN );
	AI_LOAD_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_PATROL_WALK );
	AI_LOAD_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_DROWN_VICTIM );
	AI_LOAD_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_MELEE_ATTACK1 );
	AI_LOAD_SCHEDULE( CNPC_Ichthyosaur,	SCHED_ICH_THRASH );
}

LINK_ENTITY_TO_CLASS( npc_ichthyosaur, CNPC_Ichthyosaur );
IMPLEMENT_CUSTOM_AI( npc_ichthyosaur, CNPC_Ichthyosaur );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::Precache( void )
{
	engine->PrecacheModel( ICHTHYOSAUR_MODEL );

	BaseClass::Precache();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::Spawn( void )
{
	Precache();

	SetModel( ICHTHYOSAUR_MODEL );

	SetHullType(HULL_LARGE_CENTERED);
	SetHullSizeNormal();
	SetDefaultEyeOffset();
	
	SetNavType( NAV_FLY );
	m_NPCState				= NPC_STATE_NONE;
	SetBloodColor( BLOOD_COLOR_RED );
	m_iHealth				= sk_ichthyosaur_health.GetFloat();
	m_iMaxHealth			= m_iHealth;
	m_flFieldOfView			= -0.707;	// 270 degrees
	SetDistLook( 1024 );

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_STEP );
	AddFlag( FL_FLY | FL_STEPMOVEMENT );

	m_flGroundSpeed			= ICH_SWIM_SPEED_RUN;

	m_bIgnoreSurface		= false;

	m_flSwimSpeed			= 0.0f;
	m_flTailYaw				= 0.0f;
	m_flTailPitch			= 0.0f;

	m_flNextBiteTime		= gpGlobals->curtime;
	m_flHoldTime			= gpGlobals->curtime;
	m_flNextPingTime		= gpGlobals->curtime;
	m_flNextGrowlTime		= gpGlobals->curtime;

#if FEELER_COLLISION

	Vector	forward;

	GetVectors( &forward, NULL, NULL );

	m_vecCurrentVelocity	= forward * m_flGroundSpeed;

#endif

	//SetTouch( IchTouch );

	CapabilitiesClear();
	CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_INNATE_MELEE_ATTACK1 );

	NPCInit();

	//m_pSwimSound	= ENVELOPE_CONTROLLER.SoundCreate( edict(), CHAN_BODY,	"npc/ichthyosaur/ich_amb1.wav", ATTN_NORM );
	//m_pVoiceSound	= ENVELOPE_CONTROLLER.SoundCreate( edict(), CHAN_VOICE,	"npc/ichthyosaur/water_breath.wav", ATTN_IDLE );

	//ENVELOPE_CONTROLLER.Play( m_pSwimSound,	1.0f, 100 );
	//ENVELOPE_CONTROLLER.Play( m_pVoiceSound,1.0f, 100 );

	BaseClass::Spawn();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pOther - 
//-----------------------------------------------------------------------------
/*
void CNPC_Ichthyosaur::IchTouch( CBaseEntity *pOther )
{
}
*/

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CNPC_Ichthyosaur::SelectSchedule( void )
{
	if ( m_NPCState == NPC_STATE_COMBAT )
	{
		if ( m_flHoldTime > gpGlobals->curtime )
			return SCHED_ICH_DROWN_VICTIM;

		if ( m_flNextBiteTime > gpGlobals->curtime )
			return	SCHED_PATROL_RUN;

		if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
			return	SCHED_MELEE_ATTACK1;

		return SCHED_CHASE_ENEMY;
	}

	return BaseClass::SelectSchedule();
}

//-----------------------------------------------------------------------------
// Purpose: Handles movement towards the last move target.
// Input  : flInterval - 
//-----------------------------------------------------------------------------
bool CNPC_Ichthyosaur::OverrideMove( float flInterval )
{
	m_flGroundSpeed = GetGroundSpeed();

	if ( m_bHasMoveTarget )
	{
		DoMovement( flInterval, m_vecLastMoveTarget, ICH_MOVETYPE_ARRIVE );
	}
	else
	{
		DoMovement( flInterval, GetLocalOrigin(), ICH_MOVETYPE_ARRIVE );
	}
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &probe - 
// Output : Vector
//-----------------------------------------------------------------------------
#if FEELER_COLLISION

Vector CNPC_Ichthyosaur::DoProbe( const Vector &probe )
{
	trace_t	tr;
	float	fraction = 1.0f;
	bool	collided = false;
	Vector	normal	 = Vector( 0, 0, -1 );

	float	waterLevel = UTIL_WaterLevel( GetAbsOrigin(), GetAbsOrigin().z, GetAbsOrigin().z+150 );

	waterLevel -= GetAbsOrigin().z;
	waterLevel /= 150;

	if ( waterLevel < 1.0f )
	{
		collided = true;
		fraction = waterLevel;
	}

	AI_TraceHull( GetAbsOrigin(), probe, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
	
	if ( ( collided == false ) || ( tr.fraction < fraction ) )
	{
		fraction	= tr.fraction;
		normal		= tr.plane.normal;
	}

	if ( ( fraction < 1.0f ) && ( GetEnemy() == NULL || tr.u.ent != GetEnemy()->pev ) )
	{
#if FEELER_COLLISION_VISUALIZE
		NDebugOverlay::Line( GetLocalOrigin(), probe, 255, 0, 0, false, 0.1f );
#endif
		
		Vector	probeDir = probe - GetLocalOrigin();

		Vector	normalToProbeAndWallNormal = probeDir.Cross( normal );
		Vector	steeringVector = normalToProbeAndWallNormal.Cross( probeDir );

		Vector	velDir = GetAbsVelocity();
		VectorNormalize( velDir );

		float	steeringForce = m_flGroundSpeed * ( 1.0f - fraction ) * normal.Dot( velDir );

		if ( steeringForce < 0.0f )
		{
			steeringForce = -steeringForce;
		}

		velDir = steeringVector;
		VectorNormalize( velDir );

		steeringVector = steeringForce * velDir;
		
		return steeringVector;
	}

#if FEELER_COLLISION_VISUALIZE
	NDebugOverlay::Line( GetLocalOrigin(), probe, 0, 255, 0, false, 0.1f );
#endif

	return Vector( 0.0f, 0.0f, 0.0f );
}

#endif

//-----------------------------------------------------------------------------
// Purpose: Move the victim of a drag along with us
// Input  : moveDist - our amount of travel
//-----------------------------------------------------------------------------

#define	DRAG_OFFSET	50.0f

void CNPC_Ichthyosaur::DragVictim( float moveDist )
{
	Vector	mins, maxs;
	float	width;
	
	mins	= WorldAlignMins();
	maxs	= WorldAlignMaxs();
	width	= ( maxs.y - mins.y ) * 0.5f;

	Vector	forward, up;
	GetVectors( &forward, NULL, &up );

	Vector	newPos = GetAbsOrigin() + ( (forward+(up*0.25f)) * ( moveDist + width + DRAG_OFFSET ) );

	trace_t	tr;
	AI_TraceEntity( this, m_pVictim->GetAbsOrigin(), newPos, MASK_NPCSOLID, &tr );

	if ( ( tr.fraction == 1.0f ) && ( tr.m_pEnt != this ) )
	{
		UTIL_SetOrigin( m_pVictim, tr.endpos );
	}
	else
	{
		ReleaseVictim();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Determines the pose parameters for the bending of the body and tail speed
// Input  : moveRel - the dot products for the deviation off of each direction (f,r,u)
//			speed - speed of the fish
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::SetPoses( Vector moveRel, float speed )
{
	float	movePerc, moveBase;

	//Find out how fast we're moving in our animations boundaries
	if ( GetIdealActivity() == ACT_WALK )
	{
		moveBase = 0.5f;
		movePerc = moveBase * ( speed / ICH_SWIM_SPEED_WALK );
	}
	else
	{
		moveBase = 1.0f;
		movePerc = moveBase * ( speed / ICH_SWIM_SPEED_RUN );
	}
	
	Vector	tailPosition;
	float	flSwimSpeed = movePerc;

	//Forward deviation
	if ( moveRel.x > 0 )
	{
		flSwimSpeed *= moveBase + (( moveRel.x / m_vecAccelerationMax.x )*moveBase);
	}
	else if ( moveRel.x < 0 )
	{
		flSwimSpeed *= moveBase - (( moveRel.x / m_vecAccelerationMin.x )*moveBase);
	}

	//Vertical deviation
	if ( moveRel.z > 0 )
	{
		tailPosition[PITCH]	= -90.0f * ( moveRel.z / m_vecAccelerationMax.z );
	}
	else if ( moveRel.z < 0 )
	{
		tailPosition[PITCH]	= 90.0f * ( moveRel.z / m_vecAccelerationMin.z );
	}
	else
	{
		tailPosition[PITCH]	= 0.0f;
	}

	//Lateral deviation
	if ( moveRel.y > 0 )
	{
		tailPosition[ROLL]	= 25 * moveRel.y / m_vecAccelerationMax.y;
		tailPosition[YAW]	= -1.0f * moveRel.y / m_vecAccelerationMax.y;
	}
	else if ( moveRel.y < 0 )
	{
		tailPosition[ROLL]	= -25 * moveRel.y / m_vecAccelerationMin.y;
		tailPosition[YAW]	= moveRel.y / m_vecAccelerationMin.y;
	}
	else
	{
		tailPosition[ROLL]	= 0.0f;
		tailPosition[YAW]	= 0.0f;
	}
	
	//Clamp
	flSwimSpeed			= clamp( flSwimSpeed, 0.25f, 1.0f );
	tailPosition[YAW]	= clamp( tailPosition[YAW], -90.0f, 90.0f );
	tailPosition[PITCH]	= clamp( tailPosition[PITCH], -90.0f, 90.0f );

	//Blend
	m_flTailYaw		= ( m_flTailYaw * 0.8f ) + ( tailPosition[YAW] * 0.2f );
	m_flTailPitch	= ( m_flTailPitch * 0.8f ) + ( tailPosition[PITCH] * 0.2f );
	m_flSwimSpeed	= ( m_flSwimSpeed * 0.8f ) + ( flSwimSpeed * 0.2f );

	//Pose the body
	SetPoseParameter( 0, m_flSwimSpeed );
	SetPoseParameter( 1, m_flTailYaw );
	SetPoseParameter( 2, m_flTailPitch );
	
	//FIXME: Until the sequence info is reset properly after SetPoseParameter
	if ( ( GetActivity() == ACT_RUN ) || ( GetActivity() == ACT_WALK ) )
	{
		ResetSequenceInfo();
	}

	//Face our current velocity
	GetMotor()->SetIdealYawAndUpdate( UTIL_AngleMod( CalcIdealYaw( GetAbsOrigin() + GetAbsVelocity() ) ), AI_KEEP_YAW_SPEED );

	float	pitch = 0.0f;

	if ( speed != 0.0f )
	{
		pitch = -RAD2DEG( asin( GetAbsVelocity().z / speed ) );
	}

	//FIXME: Framerate dependant
	QAngle angles = GetLocalAngles();

	angles.x = (angles.x * 0.8f) + (pitch * 0.2f);
	angles.z = (angles.z * 0.9f) + (tailPosition[ROLL] * 0.1f);

	SetLocalAngles( angles );
}

#define	LATERAL_NOISE_MAX	2.0f
#define	LATERAL_NOISE_FREQ	1.0f

#define	VERTICAL_NOISE_MAX	2.0f
#define	VERTICAL_NOISE_FREQ	1.0f

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : velocity - 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::AddSwimNoise( Vector *velocity )
{
	Vector	right, up;

	GetVectors( NULL, &right, &up );

	float	lNoise, vNoise;

	lNoise = LATERAL_NOISE_MAX * sin( gpGlobals->curtime * LATERAL_NOISE_FREQ );
	vNoise = VERTICAL_NOISE_MAX * sin( gpGlobals->curtime * VERTICAL_NOISE_FREQ );

	(*velocity) += ( right * lNoise ) + ( up * vNoise );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flInterval - 
//			&m_LastMoveTarget - 
//			eMoveType - 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::DoMovement( float flInterval, const Vector &MoveTarget, int eMoveType )
{
	// dvs: something is setting this bit, causing us to stop moving and get stuck that way
	Forget( bits_MEMORY_TURNING );

	Vector Steer, SteerAvoid, SteerRel;
	Vector forward, right, up;

	//Get our orientation vectors.
	GetVectors( &forward, &right, &up);

	if ( ( GetActivity() == ACT_MELEE_ATTACK1 ) && ( GetEnemy() != NULL ) )
	{
		SteerSeek( Steer, GetEnemy()->GetAbsOrigin() );
	}
	else
	{
		//If we are approaching our goal, use an arrival steering mechanism.
		if ( eMoveType == ICH_MOVETYPE_ARRIVE )
		{
			SteerArrive( Steer, MoveTarget );
		}
		else
		{
			//Otherwise use a seek steering mechanism.
			SteerSeek( Steer, MoveTarget );
		}
	}
	
#if FEELER_COLLISION

	Vector f, u, l, r, d;

	float	probeLength = GetAbsVelocity().Length();

	if ( probeLength < 150 )
		probeLength = 150;

	if ( probeLength > 500 )
		probeLength = 500;

	f = DoProbe( GetLocalOrigin() + (probeLength * forward) );
	r = DoProbe( GetLocalOrigin() + (probeLength/3 * (forward+right)) );
	l = DoProbe( GetLocalOrigin() + (probeLength/3 * (forward-right)) );
	u = DoProbe( GetLocalOrigin() + (probeLength/3 * (forward+up)) );
	d = DoProbe( GetLocalOrigin() + (probeLength/3 * (forward-up)) );

	SteerAvoid = f+r+l+u+d;
	
	//NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin()+SteerAvoid, 255, 255, 0, false, 0.1f );	

	if ( SteerAvoid.LengthSqr() )
	{
		Steer = (SteerAvoid*0.5f);
	}

	m_vecVelocity = m_vecVelocity + (Steer*0.5f);

	VectorNormalize( m_vecVelocity );

	SteerRel.x = forward.Dot( m_vecVelocity );
	SteerRel.y = right.Dot( m_vecVelocity );
	SteerRel.z = up.Dot( m_vecVelocity );

	m_vecVelocity *= m_flGroundSpeed;

#else

	//See if we need to avoid any obstacles.
	if ( SteerAvoidObstacles( SteerAvoid, GetAbsVelocity(), forward, right, up ) )
	{
		//Take the avoidance vector
		Steer = SteerAvoid;
	}

	//Clamp our ideal steering vector to within our physical limitations.
	ClampSteer( Steer, SteerRel, forward, right, up );

	ApplyAbsVelocityImpulse( Steer * flInterval );
	
#endif

	Vector vecNewVelocity = GetAbsVelocity();
	float flLength = vecNewVelocity.Length();

	//Clamp our final speed
	if ( flLength > m_flGroundSpeed )
	{
		vecNewVelocity *= ( m_flGroundSpeed / flLength );
		flLength = m_flGroundSpeed;
	}

	Vector	workVelocity = vecNewVelocity;

	AddSwimNoise( &workVelocity );

	// Pose the fish properly
	SetPoses( SteerRel, flLength );

	//Drag our victim before moving
	if ( m_pVictim != NULL )
	{
		DragVictim( (workVelocity*flInterval).Length() );
	}

	//Move along the current velocity vector
	if ( WalkMove( workVelocity * flInterval, MASK_NPCSOLID ) == false )
	{
		//Attempt a half-step
		if ( WalkMove( (workVelocity*0.5f) * flInterval,  MASK_NPCSOLID) == false )
		{
			//Restart the velocity
			//VectorNormalize( m_vecVelocity );
			vecNewVelocity *= 0.5f;
		}
		else
		{
			//Cut our velocity in half
			vecNewVelocity *= 0.5f;
		}
	}

	SetAbsVelocity( vecNewVelocity );

}

//-----------------------------------------------------------------------------
// Purpose: Gets a steering vector to arrive at a target location with a
//			relatively small velocity.
// Input  : Steer - Receives the ideal steering vector.
//			Target - Target position at which to arrive.
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::SteerArrive(Vector &Steer, const Vector &Target)
{
	Vector Offset = Target - GetLocalOrigin();
	float fTargetDistance = Offset.Length();

	float fIdealSpeed = m_flGroundSpeed * (fTargetDistance / ICH_WAYPOINT_DISTANCE);
	float fClippedSpeed = min( fIdealSpeed, m_flGroundSpeed );
	
	Vector DesiredVelocity( 0, 0, 0 );

	if ( fTargetDistance > ICH_WAYPOINT_DISTANCE )
	{
		DesiredVelocity = (fClippedSpeed / fTargetDistance) * Offset;
	}

	Steer = DesiredVelocity - GetAbsVelocity();
}


//-----------------------------------------------------------------------------
// Purpose: Gets a steering vector to move towards a target position as quickly
//			as possible.
// Input  : Steer - Receives the ideal steering vector.
//			Target - Target position to seek.
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::SteerSeek( Vector &Steer, const Vector &Target )
{
	Vector offset = Target - GetLocalOrigin();
	
	VectorNormalize( offset );
	
	Vector DesiredVelocity = m_flGroundSpeed * offset;
	
	Steer = DesiredVelocity - GetAbsVelocity();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &Steer - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Ichthyosaur::SteerAvoidObstacles(Vector &Steer, const Vector &Velocity, const Vector &Forward, const Vector &Right, const Vector &Up)
{
	trace_t	tr;

	bool	collided = false;
	Vector	dir = Velocity;
	float	speed = VectorNormalize( dir );

	//Look ahead one second and avoid whatever is in our way.
	AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + (dir*speed), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );

	Vector	forward;

	GetVectors( &forward, NULL, NULL );

	//If we're hitting our enemy, just continue on
	if ( ( GetEnemy() != NULL ) && ( tr.m_pEnt == GetEnemy() ) )
		return false;

	if ( tr.fraction < 1.0f )
	{
		CBaseEntity *pBlocker = tr.m_pEnt;
		
		if ( ( pBlocker != NULL ) && ( pBlocker->MyNPCPointer() != NULL ) )
		{
			DevMsg( 2, "Avoiding an NPC\n" );

			Vector HitOffset = tr.endpos - GetAbsOrigin();

			Vector SteerUp = CrossProduct( HitOffset, Velocity );
			Steer = CrossProduct(  SteerUp, Velocity  );
			VectorNormalize( Steer );

			/*Vector probeDir = tr.endpos - GetAbsOrigin();
			Vector normalToProbeAndWallNormal = probeDir.Cross( tr.plane.normal );
			
			Steer = normalToProbeAndWallNormal.Cross( probeDir );
			VectorNormalize( Steer );*/

			if ( tr.fraction > 0 )
			{
				Steer = (Steer * Velocity.Length()) / tr.fraction;
				//NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin()+Steer, 255, 0, 0, false, 0.1f );
			}
			else
			{
				Steer = (Steer * 1000 * Velocity.Length());
				//NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin()+Steer, 255, 0, 0, false, 0.1f );
			}
		}
		else
		{
			if ( ( pBlocker != NULL ) && ( pBlocker == GetEnemy() ) )
			{
				Msg( "Avoided collision\n" );
				return false;
			}

			DevMsg( 2, "Avoiding the world\n" );
			
			Vector	steeringVector = tr.plane.normal;

			if ( tr.fraction == 0.0f )
				return false;

			Steer = steeringVector * ( Velocity.Length() / tr.fraction );
			
			//NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin()+Steer, 255, 0, 0, false, 0.1f );
		}

		//return true;
		collided = true;
	}

	//Try to remain 8 feet above the ground.
	AI_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0, 0, -ICH_HEIGHT_PREFERENCE), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );

	if ( tr.fraction < 1.0f )
	{
		Steer += Vector( 0, 0, m_vecAccelerationMax.z / tr.fraction );
		collided = true;
	}
	
	//Stay under the surface
	if ( m_bIgnoreSurface == false )
	{
		float waterLevel = ( UTIL_WaterLevel( GetAbsOrigin(), GetAbsOrigin().z, GetAbsOrigin().z+ICH_DEPTH_PREFERENCE ) - GetAbsOrigin().z ) / ICH_DEPTH_PREFERENCE;

		if ( waterLevel < 1.0f )
		{
			Steer += -Vector( 0, 0, m_vecAccelerationMax.z / waterLevel );
			collided = true;
		}
	}

	return collided;
}


//-----------------------------------------------------------------------------
// Purpose: Clamps the desired steering vector based on the limitations of this
//			vehicle.
// Input  : SteerAbs - The vector indicating our ideal steering vector. Receives
//				the clamped steering vector in absolute (x,y,z) coordinates.
//			SteerRel - Receives the clamped steering vector in relative (forward, right, up)
//				coordinates.
//			forward - Our current forward vector.
//			right - Our current right vector.
//			up - Our current up vector.
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::ClampSteer(Vector &SteerAbs, Vector &SteerRel, Vector &forward, Vector &right, Vector &up)
{
	float fForwardSteer = DotProduct(SteerAbs, forward);
	float fRightSteer = DotProduct(SteerAbs, right);
	float fUpSteer = DotProduct(SteerAbs, up);

	if (fForwardSteer > 0)
	{
		fForwardSteer = min(fForwardSteer, m_vecAccelerationMax.x);
	}
	else
	{
		fForwardSteer = max(fForwardSteer, m_vecAccelerationMin.x);
	}

	if (fRightSteer > 0)
	{
		fRightSteer = min(fRightSteer, m_vecAccelerationMax.y);
	}
	else
	{
		fRightSteer = max(fRightSteer, m_vecAccelerationMin.y);
	}

	if (fUpSteer > 0)
	{
		fUpSteer = min(fUpSteer, m_vecAccelerationMax.z);
	}
	else
	{
		fUpSteer = max(fUpSteer, m_vecAccelerationMin.z);
	}

	SteerAbs = (fForwardSteer*forward) + (fRightSteer*right) + (fUpSteer*up);

	SteerRel.x = fForwardSteer;
	SteerRel.y = fRightSteer;
	SteerRel.z = fUpSteer;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTargetEnt - 
//			vecDir - 
//			flDistance - 
//			flInterval - 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::MoveFlyExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flDistance, float flInterval )
{
	IchthyosaurMoveType_t eMoveType = ( GetNavigator()->CurWaypointIsGoal() ) ? ICH_MOVETYPE_ARRIVE : ICH_MOVETYPE_SEEK;

	m_flGroundSpeed = GetGroundSpeed();

	Vector	moveGoal = GetNavigator()->GetCurWaypointPos();

	//See if we can move directly to our goal
	if ( ( GetEnemy() != NULL ) && ( GetNavigator()->GetGoalTarget() == (CBaseEntity *) GetEnemy() ) )
	{
		trace_t	tr;
		Vector	goalPos = GetEnemy()->GetAbsOrigin() + ( GetEnemy()->GetSmoothedVelocity() * 0.5f );

		AI_TraceHull( GetAbsOrigin(), goalPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, GetEnemy(), COLLISION_GROUP_NONE, &tr );

		if ( tr.fraction == 1.0f )
		{
			moveGoal = tr.endpos;
		}
	}

	//Move
	DoMovement( flInterval, moveGoal, eMoveType );

	//Save the info from that run
	m_vecLastMoveTarget	= moveGoal;
	m_bHasMoveTarget	= true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pEntity - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Ichthyosaur::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
{
	if ( pEntity->GetFlags() & FL_NOTARGET )
		return false;

	// don't look through water
	if ( GetWaterLevel() != pEntity->GetWaterLevel() )
		return false;

	Vector vecLookerOrigin = EyePosition();//look through the caller's 'eyes'
	Vector vecTargetOrigin = pEntity->EyePosition();

	trace_t tr;
	AI_TraceLine(vecLookerOrigin, vecTargetOrigin, MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr);
	
	// Line of sight is not established
	if ((tr.fraction != 1.0)&&(tr.m_pEnt != pEntity))
	{
		if (ppBlocker)
		{
			*ppBlocker = tr.m_pEnt;
		}
		return false;
	}

	// Line of sight is valid.
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Get our conditions for a melee attack
// Input  : flDot - 
//			flDist - 
// Output : int
//-----------------------------------------------------------------------------
int CNPC_Ichthyosaur::MeleeAttack1Conditions( float flDot, float flDist )
{
	Vector	predictedDir	= ( (GetEnemy()->GetAbsOrigin()+(GetEnemy()->GetSmoothedVelocity())) - GetAbsOrigin() );	
	float	flPredictedDist = VectorNormalize( predictedDir );
	
	Vector	vBodyDir;
	GetVectors( &vBodyDir, NULL, NULL );

	float	flPredictedDot	= DotProduct( predictedDir, vBodyDir );

	if ( flPredictedDot < 0.8f )
		return COND_NOT_FACING_ATTACK;

	if ( ( flPredictedDist > ( GetAbsVelocity().Length() * 0.5f) ) && ( flDist > 128.0f ) )
		return COND_TOO_FAR_TO_ATTACK;

	return COND_CAN_MELEE_ATTACK1;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pEvent - 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::HandleAnimEvent( animevent_t *pEvent )
{
	switch ( pEvent->event )
	{
	case ICH_AE_BITE:
		Bite();
		break;

	case ICH_AE_BITE_START:
		{
			EmitSound( "NPC_Ichthyosaur.AttackGrowl" );
		}
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::Bite( void )
{
	//Don't allow another bite too soon
	if ( m_flNextBiteTime > gpGlobals->curtime )
		return;

	CBaseEntity *pHurt;
	
	//FIXME: E3 HACK - Always damage bullseyes if we're scripted to hit them
	if ( ( GetEnemy() != NULL ) && ( GetEnemy()->Classify() == CLASS_BULLSEYE ) )
	{
		pHurt = GetEnemy();
	}
	else
	{
		pHurt = CheckTraceHullAttack( 108, Vector(-32,-32,-32), Vector(32,32,32), 0, DMG_CLUB );
	}

	//Hit something
	if ( pHurt != NULL )
	{
		CTakeDamageInfo info( this, this, sk_ichthyosaur_melee_dmg.GetInt(), DMG_CLUB );

		if ( pHurt->IsPlayer() )
		{
			CBasePlayer *pPlayer = ToBasePlayer( pHurt );

			if ( pPlayer )
			{
				if ( ( ( m_flHoldTime < gpGlobals->curtime ) && ( pPlayer->m_iHealth < (pPlayer->m_iMaxHealth*0.5f)) ) || ( pPlayer->GetWaterLevel() < 1 ) )
				{
					//EnsnareVictim( pHurt );
				}
				else
				{
					info.SetDamage( sk_ichthyosaur_melee_dmg.GetInt() * 3 );
				}
				CalculateMeleeDamageForce( &info, GetAbsVelocity(), pHurt->GetAbsOrigin() );
				pHurt->TakeDamage( info );

				color32 red = {64, 0, 0, 255};
				UTIL_ScreenFade( pPlayer, red, 0.5, 0, FFADE_IN  );

				//Disorient the player
				QAngle angles = pPlayer->GetLocalAngles();

				angles.x += random->RandomInt( 60, 25 );
				angles.y += random->RandomInt( 60, 25 );
				angles.z = 0.0f;

				pPlayer->SetLocalAngles( angles );

				pPlayer->SnapEyeAngles( angles );
			}
		}
		else
		{
			CalculateMeleeDamageForce( &info, GetAbsVelocity(), pHurt->GetAbsOrigin() );
			pHurt->TakeDamage( info );
		}

		m_flNextBiteTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f );

		//Bubbles!
		UTIL_Bubbles( pHurt->GetAbsOrigin()+Vector(-32.0f,-32.0f,-32.0f), pHurt->GetAbsOrigin()+Vector(32.0f,32.0f,0.0f), random->RandomInt( 16, 32 ) );
		
		// Play a random attack hit sound
		EmitSound( "NPC_Ichthyosaur.Bite" );

		if ( GetActivity() == ACT_MELEE_ATTACK1 )
		{
			SetActivity( (Activity) ACT_ICH_BITE_HIT );
		}
		
		return;
	}

	//Play the miss animation and sound
	if ( GetActivity() == ACT_MELEE_ATTACK1 )
	{
		SetActivity( (Activity) ACT_ICH_BITE_MISS );
	}

	//Miss sound
	EmitSound( "NPC_Ichthyosaur.BiteMiss" );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Ichthyosaur::Beached( void )
{
	trace_t	tr;
	Vector	testPos;

	testPos = GetAbsOrigin() - Vector( 0, 0, ICH_DEPTH_PREFERENCE );
	
	AI_TraceHull( GetAbsOrigin(), testPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );

	return ( tr.fraction < 1.0f );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::PrescheduleThink( void )
{
	BaseClass::PrescheduleThink();
	
	//Ambient sounds
	/*
	if ( random->RandomInt( 0, 20 ) == 10 )
	{
		if ( random->RandomInt( 0, 1 ) )
		{
			ENVELOPE_CONTROLLER.SoundChangeVolume( m_pSwimSound, random->RandomFloat( 0.0f, 0.5f ), 1.0f );
		}
		else
		{
			ENVELOPE_CONTROLLER.SoundChangeVolume( m_pVoiceSound, random->RandomFloat( 0.0f, 0.5f ), 1.0f );
		}
	}
	*/

	//Pings
	if ( m_flNextPingTime < gpGlobals->curtime )
	{
		m_flNextPingTime = gpGlobals->curtime + random->RandomFloat( 3.0f, 8.0f );
	}
	
	//Growls
	if ( ( m_NPCState == NPC_STATE_COMBAT || m_NPCState == NPC_STATE_ALERT ) && ( m_flNextGrowlTime < gpGlobals->curtime ) )
	{
		m_flNextGrowlTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 6.0f );
	}

	//Randomly emit bubbles
	if ( random->RandomInt( 0, 10 ) == 0 )
	{
		UTIL_Bubbles( GetAbsOrigin()+(GetHullMins()*0.5f), GetAbsOrigin()+(GetHullMaxs()*0.5f), 1 );
	}

	//Check our water level
	if ( GetWaterLevel() != 3 )
	{
		if ( GetWaterLevel() < 2 )
		{
			DevMsg( 2, "Came out of water\n" );
			
			if ( Beached() )
			{
				SetSchedule( SCHED_ICH_THRASH );

				Vector vecNewVelocity = GetAbsVelocity();
				vecNewVelocity[2] = 8.0f;
				SetAbsVelocity( vecNewVelocity );
			}
		}
		else
		{
			//TODO: Wake effects
		}
	}

	//If we have a victim, update them
	if ( m_pVictim != NULL )
	{
		//See if it's time to release the victim
		if ( m_flHoldTime < gpGlobals->curtime )
		{
			ReleaseVictim();
			return;
		}

		Bite();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pevInflictor - 
//			*pAttacker - 
//			flDamage - 
//			bitsDamageType - 
//-----------------------------------------------------------------------------
int	CNPC_Ichthyosaur::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	//Release the player if he's struck us while being held
	if ( m_flHoldTime > gpGlobals->curtime )
	{
		ReleaseVictim();
		
		//Don't give them as much time to flee
		m_flNextBiteTime = gpGlobals->curtime + 2.0f;

		SetSchedule( SCHED_ICH_THRASH );
	}

	return BaseClass::OnTakeDamage_Alive( info );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::EnsnareVictim( CBaseEntity *pVictim )
{
	CBaseCombatCharacter* pBCC = (CBaseCombatCharacter *) pVictim;

	if ( pBCC && pBCC->HandleInteraction( g_interactionBarnacleVictimGrab, NULL, this ) )
	{
		if ( pVictim->IsPlayer() )
		{
			CBasePlayer	*pPlayer = dynamic_cast< CBasePlayer * >((CBaseEntity *) pVictim);

			if ( pPlayer )
			{
				m_flHoldTime = max( gpGlobals->curtime+3.0f, pPlayer->PlayerDrownTime() - 2.0f );
			}
		}
		else
		{
			m_flHoldTime	= gpGlobals->curtime + 4.0f;
		}
	
		m_pVictim = pVictim;
		m_pVictim->AddSolidFlags( FSOLID_NOT_SOLID );
		UTIL_Relink( pVictim );

		SetSchedule( SCHED_ICH_DROWN_VICTIM );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::ReleaseVictim( void )
{
	CBaseCombatCharacter *pBCC = (CBaseCombatCharacter *) m_pVictim;

	pBCC->HandleInteraction( g_interactionBarnacleVictimReleased, NULL, this );

	m_pVictim->RemoveSolidFlags( FSOLID_NOT_SOLID );
	UTIL_Relink( m_pVictim );

	m_pVictim			= NULL;
	m_flNextBiteTime	= gpGlobals->curtime + 8.0f;
	m_flHoldTime		= gpGlobals->curtime - 0.1f;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : speed to move at
//-----------------------------------------------------------------------------
float CNPC_Ichthyosaur::GetGroundSpeed( void )
{
	if ( m_flHoldTime > gpGlobals->curtime )
		return	ICH_SWIM_SPEED_WALK/2.0f;

	if ( GetIdealActivity() == ACT_WALK )
		return ICH_SWIM_SPEED_WALK;

	if ( GetIdealActivity() == ACT_ICH_THRASH )
		return ICH_SWIM_SPEED_WALK;

	return ICH_SWIM_SPEED_RUN;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : type - 
// Output : int
//-----------------------------------------------------------------------------
int CNPC_Ichthyosaur::TranslateSchedule( int type )
{
	if ( type == SCHED_CHASE_ENEMY )	return SCHED_ICH_CHASE_ENEMY;
	//if ( type == SCHED_IDLE_STAND )		return SCHED_PATROL_WALK;
	if ( type == SCHED_PATROL_RUN )		return SCHED_ICH_PATROL_RUN;
	if ( type == SCHED_PATROL_WALK )	return SCHED_ICH_PATROL_WALK;
	if ( type == SCHED_MELEE_ATTACK1 )	return SCHED_ICH_MELEE_ATTACK1;

	return BaseClass::TranslateSchedule( type );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTask - 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::StartTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_ICH_THRASH_PATH:
		GetNavigator()->SetMovementActivity( (Activity) ACT_ICH_THRASH );
		TaskComplete();
		break;

	case TASK_ICH_GET_PATH_TO_RANDOM_NODE:
		{
			if ( GetEnemy() == NULL || !GetNavigator()->SetRandomGoal( GetEnemy()->GetLocalOrigin(), pTask->flTaskData ) )
			{
				if (!GetNavigator()->SetRandomGoal( pTask->flTaskData ) )
				{
					TaskFail(FAIL_NO_REACHABLE_NODE);
					return;
				}
			}
					
			TaskComplete();
		}
		break;

	case TASK_ICH_GET_PATH_TO_DROWN_NODE:
		{
			Vector	drownPos = GetLocalOrigin() - Vector( 0, 0, pTask->flTaskData );

			if ( GetNavigator()->SetGoal( drownPos, AIN_CLEAR_TARGET ) == false )
			{
				TaskFail( FAIL_NO_ROUTE );
				return;
			}

			TaskComplete();
		}
		break;

	case TASK_MELEE_ATTACK1:
		m_flPlaybackRate = 1.0f;
		BaseClass::StartTask(pTask);
		break;

	default:
		BaseClass::StartTask(pTask);
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTask - 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::RunTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_ICH_GET_PATH_TO_RANDOM_NODE:
		return;
		break;

	case TASK_ICH_GET_PATH_TO_DROWN_NODE:
		return;
		break;

	default:
		BaseClass::RunTask(pTask);
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : desired yaw speed
//-----------------------------------------------------------------------------
float CNPC_Ichthyosaur::MaxYawSpeed( void )
{
	if ( GetIdealActivity() == ACT_MELEE_ATTACK1 )
		return 16.0f;

	if ( GetIdealActivity() == ACT_ICH_THRASH )
		return 16.0f;

	//Ramp up the yaw speed as we increase our speed
	return ICH_MIN_TURN_SPEED + ( (ICH_MAX_TURN_SPEED-ICH_MIN_TURN_SPEED) * ( fabs(GetAbsVelocity().Length()) / ICH_SWIM_SPEED_RUN ) );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pEnemy - 
//			&chasePosition - 
//			&tolerance - 
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::TranslateEnemyChasePosition( CBaseEntity *pEnemy, Vector &chasePosition, float &tolerance )
{
	Vector offset = pEnemy->EyePosition() - pEnemy->GetAbsOrigin();
	chasePosition += offset;
	tolerance = GetHullWidth()*2.0f;
}


//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------

//==================================================
// SCHED_ICH_CHASE_ENEMY
//==================================================

AI_DEFINE_SCHEDULE
(
	SCHED_ICH_CHASE_ENEMY,

	"	Tasks"
	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_ICH_PATROL_WALK"
	"		TASK_SET_TOLERANCE_DISTANCE		64"
	"		TASK_SET_GOAL					GOAL:ENEMY"
	"		TASK_GET_PATH_TO_GOAL			PATH:TRAVEL"
	"		TASK_RUN_PATH					0"
	"		TASK_WAIT_FOR_MOVEMENT			0"
	""
	"	Interrupts"
	"		COND_NEW_ENEMY"
	"		COND_ENEMY_DEAD"
	"		COND_ENEMY_UNREACHABLE"
	"		COND_CAN_MELEE_ATTACK1"
	"		COND_TOO_CLOSE_TO_ATTACK"
	"		COND_LOST_ENEMY"
	"		COND_TASK_FAILED"
);
	
//==================================================
// SCHED_ICH_PATROL_RUN
//==================================================

AI_DEFINE_SCHEDULE
(
	SCHED_ICH_PATROL_RUN,

	"	Tasks"
	"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_COMBAT_FACE"
	"		TASK_SET_TOLERANCE_DISTANCE			64"
	"		TASK_SET_ROUTE_SEARCH_TIME			4"
	"		TASK_ICH_GET_PATH_TO_RANDOM_NODE	200"
	"		TASK_RUN_PATH						0"
	"		TASK_WAIT_FOR_MOVEMENT				0"
	""
	"	Interrupts"
	"		COND_CAN_MELEE_ATTACK1"
	"		COND_GIVE_WAY"
	"		COND_NEW_ENEMY"
	"		COND_LIGHT_DAMAGE"
	"		COND_HEAVY_DAMAGE"
);

//==================================================
// SCHED_ICH_PATROL_WALK
//==================================================

AI_DEFINE_SCHEDULE
(
	SCHED_ICH_PATROL_WALK,

	"	Tasks"
	"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_COMBAT_FACE"
	"		TASK_SET_TOLERANCE_DISTANCE			64"
	"		TASK_SET_ROUTE_SEARCH_TIME			4"
	"		TASK_ICH_GET_PATH_TO_RANDOM_NODE	200"
	"		TASK_WALK_PATH						0"
	"		TASK_WAIT_FOR_MOVEMENT				0"
	""
	"	Interrupts"
	"		COND_CAN_MELEE_ATTACK1"
	"		COND_GIVE_WAY"
	"		COND_NEW_ENEMY"
	"		COND_LIGHT_DAMAGE"
	"		COND_HEAVY_DAMAGE"
);

//==================================================
// SCHED_ICH_DROWN_VICTIM
//==================================================

AI_DEFINE_SCHEDULE
(
	SCHED_ICH_DROWN_VICTIM,

	"	Tasks"
	"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_COMBAT_FACE"
	"		TASK_SET_TOLERANCE_DISTANCE			64"
	"		TASK_SET_ROUTE_SEARCH_TIME			4"
	"		TASK_ICH_GET_PATH_TO_DROWN_NODE		256"
	"		TASK_WALK_PATH						0"
	"		TASK_WAIT_FOR_MOVEMENT				0"
	""
	"	Interrupts"
	"		COND_NEW_ENEMY"
	"		COND_LIGHT_DAMAGE"
	"		COND_HEAVY_DAMAGE"
);

//=========================================================
// SCHED_ICH_MELEE_ATTACK1
//=========================================================

AI_DEFINE_SCHEDULE
(
	SCHED_ICH_MELEE_ATTACK1,

	"	Tasks"
	"		TASK_ANNOUNCE_ATTACK	1"
	"		TASK_MELEE_ATTACK1		0"
	""
	"	Interrupts"
	"		COND_NEW_ENEMY"
	"		COND_ENEMY_DEAD"
	"		COND_ENEMY_OCCLUDED"
);

//==================================================
// SCHED_ICH_THRASH
//==================================================

AI_DEFINE_SCHEDULE
(
	SCHED_ICH_THRASH,

	"	Tasks"
	"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_COMBAT_FACE"
	"		TASK_SET_TOLERANCE_DISTANCE			64"
	"		TASK_SET_ROUTE_SEARCH_TIME			4"
	"		TASK_ICH_GET_PATH_TO_RANDOM_NODE	64"
	"		TASK_ICH_THRASH_PATH				0"
	"		TASK_WAIT_FOR_MOVEMENT				0"
	""
	"	Interrupts"
);