summaryrefslogtreecommitdiff
path: root/game/server/hl1/hl1_npc_ichthyosaur.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/hl1/hl1_npc_ichthyosaur.cpp')
-rw-r--r--game/server/hl1/hl1_npc_ichthyosaur.cpp1024
1 files changed, 1024 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_ichthyosaur.cpp b/game/server/hl1/hl1_npc_ichthyosaur.cpp
new file mode 100644
index 0000000..1e17a2d
--- /dev/null
+++ b/game/server/hl1/hl1_npc_ichthyosaur.cpp
@@ -0,0 +1,1024 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Ichthyosaur - buh bum... buh bum...
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "beam_shared.h"
+#include "ai_default.h"
+#include "ai_task.h"
+#include "ai_schedule.h"
+#include "ai_node.h"
+#include "ai_hull.h"
+#include "ai_hint.h"
+#include "ai_memory.h"
+#include "ai_route.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 "hl1_npc_ichthyosaur.h"
+
+ConVar sk_ichthyosaur_health ( "sk_ichthyosaur_health", "0" );
+ConVar sk_ichthyosaur_shake ( "sk_ichthyosaur_shake", "0" );
+
+#define ICH_SWIM_SPEED_WALK 150
+#define ICH_SWIM_SPEED_RUN 400
+#define PROBE_LENGTH 150
+
+enum IchthyosaurMoveType_t
+{
+ ICH_MOVETYPE_SEEK = 0, // Fly through the target without stopping.
+ ICH_MOVETYPE_ARRIVE // Slow down and stop at target.
+};
+
+enum
+{
+ SCHED_SWIM_AROUND = LAST_SHARED_SCHEDULE + 1,
+ SCHED_SWIM_AGITATED,
+ SCHED_CIRCLE_ENEMY,
+ SCHED_TWITCH_DIE,
+};
+
+
+//=========================================================
+// monster-specific tasks and states
+//=========================================================
+enum
+{
+ TASK_ICHTHYOSAUR_CIRCLE_ENEMY = LAST_SHARED_TASK + 1,
+ TASK_ICHTHYOSAUR_SWIM,
+ TASK_ICHTHYOSAUR_FLOAT,
+};
+
+
+LINK_ENTITY_TO_CLASS( monster_ichthyosaur, CNPC_Ichthyosaur );
+
+BEGIN_DATADESC( CNPC_Ichthyosaur )
+
+ // Function Pointers
+ DEFINE_ENTITYFUNC( BiteTouch ),
+
+ DEFINE_FIELD( m_SaveVelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_idealDist, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flBlink, FIELD_TIME ),
+ DEFINE_FIELD( m_flEnemyTouched, FIELD_TIME ),
+ DEFINE_FIELD( m_bOnAttack, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flMaxSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flMinSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flMaxDist, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flNextAlert, FIELD_TIME ),
+ DEFINE_FIELD( m_flMaxSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecLastMoveTarget, FIELD_VECTOR ),
+ DEFINE_FIELD( m_bHasMoveTarget, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flFlyingSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flLastAttackSound, FIELD_TIME ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartCombat", InputStartCombat ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EndCombat", InputEndCombat ),
+
+END_DATADESC()
+
+//=========================================================
+// AI Schedules Specific to this monster
+//=========================================================
+
+
+AI_BEGIN_CUSTOM_NPC( monster_ichthyosaur, CNPC_Ichthyosaur )
+
+DECLARE_TASK ( TASK_ICHTHYOSAUR_SWIM )
+DECLARE_TASK ( TASK_ICHTHYOSAUR_CIRCLE_ENEMY )
+DECLARE_TASK ( TASK_ICHTHYOSAUR_FLOAT )
+
+ //=========================================================
+ // > SCHED_SWIM_AROUND
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SWIM_AROUND,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_GLIDE"
+ " TASK_ICHTHYOSAUR_SWIM 0.0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_SEE_ENEMY"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_PLAYER"
+ " COND_HEAR_COMBAT"
+ )
+ //=========================================================
+ // > SCHED_SWIM_AGITATED
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SWIM_AGITATED,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_SWIM"
+ " TASK_WAIT 2.0"
+ " "
+ )
+ //=========================================================
+ // > SCHED_CIRCLE_ENEMY
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CIRCLE_ENEMY,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_GLIDE"
+ " TASK_ICHTHYOSAUR_CIRCLE_ENEMY 0.0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK1"
+ )
+ //=========================================================
+ // > SCHED_TWITCH_DIE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TWITCH_DIE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SOUND_DIE 0"
+ " TASK_DIE 0"
+ " TASK_ICHTHYOSAUR_FLOAT 0"
+ " "
+ )
+
+AI_END_CUSTOM_NPC()
+
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Ichthyosaur::Precache()
+{
+ PrecacheModel("models/icky.mdl");
+
+ PrecacheModel("sprites/lgtning.vmt");
+
+ PrecacheScriptSound( "Ichthyosaur.Bite" );
+ PrecacheScriptSound( "Ichthyosaur.Alert" );
+ PrecacheScriptSound( "Ichthyosaur.Pain" );
+ PrecacheScriptSound( "Ichthyosaur.Die" );
+ PrecacheScriptSound( "Ichthyosaur.Idle" );
+ PrecacheScriptSound( "Ichthyosaur.Attack" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Ichthyosaur::Spawn( void )
+{
+ Precache( );
+
+ SetModel( "models/icky.mdl");
+ UTIL_SetSize( this, Vector( -32, -32, -32 ), Vector( 32, 32, 32 ) );
+
+ SetHullType(HULL_LARGE_CENTERED);
+ SetHullSizeNormal();
+ SetDefaultEyeOffset();
+
+ // Use our hitboxes to determine our render bounds
+ CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
+
+ SetNavType( NAV_FLY );
+ m_NPCState = NPC_STATE_NONE;
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ AddFlag( FL_FLY | FL_STEPMOVEMENT );
+
+ m_flGroundSpeed = ICH_SWIM_SPEED_RUN;
+
+ m_bloodColor = BLOOD_COLOR_YELLOW;
+ m_iHealth = sk_ichthyosaur_health.GetFloat();
+ m_iMaxHealth = m_iHealth;
+ m_flFieldOfView = -0.707; // 270 degrees
+
+ AddFlag( FL_SWIM );
+
+ m_flFlyingSpeed = ICHTHYOSAUR_SPEED;
+
+ SetDistLook( 1024 );
+
+
+ SetTouch( &CNPC_Ichthyosaur::BiteTouch );
+
+ m_idealDist = 384;
+ m_flMinSpeed = 80;
+ m_flMaxSpeed = 400;
+ m_flMaxDist = 384;
+ m_flLastAttackSound = gpGlobals->curtime;
+
+ Vector vforward;
+ AngleVectors(GetAbsAngles(), &vforward );
+ VectorNormalize ( vforward );
+ SetAbsVelocity( m_flFlyingSpeed * vforward );
+ m_SaveVelocity = GetAbsVelocity();
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK1 );
+
+ m_bOnAttack = false;
+
+ NPCInit();
+}
+
+
+//=========================================================
+//=========================================================
+int CNPC_Ichthyosaur::TranslateSchedule( int scheduleType )
+{
+ switch ( scheduleType )
+ {
+ case SCHED_IDLE_WALK:
+ return SCHED_SWIM_AROUND;
+ case SCHED_STANDOFF:
+ return SCHED_CIRCLE_ENEMY;
+ case SCHED_FAIL:
+ return SCHED_SWIM_AGITATED;
+ case SCHED_DIE:
+ return SCHED_TWITCH_DIE;
+ case SCHED_CHASE_ENEMY:
+
+ if ( m_flLastAttackSound < gpGlobals->curtime )
+ {
+ AttackSound();
+ m_flLastAttackSound = gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f );
+ }
+
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+int CNPC_Ichthyosaur::SelectSchedule()
+{
+ switch(m_NPCState)
+ {
+ case NPC_STATE_IDLE:
+ m_flFlyingSpeed = 80;
+ m_flMaxSpeed = 80;
+ return TranslateSchedule( SCHED_IDLE_WALK );
+
+ case NPC_STATE_ALERT:
+ m_flFlyingSpeed = 150;
+ m_flMaxSpeed = 150;
+ return TranslateSchedule( SCHED_IDLE_WALK );
+
+ case NPC_STATE_COMBAT:
+ m_flMaxSpeed = 400;
+
+ // eat them
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ return TranslateSchedule( SCHED_MELEE_ATTACK1 );
+ }
+
+ // chase them down and eat them
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ return TranslateSchedule( SCHED_CHASE_ENEMY );
+ }
+
+ if ( HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ m_bOnAttack = true;
+ }
+
+ if ( GetHealth() < GetMaxHealth() - 20 )
+ {
+ m_bOnAttack = true;
+ }
+
+ return TranslateSchedule( SCHED_STANDOFF );
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+
+bool CNPC_Ichthyosaur::OverrideMove( float flInterval )
+{
+ if ( m_lifeState == LIFE_ALIVE )
+ {
+ if ( m_NPCState != NPC_STATE_SCRIPT)
+ {
+ MoveExecute_Alive( flInterval );
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Ichthyosaur::MoveExecute_Alive(float flInterval)
+{
+ Vector vStart = GetAbsOrigin();
+ Vector vForward, vRight, vUp;
+
+ if (GetNavigator()->IsGoalActive())
+ {
+ Vector vecDir = ( GetNavigator()->GetPath()->CurWaypointPos() - GetAbsOrigin());
+ VectorNormalize( vecDir );
+
+ m_SaveVelocity = vecDir * m_flFlyingSpeed;
+ }
+
+ // If we're attacking, accelerate to max speed
+ if (m_bOnAttack && m_flFlyingSpeed < m_flMaxSpeed)
+ {
+ m_flFlyingSpeed = MIN( m_flMaxSpeed, m_flFlyingSpeed+40 );
+ }
+
+ if (m_flFlyingSpeed < 180)
+ {
+ if (GetIdealActivity() == ACT_SWIM)
+ SetActivity( ACT_GLIDE );
+ if (GetIdealActivity() == ACT_GLIDE)
+ m_flPlaybackRate = m_flFlyingSpeed / 150.0;
+ }
+ else
+ {
+ if (GetIdealActivity() == ACT_GLIDE)
+ SetActivity( ACT_SWIM );
+ if (GetIdealActivity() == ACT_SWIM)
+ m_flPlaybackRate = m_flFlyingSpeed / 300.0;
+ }
+
+ // Steering
+ QAngle angSaveAngles;
+ VectorAngles( m_SaveVelocity, angSaveAngles );
+ AngleVectors(angSaveAngles, &vForward, &vRight, &vUp);
+
+ Vector z;
+ float frac;
+
+ Vector f, u, l, r, d;
+ f = DoProbe(vStart + (PROBE_LENGTH * vForward) );
+ r = DoProbe(vStart + ((PROBE_LENGTH/3) * (vForward + vRight)) );
+ l = DoProbe(vStart + ((PROBE_LENGTH/3) * (vForward - vRight)) );
+ u = DoProbe(vStart + ((PROBE_LENGTH/3) * (vForward + vUp)) );
+ d = DoProbe(vStart + ((PROBE_LENGTH/3) * (vForward - vUp)) );
+
+ Vector SteeringVector = f+r+l+u+d;
+
+ if( ProbeZ( vStart + vForward*50, vUp*50, &frac ) )
+ {
+ // reflect off the water surface
+ m_SaveVelocity.z = -10;
+ }
+
+ m_SaveVelocity += SteeringVector/32;
+ VectorNormalize( m_SaveVelocity );
+
+ angSaveAngles = GetAbsAngles();
+
+ AngleVectors( angSaveAngles, &vForward, &vRight, &vUp );
+
+ float flDot = DotProduct( vForward, m_SaveVelocity );
+ if (flDot > 0.5)
+ m_SaveVelocity = m_SaveVelocity * m_flFlyingSpeed;
+ else if (flDot > 0)
+ m_SaveVelocity = m_SaveVelocity * m_flFlyingSpeed * (flDot + 0.5);
+ else
+ m_SaveVelocity = m_SaveVelocity * 80;
+
+ SetAbsVelocity( m_SaveVelocity );
+
+ VectorAngles( m_SaveVelocity, angSaveAngles );
+
+ //
+ // Smooth Pitch
+ //
+ if (angSaveAngles.x > 180)
+ angSaveAngles.x = angSaveAngles.x - 360;
+
+ QAngle angAbsAngles = GetAbsAngles();
+
+ angAbsAngles.x = clamp( UTIL_Approach(angSaveAngles.x, angAbsAngles.x, 10 ), -60, 60 );
+
+ //
+ // Smooth Yaw and generate Roll
+ //
+ float turn = 360;
+
+ if (fabs(angSaveAngles.y - angAbsAngles.y) < fabs(turn))
+ {
+ turn = angSaveAngles.y - angAbsAngles.y;
+ }
+ if (fabs(angSaveAngles.y - angAbsAngles.y + 360) < fabs(turn))
+ {
+ turn = angSaveAngles.y - angAbsAngles.y + 360;
+ }
+ if (fabs(angSaveAngles.y - angAbsAngles.y - 360) < fabs(turn))
+ {
+ turn = angSaveAngles.y - angAbsAngles.y - 360;
+ }
+
+ float speed = m_flFlyingSpeed * 0.4;
+
+ if (fabs(turn) > speed)
+ {
+ if (turn < 0.0)
+ {
+ turn = -speed;
+ }
+ else
+ {
+ turn = speed;
+ }
+ }
+ angAbsAngles.y += turn;
+ angAbsAngles.z -= turn;
+ angAbsAngles.y = fmod((angAbsAngles.y + 360.0), 360.0);
+
+ // don't touch bone controller, makes swim animation look funky with all these hard turns.
+// static float yaw_adj;
+// yaw_adj = yaw_adj * 0.8 + turn;
+// SetBoneController( 0, -yaw_adj / 4.0 );
+
+ //
+ // Roll Smoothing
+ //
+ turn = 360;
+ float flTempRoll = angAbsAngles.z;
+
+ if (fabs(angSaveAngles.z - angAbsAngles.z) < fabs(turn))
+ {
+ turn = angSaveAngles.z - angAbsAngles.z;
+ }
+ if (fabs(angSaveAngles.z - angAbsAngles.z + 360) < fabs(turn))
+ {
+ turn = angSaveAngles.z - angAbsAngles.z + 360;
+ }
+ if (fabs(angSaveAngles.z - angAbsAngles.z - 360) < fabs(turn))
+ {
+ turn = angSaveAngles.z - angAbsAngles.z - 360;
+ }
+ speed = m_flFlyingSpeed/2 * 0.1;
+ if (fabs(turn) < speed)
+ {
+ flTempRoll += turn;
+ }
+ else
+ {
+ if (turn < 0.0)
+ {
+ flTempRoll -= speed;
+ }
+ else
+ {
+ flTempRoll += speed;
+ }
+ }
+
+ angAbsAngles.z = clamp( UTIL_Approach(flTempRoll, angAbsAngles.z, 5 ), -20, 20 );
+
+ SetAbsAngles( angAbsAngles );
+
+ //Move along the current velocity vector
+ if ( WalkMove( m_SaveVelocity * flInterval, MASK_NPCSOLID ) == false )
+ {
+ //Attempt a half-step
+ if ( WalkMove( (m_SaveVelocity*0.5f) * flInterval, MASK_NPCSOLID) == false )
+ {
+ //Restart the velocity
+ //VectorNormalize( m_vecVelocity );
+ m_SaveVelocity *= 0.25f;
+ }
+ else
+ {
+ //Cut our velocity in half
+ m_SaveVelocity *= 0.5f;
+ }
+ }
+
+ SetAbsVelocity( m_SaveVelocity );
+}
+
+void CNPC_Ichthyosaur::InputStartCombat( inputdata_t &input )
+{
+ m_bOnAttack = true;
+}
+
+void CNPC_Ichthyosaur::InputEndCombat( inputdata_t &input )
+{
+ m_bOnAttack = false;
+}
+
+//=========================================================
+// Start task - selects the correct activity and performs
+// any necessary calculations to start the next task on the
+// schedule.
+//=========================================================
+void CNPC_Ichthyosaur::StartTask(const Task_t *pTask)
+{
+ switch (pTask->iTask)
+ {
+ case TASK_ICHTHYOSAUR_CIRCLE_ENEMY:
+ break;
+ case TASK_ICHTHYOSAUR_SWIM:
+ break;
+ case TASK_SMALL_FLINCH:
+ if (m_idealDist > 128)
+ {
+ m_flMaxDist = 512;
+ m_idealDist = 512;
+ }
+ else
+ {
+ m_bOnAttack = true;
+ }
+ BaseClass::StartTask(pTask);
+ break;
+
+ case TASK_ICHTHYOSAUR_FLOAT:
+ m_nSkin = EYE_BASE;
+ SetSequenceByName( "bellyup" );
+ break;
+
+ default:
+ BaseClass::StartTask(pTask);
+ break;
+ }
+}
+
+void CNPC_Ichthyosaur::RunTask(const Task_t *pTask )
+{
+ QAngle angles = GetAbsAngles();
+
+ switch ( pTask->iTask )
+ {
+ case TASK_ICHTHYOSAUR_CIRCLE_ENEMY:
+
+ if (GetEnemy() == NULL )
+ {
+ TaskComplete( );
+ }
+ else if (FVisible( GetEnemy() ))
+ {
+ Vector vecFrom = GetEnemy()->EyePosition( );
+
+ Vector vecDelta = GetAbsOrigin() - vecFrom;
+ VectorNormalize( vecDelta );
+ Vector vecSwim = CrossProduct( vecDelta, Vector( 0, 0, 1 ) );
+ VectorNormalize( vecSwim );
+
+ if (DotProduct( vecSwim, m_SaveVelocity ) < 0)
+ {
+ vecSwim = vecSwim * -1.0;
+ }
+
+ Vector vecPos = vecFrom + vecDelta * m_idealDist + vecSwim * 32;
+
+ trace_t tr;
+
+// UTIL_TraceHull( vecFrom, vecPos, ignore_monsters, large_hull, m_hEnemy->edict(), &tr );
+ UTIL_TraceEntity( this, vecFrom, vecPos, MASK_NPCSOLID, &tr );
+
+ if (tr.fraction > 0.5)
+ {
+ vecPos = tr.endpos;
+ }
+
+ Vector vecNorm = vecPos - GetAbsOrigin();
+ VectorNormalize( vecNorm );
+ m_SaveVelocity = m_SaveVelocity * 0.8 + 0.2 * vecNorm * m_flFlyingSpeed;
+
+ if (HasCondition( COND_ENEMY_FACING_ME ) && GetEnemy()->FVisible( this ))
+ {
+ m_flNextAlert -= 0.1;
+
+ if (m_idealDist < m_flMaxDist)
+ {
+ m_idealDist += 4;
+ }
+ if (m_flFlyingSpeed > m_flMinSpeed)
+ {
+ m_flFlyingSpeed -= 2;
+ }
+ else if (m_flFlyingSpeed < m_flMinSpeed)
+ {
+ m_flFlyingSpeed += 2;
+ }
+ if (m_flMinSpeed < m_flMaxSpeed)
+ {
+ m_flMinSpeed += 0.5;
+ }
+ }
+ else
+ {
+ m_flNextAlert += 0.1;
+
+ if (m_idealDist > 128)
+ {
+ m_idealDist -= 4;
+ }
+ if (m_flFlyingSpeed < m_flMaxSpeed)
+ {
+ m_flFlyingSpeed += 4;
+ }
+ }
+ }
+ else
+ {
+ m_flNextAlert = gpGlobals->curtime + 0.2;
+ }
+
+ if (m_flNextAlert < gpGlobals->curtime)
+ {
+ // ALERT( at_console, "AlertSound()\n");
+ AlertSound( );
+ m_flNextAlert = gpGlobals->curtime + RandomFloat( 3, 5 );
+ }
+
+ break;
+ case TASK_ICHTHYOSAUR_SWIM:
+ if ( IsSequenceFinished() )
+ {
+ TaskComplete( );
+ }
+ break;
+ case TASK_DIE:
+ if ( IsSequenceFinished() )
+ {
+// pev->deadflag = DEAD_DEAD;
+
+ TaskComplete( );
+ }
+ break;
+
+ case TASK_ICHTHYOSAUR_FLOAT:
+ angles.x = UTIL_ApproachAngle( 0, angles.x, 20 );
+ SetAbsAngles( angles );
+
+// SetAbsVelocity( GetAbsVelocity() * 0.8 );
+// if (pev->waterlevel > 1 && GetAbsVelocity().z < 64)
+// {
+// pev->velocity.z += 8;
+// }
+// else
+// {
+// pev->velocity.z -= 8;
+// }
+ // ALERT( at_console, "%f\n", m_vecAbsVelocity.z );
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get our conditions for a melee attack
+// Input : flDot -
+// flDist -
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Ichthyosaur::MeleeAttack1Conditions( float flDot, float flDist )
+{
+ // Enemy must be submerged with us
+ if ( GetEnemy() && GetEnemy()->GetWaterLevel() != GetWaterLevel() )
+ return COND_NONE;
+
+ 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;
+}
+
+//=========================================================
+// RangeAttack1Conditions
+//=========================================================
+int CNPC_Ichthyosaur::RangeAttack1Conditions( float flDot, float flDist )
+{
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if( pEnemy && pEnemy->GetWaterLevel() != GetWaterLevel() )
+ {
+ return COND_NONE;
+ }
+
+ if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 192 && m_idealDist <= 192)))
+ {
+ return COND_CAN_RANGE_ATTACK1;
+ }
+
+ return COND_NONE;
+}
+
+void CNPC_Ichthyosaur::BiteTouch( CBaseEntity *pOther )
+{
+ // bite if we hit who we want to eat
+ if ( pOther == GetEnemy() )
+ {
+ m_flEnemyTouched = gpGlobals->curtime + 0.2f;
+ m_bOnAttack = true;
+ }
+}
+
+#define ICHTHYOSAUR_AE_SHAKE_RIGHT 1
+#define ICHTHYOSAUR_AE_SHAKE_LEFT 2
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_Ichthyosaur::HandleAnimEvent( animevent_t *pEvent )
+{
+ int bDidAttack = FALSE;
+ Vector vForward, vRight;
+ QAngle angles = GetAbsAngles();
+ AngleVectors( angles, &vForward, &vRight, NULL );
+
+ switch( pEvent->event )
+ {
+ case ICHTHYOSAUR_AE_SHAKE_RIGHT:
+ case ICHTHYOSAUR_AE_SHAKE_LEFT:
+ {
+ CBaseEntity* hEnemy = GetEnemy();
+
+ if (hEnemy != NULL && FVisible( hEnemy ))
+ {
+ CBaseEntity *pHurt = GetEnemy();
+
+ if ( m_flEnemyTouched > gpGlobals->curtime && (pHurt->BodyTarget( GetAbsOrigin() ) - GetAbsOrigin()).Length() > (32+16+32) )
+ break;
+
+ Vector vecShootOrigin = Weapon_ShootPosition();
+ Vector vecShootDir = GetShootEnemyDir( vecShootOrigin );
+
+ if (DotProduct( vecShootDir, vForward ) > 0.707)
+ {
+ angles = pHurt->GetAbsAngles();
+ m_bOnAttack = true;
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() - vRight * 300 );
+ if (pHurt->IsPlayer())
+ {
+ angles.x += RandomFloat( -35, 35 );
+ angles.y += RandomFloat( -90, 90 );
+ angles.z = 0;
+ ((CBasePlayer*) pHurt)->SetPunchAngle( angles );
+ }
+
+ CTakeDamageInfo info( this, this, sk_ichthyosaur_shake.GetInt(), DMG_SLASH );
+ CalculateMeleeDamageForce( &info, vForward, pHurt->GetAbsOrigin() );
+ pHurt->TakeDamage( info );
+ }
+ }
+
+ // Do our bite sound
+ BiteSound();
+
+ bDidAttack = TRUE;
+ }
+ break;
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+ // make bubbles when he attacks
+ if (bDidAttack)
+ {
+ Vector vecSrc = GetAbsOrigin() + vForward * 32;
+ UTIL_Bubbles( vecSrc - Vector( 8, 8, 8 ), vecSrc + Vector( 8, 8, 8 ), 16 );
+ }
+}
+
+//=========================================================
+// Classify - indicates this monster's place in the
+// relationship table.
+//=========================================================
+Class_T CNPC_Ichthyosaur::Classify ( void )
+{
+ return CLASS_ALIEN_MONSTER;
+}
+
+void CNPC_Ichthyosaur::NPCThink ( void )
+{
+ // blink the eye
+ if (m_flBlink < gpGlobals->curtime)
+ {
+ m_nSkin = EYE_CLOSED;
+ if (m_flBlink + 0.2 < gpGlobals->curtime)
+ {
+ m_flBlink = gpGlobals->curtime + random->RandomFloat( 3, 4 );
+ if (m_bOnAttack)
+ m_nSkin = EYE_MAD;
+ else
+ m_nSkin = EYE_BASE;
+ }
+ }
+
+ BaseClass::NPCThink( );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : speed to move at
+//-----------------------------------------------------------------------------
+float CNPC_Ichthyosaur::GetGroundSpeed( void )
+{
+ if ( GetIdealActivity() == ACT_GLIDE )
+ return ICH_SWIM_SPEED_WALK;
+
+ return ICH_SWIM_SPEED_RUN;
+}
+
+Vector CNPC_Ichthyosaur::DoProbe( const Vector &Probe )
+{
+ Vector WallNormal = Vector(0,0,-1); // WATER normal is Straight Down for fish.
+ float frac = 1.0;
+ bool bBumpedSomething = false; // = ProbeZ(GetAbsOrigin(), Probe, &frac);
+
+ trace_t tr;
+ UTIL_TraceEntity( this, GetAbsOrigin(), Probe, MASK_NPCSOLID, &tr );
+ if ( tr.allsolid || tr.fraction < 0.99 )
+ {
+ if (tr.fraction < 0.0) tr.fraction = 0.0;
+ if (tr.fraction > 1.0) tr.fraction = 1.0;
+ if (tr.fraction < frac)
+ {
+ frac = tr.fraction;
+ bBumpedSomething = true;
+ WallNormal = tr.plane.normal;
+ }
+ }
+ //NOTENOTE: Debug start
+ //NDebugOverlay::Line( tr.startpos, tr.endpos, 255.0f*(1.0f-tr.fraction), 255.0f * tr.fraction, 0.0f, true, 0.05f );
+ //NOTENOTE: Debug end
+
+ if (bBumpedSomething && (GetEnemy() == NULL || !tr.m_pEnt || tr.m_pEnt->entindex() != GetEnemy()->entindex()))
+ {
+ Vector ProbeDir = Probe - GetAbsOrigin();
+
+ Vector NormalToProbeAndWallNormal = CrossProduct(ProbeDir, WallNormal);
+ Vector SteeringVector = CrossProduct( NormalToProbeAndWallNormal, ProbeDir);
+
+ VectorNormalize( WallNormal );
+ VectorNormalize( m_SaveVelocity );
+
+ float SteeringForce = m_flFlyingSpeed * (1-frac) * ( DotProduct( WallNormal, m_SaveVelocity ) );
+ if (SteeringForce < 0.0)
+ {
+ SteeringForce = -SteeringForce;
+ }
+
+ Vector vSteering = SteeringVector;
+
+ VectorNormalize( vSteering );
+ SteeringVector = SteeringForce * vSteering;
+
+ //NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (SteeringVector*4.0f), 0, 255, 255, true, 0.1f );
+
+ return SteeringVector;
+ }
+ return Vector(0, 0, 0);
+}
+
+bool CNPC_Ichthyosaur::ProbeZ( const Vector &position, const Vector &probe, float *pFraction)
+{
+ int iPositionContents = UTIL_PointContents( position );
+ int iProbeContents = UTIL_PointContents( position );
+
+ if( !(iPositionContents & MASK_WATER) )
+ {
+ // we're not in the water anymore
+ *pFraction = 0.0;
+ return true; // We hit a water boundary because we are where we don't belong.
+ }
+ if( iProbeContents == iPositionContents )
+ {
+ // The probe is entirely inside the water
+ *pFraction = 1.0;
+ return false;
+ }
+
+ Vector ProbeUnit = (probe-position);
+ VectorNormalize( ProbeUnit );
+ float ProbeLength = (probe-position).Length();
+ float maxProbeLength = ProbeLength;
+ float minProbeLength = 0;
+
+ float diff = maxProbeLength - minProbeLength;
+ while (diff > 1.0)
+ {
+ float midProbeLength = minProbeLength + diff/2.0;
+ Vector midProbeVec = midProbeLength * ProbeUnit;
+ if (UTIL_PointContents(position+midProbeVec) == iPositionContents)
+ {
+ minProbeLength = midProbeLength;
+ }
+ else
+ {
+ maxProbeLength = midProbeLength;
+ }
+ diff = maxProbeLength - minProbeLength;
+ }
+ *pFraction = minProbeLength/ProbeLength;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEntity -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Ichthyosaur::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ // Can't see entities that aren't in water
+ if ( pEntity->GetWaterLevel() < 1 )
+ return false;
+
+ return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
+}
+
+void CNPC_Ichthyosaur::IdleSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Idle" );
+}
+
+void CNPC_Ichthyosaur::AlertSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Alert" );
+}
+
+void CNPC_Ichthyosaur::AttackSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Attack" );
+}
+
+void CNPC_Ichthyosaur::BiteSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Bite" );
+}
+
+void CNPC_Ichthyosaur::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Die" );
+}
+
+void CNPC_Ichthyosaur::PainSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Pain" );
+}
+
+//-----------------------------------------------------------------------------
+void CNPC_Ichthyosaur::GatherEnemyConditions( CBaseEntity *pEnemy )
+{
+ // Do the base class
+ BaseClass::GatherEnemyConditions( pEnemy );
+
+ if ( HasCondition( COND_ENEMY_UNREACHABLE ) == false )
+ {
+ if( pEnemy == NULL || pEnemy->GetWaterLevel() != GetWaterLevel() )
+ {
+ SetCondition( COND_ENEMY_UNREACHABLE );
+ }
+ }
+}