aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_basescanner.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/hl2/npc_basescanner.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/hl2/npc_basescanner.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_basescanner.cpp1847
1 files changed, 1847 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/npc_basescanner.cpp b/mp/src/game/server/hl2/npc_basescanner.cpp
new file mode 100644
index 00000000..2fcf54b2
--- /dev/null
+++ b/mp/src/game/server/hl2/npc_basescanner.cpp
@@ -0,0 +1,1847 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "npcevent.h"
+#include "ai_basenpc_physicsflyer.h"
+#include "weapon_physcannon.h"
+#include "hl2_player.h"
+#include "npc_scanner.h"
+#include "IEffects.h"
+#include "explode.h"
+#include "ai_route.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar g_debug_basescanner( "g_debug_basescanner", "0", FCVAR_CHEAT );
+
+BEGIN_DATADESC( CNPC_BaseScanner )
+ DEFINE_EMBEDDED( m_KilledInfo ),
+ DEFINE_SOUNDPATCH( m_pEngineSound ),
+
+ DEFINE_FIELD( m_flFlyNoiseBase, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flEngineStallTime, FIELD_TIME ),
+ DEFINE_FIELD( m_fNextFlySoundTime, FIELD_TIME ),
+ DEFINE_FIELD( m_nFlyMode, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_vecDiveBombDirection, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flDiveBombRollForce, FIELD_FLOAT ),
+
+ // Physics Influence
+ DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_flGoalOverrideDistance, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_flAttackNearDist, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flAttackFarDist, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flAttackRange, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_nPoseTail, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nPoseDynamo, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nPoseFlare, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nPoseFaceVert, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nPoseFaceHoriz, FIELD_INTEGER ),
+
+ // DEFINE_FIELD( m_bHasSpoken, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_pSmokeTrail, FIELD_CLASSPTR ),
+
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDistanceOverride", InputSetDistanceOverride ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetFlightSpeed", InputSetFlightSpeed ),
+
+ DEFINE_THINKFUNC( DiveBombSoundThink ),
+END_DATADESC()
+
+ConVar sk_scanner_dmg_dive( "sk_scanner_dmg_dive","0");
+
+//-----------------------------------------------------------------------------
+// Think contexts
+//-----------------------------------------------------------------------------
+static const char *s_pDiveBombSoundThinkContext = "DiveBombSoundThinkContext";
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CNPC_BaseScanner::CNPC_BaseScanner()
+{
+#ifdef _DEBUG
+ m_vCurrentBanking.Init();
+#endif
+ m_pEngineSound = NULL;
+ m_bHasSpoken = false;
+
+ m_flAttackNearDist = SCANNER_ATTACK_NEAR_DIST;
+ m_flAttackFarDist = SCANNER_ATTACK_FAR_DIST;
+ m_flAttackRange = SCANNER_ATTACK_RANGE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::Spawn(void)
+{
+#ifdef _XBOX
+ // Always fade the corpse
+ AddSpawnFlags( SF_NPC_FADE_CORPSE );
+ AddEffects( EF_NOSHADOW );
+#endif // _XBOX
+
+ SetHullType( HULL_TINY_CENTERED );
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+ SetMoveType( MOVETYPE_VPHYSICS );
+
+ m_bloodColor = DONT_BLEED;
+ SetViewOffset( Vector(0, 0, 10) ); // Position of the eyes relative to NPC's origin.
+ m_flFieldOfView = 0.2;
+ m_NPCState = NPC_STATE_NONE;
+
+ SetNavType( NAV_FLY );
+
+ AddFlag( FL_FLY );
+
+ // This entity cannot be dissolved by the combine balls,
+ // nor does it get killed by the mega physcannon.
+ AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL );
+
+ m_flGoalOverrideDistance = 0.0f;
+
+ m_nFlyMode = SCANNER_FLY_PATROL;
+ AngleVectors( GetLocalAngles(), &m_vCurrentBanking );
+ m_fHeadYaw = 0;
+ m_pSmokeTrail = NULL;
+
+ SetCurrentVelocity( vec3_origin );
+
+ // Noise modifier
+ Vector bobAmount;
+ bobAmount.x = random->RandomFloat( -2.0f, 2.0f );
+ bobAmount.y = random->RandomFloat( -2.0f, 2.0f );
+ bobAmount.z = random->RandomFloat( 2.0f, 4.0f );
+ if ( random->RandomInt( 0, 1 ) )
+ {
+ bobAmount.z *= -1.0f;
+ }
+ SetNoiseMod( bobAmount );
+
+ // set flight speed
+ m_flSpeed = GetMaxSpeed();
+
+ // --------------------------------------------
+
+ CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_SKIP_NAV_GROUND_CHECK );
+
+ NPCInit();
+
+ m_flFlyNoiseBase = random->RandomFloat( 0, M_PI );
+
+ m_flNextAttack = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::UpdateEfficiency( bool bInPVS )
+{
+ SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL );
+ SetMoveEfficiency( AIME_NORMAL );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+float CNPC_BaseScanner::GetAutoAimRadius()
+{
+ if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
+ {
+ return 24.0f;
+ }
+
+ return 12.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called just before we are deleted.
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::UpdateOnRemove( void )
+{
+ // Stop combat loops if I'm alive. If I'm dead, the die sound will already have stopped it.
+ if ( IsAlive() && m_bHasSpoken )
+ {
+ SentenceStop();
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets the appropriate next schedule based on current condition
+// bits.
+//-----------------------------------------------------------------------------
+int CNPC_BaseScanner::SelectSchedule(void)
+{
+ // ----------------------------------------------------
+ // If I'm dead, go into a dive bomb
+ // ----------------------------------------------------
+ if ( m_iHealth <= 0 )
+ {
+ m_flSpeed = SCANNER_MAX_DIVE_BOMB_SPEED;
+ return SCHED_SCANNER_ATTACK_DIVEBOMB;
+ }
+
+ // -------------------------------
+ // If I'm in a script sequence
+ // -------------------------------
+ if ( m_NPCState == NPC_STATE_SCRIPT )
+ return(BaseClass::SelectSchedule());
+
+ // -------------------------------
+ // Flinch
+ // -------------------------------
+ if ( HasCondition(COND_LIGHT_DAMAGE) || HasCondition(COND_HEAVY_DAMAGE) )
+ {
+ if ( IsHeldByPhyscannon( ) )
+ return SCHED_SMALL_FLINCH;
+
+ if ( m_NPCState == NPC_STATE_IDLE )
+ return SCHED_SMALL_FLINCH;
+
+ if ( m_NPCState == NPC_STATE_ALERT )
+ {
+ if ( m_iHealth < ( 3 * m_iMaxHealth / 4 ))
+ return SCHED_TAKE_COVER_FROM_ORIGIN;
+
+ if ( SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 )
+ return SCHED_SMALL_FLINCH;
+ }
+ else
+ {
+ if ( random->RandomInt( 0, 10 ) < 4 )
+ return SCHED_SMALL_FLINCH;
+ }
+ }
+
+ // I'm being held by the physcannon... struggle!
+ if ( IsHeldByPhyscannon( ) )
+ return SCHED_SCANNER_HELD_BY_PHYSCANNON;
+
+ // ----------------------------------------------------------
+ // If I have an enemy
+ // ----------------------------------------------------------
+ if ( GetEnemy() != NULL && GetEnemy()->IsAlive() )
+ {
+ // Patrol if the enemy has vanished
+ if ( HasCondition( COND_LOST_ENEMY ) )
+ return SCHED_SCANNER_PATROL;
+
+ // Chase via route if we're directly blocked
+ if ( HasCondition( COND_SCANNER_FLY_BLOCKED ) )
+ return SCHED_SCANNER_CHASE_ENEMY;
+
+ // Attack if it's time
+ if ( gpGlobals->curtime >= m_flNextAttack )
+ {
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ return SCHED_SCANNER_ATTACK;
+ }
+
+ // Otherwise fly in low for attack
+ return SCHED_SCANNER_ATTACK_HOVER;
+ }
+
+ // Default to patrolling around
+ return SCHED_SCANNER_PATROL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::OnScheduleChange( void )
+{
+ m_flSpeed = GetMaxSpeed();
+
+ BaseClass::OnScheduleChange();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: For innate melee attack
+//-----------------------------------------------------------------------------
+int CNPC_BaseScanner::MeleeAttack1Conditions( float flDot, float flDist )
+{
+ if (GetEnemy() == NULL)
+ {
+ return COND_NONE;
+ }
+
+ // Check too far to attack with 2D distance
+ float vEnemyDist2D = (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()).Length2D();
+
+ if (m_flNextAttack > gpGlobals->curtime)
+ {
+ return COND_NONE;
+ }
+ else if (vEnemyDist2D > m_flAttackRange)
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+ else if (flDot < 0.7)
+ {
+ return COND_NOT_FACING_ATTACK;
+ }
+ return COND_CAN_MELEE_ATTACK1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : eOldState -
+// eNewState -
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::OnStateChange( NPC_STATE eOldState, NPC_STATE eNewState )
+{
+ if (( eNewState == NPC_STATE_ALERT ) || ( eNewState == NPC_STATE_COMBAT ))
+ {
+ SetPoseParameter(m_nPoseFlare, 1.0f);
+ }
+ else
+ {
+ SetPoseParameter(m_nPoseFlare, 0);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pTask -
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::StartTask( const Task_t *pTask )
+{
+ switch (pTask->iTask)
+ {
+ case TASK_SCANNER_SET_FLY_PATROL:
+ {
+ // Fly in patrol mode and clear any
+ // remaining target entity
+ m_nFlyMode = SCANNER_FLY_PATROL;
+ TaskComplete();
+ break;
+ }
+ case TASK_SCANNER_SET_FLY_CHASE:
+ {
+ m_nFlyMode = SCANNER_FLY_CHASE;
+ TaskComplete();
+ break;
+ }
+ case TASK_SCANNER_SET_FLY_ATTACK:
+ {
+ m_nFlyMode = SCANNER_FLY_ATTACK;
+ TaskComplete();
+ break;
+ }
+
+ case TASK_SCANNER_SET_FLY_DIVE:
+ {
+ // Pick a direction to divebomb.
+ if ( GetEnemy() != NULL )
+ {
+ // Fly towards my enemy
+ Vector vEnemyPos = GetEnemyLKP();
+ m_vecDiveBombDirection = vEnemyPos - GetLocalOrigin();
+ }
+ else
+ {
+ // Pick a random forward and down direction.
+ Vector forward;
+ GetVectors( &forward, NULL, NULL );
+ m_vecDiveBombDirection = forward + Vector( random->RandomFloat( -10, 10 ), random->RandomFloat( -10, 10 ), random->RandomFloat( -20, -10 ) );
+ }
+ VectorNormalize( m_vecDiveBombDirection );
+
+ // Calculate a roll force.
+ m_flDiveBombRollForce = random->RandomFloat( 20.0, 420.0 );
+ if ( random->RandomInt( 0, 1 ) )
+ {
+ m_flDiveBombRollForce *= -1;
+ }
+
+ DiveBombSoundThink();
+
+ m_nFlyMode = SCANNER_FLY_DIVE;
+ TaskComplete();
+ break;
+ }
+
+ default:
+ BaseClass::StartTask(pTask);
+ break;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Override to split in two when attacked
+//------------------------------------------------------------------------------
+int CNPC_BaseScanner::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ // Start smoking when we're nearly dead
+ if ( m_iHealth < ( m_iMaxHealth - ( m_iMaxHealth / 4 ) ) )
+ {
+ StartSmokeTrail();
+ }
+
+ return (BaseClass::OnTakeDamage_Alive( info ));
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Override to split in two when attacked
+//------------------------------------------------------------------------------
+int CNPC_BaseScanner::OnTakeDamage_Dying( const CTakeDamageInfo &info )
+{
+ // do the damage
+ m_iHealth -= info.GetDamage();
+
+ if ( m_iHealth < -40 )
+ {
+ Gib();
+ return 1;
+ }
+
+ return VPhysicsTakeDamage( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ if ( info.GetDamageType() & DMG_BULLET)
+ {
+ g_pEffects->Ricochet(ptr->endpos,ptr->plane.normal);
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+//-----------------------------------------------------------------------------
+// Take damage from being thrown by a physcannon
+//-----------------------------------------------------------------------------
+#define SCANNER_SMASH_SPEED 250.0 // How fast a scanner must slam into something to take full damage
+void CNPC_BaseScanner::TakeDamageFromPhyscannon( CBasePlayer *pPlayer )
+{
+ CTakeDamageInfo info;
+ info.SetDamageType( DMG_GENERIC );
+ info.SetInflictor( this );
+ info.SetAttacker( pPlayer );
+ info.SetDamagePosition( GetAbsOrigin() );
+ info.SetDamageForce( Vector( 1.0, 1.0, 1.0 ) );
+
+ // Convert velocity into damage.
+ Vector vel;
+ VPhysicsGetObject()->GetVelocity( &vel, NULL );
+ float flSpeed = vel.Length();
+
+ float flFactor = flSpeed / SCANNER_SMASH_SPEED;
+
+ // Clamp. Don't inflict negative damage or massive damage!
+ flFactor = clamp( flFactor, 0.0f, 2.0f );
+ float flDamage = m_iMaxHealth * flFactor;
+
+#if 0
+ Msg("Doing %f damage for %f speed!\n", flDamage, flSpeed );
+#endif
+
+ info.SetDamage( flDamage );
+ TakeDamage( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// Take damage from physics impacts
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::TakeDamageFromPhysicsImpact( int index, gamevcollisionevent_t *pEvent )
+{
+ CBaseEntity *pHitEntity = pEvent->pEntities[!index];
+
+ // NOTE: Augment the normal impact energy scale here.
+ float flDamageScale = PlayerHasMegaPhysCannon() ? 10.0f : 5.0f;
+
+ // Scale by the mapmaker's energyscale
+ flDamageScale *= m_impactEnergyScale;
+
+ int damageType = 0;
+ float damage = CalculateDefaultPhysicsDamage( index, pEvent, flDamageScale, true, damageType );
+ if ( damage == 0 )
+ return;
+
+ Vector damagePos;
+ pEvent->pInternalData->GetContactPoint( damagePos );
+ Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
+ if ( damageForce == vec3_origin )
+ {
+ // This can happen if this entity is motion disabled, and can't move.
+ // Use the velocity of the entity that hit us instead.
+ damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
+ }
+
+ // FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision
+ PhysCallbackDamage( this, CTakeDamageInfo( pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType ), *pEvent, index );
+}
+
+//-----------------------------------------------------------------------------
+// Is the scanner being held?
+//-----------------------------------------------------------------------------
+bool CNPC_BaseScanner::IsHeldByPhyscannon( )
+{
+ return VPhysicsGetObject() && (VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD);
+}
+
+//------------------------------------------------------------------------------
+// Physics impact
+//------------------------------------------------------------------------------
+#define SCANNER_SMASH_TIME 0.75 // How long after being thrown from a physcannon that a manhack is eligible to die from impact
+void CNPC_BaseScanner::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
+{
+ BaseClass::VPhysicsCollision( index, pEvent );
+
+ // Take no impact damage while being carried.
+ if ( IsHeldByPhyscannon( ) )
+ return;
+
+ CBasePlayer *pPlayer = HasPhysicsAttacker( SCANNER_SMASH_TIME );
+ if( pPlayer )
+ {
+ TakeDamageFromPhyscannon( pPlayer );
+ return;
+ }
+
+ // It also can take physics damage from things thrown by the player.
+ int otherIndex = !index;
+ CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
+ if ( pHitEntity )
+ {
+ if ( pHitEntity->HasPhysicsAttacker( 0.5f ) )
+ {
+ // It can take physics damage from things thrown by the player.
+ TakeDamageFromPhysicsImpact( index, pEvent );
+ }
+ else if ( FClassnameIs( pHitEntity, "prop_combine_ball" ) )
+ {
+ // It also can take physics damage from a combine ball.
+ TakeDamageFromPhysicsImpact( index, pEvent );
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CNPC_BaseScanner::Gib( void )
+{
+ if ( IsMarkedForDeletion() )
+ return;
+
+ // Sparks
+ for ( int i = 0; i < 4; i++ )
+ {
+ Vector sparkPos = GetAbsOrigin();
+ sparkPos.x += random->RandomFloat(-12,12);
+ sparkPos.y += random->RandomFloat(-12,12);
+ sparkPos.z += random->RandomFloat(-12,12);
+ g_pEffects->Sparks(sparkPos);
+ }
+
+ // Light
+ CBroadcastRecipientFilter filter;
+ te->DynamicLight( filter, 0.0, &WorldSpaceCenter(), 255, 180, 100, 0, 100, 0.1, 0 );
+
+ // Cover the gib spawn
+ ExplosionCreate( WorldSpaceCenter(), GetAbsAngles(), this, 64, 64, false );
+
+ // Turn off any smoke trail
+ if ( m_pSmokeTrail )
+ {
+ m_pSmokeTrail->m_ParticleLifetime = 0;
+ UTIL_Remove(m_pSmokeTrail);
+ m_pSmokeTrail = NULL;
+ }
+
+ // FIXME: This is because we couldn't save/load the CTakeDamageInfo.
+ // because it's midnight before the teamwide playtest. Real solution
+ // is to add a datadesc to CTakeDamageInfo
+ if ( m_KilledInfo.GetInflictor() )
+ {
+ BaseClass::Event_Killed( m_KilledInfo );
+ }
+
+ UTIL_Remove(this);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPhysGunUser -
+// bPunting -
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
+{
+ m_hPhysicsAttacker = pPhysGunUser;
+ m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
+
+ if ( reason == PUNTED_BY_CANNON )
+ {
+ // There's about to be a massive change in velocity.
+ // Think immediately to handle changes in m_vCurrentVelocity;
+ SetNextThink( gpGlobals->curtime + 0.01f );
+
+ m_flEngineStallTime = gpGlobals->curtime + 2.0f;
+ ScannerEmitSound( "DiveBomb" );
+ }
+ else
+ {
+ SetCondition( COND_SCANNER_GRABBED_BY_PHYSCANNON );
+ ClearCondition( COND_SCANNER_RELEASED_FROM_PHYSCANNON );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPhysGunUser -
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
+{
+ m_hPhysicsAttacker = pPhysGunUser;
+ m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
+
+ ClearCondition( COND_SCANNER_GRABBED_BY_PHYSCANNON );
+ SetCondition( COND_SCANNER_RELEASED_FROM_PHYSCANNON );
+
+ if ( Reason == LAUNCHED_BY_CANNON )
+ {
+ m_flEngineStallTime = gpGlobals->curtime + 2.0f;
+
+ // There's about to be a massive change in velocity.
+ // Think immediately to handle changes in m_vCurrentVelocity;
+ SetNextThink( gpGlobals->curtime + 0.01f );
+ ScannerEmitSound( "DiveBomb" );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Do we have a physics attacker?
+//------------------------------------------------------------------------------
+CBasePlayer *CNPC_BaseScanner::HasPhysicsAttacker( float dt )
+{
+ // If the player is holding me now, or I've been recently thrown
+ // then return a pointer to that player
+ if ( IsHeldByPhyscannon( ) || (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) )
+ {
+ return m_hPhysicsAttacker;
+ }
+ return NULL;
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CNPC_BaseScanner::StopLoopingSounds(void)
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundDestroy( m_pEngineSound );
+ m_pEngineSound = NULL;
+
+ BaseClass::StopLoopingSounds();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pInflictor -
+// pAttacker -
+// flDamage -
+// bitsDamageType -
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::Event_Killed( const CTakeDamageInfo &info )
+{
+ // Copy off the takedamage info that killed me, since we're not going to call
+ // up into the base class's Event_Killed() until we gib. (gibbing is ultimate death)
+ m_KilledInfo = info;
+
+ // Interrupt whatever schedule I'm on
+ SetCondition(COND_SCHEDULE_DONE);
+
+ // If I have an enemy and I'm up high, do a dive bomb (unless dissolved)
+ if ( GetEnemy() != NULL && (info.GetDamageType() & DMG_DISSOLVE) == false )
+ {
+ Vector vecDelta = GetLocalOrigin() - GetEnemy()->GetLocalOrigin();
+ if ( ( vecDelta.z > 120 ) && ( vecDelta.Length() > 360 ) )
+ {
+ // If I'm divebombing, don't take any more damage. It will make Event_Killed() be called again.
+ // This is especially bad if someone machineguns the divebombing scanner.
+ AttackDivebomb();
+ return;
+ }
+ }
+
+ Gib();
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CNPC_BaseScanner::AttackDivebomb( void )
+{
+ ScannerEmitSound( "DiveBomb" );
+
+ m_takedamage = DAMAGE_NO;
+
+ StartSmokeTrail();
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Checks to see if we hit anything while dive bombing.
+//------------------------------------------------------------------------------
+void CNPC_BaseScanner::AttackDivebombCollide(float flInterval)
+{
+ //
+ // Trace forward to see if I hit anything
+ //
+ Vector checkPos = GetAbsOrigin() + (GetCurrentVelocity() * flInterval);
+ trace_t tr;
+ CBaseEntity* pHitEntity = NULL;
+ AI_TraceHull( GetAbsOrigin(), checkPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if (tr.m_pEnt)
+ {
+ pHitEntity = tr.m_pEnt;
+
+ // Did I hit an entity that isn't another scanner?
+ if (pHitEntity && pHitEntity->Classify()!=CLASS_SCANNER)
+ {
+ if ( !pHitEntity->ClassMatches("item_battery") )
+ {
+ if ( !pHitEntity->IsWorld() )
+ {
+ CTakeDamageInfo info( this, this, sk_scanner_dmg_dive.GetFloat(), DMG_CLUB );
+ CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
+ pHitEntity->TakeDamage( info );
+ }
+ Gib();
+ }
+ }
+ }
+
+ if (tr.fraction != 1.0)
+ {
+ // We've hit something so deflect our velocity based on the surface
+ // norm of what we've hit
+ if (flInterval > 0)
+ {
+ float moveLen = (1.0 - tr.fraction)*(GetAbsOrigin() - checkPos).Length();
+ Vector vBounceVel = moveLen*tr.plane.normal/flInterval;
+
+ // If I'm right over the ground don't push down
+ if (vBounceVel.z < 0)
+ {
+ float floorZ = GetFloorZ(GetAbsOrigin());
+ if (abs(GetAbsOrigin().z - floorZ) < 36)
+ {
+ vBounceVel.z = 0;
+ }
+ }
+ SetCurrentVelocity( GetCurrentVelocity() + vBounceVel );
+ }
+ CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pHitEntity );
+
+ if (pBCC)
+ {
+ // Spawn some extra blood where we hit
+ SpawnBlood(tr.endpos, g_vecAttackDir, pBCC->BloodColor(), sk_scanner_dmg_dive.GetFloat());
+ }
+ else
+ {
+ if (!(m_spawnflags & SF_NPC_GAG))
+ {
+ // <<TEMP>> need better sound here...
+ ScannerEmitSound( "Shoot" );
+ }
+ // For sparks we must trace a line in the direction of the surface norm
+ // that we hit.
+ checkPos = GetAbsOrigin() - (tr.plane.normal * 24);
+
+ AI_TraceLine( GetAbsOrigin(), checkPos,MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+ if (tr.fraction != 1.0)
+ {
+ g_pEffects->Sparks( tr.endpos );
+
+ CBroadcastRecipientFilter filter;
+ te->DynamicLight( filter, 0.0,
+ &GetAbsOrigin(), 255, 180, 100, 0, 50, 0.1, 0 );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::PlayFlySound(void)
+{
+ if ( IsMarkedForDeletion() )
+ return;
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ //Setup the sound if we're not already
+ if ( m_pEngineSound == NULL )
+ {
+ // Create the sound
+ CPASAttenuationFilter filter( this );
+
+ m_pEngineSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, GetEngineSound(), ATTN_NORM );
+
+ Assert(m_pEngineSound);
+
+ // Start the engine sound
+ controller.Play( m_pEngineSound, 0.0f, 100.0f );
+ controller.SoundChangeVolume( m_pEngineSound, 1.0f, 2.0f );
+ }
+
+ float speed = GetCurrentVelocity().Length();
+ float flVolume = 0.25f + (0.75f*(speed/GetMaxSpeed()));
+ int iPitch = MIN( 255, 80 + (20*(speed/GetMaxSpeed())) );
+
+ //Update our pitch and volume based on our speed
+ controller.SoundChangePitch( m_pEngineSound, iPitch, 0.1f );
+ controller.SoundChangeVolume( m_pEngineSound, flVolume, 0.1f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::ScannerEmitSound( const char *pszSoundName )
+{
+ CFmtStr snd;
+ snd.sprintf("%s.%s", GetScannerSoundPrefix(), pszSoundName );
+
+ m_bHasSpoken = true;
+
+ EmitSound( snd.Access() );
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CNPC_BaseScanner::SpeakSentence( int sentenceType )
+{
+ if (sentenceType == SCANNER_SENTENCE_ATTENTION)
+ {
+ ScannerEmitSound( "Attention" );
+ }
+ else if (sentenceType == SCANNER_SENTENCE_HANDSUP)
+ {
+ ScannerEmitSound( "Scan" );
+ }
+ else if (sentenceType == SCANNER_SENTENCE_PROCEED)
+ {
+ ScannerEmitSound( "Proceed" );
+ }
+ else if (sentenceType == SCANNER_SENTENCE_CURIOUS)
+ {
+ ScannerEmitSound( "Curious" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::InputSetFlightSpeed(inputdata_t &inputdata)
+{
+ //FIXME: Currently unsupported
+
+ /*
+ m_flFlightSpeed = inputdata.value.Int();
+ m_bFlightSpeedOverridden = (m_flFlightSpeed > 0);
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::StartSmokeTrail( void )
+{
+ if ( m_pSmokeTrail != NULL )
+ return;
+
+ m_pSmokeTrail = SmokeTrail::CreateSmokeTrail();
+
+ if ( m_pSmokeTrail )
+ {
+ m_pSmokeTrail->m_SpawnRate = 10;
+ m_pSmokeTrail->m_ParticleLifetime = 1;
+ m_pSmokeTrail->m_StartSize = 8;
+ m_pSmokeTrail->m_EndSize = 50;
+ m_pSmokeTrail->m_SpawnRadius = 10;
+ m_pSmokeTrail->m_MinSpeed = 15;
+ m_pSmokeTrail->m_MaxSpeed = 25;
+
+ m_pSmokeTrail->m_StartColor.Init( 0.5f, 0.5f, 0.5f );
+ m_pSmokeTrail->m_EndColor.Init( 0, 0, 0 );
+ m_pSmokeTrail->SetLifetime( 500.0f );
+ m_pSmokeTrail->FollowEntity( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::BlendPhyscannonLaunchSpeed()
+{
+ // Blend out desired velocity when launched by the physcannon
+ if (!VPhysicsGetObject())
+ return;
+
+ if ( HasPhysicsAttacker( SCANNER_SMASH_TIME ) && !IsHeldByPhyscannon( ) )
+ {
+ Vector vecCurrentVelocity;
+ VPhysicsGetObject()->GetVelocity( &vecCurrentVelocity, NULL );
+ float flLerpFactor = (gpGlobals->curtime - m_flLastPhysicsInfluenceTime) / SCANNER_SMASH_TIME;
+ flLerpFactor = clamp( flLerpFactor, 0.0f, 1.0f );
+ flLerpFactor = SimpleSplineRemapVal( flLerpFactor, 0.0f, 1.0f, 0.0f, 1.0f );
+ flLerpFactor *= flLerpFactor;
+ VectorLerp( vecCurrentVelocity, m_vCurrentVelocity, flLerpFactor, m_vCurrentVelocity );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::MoveExecute_Alive(float flInterval)
+{
+ // Amount of noise to add to flying
+ float noiseScale = 3.0f;
+
+ // -------------------------------------------
+ // Avoid obstacles, unless I'm dive bombing
+ // -------------------------------------------
+ if (m_nFlyMode != SCANNER_FLY_DIVE)
+ {
+ SetCurrentVelocity( GetCurrentVelocity() + VelocityToAvoidObstacles(flInterval) );
+ }
+ // If I am dive bombing add more noise to my flying
+ else
+ {
+ AttackDivebombCollide(flInterval);
+ noiseScale *= 4;
+ }
+
+ IPhysicsObject *pPhysics = VPhysicsGetObject();
+
+ if ( pPhysics && pPhysics->IsAsleep() )
+ {
+ pPhysics->Wake();
+ }
+
+ // Add time-coherent noise to the current velocity so that it never looks bolted in place.
+ AddNoiseToVelocity( noiseScale );
+
+ AdjustScannerVelocity();
+
+ float maxSpeed = GetEnemy() ? ( GetMaxSpeed() * 2.0f ) : GetMaxSpeed();
+ if ( m_nFlyMode == SCANNER_FLY_DIVE )
+ {
+ maxSpeed = -1;
+ }
+
+ // Limit fall speed
+ LimitSpeed( maxSpeed );
+
+ // Blend out desired velocity when launched by the physcannon
+ BlendPhyscannonLaunchSpeed();
+
+ // Update what we're looking at
+ UpdateHead( flInterval );
+
+ // Control the tail based on our vertical travel
+ float tailPerc = clamp( GetCurrentVelocity().z, -150, 250 );
+ tailPerc = SimpleSplineRemapVal( tailPerc, -150, 250, -25, 80 );
+
+ SetPoseParameter( m_nPoseTail, tailPerc );
+
+ // Spin the dynamo based upon our speed
+ float flCurrentDynamo = GetPoseParameter( m_nPoseDynamo );
+ float speed = GetCurrentVelocity().Length();
+ float flDynamoSpeed = (maxSpeed > 0 ? speed / maxSpeed : 1.0) * 60;
+ flCurrentDynamo -= flDynamoSpeed;
+ if ( flCurrentDynamo < -180.0 )
+ {
+ flCurrentDynamo += 360.0;
+ }
+ SetPoseParameter( m_nPoseDynamo, flCurrentDynamo );
+
+ PlayFlySound();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles movement towards the last move target.
+// Input : flInterval -
+//-----------------------------------------------------------------------------
+bool CNPC_BaseScanner::OverridePathMove( CBaseEntity *pMoveTarget, float flInterval )
+{
+ // Save our last patrolling direction
+ Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetAbsOrigin();
+
+ // Continue on our path
+ if ( ProgressFlyPath( flInterval, pMoveTarget, (MASK_NPCSOLID|CONTENTS_WATER), false, 64 ) == AINPP_COMPLETE )
+ {
+ if ( IsCurSchedule( SCHED_SCANNER_PATROL ) )
+ {
+ m_vLastPatrolDir = lastPatrolDir;
+ VectorNormalize(m_vLastPatrolDir);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flInterval -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_BaseScanner::OverrideMove( float flInterval )
+{
+ // ----------------------------------------------
+ // If dive bombing
+ // ----------------------------------------------
+ if (m_nFlyMode == SCANNER_FLY_DIVE)
+ {
+ MoveToDivebomb( flInterval );
+ }
+ else
+ {
+ Vector vMoveTargetPos(0,0,0);
+ CBaseEntity *pMoveTarget = NULL;
+
+ // The original line of code was, due to the accidental use of '|' instead of
+ // '&', always true. Replacing with 'true' to suppress the warning without changing
+ // the (long-standing) behavior.
+ if ( true ) //!GetNavigator()->IsGoalActive() || ( GetNavigator()->GetCurWaypointFlags() | bits_WP_TO_PATHCORNER ) )
+ {
+ // Select move target
+ if ( GetTarget() != NULL )
+ {
+ pMoveTarget = GetTarget();
+ }
+ else if ( GetEnemy() != NULL )
+ {
+ pMoveTarget = GetEnemy();
+ }
+
+ // Select move target position
+ if ( GetEnemy() != NULL )
+ {
+ vMoveTargetPos = GetEnemy()->GetAbsOrigin();
+ }
+ }
+ else
+ {
+ vMoveTargetPos = GetNavigator()->GetCurWaypointPos();
+ }
+
+ ClearCondition( COND_SCANNER_FLY_CLEAR );
+ ClearCondition( COND_SCANNER_FLY_BLOCKED );
+
+ // See if we can fly there directly
+ if ( pMoveTarget )
+ {
+ trace_t tr;
+ AI_TraceHull( GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ float fTargetDist = (1.0f-tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length();
+
+ if ( ( tr.m_pEnt == pMoveTarget ) || ( fTargetDist < 50 ) )
+ {
+ if ( g_debug_basescanner.GetBool() )
+ {
+ NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 0,255,0, true, 0);
+ NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),0,255,0,true,0.1);
+ }
+
+ SetCondition( COND_SCANNER_FLY_CLEAR );
+ }
+ else
+ {
+ //HANDY DEBUG TOOL
+ if ( g_debug_basescanner.GetBool() )
+ {
+ NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 255,0,0, true, 0);
+ NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),255,0,0,true,0.1);
+ }
+
+ SetCondition( COND_SCANNER_FLY_BLOCKED );
+ }
+ }
+
+ // If I have a route, keep it updated and move toward target
+ if ( GetNavigator()->IsGoalActive() )
+ {
+ if ( OverridePathMove( pMoveTarget, flInterval ) )
+ {
+ BlendPhyscannonLaunchSpeed();
+ return true;
+ }
+ }
+ // ----------------------------------------------
+ // If attacking
+ // ----------------------------------------------
+ else if (m_nFlyMode == SCANNER_FLY_ATTACK)
+ {
+ MoveToAttack( flInterval );
+ }
+ // -----------------------------------------------------------------
+ // If I don't have a route, just decelerate
+ // -----------------------------------------------------------------
+ else if (!GetNavigator()->IsGoalActive())
+ {
+ float myDecay = 9.5;
+ Decelerate( flInterval, myDecay);
+ }
+ }
+
+ MoveExecute_Alive( flInterval );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &goalPos -
+// &startPos -
+// idealRange -
+// idealHeight -
+// Output : Vector
+//-----------------------------------------------------------------------------
+Vector CNPC_BaseScanner::IdealGoalForMovement( const Vector &goalPos, const Vector &startPos, float idealRange, float idealHeightDiff )
+{
+ Vector vMoveDir;
+
+ if ( GetGoalDirection( &vMoveDir ) == false )
+ {
+ vMoveDir = ( goalPos - startPos );
+ vMoveDir.z = 0;
+ VectorNormalize( vMoveDir );
+ }
+
+ // Move up from the position by the desired amount
+ Vector vIdealPos = goalPos + Vector( 0, 0, idealHeightDiff ) + ( vMoveDir * -idealRange );
+
+ // Trace down and make sure we can fit here
+ trace_t tr;
+ AI_TraceHull( vIdealPos, vIdealPos - Vector( 0, 0, MinGroundDist() ), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ // Move up otherwise
+ if ( tr.fraction < 1.0f )
+ {
+ vIdealPos.z += ( MinGroundDist() * ( 1.0f - tr.fraction ) );
+ }
+
+ //FIXME: We also need to make sure that we fit here at all, and if not, chose a new spot
+
+ // Debug tools
+ if ( g_debug_basescanner.GetBool() )
+ {
+ NDebugOverlay::Cross3D( goalPos, -Vector(8,8,8), Vector(8,8,8), 255, 255, 0, true, 0.1f );
+ NDebugOverlay::Cross3D( startPos, -Vector(8,8,8), Vector(8,8,8), 255, 0, 255, true, 0.1f );
+ NDebugOverlay::Cross3D( vIdealPos, -Vector(8,8,8), Vector(8,8,8), 255, 255, 255, true, 0.1f );
+ NDebugOverlay::Line( startPos, goalPos, 0, 255, 0, true, 0.1f );
+
+ NDebugOverlay::Cross3D( goalPos + ( vMoveDir * -idealRange ), -Vector(8,8,8), Vector(8,8,8), 255, 255, 255, true, 0.1f );
+ NDebugOverlay::Line( goalPos, goalPos + ( vMoveDir * -idealRange ), 255, 255, 0, true, 0.1f );
+ NDebugOverlay::Line( goalPos + ( vMoveDir * -idealRange ), vIdealPos, 255, 255, 0, true, 0.1f );
+ }
+
+ return vIdealPos;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flInterval -
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::MoveToAttack(float flInterval)
+{
+ if (GetEnemy() == NULL)
+ return;
+
+ if ( flInterval <= 0 )
+ return;
+
+ Vector vTargetPos = GetEnemyLKP();
+
+ //float flDesiredDist = m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 );
+
+ Vector idealPos = IdealGoalForMovement( vTargetPos, GetAbsOrigin(), GetGoalDistance(), m_flAttackNearDist );
+
+ MoveToTarget( flInterval, idealPos );
+
+ //FIXME: Re-implement?
+
+ /*
+ // ---------------------------------------------------------
+ // Add evasion if I have taken damage recently
+ // ---------------------------------------------------------
+ if ((m_flLastDamageTime + SCANNER_EVADE_TIME) > gpGlobals->curtime)
+ {
+ vFlyDirection = vFlyDirection + VelocityToEvade(GetEnemyCombatCharacterPointer());
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Accelerates toward a given position.
+// Input : flInterval - Time interval over which to move.
+// vecMoveTarget - Position to move toward.
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::MoveToTarget( float flInterval, const Vector &vecMoveTarget )
+{
+ // Don't move if stalling
+ if ( m_flEngineStallTime > gpGlobals->curtime )
+ return;
+
+ // Look at our inspection target if we have one
+ if ( GetEnemy() != NULL )
+ {
+ // Otherwise at our enemy
+ TurnHeadToTarget( flInterval, GetEnemy()->EyePosition() );
+ }
+ else
+ {
+ // Otherwise face our motion direction
+ TurnHeadToTarget( flInterval, vecMoveTarget );
+ }
+
+ // -------------------------------------
+ // Move towards our target
+ // -------------------------------------
+ float myAccel;
+ float myZAccel = 400.0f;
+ float myDecay = 0.15f;
+
+ Vector vecCurrentDir;
+
+ // Get the relationship between my current velocity and the way I want to be going.
+ vecCurrentDir = GetCurrentVelocity();
+ VectorNormalize( vecCurrentDir );
+
+ Vector targetDir = vecMoveTarget - GetAbsOrigin();
+ float flDist = VectorNormalize(targetDir);
+
+ float flDot;
+ flDot = DotProduct( targetDir, vecCurrentDir );
+
+ if( flDot > 0.25 )
+ {
+ // If my target is in front of me, my flight model is a bit more accurate.
+ myAccel = 250;
+ }
+ else
+ {
+ // Have a harder time correcting my course if I'm currently flying away from my target.
+ myAccel = 128;
+ }
+
+ if ( myAccel > flDist / flInterval )
+ {
+ myAccel = flDist / flInterval;
+ }
+
+ if ( myZAccel > flDist / flInterval )
+ {
+ myZAccel = flDist / flInterval;
+ }
+
+ MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay );
+
+ // calc relative banking targets
+ Vector forward, right, up;
+ GetVectors( &forward, &right, &up );
+
+ m_vCurrentBanking.x = targetDir.x;
+ m_vCurrentBanking.z = 120.0f * DotProduct( right, targetDir );
+ m_vCurrentBanking.y = 0;
+
+ float speedPerc = SimpleSplineRemapVal( GetCurrentVelocity().Length(), 0.0f, GetMaxSpeed(), 0.0f, 1.0f );
+
+ speedPerc = clamp( speedPerc, 0.0f, 1.0f );
+
+ m_vCurrentBanking *= speedPerc;
+}
+
+//-----------------------------------------------------------------------------
+// Danger sounds.
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::DiveBombSoundThink()
+{
+ Vector vecPosition, vecVelocity;
+ IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
+
+ if ( pPhysicsObject == NULL )
+ return;
+
+ pPhysicsObject->GetPosition( &vecPosition, NULL );
+ pPhysicsObject->GetVelocity( &vecVelocity, NULL );
+
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( pPlayer )
+ {
+ Vector vecDelta;
+ VectorSubtract( pPlayer->GetAbsOrigin(), vecPosition, vecDelta );
+ VectorNormalize( vecDelta );
+ if ( DotProduct( vecDelta, vecVelocity ) > 0.5f )
+ {
+ Vector vecEndPoint;
+ VectorMA( vecPosition, 2.0f * TICK_INTERVAL, vecVelocity, vecEndPoint );
+ float flDist = CalcDistanceToLineSegment( pPlayer->GetAbsOrigin(), vecPosition, vecEndPoint );
+ if ( flDist < 200.0f )
+ {
+ ScannerEmitSound( "DiveBombFlyby" );
+ SetContextThink( &CNPC_BaseScanner::DiveBombSoundThink, gpGlobals->curtime + 0.5f, s_pDiveBombSoundThinkContext );
+ return;
+ }
+ }
+ }
+
+ SetContextThink( &CNPC_BaseScanner::DiveBombSoundThink, gpGlobals->curtime + 2.0f * TICK_INTERVAL, s_pDiveBombSoundThinkContext );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flInterval -
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::MoveToDivebomb(float flInterval)
+{
+ float myAccel = 1600;
+ float myDecay = 0.05f; // decay current velocity to 10% in 1 second
+
+ // Fly towards my enemy
+ Vector vEnemyPos = GetEnemyLKP();
+ Vector vFlyDirection = vEnemyPos - GetLocalOrigin();
+ VectorNormalize( vFlyDirection );
+
+ // Set net velocity
+ MoveInDirection( flInterval, m_vecDiveBombDirection, myAccel, myAccel, myDecay);
+
+ // Spin out of control.
+ Vector forward;
+ VPhysicsGetObject()->LocalToWorldVector( &forward, Vector( 1.0, 0.0, 0.0 ) );
+ AngularImpulse torque = forward * m_flDiveBombRollForce;
+ VPhysicsGetObject()->ApplyTorqueCenter( torque );
+
+ // BUGBUG: why Y axis and not Z?
+ Vector up;
+ VPhysicsGetObject()->LocalToWorldVector( &up, Vector( 0.0, 1.0, 0.0 ) );
+ VPhysicsGetObject()->ApplyForceCenter( up * 2000 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_BaseScanner::IsEnemyPlayerInSuit()
+{
+ if( GetEnemy() && GetEnemy()->IsPlayer() )
+ {
+ CHL2_Player *pPlayer = NULL;
+ pPlayer = (CHL2_Player *)GetEnemy();
+
+ if( pPlayer && pPlayer->IsSuitEquipped() )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float CNPC_BaseScanner::GetGoalDistance( void )
+{
+ if ( m_flGoalOverrideDistance != 0.0f )
+ return m_flGoalOverrideDistance;
+
+ switch ( m_nFlyMode )
+ {
+ case SCANNER_FLY_ATTACK:
+ {
+ float goalDist = ( m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 ) );
+ if( IsEnemyPlayerInSuit() )
+ {
+ goalDist *= 0.5;
+ }
+ return goalDist;
+ }
+ break;
+ }
+
+ return 128.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &vOut -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_BaseScanner::GetGoalDirection( Vector *vOut )
+{
+ CBaseEntity *pTarget = GetTarget();
+
+ if ( pTarget == NULL )
+ return false;
+
+ if ( FClassnameIs( pTarget, "info_hint_air" ) || FClassnameIs( pTarget, "info_target" ) )
+ {
+ AngleVectors( pTarget->GetAbsAngles(), vOut );
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CNPC_BaseScanner::VelocityToEvade(CBaseCombatCharacter *pEnemy)
+{
+ if (pEnemy)
+ {
+ // -----------------------------------------
+ // Keep out of enemy's shooting position
+ // -----------------------------------------
+ Vector vEnemyFacing = pEnemy->BodyDirection2D( );
+ Vector vEnemyDir = pEnemy->EyePosition() - GetLocalOrigin();
+ VectorNormalize(vEnemyDir);
+ float fDotPr = DotProduct(vEnemyFacing,vEnemyDir);
+
+ if (fDotPr < -0.9)
+ {
+ Vector vDirUp(0,0,1);
+ Vector vDir;
+ CrossProduct( vEnemyFacing, vDirUp, vDir);
+
+ Vector crossProduct;
+ CrossProduct(vEnemyFacing, vEnemyDir, crossProduct);
+ if (crossProduct.y < 0)
+ {
+ vDir = vDir * -1;
+ }
+ return (vDir);
+ }
+ else if (fDotPr < -0.85)
+ {
+ Vector vDirUp(0,0,1);
+ Vector vDir;
+ CrossProduct( vEnemyFacing, vDirUp, vDir);
+
+ Vector crossProduct;
+ CrossProduct(vEnemyFacing, vEnemyDir, crossProduct);
+ if (random->RandomInt(0,1))
+ {
+ vDir = vDir * -1;
+ }
+ return (vDir);
+ }
+ }
+ return vec3_origin;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_BaseScanner::DrawDebugTextOverlays(void)
+{
+ int nOffset = BaseClass::DrawDebugTextOverlays();
+
+ if ( m_debugOverlays & OVERLAY_TEXT_BIT )
+ {
+ Vector vel;
+ GetVelocity( &vel, NULL );
+
+ char tempstr[512];
+ Q_snprintf( tempstr, sizeof(tempstr), "speed (max): %.2f (%.2f)", vel.Length(), m_flSpeed );
+ EntityText( nOffset, tempstr, 0 );
+ nOffset++;
+ }
+
+ return nOffset;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float CNPC_BaseScanner::GetHeadTurnRate( void )
+{
+ if ( GetEnemy() )
+ return 800.0f;
+
+ return 350.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+inline CBaseEntity *CNPC_BaseScanner::EntityToWatch( void )
+{
+ return ( GetTarget() != NULL ) ? GetTarget() : GetEnemy(); // Okay if NULL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flInterval -
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::UpdateHead( float flInterval )
+{
+ float yaw = GetPoseParameter( m_nPoseFaceHoriz );
+ float pitch = GetPoseParameter( m_nPoseFaceVert );
+
+ CBaseEntity *pTarget = EntityToWatch();
+
+ Vector vLookPos;
+
+ if ( !HasCondition( COND_IN_PVS ) || GetAttachment( "eyes", vLookPos ) == false )
+ {
+ vLookPos = EyePosition();
+ }
+
+ if ( pTarget != NULL )
+ {
+ Vector lookDir = pTarget->EyePosition() - vLookPos;
+ VectorNormalize( lookDir );
+
+ if ( DotProduct( lookDir, BodyDirection3D() ) < 0.0f )
+ {
+ SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( 0, yaw, 10 ) );
+ SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( 0, pitch, 10 ) );
+
+ return;
+ }
+
+ float facingYaw = VecToYaw( BodyDirection3D() );
+ float yawDiff = VecToYaw( lookDir );
+ yawDiff = UTIL_AngleDiff( yawDiff, facingYaw + yaw );
+
+ float facingPitch = UTIL_VecToPitch( BodyDirection3D() );
+ float pitchDiff = UTIL_VecToPitch( lookDir );
+ pitchDiff = UTIL_AngleDiff( pitchDiff, facingPitch + pitch );
+
+ SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( yaw + yawDiff, yaw, 50 ) );
+ SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( pitch + pitchDiff, pitch, 50 ) );
+ }
+ else
+ {
+ SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( 0, yaw, 10 ) );
+ SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( 0, pitch, 10 ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &linear -
+// &angular -
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::ClampMotorForces( Vector &linear, AngularImpulse &angular )
+{
+ // limit reaction forces
+ if ( m_nFlyMode != SCANNER_FLY_DIVE )
+ {
+ linear.x = clamp( linear.x, -500, 500 );
+ linear.y = clamp( linear.y, -500, 500 );
+ linear.z = clamp( linear.z, -500, 500 );
+ }
+
+ // If we're dive bombing, we need to drop faster than normal
+ if ( m_nFlyMode != SCANNER_FLY_DIVE )
+ {
+ // Add in weightlessness
+ linear.z += 800;
+ }
+
+ angular.z = clamp( angular.z, -GetHeadTurnRate(), GetHeadTurnRate() );
+ if ( m_nFlyMode == SCANNER_FLY_DIVE )
+ {
+ // Disable pitch and roll motors while crashing.
+ angular.x = 0;
+ angular.y = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::InputSetDistanceOverride( inputdata_t &inputdata )
+{
+ m_flGoalOverrideDistance = inputdata.value.Float();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Emit sounds specific to the NPC's state.
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::AlertSound(void)
+{
+ ScannerEmitSound( "Alert" );
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CNPC_BaseScanner::DeathSound( const CTakeDamageInfo &info )
+{
+ ScannerEmitSound( "Die" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Overridden so that scanners play battle sounds while fighting.
+// Output : Returns TRUE on success, FALSE on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_BaseScanner::ShouldPlayIdleSound( void )
+{
+ if ( HasSpawnFlags( SF_NPC_GAG ) )
+ return false;
+
+ if ( random->RandomInt( 0, 25 ) != 0 )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Plays sounds while idle or in combat.
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::IdleSound(void)
+{
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ {
+ // dvs: the combat sounds should be related to what is happening, rather than random
+ ScannerEmitSound( "Combat" );
+ }
+ else
+ {
+ ScannerEmitSound( "Idle" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Plays a sound when hurt.
+//-----------------------------------------------------------------------------
+void CNPC_BaseScanner::PainSound( const CTakeDamageInfo &info )
+{
+ ScannerEmitSound( "Pain" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CNPC_BaseScanner::GetMaxSpeed()
+{
+ return SCANNER_MAX_SPEED;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( npc_basescanner, CNPC_BaseScanner )
+
+ DECLARE_TASK( TASK_SCANNER_SET_FLY_PATROL )
+ DECLARE_TASK( TASK_SCANNER_SET_FLY_CHASE )
+ DECLARE_TASK( TASK_SCANNER_SET_FLY_ATTACK )
+ DECLARE_TASK( TASK_SCANNER_SET_FLY_DIVE )
+
+ DECLARE_CONDITION(COND_SCANNER_FLY_CLEAR)
+ DECLARE_CONDITION(COND_SCANNER_FLY_BLOCKED)
+ DECLARE_CONDITION(COND_SCANNER_RELEASED_FROM_PHYSCANNON)
+ DECLARE_CONDITION(COND_SCANNER_GRABBED_BY_PHYSCANNON)
+
+ //=========================================================
+ // > SCHED_SCANNER_PATROL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCANNER_PATROL,
+
+ " Tasks"
+ " TASK_SCANNER_SET_FLY_PATROL 0"
+ " TASK_SET_TOLERANCE_DISTANCE 32"
+ " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck
+ " TASK_GET_PATH_TO_RANDOM_NODE 2000"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_GIVE_WAY"
+ " COND_NEW_ENEMY"
+ " COND_SEE_ENEMY"
+ " COND_SEE_FEAR"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_PLAYER"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_PROVOKED"
+ " COND_SCANNER_GRABBED_BY_PHYSCANNON"
+ )
+
+ //=========================================================
+ // > SCHED_SCANNER_ATTACK
+ //
+ // This task does nothing. Translate it in your derived
+ // class to perform your attack.
+ //
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCANNER_ATTACK,
+
+ " Tasks"
+ " TASK_SCANNER_SET_FLY_ATTACK 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 0.1"
+ ""
+ " Interrupts"
+ " COND_TOO_FAR_TO_ATTACK"
+ " COND_SCANNER_FLY_BLOCKED"
+ " COND_NEW_ENEMY"
+ " COND_SCANNER_GRABBED_BY_PHYSCANNON"
+ )
+
+ //=========================================================
+ // > SCHED_SCANNER_ATTACK_HOVER
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCANNER_ATTACK_HOVER,
+
+ " Tasks"
+ " TASK_SCANNER_SET_FLY_ATTACK 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 0.1"
+ ""
+ " Interrupts"
+ " COND_TOO_FAR_TO_ATTACK"
+ " COND_SCANNER_FLY_BLOCKED"
+ " COND_NEW_ENEMY"
+ " COND_SCANNER_GRABBED_BY_PHYSCANNON"
+ )
+
+ //=========================================================
+ // > SCHED_SCANNER_ATTACK_DIVEBOMB
+ //
+ // Only done when scanner is dead
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCANNER_ATTACK_DIVEBOMB,
+
+ " Tasks"
+ " TASK_SCANNER_SET_FLY_DIVE 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 10"
+ ""
+ " Interrupts"
+ " COND_SCANNER_GRABBED_BY_PHYSCANNON"
+ )
+
+ //=========================================================
+ // > SCHED_SCANNER_CHASE_ENEMY
+ //
+ // Different interrupts than normal chase enemy.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCANNER_CHASE_ENEMY,
+
+ " Tasks"
+ " TASK_SCANNER_SET_FLY_CHASE 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCANNER_PATROL"
+ " TASK_SET_TOLERANCE_DISTANCE 120"
+ " TASK_GET_PATH_TO_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ ""
+ " Interrupts"
+ " COND_SCANNER_FLY_CLEAR"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_LOST_ENEMY"
+ " COND_SCANNER_GRABBED_BY_PHYSCANNON"
+ )
+
+ //=========================================================
+ // > SCHED_SCANNER_CHASE_TARGET
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCANNER_CHASE_TARGET,
+
+ " Tasks"
+ " TASK_SCANNER_SET_FLY_CHASE 0"
+ " TASK_SET_TOLERANCE_DISTANCE 64"
+ " TASK_GET_PATH_TO_TARGET 0" //FIXME: This is wrong!
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_SCANNER_FLY_CLEAR"
+ " COND_NEW_ENEMY"
+ " COND_SCANNER_GRABBED_BY_PHYSCANNON"
+ )
+
+ //=========================================================
+ // > SCHED_SCANNER_FOLLOW_HOVER
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCANNER_FOLLOW_HOVER,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 0.1"
+ ""
+ " Interrupts"
+ " COND_SCANNER_FLY_BLOCKED"
+ " COND_SCANNER_GRABBED_BY_PHYSCANNON"
+ )
+
+ //=========================================================
+ // > SCHED_SCANNER_HELD_BY_PHYSCANNON
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCANNER_HELD_BY_PHYSCANNON,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 5.0"
+ ""
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_SCANNER_RELEASED_FROM_PHYSCANNON"
+ )
+
+AI_END_CUSTOM_NPC()