aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_ichthyosaur.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/hl2/npc_ichthyosaur.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/hl2/npc_ichthyosaur.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_ichthyosaur.cpp1558
1 files changed, 1558 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/npc_ichthyosaur.cpp b/mp/src/game/server/hl2/npc_ichthyosaur.cpp
new file mode 100644
index 00000000..72290476
--- /dev/null
+++ b/mp/src/game/server/hl2/npc_ichthyosaur.cpp
@@ -0,0 +1,1558 @@
+//========= Copyright Valve Corporation, 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 "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "movevars_shared.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.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 TranslateNavGoal( CBaseEntity *pEnemy, Vector &chasePosition );
+ float GetDefaultNavGoalTolerance();
+
+ float MaxYawSpeed( void );
+
+ Class_T Classify( void ) { return CLASS_ANTLION; } //FIXME: No classification for various wildlife?
+
+ bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, 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( m_LastSteer, FIELD_VECTOR ),
+
+ DEFINE_FIELD( m_pVictim, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_vecLastMoveTarget, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flNextBiteTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flHoldTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flSwimSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTailYaw, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTailPitch, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flNextPingTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flNextGrowlTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_bHasMoveTarget, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bIgnoreSurface, FIELD_BOOLEAN ),
+
+ //DEFINE_FUNCTION( 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 )
+{
+ PrecacheModel( ICHTHYOSAUR_MODEL );
+
+ PrecacheScriptSound( "NPC_Ichthyosaur.Bite" );
+ PrecacheScriptSound( "NPC_Ichthyosaur.BiteMiss" );
+ PrecacheScriptSound( "NPC_Ichthyosaur.AttackGrowl" );
+
+ 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, "xxxCONVERTTOGAMESOUNDS!!!npc/ichthyosaur/ich_amb1wav", ATTN_NORM );
+ //m_pVoiceSound = ENVELOPE_CONTROLLER.SoundCreate( edict(), CHAN_VOICE, "xxxCONVERTTOGAMESOUNDS!!!npc/ichthyosaur/water_breathwav", 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() ) )
+ {
+ DevMsg( "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 )
+{
+ // don't look through water
+ if ( GetWaterLevel() != pEntity->GetWaterLevel() )
+ return false;
+
+ return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
+}
+
+//-----------------------------------------------------------------------------
+// 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->DispatchInteraction( 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 );
+
+ SetSchedule( SCHED_ICH_DROWN_VICTIM );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Ichthyosaur::ReleaseVictim( void )
+{
+ CBaseCombatCharacter *pBCC = (CBaseCombatCharacter *) m_pVictim;
+
+ pBCC->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this );
+
+ m_pVictim->RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ 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::TranslateNavGoal( CBaseEntity *pEnemy, Vector &chasePosition )
+{
+ Vector offset = pEnemy->EyePosition() - pEnemy->GetAbsOrigin();
+ chasePosition += offset;
+}
+
+float CNPC_Ichthyosaur::GetDefaultNavGoalTolerance()
+{
+ return 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"
+);