Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1024 lines
25 KiB

//========= 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 );
}
}
}