diff options
Diffstat (limited to 'game/server/hl1/hl1_npc_ichthyosaur.cpp')
| -rw-r--r-- | game/server/hl1/hl1_npc_ichthyosaur.cpp | 1024 |
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 ); + } + } +} |