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