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