summaryrefslogtreecommitdiff
path: root/game/server/hl2/npc_combinegunship.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/hl2/npc_combinegunship.cpp')
-rw-r--r--game/server/hl2/npc_combinegunship.cpp3228
1 files changed, 3228 insertions, 0 deletions
diff --git a/game/server/hl2/npc_combinegunship.cpp b/game/server/hl2/npc_combinegunship.cpp
new file mode 100644
index 0000000..2583105
--- /dev/null
+++ b/game/server/hl2/npc_combinegunship.cpp
@@ -0,0 +1,3228 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "baseanimating.h"
+#include "ai_network.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_node.h"
+#include "ai_task.h"
+#include "ai_motor.h"
+#include "entitylist.h"
+#include "basecombatweapon.h"
+#include "soundenvelope.h"
+#include "gib.h"
+#include "gamerules.h"
+#include "ammodef.h"
+#include "cbasehelicopter.h"
+#include "npcevent.h"
+#include "ndebugoverlay.h"
+#include "decals.h"
+#include "explode.h" // temp (sjb)
+#include "smoke_trail.h" // temp (sjb)
+#include "IEffects.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "ar2_explosion.h"
+#include "te_effect_dispatch.h"
+#include "rope.h"
+#include "effect_dispatch_data.h"
+#include "trains.h"
+#include "globals.h"
+#include "physics_prop_ragdoll.h"
+#include "iservervehicle.h"
+#include "soundent.h"
+#include "npc_citizen17.h"
+#include "physics_saverestore.h"
+#include "hl2_shareddefs.h"
+#include "props.h"
+#include "npc_attackchopper.h"
+#include "citadel_effects_shared.h"
+#include "eventqueue.h"
+#include "beam_flags.h"
+#include "ai_eventresponse.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define GUNSHIP_MSG_BIG_SHOT 1
+#define GUNSHIP_MSG_STREAKS 2
+
+#define GUNSHIP_NUM_DAMAGE_OUTPUTS 4
+
+extern short g_sModelIndexFireball; // holds the index for the fireball
+
+int g_iGunshipEffectIndex = -1;
+
+#define GUNSHIP_ACCEL_RATE 500
+
+// Spawnflags
+#define SF_GUNSHIP_NO_GROUND_ATTACK ( 1 << 12 )
+#define SF_GUNSHIP_USE_CHOPPER_MODEL ( 1 << 13 )
+
+ConVar sk_gunship_burst_size("sk_gunship_burst_size", "15" );
+ConVar sk_gunship_burst_min("sk_gunship_burst_min", "800" );
+ConVar sk_gunship_burst_dist("sk_gunship_burst_dist", "768" );
+
+// Number of times the gunship must be struck by explosive damage
+ConVar sk_gunship_health_increments( "sk_gunship_health_increments", "0" );
+
+/*
+
+Wedge's notes:
+
+ Gunship should move its head according to flight model when the target is behind the gunship,
+ or when the target is too far away to shoot at. Otherwise, the head should aim at the target.
+
+ Negative angvelocity.y is a RIGHT turn.
+ Negative angvelocity.x is UP
+
+*/
+
+#define GUNSHIP_AP_MUZZLE 5
+
+#define GUNSHIP_MAX_SPEED 1056.0f
+
+#define GUNSHIP_MAX_FIRING_SPEED 200.0f
+#define GUNSHIP_MIN_ROCKET_DIST 1000.0f
+#define GUNSHIP_MAX_GUN_DIST 2000.0f
+#define GUNSHIP_ARRIVE_DIST 128.0f
+
+#define GUNSHIP_HOVER_SPEED 300.0f // play hover animation if moving slower than this.
+
+#define GUNSHIP_AE_THRUST 1
+
+#define GUNSHIP_HEAD_MAX_UP -65
+#define GUNSHIP_HEAD_MAX_DOWN 60
+#define GUNSHIP_HEAD_MAX_LEFT 60
+#define GUNSHIP_HEAD_MAX_RIGHT -60
+
+#define BASE_STITCH_VELOCITY 800 //Units per second
+#define MAX_STITCH_VELOCITY 1000 //Units per second
+
+#define GUNSHIP_LEAD_DISTANCE 800.0f
+#define GUNSHIP_AVOID_DIST 512.0f
+#define GUNSHIP_STITCH_MIN 512.0f
+
+#define GUNSHIP_MIN_CHASE_DIST_DIFF 128.0f // Distance threshold used to determine when a target has moved enough to update our navigation to it
+
+#define MIN_GROUND_ATTACK_DIST 500.0f // Minimum distance a target has to be for the gunship to consider using the ground attack weapon
+#define MIN_GROUND_ATTACK_HEIGHT_DIFF 128.0f // Target's position and hit position must be within this threshold vertically
+
+#define GUNSHIP_WASH_ALTITUDE 1024.0f
+
+#define GUNSHIP_MIN_DAMAGE_THRESHOLD 50.0f
+
+#define GUNSHIP_INNER_NAV_DIST 400.0f
+#define GUNSHIP_OUTER_NAV_DIST 800.0f
+
+#define GUNSHIP_BELLYBLAST_TARGET_HEIGHT 512.0 // Height above targets that the gunship wants to be when bellyblasting
+
+#define GUNSHIP_MISSILE_MAX_RESPONSE_TIME 0.4
+#define GUNSHIP_MAX_HITS_PER_BURST 5
+
+#define GUNSHIP_FLARE_IGNORE_TIME 6.0
+
+//=====================================
+// Custom activities
+//=====================================
+Activity ACT_GUNSHIP_PATROL;
+Activity ACT_GUNSHIP_HOVER;
+Activity ACT_GUNSHIP_CRASH;
+
+#define GUNSHIP_DEBUG_LEADING 1
+#define GUNSHIP_DEBUG_PATH 2
+#define GUNSHIP_DEBUG_STITCHING 3
+#define GUNSHIP_DEBUG_BELLYBLAST 4
+
+ConVar g_debug_gunship( "g_debug_gunship", "0", FCVAR_CHEAT );
+
+//-----------------------------------------------------------------------------
+// Purpose: Dying gunship ragdoll controller
+//-----------------------------------------------------------------------------
+class CGunshipRagdollMotion : public IMotionEvent
+{
+ DECLARE_SIMPLE_DATADESC();
+public:
+ virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
+ {
+ linear = Vector(0,0,400);
+ angular = Vector(0,600,100);
+
+ return SIM_GLOBAL_ACCELERATION;
+ }
+};
+
+BEGIN_SIMPLE_DATADESC( CGunshipRagdollMotion )
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTargetGunshipCrash : public CPointEntity
+{
+ DECLARE_CLASS( CTargetGunshipCrash, CPointEntity );
+public:
+ DECLARE_DATADESC();
+
+ void InputEnable( inputdata_t &inputdata )
+ {
+ m_bDisabled = false;
+ }
+ void InputDisable( inputdata_t &inputdata )
+ {
+ m_bDisabled = true;
+ }
+ bool IsDisabled( void )
+ {
+ return m_bDisabled;
+ }
+ void GunshipCrashedOnTarget( void )
+ {
+ m_OnCrashed.FireOutput( this, this );
+ }
+
+private:
+ bool m_bDisabled;
+
+ COutputEvent m_OnCrashed;
+};
+
+LINK_ENTITY_TO_CLASS( info_target_gunshipcrash, CTargetGunshipCrash );
+
+BEGIN_DATADESC( CTargetGunshipCrash )
+ DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+
+ // Outputs
+ DEFINE_OUTPUT( m_OnCrashed, "OnCrashed" ),
+END_DATADESC()
+
+
+//===================================================================
+// Gunship - the combine dugongic like attack vehicle.
+//===================================================================
+class CNPC_CombineGunship : public CBaseHelicopter
+{
+public:
+ DECLARE_CLASS( CNPC_CombineGunship, CBaseHelicopter );
+
+ CNPC_CombineGunship( void );
+ ~CNPC_CombineGunship( void );
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+ DEFINE_CUSTOM_AI;
+
+ void PlayPatrolLoop( void );
+ void PlayAngryLoop( void );
+
+ void Spawn( void );
+ void Precache( void );
+ void OnRestore( void );
+ void PrescheduleThink( void );
+ void HelicopterPostThink( void );
+ void StopLoopingSounds( void );
+
+ bool IsValidEnemy( CBaseEntity *pEnemy );
+ void GatherEnemyConditions( CBaseEntity *pEnemy );
+
+ void Flight( void );
+
+ bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ void FireDamageOutputsUpto( int iDamageNumber );
+
+ virtual float GetAcceleration( void ) { return 15; }
+
+ virtual void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
+ virtual void DoImpactEffect( trace_t &tr, int nDamageType );
+
+ void MoveHead( void );
+ void UpdateDesiredPosition( void );
+ void DoCombat( void );
+ bool ChooseEnemy( void );
+ void DoMuzzleFlash( void );
+ void Ping( void );
+
+ void FireCannonRound( void );
+
+ // Gunship death process
+ void Event_Killed( const CTakeDamageInfo &info );
+ void BeginCrash( void ); // I'm going to go to a crash point and die there
+ void BeginDestruct( void ); // I want to die now, so create my ragdoll
+ void SelfDestruct( void ); // I'm now fully dead, so remove myself.
+ void CreateSmokeTrail( void );
+ bool FindNearestGunshipCrash( void );
+
+ int BloodColor( void ) { return DONT_BLEED; }
+ void GibMonster( void );
+
+ void UpdateRotorSoundPitch( int iPitch );
+ void InitializeRotorSound( void );
+
+ void ApplyGeneralDrag( void );
+ void ApplySidewaysDrag( const Vector &vecRight );
+
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+
+ void UpdateEnemyTarget( void );
+
+ Vector GetEnemyTarget( void );
+ Vector GetMissileTarget( void );
+
+ float GroundDistToPosition( const Vector &pos );
+
+ bool FireGun( void );
+ bool IsTargettingMissile( void );
+
+ Class_T Classify( void ) { return CLASS_COMBINE_GUNSHIP; } // for now
+ float GetAutoAimRadius() { return 144.0f; }
+
+ // Input functions
+ void InputSetPenetrationDepth( inputdata_t &inputdata );
+ void InputOmniscientOn( inputdata_t &inputdata );
+ void InputOmniscientOff( inputdata_t &inputdata );
+ void InputBlindfireOn( inputdata_t &inputdata );
+ void InputBlindfireOff( inputdata_t &inputdata );
+ void InputSelfDestruct( inputdata_t &inputdata );
+ void InputSetDockingBBox( inputdata_t &inputdata );
+ void InputSetNormalBBox( inputdata_t &inputdata );
+ void InputEnableGroundAttack( inputdata_t &inputdata );
+ void InputDisableGroundAttack( inputdata_t &inputdata );
+ void InputDoGroundAttack( inputdata_t &inputdata );
+
+ //NOTENOTE: I'm rather queasy about adding these, as they can lead to nasty bugs...
+ void InputBecomeInvulnerable( inputdata_t &inputdata );
+ void InputBecomeVulnerable( inputdata_t &inputdata );
+
+ bool PoseGunTowardTargetDirection( const Vector &vTargetDir );
+ void StartCannonBurst( int iBurstSize );
+ void StopCannonBurst( void );
+
+ bool CheckGroundAttack( void );
+ void StartGroundAttack( void );
+ void StopGroundAttack( bool bDoAttack );
+ Vector GetGroundAttackHitPosition( void );
+ void DoGroundAttackExplosion( void );
+ void DrawRotorWash( float flAltitude, const Vector &vecRotorOrigin );
+
+ void ManageWarningBeam( void );
+ void DoBellyBlastDamage( trace_t &tr, Vector vMins, Vector vMaxs );
+
+ // Updates the facing direction
+ void UpdateFacingDirection( void );
+ void CreateBellyBlastEnergyCore( void );
+
+protected:
+ // Because the combine gunship is a leaf class, we can use
+ // static variables to store this information, and save some memory.
+ // Should the gunship end up having inheritors, their activate may
+ // stomp these numbers, in which case you should make these ordinary members
+ // again.
+ static int m_poseFlex_Horz, m_poseFlex_Vert, m_posePitch, m_poseYaw, m_poseFin_Accel, m_poseFin_Sway;
+ static int m_poseWeapon_Pitch, m_poseWeapon_Yaw;
+
+ static bool m_sbStaticPoseParamsLoaded;
+ virtual void PopulatePoseParameters( void );
+
+private:
+ // Outputs
+ COutputEvent m_OnFireCannon;
+ COutputEvent m_OnCrashed;
+
+ COutputEvent m_OnFirstDamage; // First damage tick
+ COutputEvent m_OnSecondDamage;
+ COutputEvent m_OnThirdDamage;
+ COutputEvent m_OnFourthDamage;
+ // Keep track of which damage outputs we've fired. This is necessary
+ // to ensure that the game doesn't break if a mapmaker has outputs that
+ // must be fired on gunships, and the player switches skill levels
+ // midway through a gunship battle.
+ bool m_bDamageOutputsFired[GUNSHIP_NUM_DAMAGE_OUTPUTS];
+
+ float m_flNextGroundAttack; // Time to wait before the next ground attack
+ bool m_bIsGroundAttacking; // Denotes that we are ground attacking
+ bool m_bCanGroundAttack; // Denotes whether we can ground attack or not
+ float m_flGroundAttackTime; // Delay before blast happens from ground attack
+
+ CHandle<SmokeTrail> m_pSmokeTrail;
+ EHANDLE m_hGroundAttackTarget;
+
+ CSoundPatch *m_pAirExhaustSound;
+ CSoundPatch *m_pAirBlastSound;
+ CSoundPatch *m_pCannonSound;
+
+ CBaseEntity *m_pRotorWashModel;
+ QAngle m_vecAngAcceleration;
+
+ float m_flEndDestructTime;
+
+ int m_iDoSmokePuff;
+ int m_iAmmoType;
+ int m_iBurstSize;
+
+ bool m_fBlindfire;
+ bool m_fOmniscient;
+ bool m_bIsFiring;
+ int m_iBurstHits;
+ bool m_bPreFire;
+ bool m_bInvulnerable;
+
+ float m_flTimeNextPing;
+ float m_flPenetrationDepth;
+ float m_flDeltaT;
+ float m_flTimeNextAttack;
+ float m_flNextSeeEnemySound;
+ float m_flNextRocket;
+ float m_flBurstDelay;
+
+ Vector m_vecAttackPosition;
+ Vector m_vecAttackVelocity;
+
+ // Used when the gunships using the chopper model
+ Vector m_angGun;
+
+ // For my death throes
+ IPhysicsMotionController *m_pCrashingController;
+ CGunshipRagdollMotion m_crashCallback;
+ EHANDLE m_hRagdoll;
+ CHandle<CTargetGunshipCrash> m_hCrashTarget;
+ float m_flNextGunshipCrashFind;
+
+ CHandle<CCitadelEnergyCore> m_hEnergyCore;
+
+ CNetworkVector( m_vecHitPos );
+
+ // If true, playing patrol loop.
+ // Else, playing angry.
+ bool m_fPatrolLoopPlaying;
+};
+
+LINK_ENTITY_TO_CLASS( npc_combinegunship, CNPC_CombineGunship );
+
+IMPLEMENT_SERVERCLASS_ST( CNPC_CombineGunship, DT_CombineGunship )
+ SendPropVector(SENDINFO(m_vecHitPos), -1, SPROP_COORD),
+END_SEND_TABLE()
+
+BEGIN_DATADESC( CNPC_CombineGunship )
+
+ DEFINE_ENTITYFUNC( FlyTouch ),
+
+ DEFINE_FIELD( m_flNextGroundAttack,FIELD_TIME ),
+ DEFINE_FIELD( m_bIsGroundAttacking,FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bCanGroundAttack, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flGroundAttackTime,FIELD_TIME ),
+ DEFINE_FIELD( m_pRotorWashModel, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_pSmokeTrail, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hGroundAttackTarget, FIELD_EHANDLE ),
+ DEFINE_SOUNDPATCH( m_pAirExhaustSound ),
+ DEFINE_SOUNDPATCH( m_pAirBlastSound ),
+ DEFINE_SOUNDPATCH( m_pCannonSound ),
+ DEFINE_FIELD( m_vecAngAcceleration,FIELD_VECTOR ),
+ DEFINE_FIELD( m_flDeltaT, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTimeNextAttack, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextSeeEnemySound, FIELD_TIME ),
+ DEFINE_FIELD( m_flEndDestructTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextRocket, FIELD_TIME ),
+ DEFINE_FIELD( m_iDoSmokePuff, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iBurstSize, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flBurstDelay, FIELD_FLOAT ),
+ DEFINE_FIELD( m_fBlindfire, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fOmniscient, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bIsFiring, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_iBurstHits, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flTimeNextPing, FIELD_TIME ),
+ DEFINE_FIELD( m_flPenetrationDepth,FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecAttackPosition, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecAttackVelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_angGun, FIELD_VECTOR ),
+ DEFINE_PHYSPTR( m_pCrashingController ),
+ DEFINE_EMBEDDED( m_crashCallback ),
+ DEFINE_FIELD( m_hRagdoll, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hCrashTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_vecHitPos, FIELD_VECTOR ),
+ DEFINE_FIELD( m_fPatrolLoopPlaying,FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bPreFire, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bInvulnerable, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flNextGunshipCrashFind, FIELD_TIME ),
+
+ DEFINE_FIELD( m_hEnergyCore, FIELD_EHANDLE ),
+
+ DEFINE_ARRAY( m_bDamageOutputsFired, FIELD_BOOLEAN, GUNSHIP_NUM_DAMAGE_OUTPUTS ),
+
+ // Function pointers
+ DEFINE_INPUTFUNC( FIELD_VOID, "OmniscientOn", InputOmniscientOn ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "OmniscientOff", InputOmniscientOff ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPenetrationDepth", InputSetPenetrationDepth ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "BlindfireOn", InputBlindfireOn ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "BlindfireOff", InputBlindfireOff ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetDockingBBox", InputSetDockingBBox ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetNormalBBox", InputSetNormalBBox ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableGroundAttack", InputEnableGroundAttack ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableGroundAttack", InputDisableGroundAttack ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "DoGroundAttack", InputDoGroundAttack ),
+
+ DEFINE_OUTPUT( m_OnFireCannon, "OnFireCannon" ),
+ DEFINE_OUTPUT( m_OnFirstDamage, "OnFirstDamage" ),
+ DEFINE_OUTPUT( m_OnSecondDamage, "OnSecondDamage" ),
+ DEFINE_OUTPUT( m_OnThirdDamage, "OnThirdDamage" ),
+ DEFINE_OUTPUT( m_OnFourthDamage, "OnFourthDamage" ),
+ DEFINE_OUTPUT( m_OnCrashed, "OnCrashed" ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CNPC_CombineGunship::CNPC_CombineGunship( void )
+{
+ m_hGroundAttackTarget = NULL;
+ m_pSmokeTrail = NULL;
+ m_iAmmoType = -1;
+ m_pCrashingController = NULL;
+ m_hRagdoll = NULL;
+ m_hCrashTarget = NULL;
+}
+
+
+void CNPC_CombineGunship::CreateBellyBlastEnergyCore( void )
+{
+ CCitadelEnergyCore *pCore = static_cast<CCitadelEnergyCore*>( CreateEntityByName( "env_citadel_energy_core" ) );
+
+ if ( pCore == NULL )
+ return;
+
+ m_hEnergyCore = pCore;
+
+ int iAttachment = LookupAttachment( "BellyGun" );
+
+ Vector vOrigin;
+ QAngle vAngle;
+
+ GetAttachment( iAttachment, vOrigin, vAngle );
+
+ pCore->SetAbsOrigin( vOrigin );
+ pCore->SetAbsAngles( vAngle );
+
+ DispatchSpawn( pCore );
+ pCore->Activate();
+
+ pCore->SetParent( this, iAttachment );
+ pCore->SetScale( 4.0f );
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::Spawn( void )
+{
+ Precache( );
+
+ if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
+ {
+ SetModel( "models/combine_helicopter.mdl" );
+ }
+ else
+ {
+ SetModel( "models/gunship.mdl" );
+ }
+
+ ExtractBbox( SelectHeaviestSequence( ACT_GUNSHIP_PATROL ), m_cullBoxMins, m_cullBoxMaxs );
+ BaseClass::Spawn();
+
+ InitPathingData( GUNSHIP_ARRIVE_DIST, GUNSHIP_MIN_CHASE_DIST_DIFF, sk_gunship_burst_min.GetFloat() );
+ AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION );
+
+ m_takedamage = DAMAGE_YES;
+
+ SetHullType(HULL_LARGE_CENTERED);
+ SetHullSizeNormal();
+
+ m_iMaxHealth = m_iHealth = 100;
+
+ m_flFieldOfView = -0.707; // 270 degrees
+
+ m_fHelicopterFlags |= BITS_HELICOPTER_GUN_ON;
+
+ InitBoneControllers();
+
+ InitCustomSchedules();
+
+ SetActivity( (Activity)ACT_GUNSHIP_PATROL );
+ SetCollisionGroup( HL2COLLISION_GROUP_GUNSHIP );
+
+ m_flMaxSpeed = GUNSHIP_MAX_SPEED;
+ m_flMaxSpeedFiring = GUNSHIP_MAX_SPEED;
+
+ m_flTimeNextAttack = gpGlobals->curtime;
+ m_flNextSeeEnemySound = gpGlobals->curtime;
+
+ // Init the pose parameters
+ SetPoseParameter( "flex_horz", 0 );
+ SetPoseParameter( "flex_vert", 0 );
+ SetPoseParameter( "fin_accel", 0 );
+ SetPoseParameter( "fin_sway", 0 );
+
+ if( m_iAmmoType == -1 )
+ {
+ // Since there's no weapon to index the ammo type,
+ // do it manually here.
+ m_iAmmoType = GetAmmoDef()->Index("CombineCannon");
+ }
+
+ //!!!HACKHACK
+ // This tricks the AI code that constantly complains that the gunship has no schedule.
+ SetSchedule( SCHED_IDLE_STAND );
+
+ AddRelationship( "env_flare D_LI 9", NULL );
+ AddRelationship( "rpg_missile D_HT 99", NULL );
+
+ m_flTimeNextPing = gpGlobals->curtime + 2;
+
+ m_flPenetrationDepth = 24;
+ m_flBurstDelay = 2.0f;
+
+ // Blindfire and Omniscience default to off
+ m_fBlindfire = false;
+ m_fOmniscient = false;
+ m_bIsFiring = false;
+ m_bPreFire = false;
+ m_bInvulnerable = false;
+
+ // See if we should start being able to attack
+ m_bCanGroundAttack = ( m_spawnflags & SF_GUNSHIP_NO_GROUND_ATTACK ) ? false : true;
+
+ m_flEndDestructTime = 0;
+
+ m_iBurstSize = 0;
+ m_iBurstHits = 0;
+
+ // Do not dissolve
+ AddEFlags( EFL_NO_DISSOLVE );
+
+ for ( int i = 0; i < GUNSHIP_NUM_DAMAGE_OUTPUTS; i++ )
+ {
+ m_bDamageOutputsFired[i] = false;
+ }
+
+ CapabilitiesAdd( bits_CAP_SQUAD);
+
+ if ( hl2_episodic.GetBool() == true )
+ {
+ CreateBellyBlastEnergyCore();
+ }
+
+ // Allows autoaim to help attack the gunship.
+ if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
+ {
+ AddFlag( FL_AIMTARGET );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Restore the motion controller
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::OnRestore( void )
+{
+ BaseClass::OnRestore();
+
+ if ( m_pCrashingController )
+ {
+ m_pCrashingController->SetEventHandler( &m_crashCallback );
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::Precache( void )
+{
+ if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
+ {
+ PrecacheModel( "models/combine_helicopter.mdl" );
+ Chopper_PrecacheChunks( this );
+ }
+ else
+ {
+ PrecacheModel("models/gunship.mdl");
+ }
+
+ PrecacheModel("sprites/lgtning.vmt");
+
+ PrecacheMaterial( "effects/ar2ground2" );
+ PrecacheMaterial( "effects/blueblackflash" );
+
+ PrecacheScriptSound( "NPC_CombineGunship.SearchPing" );
+ PrecacheScriptSound( "NPC_CombineGunship.PatrolPing" );
+ PrecacheScriptSound( "NPC_Strider.Charge" );
+ PrecacheScriptSound( "NPC_Strider.Shoot" );
+ PrecacheScriptSound( "NPC_CombineGunship.SeeEnemy" );
+ PrecacheScriptSound( "NPC_CombineGunship.CannonStartSound" );
+ PrecacheScriptSound( "NPC_CombineGunship.Explode");
+ PrecacheScriptSound( "NPC_CombineGunship.Pain" );
+ PrecacheScriptSound( "NPC_CombineGunship.CannonStopSound" );
+
+ PrecacheScriptSound( "NPC_CombineGunship.DyingSound" );
+ PrecacheScriptSound( "NPC_CombineGunship.CannonSound" );
+ PrecacheScriptSound( "NPC_CombineGunship.RotorSound" );
+ PrecacheScriptSound( "NPC_CombineGunship.ExhaustSound" );
+ PrecacheScriptSound( "NPC_CombineGunship.RotorBlastSound" );
+
+ if ( hl2_episodic.GetBool() == true )
+ {
+ UTIL_PrecacheOther( "env_citadel_energy_core" );
+ g_iGunshipEffectIndex = PrecacheModel( "sprites/physbeam.vmt" );
+ }
+
+ PropBreakablePrecacheAll( MAKE_STRING("models/gunship.mdl") );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Cache whatever pose parameters we intend to use
+//-----------------------------------------------------------------------------
+bool CNPC_CombineGunship::m_sbStaticPoseParamsLoaded = false;
+int CNPC_CombineGunship::m_poseFlex_Horz = 0;
+int CNPC_CombineGunship::m_poseFlex_Vert = 0;
+int CNPC_CombineGunship::m_posePitch = 0;
+int CNPC_CombineGunship::m_poseYaw = 0;
+int CNPC_CombineGunship::m_poseFin_Accel = 0;
+int CNPC_CombineGunship::m_poseFin_Sway = 0;
+int CNPC_CombineGunship::m_poseWeapon_Pitch = 0;
+int CNPC_CombineGunship::m_poseWeapon_Yaw = 0;
+void CNPC_CombineGunship::PopulatePoseParameters( void )
+{
+ if (!m_sbStaticPoseParamsLoaded)
+ {
+ m_poseFlex_Horz = LookupPoseParameter( "flex_horz");
+ m_poseFlex_Vert = LookupPoseParameter( "flex_vert" );
+ m_posePitch = LookupPoseParameter( "pitch" );
+ m_poseYaw = LookupPoseParameter( "yaw" );
+ m_poseFin_Accel = LookupPoseParameter( "fin_accel" );
+ m_poseFin_Sway = LookupPoseParameter( "fin_sway" );
+
+ m_poseWeapon_Pitch = LookupPoseParameter( "weapon_pitch" );
+ m_poseWeapon_Yaw = LookupPoseParameter( "weapon_yaw" );
+
+ m_sbStaticPoseParamsLoaded = true;
+ }
+
+ BaseClass::PopulatePoseParameters();
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+CNPC_CombineGunship::~CNPC_CombineGunship(void)
+{
+ StopLoopingSounds();
+
+ if ( m_pCrashingController )
+ {
+ physenv->DestroyMotionController( m_pCrashingController );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::Ping( void )
+{
+ if( IsCrashing() )
+ return;
+
+ if( GetEnemy() != NULL )
+ {
+ if( !HasCondition(COND_SEE_ENEMY) && gpGlobals->curtime > m_flTimeNextPing )
+ {
+ EmitSound( "NPC_CombineGunship.SearchPing" );
+ m_flTimeNextPing = gpGlobals->curtime + 3;
+ }
+ }
+ else
+ {
+ if( gpGlobals->curtime > m_flTimeNextPing )
+ {
+ EmitSound( "NPC_CombineGunship.PatrolPing" );
+ m_flTimeNextPing = gpGlobals->curtime + 3;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &pos -
+// Output : float
+//-----------------------------------------------------------------------------
+float CNPC_CombineGunship::GroundDistToPosition( const Vector &pos )
+{
+ Vector vecDiff;
+ VectorSubtract( GetAbsOrigin(), pos, vecDiff );
+
+ // Only interested in the 2d dist
+ vecDiff.z = 0;
+
+ return vecDiff.Length();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::PlayPatrolLoop( void )
+{
+ m_fPatrolLoopPlaying = true;
+ /*
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pPatrolSound, 1.0, 1.0 );
+ controller.SoundChangeVolume( m_pAngrySound, 0.0, 1.0 );
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::PlayAngryLoop( void )
+{
+ m_fPatrolLoopPlaying = false;
+ /*
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pPatrolSound, 0.0, 1.0 );
+ controller.SoundChangeVolume( m_pAngrySound, 1.0, 1.0 );
+ */
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::HelicopterPostThink( void )
+{
+ // After HelicopterThink()
+ if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ if ( m_bIsFiring )
+ {
+ // Fire more shots at the dead body for effect
+ if ( m_iBurstSize > 8 )
+ {
+ m_iBurstSize = 8;
+ }
+ }
+
+ // Fade out search sound, fade in patrol sound.
+ PlayPatrolLoop();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Vector
+//-----------------------------------------------------------------------------
+Vector CNPC_CombineGunship::GetGroundAttackHitPosition( void )
+{
+ trace_t tr;
+ Vector vecShootPos, vecShootDir;
+
+ GetAttachment( "BellyGun", vecShootPos, &vecShootDir, NULL, NULL );
+
+ AI_TraceLine( vecShootPos, vecShootPos + Vector( 0, 0, -MAX_TRACE_LENGTH ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( m_hGroundAttackTarget )
+ {
+ return Vector( tr.endpos.x, tr.endpos.y, m_hGroundAttackTarget->WorldSpaceCenter().z );
+ }
+ return tr.endpos;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_CombineGunship::CheckGroundAttack( void )
+{
+ if ( m_bCanGroundAttack == false )
+ return false;
+
+ if ( m_bIsGroundAttacking )
+ return false;
+
+ // Must have an enemy
+ if ( GetEnemy() == NULL )
+ return false;
+
+ // Must not have done it too recently
+ if ( m_flNextGroundAttack > gpGlobals->curtime )
+ return false;
+
+ Vector predPos, predDest;
+
+ // Find where the enemy is most likely to be in two seconds
+ UTIL_PredictedPosition( GetEnemy(), 1.0f, &predPos );
+ UTIL_PredictedPosition( this, 1.0f, &predDest );
+
+ Vector predGap = ( predDest - predPos );
+ predGap.z = 0;
+
+ float predDistance = predGap.Length();
+
+ // Must be within distance
+ if ( predDistance > MIN_GROUND_ATTACK_DIST )
+ return false;
+
+ // Can't ground attack missiles
+ if ( IsTargettingMissile() )
+ return false;
+
+ //FIXME: Check to make sure we're not firing too far above or below the target
+ if ( fabs( GetGroundAttackHitPosition().z - GetEnemy()->WorldSpaceCenter().z ) > MIN_GROUND_ATTACK_HEIGHT_DIFF )
+ return false;
+
+ //FIXME: Check for ground movement capabilities?
+
+ //TODO: Check for friendly-fire
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::StartGroundAttack( void )
+{
+ // Mark us as attacking
+ m_bIsGroundAttacking = true;
+ m_flGroundAttackTime = gpGlobals->curtime + 3.0f;
+
+ // Setup the attack effects
+ Vector vecShootPos;
+
+ GetAttachment( "BellyGun", vecShootPos );
+
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( GUNSHIP_MSG_STREAKS );
+ WRITE_VEC3COORD( vecShootPos );
+ MessageEnd();
+
+ CPASAttenuationFilter filter2( this, "NPC_Strider.Charge" );
+ EmitSound( filter2, entindex(), "NPC_Strider.Charge" );
+
+ Vector endpos = GetGroundAttackHitPosition();
+
+ CSoundEnt::InsertSound ( SOUND_DANGER, endpos, 1024, 0.5f );
+
+ if ( hl2_episodic.GetBool() == true )
+ {
+ if ( m_hEnergyCore )
+ {
+ variant_t value;
+ value.SetFloat( 3.0f );
+
+ g_EventQueue.AddEvent( m_hEnergyCore, "StartCharge", value, 0, this, this );
+ }
+ }
+}
+
+#define GUNSHIP_BELLY_BLAST_RADIUS 256.0f
+#define BELLY_BLAST_MAX_PUNCH 5
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::ManageWarningBeam( void )
+{
+ Vector vecSrc, vecShootDir;
+ GetAttachment( "BellyGun", vecSrc, NULL, NULL, NULL );
+
+ trace_t tr;
+ CTraceFilterSkipTwoEntities filter( m_hGroundAttackTarget, this, COLLISION_GROUP_NONE );
+
+ UTIL_TraceLine( vecSrc, m_vecHitPos, MASK_SOLID, &filter, &tr );
+
+ int iPunch = 0;
+
+ while ( tr.endpos != m_vecHitPos )
+ {
+ iPunch++;
+
+ if ( iPunch > BELLY_BLAST_MAX_PUNCH )
+ break;
+
+ if ( tr.fraction != 1.0 )
+ {
+ if ( tr.m_pEnt )
+ {
+ CTakeDamageInfo info( this, this, 1.0f, DMG_ENERGYBEAM );
+
+ Vector vTargetDir = tr.m_pEnt->BodyTarget( tr.endpos, false ) - tr.endpos;
+
+ VectorNormalize( vTargetDir );
+
+ info.SetDamagePosition( tr.endpos + ( tr.plane.normal * 64.0f ) );
+ info.SetDamageForce( vTargetDir * 100 );
+
+ if ( tr.m_pEnt->m_takedamage != DAMAGE_NO )
+ {
+ // Deal damage
+ tr.m_pEnt->TakeDamage( info );
+ }
+ }
+
+ Vector vDir = m_vecHitPos - vecSrc;
+ VectorNormalize( vDir );
+
+ Vector vStartPunch = tr.endpos + vDir * 1;
+
+ UTIL_TraceLine( vStartPunch, m_vecHitPos, MASK_SOLID, &filter, &tr );
+
+ if ( tr.startsolid )
+ {
+ float flLength = (vStartPunch - tr.endpos).Length();
+
+ Vector vEndPunch = vStartPunch + vDir * ( flLength * tr.fractionleftsolid );
+
+ UTIL_TraceLine( vEndPunch, m_vecHitPos, MASK_SOLID, &filter, &tr );
+
+ trace_t tr2;
+ UTIL_TraceLine( vEndPunch, vEndPunch - vDir * 2, MASK_SOLID, &filter, &tr2 );
+
+ if ( (m_flGroundAttackTime - gpGlobals->curtime) <= 2.0f )
+ {
+ g_pEffects->EnergySplash( tr2.endpos + vDir * 8, tr2.plane.normal, true );
+ }
+
+ g_pEffects->Sparks( tr2.endpos, 3.0f - (m_flGroundAttackTime-gpGlobals->curtime), 3.5f - (m_flGroundAttackTime-gpGlobals->curtime), &tr2.plane.normal );
+
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::DoBellyBlastDamage( trace_t &tr, Vector vMins, Vector vMaxs )
+{
+ CBaseEntity* pList[100];
+
+ if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_BELLYBLAST )
+ {
+ NDebugOverlay::Box( tr.endpos, vMins, vMaxs, 255, 255, 0, true, 5.0f );
+ }
+
+ int count = UTIL_EntitiesInBox( pList, 100, tr.endpos + vMins, tr.endpos + vMaxs, 0 );
+
+ for ( int i = 0; i < count; i++ )
+ {
+ CBaseEntity *pEntity = pList[i];
+
+ if ( pEntity == this )
+ continue;
+
+ if ( pEntity->m_takedamage == DAMAGE_NO )
+ continue;
+
+ float damage = 150;
+
+ if ( pEntity->IsPlayer() )
+ {
+ float damageDist = ( pEntity->GetAbsOrigin() - tr.endpos ).Length();
+ damage = RemapValClamped( damageDist, 0, 300, 200, 0 );
+ }
+
+ CTakeDamageInfo info( this, this, damage, DMG_DISSOLVE );
+
+ Vector vTargetDir = pEntity->BodyTarget( tr.endpos, false ) - tr.endpos;
+
+ VectorNormalize( vTargetDir );
+
+ info.SetDamagePosition( tr.endpos + ( tr.plane.normal * 64.0f ) );
+ info.SetDamageForce( vTargetDir * 25000 );
+
+ // Deal damage
+ pEntity->TakeDamage( info );
+
+ trace_t groundTrace;
+ UTIL_TraceLine( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin() - Vector( 0, 0, 256 ), MASK_SOLID, pEntity, COLLISION_GROUP_NONE, &groundTrace );
+
+ if ( tr.fraction < 1.0f )
+ {
+ CEffectData data;
+
+ // Find the floor and add a dissolve explosion at that point
+ data.m_flRadius = GUNSHIP_BELLY_BLAST_RADIUS * 0.5f;
+ data.m_vNormal = groundTrace.plane.normal;
+ data.m_vOrigin = groundTrace.endpos;
+
+ DispatchEffect( "AR2Explosion", data );
+ }
+
+ // If the creature was killed, then dissolve it
+ if ( pEntity->GetHealth() <= 0.0f )
+ {
+ if ( pEntity->GetBaseAnimating() != NULL && !pEntity->IsEFlagSet( EFL_NO_DISSOLVE ) )
+ {
+ pEntity->GetBaseAnimating()->Dissolve( NULL, gpGlobals->curtime );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::DoGroundAttackExplosion( void )
+{
+ // Fire the bullets
+ Vector vecSrc, vecShootDir;
+ Vector vecAttachmentOrigin;
+ GetAttachment( "BellyGun", vecAttachmentOrigin, &vecShootDir, NULL, NULL );
+
+ vecSrc = vecAttachmentOrigin;
+
+ if ( m_hGroundAttackTarget )
+ {
+ vecSrc = m_hGroundAttackTarget->GetAbsOrigin();
+ }
+
+ Vector impactPoint = vecSrc + ( Vector( 0, 0, -1 ) * MAX_TRACE_LENGTH );
+
+ trace_t tr;
+ UTIL_TraceLine( vecSrc, impactPoint, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ UTIL_DecalTrace( &tr, "Scorch" );
+
+ if ( hl2_episodic.GetBool() == true )
+ {
+ g_pEffects->EnergySplash( tr.endpos, tr.plane.normal );
+
+ CBroadcastRecipientFilter filter;
+ te->BeamRingPoint( filter, 0.0,
+ tr.endpos, //origin
+ 0, //start radius
+ GUNSHIP_BELLY_BLAST_RADIUS, //end radius
+ g_iGunshipEffectIndex, //texture
+ 0, //halo index
+ 0, //start frame
+ 0, //framerate
+ 0.2, //life
+ 10, //width
+ 0, //spread
+ 0, //amplitude
+ 255, //r
+ 255, //g
+ 255, //b
+ 50, //a
+ 0, //speed
+ FBEAM_FADEOUT
+ );
+ }
+
+ // Send the effect over
+ CEffectData data;
+
+ // Do an extra effect if we struck the world
+ if ( tr.m_pEnt && tr.m_pEnt->IsWorld() )
+ {
+ data.m_flRadius = GUNSHIP_BELLY_BLAST_RADIUS;
+ data.m_vNormal = tr.plane.normal;
+ data.m_vOrigin = tr.endpos;
+
+ DispatchEffect( "AR2Explosion", data );
+ }
+
+ float flZLength = vecAttachmentOrigin.z - tr.endpos.z;
+
+ Vector vBeamMins = Vector( -16, -16, 0 );
+ Vector vBeamMaxs = Vector( 16, 16, flZLength );
+
+ DoBellyBlastDamage( tr, vBeamMins, vBeamMaxs );
+
+ Vector vBlastMins = Vector( -GUNSHIP_BELLY_BLAST_RADIUS, -GUNSHIP_BELLY_BLAST_RADIUS, 0 );
+ Vector vBlastMaxs = Vector( GUNSHIP_BELLY_BLAST_RADIUS, GUNSHIP_BELLY_BLAST_RADIUS, 96 );
+
+ DoBellyBlastDamage( tr, vBlastMins, vBlastMaxs );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::StopGroundAttack( bool bDoAttack )
+{
+ if ( !m_bIsGroundAttacking )
+ return;
+
+ // Mark us as no longer attacking
+ m_bIsGroundAttacking = false;
+ m_flNextGroundAttack = gpGlobals->curtime + 4.0f;
+ m_flTimeNextAttack = gpGlobals->curtime + 2.0f;
+
+ Vector hitPos = GetGroundAttackHitPosition();
+
+ // tell the client side effect to complete
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( GUNSHIP_MSG_BIG_SHOT );
+ WRITE_VEC3COORD( hitPos );
+ MessageEnd();
+
+ if ( hl2_episodic.GetBool() == true )
+ {
+ if ( m_hEnergyCore )
+ {
+ variant_t value;
+ value.SetFloat( 1.0f );
+
+ g_EventQueue.AddEvent( m_hEnergyCore, "Stop", value, 0, this, this );
+ }
+ }
+
+ // Only attack if told to
+ if ( bDoAttack )
+ {
+ CPASAttenuationFilter filter2( this, "NPC_Strider.Shoot" );
+ EmitSound( filter2, entindex(), "NPC_Strider.Shoot");
+
+ ApplyAbsVelocityImpulse( Vector( 0, 0, 200.0f ) );
+
+ //ExplosionCreate( hitPos, QAngle( 0, 0, 1 ), this, 500, 500, true );
+ DoGroundAttackExplosion();
+ }
+
+ // If we were attacking a target, revert to our previous target
+ if ( m_hGroundAttackTarget )
+ {
+ m_hGroundAttackTarget = NULL;
+ if ( GetDestPathTarget() )
+ {
+ // Return to our old path
+ SetupNewCurrentTarget( GetDestPathTarget() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::DrawRotorWash( float flAltitude, const Vector &vecRotorOrigin )
+{
+ // If we have a ragdoll, we want the wash under that, not me
+ if ( m_hRagdoll )
+ {
+ BaseClass::DrawRotorWash( flAltitude, m_hRagdoll->GetAbsOrigin() );
+ return;
+ }
+
+ BaseClass::DrawRotorWash( flAltitude, vecRotorOrigin );
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Override the desired position if your derived helicopter is doing something special
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::UpdateDesiredPosition( void )
+{
+ if ( m_hCrashTarget )
+ {
+ SetDesiredPosition( m_hCrashTarget->WorldSpaceCenter() + Vector(0,0,128) );
+ }
+ else if ( m_hGroundAttackTarget )
+ {
+ SetDesiredPosition( m_hGroundAttackTarget->GetAbsOrigin() + Vector(0,0,GUNSHIP_BELLYBLAST_TARGET_HEIGHT) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: do all of the stuff related to having an enemy, attacking, etc.
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::DoCombat( void )
+{
+ // Check for enemy change-overs
+ if ( HasEnemy() )
+ {
+ if ( HasCondition( COND_NEW_ENEMY ) )
+ {
+ if ( GetEnemy() && GetEnemy()->IsPlayer() && m_flNextSeeEnemySound < gpGlobals->curtime )
+ {
+ m_flNextSeeEnemySound = gpGlobals->curtime + 5.0;
+
+ if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
+ {
+ EmitSound( "NPC_CombineGunship.SeeEnemy" );
+ }
+ }
+
+ // If we're shooting at a missile, do it immediately!
+ if ( IsTargettingMissile() )
+ {
+ EmitSound( "NPC_CombineGunship.SeeMissile" );
+
+ // Allow the gunship to attack again immediately
+ if ( ( m_flTimeNextAttack > gpGlobals->curtime ) && ( ( m_flTimeNextAttack - gpGlobals->curtime ) > GUNSHIP_MISSILE_MAX_RESPONSE_TIME ) )
+ {
+ m_flTimeNextAttack = gpGlobals->curtime + GUNSHIP_MISSILE_MAX_RESPONSE_TIME;
+ m_iBurstSize = sk_gunship_burst_size.GetInt();
+ }
+ }
+
+ // Fade in angry sound, fade out patrol sound.
+ PlayAngryLoop();
+ }
+ }
+
+ // Do we have a belly blast target?
+ if ( m_hGroundAttackTarget && !m_bIsGroundAttacking )
+ {
+ // If we're over it, blast. Can't use GetDesiredPosition() because it's not updated yet.
+ Vector vecTarget = m_hGroundAttackTarget->GetAbsOrigin() + Vector(0,0,GUNSHIP_BELLYBLAST_TARGET_HEIGHT);
+ Vector vecToTarget = (vecTarget - GetAbsOrigin());
+ float flDistance = vecToTarget.Length();
+
+ // Get the difference between our velocity & the target's velocity
+ Vector vec2DVelocity = GetAbsVelocity();
+ Vector vec2DTargetVelocity = m_hGroundAttackTarget->GetAbsVelocity();
+ vec2DVelocity.z = vec2DTargetVelocity.z = 0;
+ float flVelocityDiff = (vec2DVelocity - vec2DTargetVelocity).Length();
+ if ( flDistance < 100 && flVelocityDiff < 200 )
+ {
+ StartGroundAttack();
+ }
+ }
+
+ // Update our firing
+ if ( m_bIsFiring )
+ {
+ // Fire if we have rounds remaining in this burst
+ if ( ( m_iBurstSize > 0 ) && ( gpGlobals->curtime > m_flTimeNextAttack ) )
+ {
+ UpdateEnemyTarget();
+ FireCannonRound();
+ }
+ else if ( m_iBurstSize < 1 )
+ {
+ // We're done firing
+ StopCannonBurst();
+
+ if ( IsTargettingMissile() )
+ {
+ m_flTimeNextAttack = gpGlobals->curtime + 0.5f;
+ }
+ }
+ }
+ else
+ {
+ // If we're not firing, look at the enemy
+ if ( GetEnemy() )
+ {
+ m_vecAttackPosition = GetEnemy()->EyePosition();
+ }
+
+#ifdef BELLYBLAST
+ // Check for a ground attack
+ if ( CheckGroundAttack() )
+ {
+ StartGroundAttack();
+ }
+#endif
+
+ // See if we're attacking
+ if ( m_bIsGroundAttacking )
+ {
+ m_vecHitPos = GetGroundAttackHitPosition();
+
+ ManageWarningBeam();
+
+ // If our time is up, fire the blast and be done
+ if ( m_flGroundAttackTime < gpGlobals->curtime )
+ {
+ // Fire!
+ StopGroundAttack( true );
+ }
+ }
+ }
+
+ // If we're using the chopper model, align the gun towards the target
+ if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
+ {
+ Vector vGunPosition;
+ GetAttachment( "gun", vGunPosition );
+ Vector vecToAttackPos = (m_vecAttackPosition - vGunPosition);
+ PoseGunTowardTargetDirection( vecToAttackPos );
+ }
+
+ // Forget flares once I've seen them for a while
+ float flDeltaSeen = m_flLastSeen - m_flPrevSeen;
+ if ( GetEnemy() != NULL && GetEnemy()->Classify() == CLASS_FLARE && flDeltaSeen > GUNSHIP_FLARE_IGNORE_TIME )
+ {
+ AddEntityRelationship( GetEnemy(), D_NU, 5 );
+
+ PlayPatrolLoop();
+
+ // Forget the flare now.
+ SetEnemy( NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_CombineGunship::ChooseEnemy( void )
+{
+ // If we're firing, don't switch enemies. This stops the gunship occasionally
+ // stopping a burst before he's really fired at all, which makes him look indecisive.
+ if ( m_bIsFiring )
+ return true;
+
+ return BaseClass::ChooseEnemy();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: There's a lot of code in here now. We should consider moving
+// helicopters and such to scheduled AI. (sjb)
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::MoveHead( void )
+{
+ float flYaw = GetPoseParameter( m_poseFlex_Horz );
+ float flPitch = GetPoseParameter( m_poseFlex_Vert );
+
+/*
+ This head-turning code will cause the head to POP when switching from looking at the enemy
+ to looking according to the flight model. I will fix this later. Right now I'm turning
+ the code over to Ken for some aiming fixups. (sjb)
+*/
+
+ while( 1 )
+ {
+ if ( GetEnemy() != NULL )
+ {
+ Vector vecToEnemy, vecAimDir;
+ float flDot;
+
+ Vector vTargetPos, vGunPosition;
+ Vector vecTargetOffset;
+ QAngle vGunAngles;
+
+ GetAttachment( "muzzle", vGunPosition, vGunAngles );
+
+ vTargetPos = GetEnemyTarget();
+
+ VectorSubtract( vTargetPos, vGunPosition, vecToEnemy );
+ VectorNormalize( vecToEnemy );
+
+ // get angles relative to body position
+ AngleVectors( GetAbsAngles(), &vecAimDir );
+ flDot = DotProduct( vecAimDir, vecToEnemy );
+
+ // Look at Enemy!!
+ if ( flDot > 0.3f )
+ {
+ float flDiff;
+
+ float flDesiredYaw = VecToYaw(vTargetPos - vGunPosition);
+ flDiff = UTIL_AngleDiff( flDesiredYaw, vGunAngles.y ) * 0.90;
+ flYaw = UTIL_Approach( flYaw + flDiff, flYaw, 5.0 );
+
+ float flDesiredPitch = UTIL_VecToPitch(vTargetPos - vGunPosition);
+ flDiff = UTIL_AngleDiff( flDesiredPitch, vGunAngles.x ) * 0.90;
+ flPitch = UTIL_Approach( flPitch + flDiff, flPitch, 5.0 );
+
+ break;
+ }
+ }
+
+ // Look where going!
+#if 1 // old way- look according to rotational velocity
+ flYaw = UTIL_Approach( GetLocalAngularVelocity().y, flYaw, 2.0 * 10 * m_flDeltaT );
+ flPitch = UTIL_Approach( GetLocalAngularVelocity().x, flPitch, 2.0 * 10 * m_flDeltaT );
+#else // new way- look towards the next waypoint?
+ // !!!UNDONE
+#endif
+ break;
+ }
+
+ // Set the body flexes
+ SetPoseParameter( m_poseFlex_Vert, flPitch );
+ SetPoseParameter( m_poseFlex_Horz, flYaw );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: There's a lot of code in here now. We should consider moving
+// helicopters and such to scheduled AI. (sjb)
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::PrescheduleThink( void )
+{
+ m_flDeltaT = gpGlobals->curtime - GetLastThink();
+
+ // Are we crashing?
+ if ( m_flEndDestructTime && gpGlobals->curtime > m_flEndDestructTime )
+ {
+ // We're dead, remove ourselves
+ SelfDestruct();
+ return;
+ }
+
+ if( m_lifeState == LIFE_ALIVE )
+ {
+ // Chopper doesn't ping
+ if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
+ {
+ Ping();
+ }
+
+ DoCombat();
+ MoveHead();
+ }
+ else if( m_lifeState == LIFE_DYING )
+ {
+ // Increase the number of explosions as he gets closer to death
+ bool bCreateExplosion = false;
+ float flTimeLeft = m_flEndDestructTime - gpGlobals->curtime;
+ if ( flTimeLeft > 1.5 )
+ {
+ bCreateExplosion = (random->RandomInt( 0, 3 ) == 0);
+ }
+ else
+ {
+ bCreateExplosion = (random->RandomInt( 0, 2 ) == 0);
+ }
+
+ if ( bCreateExplosion )
+ {
+ Vector explodePoint;
+ if ( m_hRagdoll )
+ {
+ m_hRagdoll->CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );
+ }
+ else
+ {
+ CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );
+
+ // Knock the gunship a little, but not if we're trying to fly to a point
+ if ( !m_hCrashTarget )
+ {
+ Vector vecPush = (GetAbsOrigin() - explodePoint);
+ VectorNormalize( vecPush );
+ ApplyAbsVelocityImpulse( vecPush * 128 );
+ }
+ }
+
+ ExplosionCreate( explodePoint, QAngle(0,0,1), this, 100, 128, false );
+ }
+
+ // Have we reached our crash point?
+ if ( m_flNextGunshipCrashFind && !m_hRagdoll )
+ {
+ // Update nearest crash point. The RPG that killed us may have knocked us
+ // closer to a different point than the one we were near when we first died.
+ if ( m_flNextGunshipCrashFind < gpGlobals->curtime )
+ {
+ FindNearestGunshipCrash();
+ }
+
+ if ( m_hCrashTarget )
+ {
+ MoveHead();
+
+ UpdateDesiredPosition();
+
+ // If we're over it, destruct
+ Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
+ if ( vecToTarget.LengthSqr() < (384 * 384) )
+ {
+ BeginDestruct();
+ m_OnCrashed.FireOutput( this, this );
+ m_hCrashTarget->GunshipCrashedOnTarget();
+ return;
+ }
+ }
+ }
+ }
+
+ BaseClass::PrescheduleThink();
+
+#ifdef JACOBS_GUNSHIP
+ SetPoseParameter( m_posePitch, random->RandomFloat( GUNSHIP_HEAD_MAX_LEFT, GUNSHIP_HEAD_MAX_RIGHT ) );
+ SetPoseParameter( m_poseYaw, random->RandomFloat( GUNSHIP_HEAD_MAX_UP, GUNSHIP_HEAD_MAX_DOWN ) );
+#endif
+
+}
+
+//------------------------------------------------------------------------------
+// Purpose : If the enemy is in front of the gun, load up a burst.
+// Actual gunfire is handled in PrescheduleThink
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+bool CNPC_CombineGunship::FireGun( void )
+{
+ if ( m_lifeState != LIFE_ALIVE )
+ return false;
+
+ if ( m_bIsGroundAttacking )
+ return false;
+
+ if ( GetEnemy() && !m_bIsFiring && gpGlobals->curtime > m_flTimeNextAttack )
+ {
+ // We want to decelerate to attack
+ if (m_flGoalSpeed > GetMaxSpeedFiring() )
+ {
+ m_flGoalSpeed = GetMaxSpeedFiring();
+ }
+
+ bool bTargetingMissile = IsTargettingMissile();
+ if ( !bTargetingMissile && !m_bPreFire )
+ {
+ m_bPreFire = true;
+ m_flTimeNextAttack = gpGlobals->curtime + 0.5f;
+
+ EmitSound( "NPC_CombineGunship.CannonStartSound" );
+ return false;
+ }
+
+ //TODO: Emit the danger noise and wait until it's finished
+
+ // Don't fire at an occluded enemy unless blindfire is on.
+ if ( HasCondition( COND_ENEMY_OCCLUDED ) && ( m_fBlindfire == false ) )
+ return false;
+
+ // Don't shoot if the enemy is too close
+ if ( !bTargetingMissile && GroundDistToPosition( GetEnemy()->GetAbsOrigin() ) < GUNSHIP_STITCH_MIN )
+ return false;
+
+ Vector vecAimDir, vecToEnemy;
+ Vector vecMuzzle, vecEnemyTarget;
+
+ GetAttachment( "muzzle", vecMuzzle, &vecAimDir, NULL, NULL );
+ vecEnemyTarget = GetEnemyTarget();
+
+ // Aim with the muzzle's attachment point.
+ VectorSubtract( vecEnemyTarget, vecMuzzle, vecToEnemy );
+
+ VectorNormalize( vecToEnemy );
+ VectorNormalize( vecAimDir );
+
+ if ( DotProduct( vecToEnemy, vecAimDir ) > 0.9 )
+ {
+ StartCannonBurst( sk_gunship_burst_size.GetInt() );
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Fire a round from the cannon
+// Notes: Only call this if you have an enemy.
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::FireCannonRound( void )
+{
+ Vector vecPenetrate;
+ trace_t tr;
+
+ Vector vecToEnemy, vecEnemyTarget;
+ Vector vecMuzzle;
+ Vector vecAimDir;
+
+ GetAttachment( "muzzle", vecMuzzle, &vecAimDir );
+ vecEnemyTarget = GetEnemyTarget();
+
+ // Aim with the muzzle's attachment point.
+ VectorSubtract( vecEnemyTarget, vecMuzzle, vecToEnemy );
+ VectorNormalize( vecToEnemy );
+
+ // If the gun is wildly off target, stop firing!
+ // FIXME - this should use a vector pointing
+ // to the enemy's location PLUS the stitching
+ // error! (sjb) !!!BUGBUG
+
+ if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
+ {
+ QAngle vecAimAngle;
+ Vector vForward, vRight, vUp;
+ GetAttachment( "muzzle", vecMuzzle, &vForward, &vRight, &vUp );
+ AngleVectors( vecAimAngle, &vForward, &vRight, &vUp );
+ NDebugOverlay::Line( vecMuzzle, vecEnemyTarget, 255, 255, 0, true, 1.0f );
+
+ NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vForward * 64.0f ), 255, 0, 0, true, 1.0f );
+ NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vRight * 32.0f ), 0, 255, 0, true, 1.0f );
+ NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vUp * 32.0f ), 0, 0, 255, true, 1.0f );
+ }
+
+ // Robin: Check the dotproduct to the enemy, NOT to the offsetted firing angle
+ // Fixes problems firing at close enemies, where the enemy is valid but
+ // the offset firing stitch isn't.
+ Vector vecDotCheck = vecToEnemy;
+ if ( GetEnemy() )
+ {
+ VectorSubtract( GetEnemy()->GetAbsOrigin(), vecMuzzle, vecDotCheck );
+ VectorNormalize( vecDotCheck );
+ }
+
+ if ( DotProduct( vecDotCheck, vecAimDir ) < 0.8f )
+ {
+ StopCannonBurst();
+ return;
+ }
+
+ DoMuzzleFlash();
+
+ m_OnFireCannon.FireOutput( this, this, 0 );
+
+ m_flTimeNextAttack = gpGlobals->curtime + 0.05f;
+
+ float flPrevHealth = 0;
+ if ( GetEnemy() )
+ {
+ flPrevHealth = GetEnemy()->GetHealth();
+ }
+
+ // Make sure we hit missiles
+ if ( IsTargettingMissile() )
+ {
+ // Fire a fake shot
+ FireBullets( 1, vecMuzzle, vecToEnemy, VECTOR_CONE_5DEGREES, 8192, m_iAmmoType, 1 );
+
+ CBaseEntity *pMissile = GetEnemy();
+
+ Vector missileDir, threatDir;
+
+ AngleVectors( pMissile->GetAbsAngles(), &missileDir );
+
+ threatDir = ( WorldSpaceCenter() - pMissile->GetAbsOrigin() );
+ float threatDist = VectorNormalize( threatDir );
+
+ // Check that the target is within some threshold
+ if ( ( DotProduct( threatDir, missileDir ) > 0.95f ) && ( threatDist < 1024.0f ) )
+ {
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ CTakeDamageInfo info( this, this, 200, DMG_MISSILEDEFENSE );
+ CalculateBulletDamageForce( &info, m_iAmmoType, -threatDir, WorldSpaceCenter() );
+ GetEnemy()->TakeDamage( info );
+ }
+ }
+ else
+ {
+ //FIXME: Some other metric
+ }
+ }
+ else
+ {
+ m_iBurstSize--;
+
+ // Fire directly at the target
+ FireBulletsInfo_t info( 1, vecMuzzle, vecToEnemy, vec3_origin, MAX_COORD_RANGE, m_iAmmoType );
+ info.m_iTracerFreq = 1;
+ CAmmoDef *pAmmoDef = GetAmmoDef();
+ info.m_iPlayerDamage = pAmmoDef->PlrDamage( m_iAmmoType );
+
+ // If we've already hit the player, do 0 damage. This ensures we don't hit the
+ // player multiple times during a single burst.
+ if ( m_iBurstHits >= GUNSHIP_MAX_HITS_PER_BURST )
+ {
+ info.m_iPlayerDamage = 1;
+ }
+
+ FireBullets( info );
+
+ if ( GetEnemy() && flPrevHealth != GetEnemy()->GetHealth() )
+ {
+ m_iBurstHits++;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::DoMuzzleFlash( void )
+{
+ BaseClass::DoMuzzleFlash();
+
+ CEffectData data;
+
+ data.m_nAttachmentIndex = LookupAttachment( "muzzle" );
+ data.m_nEntIndex = entindex();
+ DispatchEffect( "GunshipMuzzleFlash", data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_CombineGunship::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ bool fReturn = BaseClass::FVisible( pEntity, traceMask, ppBlocker );
+
+ if( m_fOmniscient )
+ {
+ if( !fReturn )
+ {
+ // Set this condition so that we can check it later and know that the
+ // enemy truly is occluded, but the gunship regards it as visible due
+ // to omniscience.
+ SetCondition( COND_ENEMY_OCCLUDED );
+ }
+ else
+ {
+ ClearCondition( COND_ENEMY_OCCLUDED );
+ }
+
+ return true;
+ }
+
+ if( fReturn )
+ {
+ ClearCondition( COND_ENEMY_OCCLUDED );
+ }
+ else
+ {
+ SetCondition( COND_ENEMY_OCCLUDED );
+ }
+
+ return fReturn;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Change the depth that gunship bullets can penetrate through solids
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::InputSetPenetrationDepth( inputdata_t &inputdata )
+{
+ m_flPenetrationDepth = inputdata.value.Float();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow the gunship to sense its enemy's location even when enemy
+// is hidden from sight.
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::InputOmniscientOn( inputdata_t &inputdata )
+{
+ m_fOmniscient = true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the gunship to its default requirement that it see the
+// enemy to know its current position
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::InputOmniscientOff( inputdata_t &inputdata )
+{
+ m_fOmniscient = false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows the gunship to fire at an unseen enemy. The gunship is relying
+// on hitting the target with bullets that will punch through the
+// cover that the enemy is hiding behind. (Such as the Depot lighthouse)
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::InputBlindfireOn( inputdata_t &inputdata )
+{
+ m_fBlindfire = true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the gunship to default rules for attacking the enemy. The
+// enemy must be seen to be fired at.
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::InputBlindfireOff( inputdata_t &inputdata )
+{
+ m_fBlindfire = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the gunship's paddles flailing!
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::Event_Killed( const CTakeDamageInfo &info )
+{
+ m_takedamage = DAMAGE_NO;
+
+ StopCannonBurst();
+
+ // Replace the rotor sound with broken engine sound.
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundDestroy( m_pRotorSound );
+
+ // BUGBUG: Isn't this sound just going to get stomped when the base class calls StopLoopingSounds() ??
+ CPASAttenuationFilter filter2( this );
+ m_pRotorSound = controller.SoundCreate( filter2, entindex(), "NPC_CombineGunship.DyingSound" );
+ controller.Play( m_pRotorSound, 1.0, 100 );
+
+ m_OnDeath.FireOutput( info.GetAttacker(), this );
+ SendOnKilledGameEvent( info );
+
+ BeginCrash();
+
+ // we deliberately do not call BaseClass::EventKilled
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::BeginCrash( void )
+{
+ m_lifeState = LIFE_DYING;
+ StopGroundAttack( false );
+
+ // Increase our smoke trail
+ CreateSmokeTrail();
+ if ( m_pSmokeTrail )
+ {
+ m_pSmokeTrail->SetLifetime( -1 );
+ m_pSmokeTrail->m_StartSize = 64;
+ m_pSmokeTrail->m_EndSize = 128;
+ m_pSmokeTrail->m_Opacity = 0.5f;
+ }
+
+ if ( !FindNearestGunshipCrash() )
+ {
+ // We couldn't find a crash target, so just die right here.
+ BeginDestruct();
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_CombineGunship::FindNearestGunshipCrash( void )
+{
+ // Find the nearest crash point. If we find one, we'll try to fly to it and die.
+ // If we can't find one, we'll die right here.
+ bool bFoundAnyCrashTargets = false;
+ float flNearest = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH;
+ CTargetGunshipCrash *pNearest = NULL;
+ CBaseEntity *pEnt = NULL;
+ while( (pEnt = gEntList.FindEntityByClassname(pEnt, "info_target_gunshipcrash")) != NULL )
+ {
+ CTargetGunshipCrash *pCrashTarget = assert_cast<CTargetGunshipCrash*>(pEnt);
+ if ( pCrashTarget->IsDisabled() )
+ continue;
+
+ bFoundAnyCrashTargets = true;
+
+ float flDist = ( pEnt->WorldSpaceCenter() - WorldSpaceCenter() ).LengthSqr();
+ if( flDist < flNearest )
+ {
+ trace_t tr;
+ UTIL_TraceLine( WorldSpaceCenter(), pEnt->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
+ if( tr.fraction == 1.0 )
+ {
+ pNearest = pCrashTarget;
+ flNearest = flDist;
+ }
+ else if ( g_debug_gunship.GetInt() )
+ {
+ NDebugOverlay::Line( WorldSpaceCenter(), tr.endpos, 255,0,0, true, 99);
+ }
+ }
+ }
+
+ if ( !pNearest )
+ {
+ // If we found a gunship crash, but none near enough, claim we did find one, so that we
+ // don't blow up yet. This will give us 3 seconds to attempt to find one before dying.
+ if ( !m_hCrashTarget && bFoundAnyCrashTargets )
+ {
+ m_flNextGunshipCrashFind = gpGlobals->curtime + 0.5;
+ m_flEndDestructTime = gpGlobals->curtime + 3.0;
+ return true;
+ }
+
+ return false;
+ }
+
+ // Fly to the crash point and destruct there
+ m_hCrashTarget = pNearest;
+ m_flNextGunshipCrashFind = gpGlobals->curtime + 0.5;
+ m_flEndDestructTime = 0;
+
+ if ( g_debug_gunship.GetInt() )
+ {
+ NDebugOverlay::Line(GetAbsOrigin(), m_hCrashTarget->GetAbsOrigin(), 0,255,0, true, 0.5);
+ NDebugOverlay::Box( m_hCrashTarget->GetAbsOrigin(), -Vector(200,200,200), Vector(200,200,200), 0,255,0, 128, 0.5 );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: I'm now ready to die. Create my ragdoll & hide myself.
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::BeginDestruct( void )
+{
+ m_flEndDestructTime = gpGlobals->curtime + 3.0;
+
+ // Clamp velocity
+ if( hl2_episodic.GetBool() && GetAbsVelocity().Length() > 700.0f )
+ {
+ Vector vecVelocity = GetAbsVelocity();
+ VectorNormalize( vecVelocity );
+ SetAbsVelocity( vecVelocity * 700.0f );
+ }
+
+ CTakeDamageInfo info;
+ info.SetDamage( 40000 );
+ CalculateExplosiveDamageForce( &info, GetAbsVelocity(), GetAbsOrigin() );
+
+ // Don't create a ragdoll if we're going to explode into gibs
+ if ( !m_hCrashTarget )
+ return;
+
+ // Switch to damaged skin
+ m_nSkin = 1;
+
+ if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
+ {
+ Chopper_BecomeChunks( this );
+ SetThink( &CNPC_CombineGunship::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ AddEffects( EF_NODRAW );
+ return;
+ }
+
+ // Create the ragdoll
+ m_hRagdoll = CreateServerRagdoll( this, 0, info, COLLISION_GROUP_NONE );
+ if ( !m_hRagdoll )
+ {
+ // Failed, just explode
+ SelfDestruct();
+ return;
+ }
+
+ m_hRagdoll->SetName( AllocPooledString( UTIL_VarArgs("%s_ragdoll", STRING(GetEntityName()) ) ) );
+
+ // Tell the smoke trail to follow the ragdoll
+ CreateSmokeTrail();
+ if ( m_pSmokeTrail )
+ {
+ // Force the smoke trail to stay on, and tell it to follow the ragdoll
+ m_pSmokeTrail->SetLifetime( -1 );
+ m_pSmokeTrail->FollowEntity( m_hRagdoll );
+
+ m_pSmokeTrail->m_StartSize = 64;
+ m_pSmokeTrail->m_EndSize = 128;
+ m_pSmokeTrail->m_Opacity = 0.5f;
+ }
+
+ /*
+ // ROBIN: Disabled this for now.
+ //
+ // Create the crashing controller and attach it to the ragdoll physics objects
+ m_pCrashingController = physenv->CreateMotionController( &m_crashCallback );
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = m_hRagdoll->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ for ( int i = 0; i < count; i++ )
+ {
+ m_pCrashingController->AttachObject( pList[i], false );
+ }
+ */
+
+ // Hide myself, because the ragdoll's now taken my place
+ AddEffects( EF_NODRAW );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a smoke trail
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::CreateSmokeTrail( void )
+{
+ if ( m_pSmokeTrail )
+ return;
+
+ m_pSmokeTrail = SmokeTrail::CreateSmokeTrail();
+
+ if ( m_pSmokeTrail )
+ {
+ m_pSmokeTrail->m_SpawnRate = 48;
+ m_pSmokeTrail->m_ParticleLifetime = 2.5f;
+
+ m_pSmokeTrail->m_StartColor.Init( 0.25f, 0.25f, 0.25f );
+ m_pSmokeTrail->m_EndColor.Init( 0.0, 0.0, 0.0 );
+
+ m_pSmokeTrail->m_StartSize = 24;
+ m_pSmokeTrail->m_EndSize = 128;
+ m_pSmokeTrail->m_SpawnRadius = 4;
+ m_pSmokeTrail->m_MinSpeed = 8;
+ m_pSmokeTrail->m_MaxSpeed = 64;
+ m_pSmokeTrail->m_Opacity = 0.2f;
+
+ m_pSmokeTrail->SetLifetime( -1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::ApplyGeneralDrag( void )
+{
+ Vector vecNewVelocity = GetAbsVelocity();
+
+ // See if we need to stop more quickly
+ if ( m_bIsGroundAttacking )
+ {
+ vecNewVelocity *= 0.95f;
+ }
+ else
+ {
+ vecNewVelocity *= 0.995;
+ }
+
+ SetAbsVelocity( vecNewVelocity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void CNPC_CombineGunship::Flight( void )
+{
+ if( GetFlags() & FL_ONGROUND )
+ {
+ //This would be really bad.
+ SetGroundEntity( NULL );
+ }
+
+ if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_PATH )
+ {
+ NDebugOverlay::Line(GetLocalOrigin(), GetDesiredPosition(), 0,0,255, true, 0.1);
+ }
+
+ // calc desired acceleration
+ float dt = 1.0f;
+
+ Vector accel;
+ float accelRate = GUNSHIP_ACCEL_RATE;
+ float maxSpeed = GetMaxSpeed();
+
+ if ( m_lifeState == LIFE_DYING && m_hCrashTarget != NULL )
+ {
+ // Gunship can fly faster to the place where it's supposed to crash, but
+ // maintain normal speeds if we haven't found a place to crash.
+ accelRate *= 2.0;
+ maxSpeed *= 4.0;
+ }
+
+ float flCurrentSpeed = GetAbsVelocity().Length();
+ float flDist = MIN( flCurrentSpeed + accelRate, maxSpeed );
+
+ Vector deltaPos;
+ if ( m_lifeState == LIFE_DYING || m_hGroundAttackTarget )
+ {
+ // Move directly to the target point
+ deltaPos = GetDesiredPosition();
+ }
+ else
+ {
+ ComputeActualTargetPosition( flDist, dt, 0.0f, &deltaPos );
+ }
+ deltaPos -= GetAbsOrigin();
+
+ // calc goal linear accel to hit deltaPos in dt time.
+ accel.x = 2.0 * (deltaPos.x - GetAbsVelocity().x * dt) / (dt * dt);
+ accel.y = 2.0 * (deltaPos.y - GetAbsVelocity().y * dt) / (dt * dt);
+ accel.z = 2.0 * (deltaPos.z - GetAbsVelocity().z * dt + 0.5 * 384 * dt * dt) / (dt * dt);
+
+ float flDistFromPath = 0.0f;
+ Vector vecPoint, vecDelta;
+ if ( m_lifeState != LIFE_DYING && IsOnPathTrack() )
+ {
+ // Also, add in a little force to get us closer to our current line segment if we can
+ ClosestPointToCurrentPath( &vecPoint );
+ VectorSubtract( vecPoint, GetAbsOrigin(), vecDelta );
+ flDistFromPath = VectorNormalize( vecDelta );
+ if ( flDistFromPath > GUNSHIP_OUTER_NAV_DIST )
+ {
+ // Strongly constrain to an n unit pipe around the current path
+ // by damping out all impulse forces that would push us further from the pipe
+ float flAmount = (flDistFromPath - GUNSHIP_OUTER_NAV_DIST) / 200.0f;
+ flAmount = clamp( flAmount, 0, 1 );
+ VectorMA( accel, flAmount * 200.0f, vecDelta, accel );
+ }
+ }
+
+ Vector vecAvoidForce;
+ CAvoidSphere::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
+ accel += vecAvoidForce;
+ CAvoidBox::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
+ accel += vecAvoidForce;
+
+ if ( m_lifeState != LIFE_DYING || m_hCrashTarget == NULL )
+ {
+ // don't fall faster than 0.2G or climb faster than 2G
+ accel.z = clamp( accel.z, 384 * 0.2, 384 * 2.0 );
+ }
+
+ Vector forward, right, up;
+ GetVectors( &forward, &right, &up );
+
+ Vector goalUp = accel;
+ VectorNormalize( goalUp );
+
+ // calc goal orientation to hit linear accel forces
+ float goalPitch = RAD2DEG( asin( DotProduct( forward, goalUp ) ) );
+ float goalYaw = UTIL_VecToYaw( m_vecDesiredFaceDir );
+ float goalRoll = RAD2DEG( asin( DotProduct( right, goalUp ) ) );
+
+ // clamp goal orientations
+ goalPitch = clamp( goalPitch, -45, 60 );
+ goalRoll = clamp( goalRoll, -45, 45 );
+
+ // calc angular accel needed to hit goal pitch in dt time.
+ dt = 0.6;
+ QAngle goalAngAccel;
+ goalAngAccel.x = 2.0 * (AngleDiff( goalPitch, AngleNormalize( GetLocalAngles().x ) ) - GetLocalAngularVelocity().x * dt) / (dt * dt);
+ goalAngAccel.y = 2.0 * (AngleDiff( goalYaw, AngleNormalize( GetLocalAngles().y ) ) - GetLocalAngularVelocity().y * dt) / (dt * dt);
+ goalAngAccel.z = 2.0 * (AngleDiff( goalRoll, AngleNormalize( GetLocalAngles().z ) ) - GetLocalAngularVelocity().z * dt) / (dt * dt);
+
+ goalAngAccel.x = clamp( goalAngAccel.x, -300, 300 );
+ //goalAngAccel.y = clamp( goalAngAccel.y, -60, 60 );
+ goalAngAccel.y = clamp( goalAngAccel.y, -120, 120 );
+ goalAngAccel.z = clamp( goalAngAccel.z, -300, 300 );
+
+ // limit angular accel changes to similate mechanical response times
+ dt = 0.1;
+ QAngle angAccelAccel;
+ angAccelAccel.x = (goalAngAccel.x - m_vecAngAcceleration.x) / dt;
+ angAccelAccel.y = (goalAngAccel.y - m_vecAngAcceleration.y) / dt;
+ angAccelAccel.z = (goalAngAccel.z - m_vecAngAcceleration.z) / dt;
+
+ angAccelAccel.x = clamp( angAccelAccel.x, -1000, 1000 );
+ angAccelAccel.y = clamp( angAccelAccel.y, -1000, 1000 );
+ angAccelAccel.z = clamp( angAccelAccel.z, -1000, 1000 );
+
+ m_vecAngAcceleration += angAccelAccel * 0.1;
+
+ // DevMsg( "pitch %6.1f (%6.1f:%6.1f) ", goalPitch, GetLocalAngles().x, m_vecAngVelocity.x );
+ // DevMsg( "roll %6.1f (%6.1f:%6.1f) : ", goalRoll, GetLocalAngles().z, m_vecAngVelocity.z );
+ // DevMsg( "%6.1f %6.1f %6.1f : ", goalAngAccel.x, goalAngAccel.y, goalAngAccel.z );
+ // DevMsg( "%6.0f %6.0f %6.0f\n", angAccelAccel.x, angAccelAccel.y, angAccelAccel.z );
+
+ ApplySidewaysDrag( right );
+ ApplyGeneralDrag();
+
+ QAngle angVel = GetLocalAngularVelocity();
+ angVel += m_vecAngAcceleration * 0.1;
+
+ //angVel.y = clamp( angVel.y, -60, 60 );
+ //angVel.y = clamp( angVel.y, -120, 120 );
+ angVel.y = clamp( angVel.y, -120, 120 );
+
+ SetLocalAngularVelocity( angVel );
+
+ m_flForce = m_flForce * 0.8 + (accel.z + fabs( accel.x ) * 0.1 + fabs( accel.y ) * 0.1) * 0.1 * 0.2;
+
+ Vector vecImpulse = m_flForce * up;
+
+ if ( !m_hCrashTarget && m_lifeState == LIFE_DYING && !hl2_episodic.GetBool() )
+ {
+ // Force gunship to the ground if it doesn't have a specific place to crash.
+ // EXCEPT In episodic, where forcing it to the ground means it crashes where the player can't see (attic showdown) (sjb)
+ vecImpulse.z = -10;
+ }
+ else
+ {
+ vecImpulse.z -= 38.4; // 32ft/sec
+ }
+
+ // Find our current velocity
+ Vector vecVelDir = GetAbsVelocity();
+ VectorNormalize( vecVelDir );
+
+ if ( flDistFromPath > GUNSHIP_INNER_NAV_DIST )
+ {
+ // Strongly constrain to an n unit pipe around the current path
+ // by damping out all impulse forces that would push us further from the pipe
+ float flDot = DotProduct( vecImpulse, vecDelta );
+ if ( flDot < 0.0f )
+ {
+ VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
+ }
+
+ // Also apply an extra impulse to compensate for the current velocity
+ flDot = DotProduct( vecVelDir, vecDelta );
+ if ( flDot < 0.0f )
+ {
+ VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
+ }
+ }
+
+ // Find our acceleration direction
+ Vector vecAccelDir = vecImpulse;
+ VectorNormalize( vecAccelDir );
+
+ // Level out our plane of movement
+ vecAccelDir.z = 0.0f;
+ vecVelDir.z = 0.0f;
+ forward.z = 0.0f;
+ right.z = 0.0f;
+
+ // Find out how "fast" we're moving in relation to facing and acceleration
+ float speed = m_flForce * DotProduct( vecVelDir, vecAccelDir );// * DotProduct( forward, vecVelDir );
+
+ // Apply the acceleration blend to the fins
+ float finAccelBlend = SimpleSplineRemapVal( speed, -60, 60, -1, 1 );
+ float curFinAccel = GetPoseParameter( m_poseFin_Accel );
+
+ curFinAccel = UTIL_Approach( finAccelBlend, curFinAccel, 0.5f );
+ SetPoseParameter( m_poseFin_Accel, curFinAccel );
+
+ speed = m_flForce * DotProduct( vecVelDir, right );
+
+ // Apply the spin sway to the fins
+ float finSwayBlend = SimpleSplineRemapVal( speed, -60, 60, -1, 1 );
+ float curFinSway = GetPoseParameter( m_poseFin_Sway );
+
+ curFinSway = UTIL_Approach( finSwayBlend, curFinSway, 0.5f );
+ SetPoseParameter( m_poseFin_Sway, curFinSway );
+
+ if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_PATH )
+ {
+ NDebugOverlay::Line(GetLocalOrigin(), GetLocalOrigin() + vecImpulse, 255,0,0, true, 0.1);
+ }
+
+ // Add in our velocity pulse for this frame
+ ApplyAbsVelocityImpulse( vecImpulse );
+}
+
+//------------------------------------------------------------------------------
+// Updates the facing direction
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::UpdateFacingDirection( void )
+{
+ if ( GetEnemy() )
+ {
+ if ( !IsCrashing() && m_flLastSeen + 5 > gpGlobals->curtime )
+ {
+ // If we've seen the target recently, face the target.
+ //Msg( "Facing Target \n" );
+ m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
+ }
+ else
+ {
+ // Remain facing the way you were facing...
+ }
+ }
+ else
+ {
+ // Face our desired position.
+ if ( GetDesiredPosition().DistToSqr( GetAbsOrigin() ) > 1 )
+ {
+ m_vecDesiredFaceDir = GetDesiredPosition() - GetAbsOrigin();
+ }
+ else
+ {
+ GetVectors( &m_vecDesiredFaceDir, NULL, NULL );
+ }
+ }
+ VectorNormalize( m_vecDesiredFaceDir );
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Fire up the Gunships 'second' rotor sound. The Search sound.
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::InitializeRotorSound( void )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+
+ m_pCannonSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.CannonSound" );
+ m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.RotorSound" );
+ m_pAirExhaustSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.ExhaustSound" );
+ m_pAirBlastSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.RotorBlastSound" );
+
+ controller.Play( m_pCannonSound, 0.0, 100 );
+ controller.Play( m_pAirExhaustSound, 0.0, 100 );
+ controller.Play( m_pAirBlastSound, 0.0, 100 );
+
+ BaseClass::InitializeRotorSound();
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::UpdateRotorSoundPitch( int iPitch )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ // Apply the pitch to both sounds.
+ controller.SoundChangePitch( m_pAirExhaustSound, iPitch, 0.1 );
+
+ // FIXME: Doesn't work in multiplayer
+ CBaseEntity *pPlayer = UTIL_PlayerByIndex(1);
+ if (pPlayer)
+ {
+ Vector pos;
+ Vector up;
+ GetAttachment( "rotor", pos, NULL, NULL, &up );
+
+ float flDistance = (pPlayer->WorldSpaceCenter() - pos).Length2DSqr();
+
+ // Fade in exhaust when we're far from the player
+ float flVolume = clamp( RemapVal( flDistance, (900*900), (1800*1800), 1, 0 ), 0, 1 );
+ controller.SoundChangeVolume( m_pAirExhaustSound, flVolume * GetRotorVolume(), 0.1 );
+
+ // Fade in the blast when it's close to the player (in 2D)
+ flVolume = clamp( RemapVal( flDistance, (600*600), (700*700), 1, 0 ), 0, 1 );
+ controller.SoundChangeVolume( m_pAirBlastSound, flVolume * GetRotorVolume(), 0.1 );
+ }
+
+ BaseClass::UpdateRotorSoundPitch( iPitch );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::ApplySidewaysDrag( const Vector &vecRight )
+{
+ Vector vecVelocity = GetAbsVelocity();
+ if( m_lifeState == LIFE_ALIVE )
+ {
+ vecVelocity.x *= (1.0 - fabs( vecRight.x ) * 0.04);
+ vecVelocity.y *= (1.0 - fabs( vecRight.y ) * 0.04);
+ vecVelocity.z *= (1.0 - fabs( vecRight.z ) * 0.04);
+ }
+ else
+ {
+ vecVelocity.x *= (1.0 - fabs( vecRight.x ) * 0.03);
+ vecVelocity.y *= (1.0 - fabs( vecRight.y ) * 0.03);
+ vecVelocity.z *= (1.0 - fabs( vecRight.z ) * 0.09);
+ }
+ SetAbsVelocity( vecVelocity );
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Explode the gunship.
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::SelfDestruct( void )
+{
+ SetThink( NULL );
+ m_lifeState = LIFE_DEAD;
+
+ StopLoopingSounds();
+ StopCannonBurst();
+
+ Vector vecVelocity = GetAbsVelocity();
+ vecVelocity.z = 0.0; // stop falling.
+ SetAbsVelocity( vecVelocity );
+
+ CBaseEntity *pBreakEnt = this;
+
+ // If we've ragdolled, play the explosions on the ragdoll instead
+ Vector vecOrigin;
+ if ( m_hRagdoll )
+ {
+ m_hRagdoll->EmitSound( "NPC_CombineGunship.Explode" );
+ vecOrigin = m_hRagdoll->GetAbsOrigin();
+ pBreakEnt = m_hRagdoll;
+ }
+ else
+ {
+ EmitSound( "NPC_CombineGunship.Explode" );
+ vecOrigin = GetAbsOrigin();
+ }
+
+ // Create some explosions on the gunship body
+ Vector vecDelta;
+ for( int i = 0 ; i < 6 ; i++ )
+ {
+ vecDelta = RandomVector( -200,200 );
+ ExplosionCreate( vecOrigin + vecDelta, QAngle( -90, 0, 0 ), this, 10, 10, false );
+ }
+
+ AR2Explosion *pExplosion = AR2Explosion::CreateAR2Explosion( vecOrigin );
+ if ( pExplosion )
+ {
+ pExplosion->SetLifetime( 10 );
+ }
+
+ // If we don't have a crash target, explode into chunks
+ if ( !m_hCrashTarget )
+ {
+ Vector angVelocity;
+ QAngleToAngularImpulse( pBreakEnt->GetLocalAngularVelocity(), angVelocity );
+ PropBreakableCreateAll( pBreakEnt->GetModelIndex(), pBreakEnt->VPhysicsGetObject(), pBreakEnt->GetAbsOrigin(), pBreakEnt->GetAbsAngles(), pBreakEnt->GetAbsVelocity(), angVelocity, 1.0, 800, COLLISION_GROUP_NPC, pBreakEnt );
+
+ // Throw out some small chunks too
+ CPVSFilter filter( GetAbsOrigin() );
+ for ( int i = 0; i < 20; i++ )
+ {
+ Vector gibVelocity = RandomVector(-100,100) * 10;
+ int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );
+ te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, Vector(40,40,40), gibVelocity, iModelIndex, 400, 1, 2.5, BREAK_METAL );
+ }
+
+ if ( m_hRagdoll )
+ {
+ UTIL_Remove( m_hRagdoll );
+ }
+ }
+ else
+ {
+ if ( m_pSmokeTrail )
+ {
+ // If we have a ragdoll, let it smoke for a few more seconds
+ if ( m_hRagdoll )
+ {
+ m_pSmokeTrail->SetLifetime(3.0f);
+ }
+ else
+ {
+ m_pSmokeTrail->SetLifetime(0.1f);
+ }
+ m_pSmokeTrail = NULL;
+ }
+ }
+
+ UTIL_Remove( this );
+
+ // Record this so a nearby citizen can respond.
+ if ( GetCitizenResponse() )
+ {
+ GetCitizenResponse()->AddResponseTrigger( CR_PLAYER_KILLED_GUNSHIP );
+ }
+
+#ifdef HL2_EPISODIC
+ NPCEventResponse()->TriggerEvent( "TLK_CITIZEN_RESPONSE_KILLED_GUNSHIP", false, false );
+#endif
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : Explode the gunship.
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::InputSelfDestruct( inputdata_t &inputdata )
+{
+ BeginCrash();
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Shrink the gunship's bbox so that it fits in docking bays
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::InputSetDockingBBox( inputdata_t &inputdata )
+{
+ Vector vecSize( 32, 32, 32 );
+
+ UTIL_SetSize( this, vecSize * -1, vecSize );
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Set the gunship BBox to normal size
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::InputSetNormalBBox( inputdata_t &inputdata )
+{
+ Vector vecBBMin, vecBBMax;
+
+ ExtractBbox( SelectHeaviestSequence( ACT_GUNSHIP_PATROL ), vecBBMin, vecBBMax );
+
+ // Trim the bounding box a bit. It's huge.
+#define GUNSHIP_TRIM_BOX 38
+ vecBBMin.x += GUNSHIP_TRIM_BOX;
+ vecBBMax.x -= GUNSHIP_TRIM_BOX;
+ vecBBMin.y += GUNSHIP_TRIM_BOX;
+ vecBBMax.y -= GUNSHIP_TRIM_BOX;
+
+ UTIL_SetSize( this, vecBBMin, vecBBMax );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::InputEnableGroundAttack( inputdata_t &inputdata )
+{
+ m_bCanGroundAttack = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::InputDisableGroundAttack( inputdata_t &inputdata )
+{
+ m_bCanGroundAttack = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::InputDoGroundAttack( inputdata_t &inputdata )
+{
+ // Was a target node specified?
+ CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID(), NULL, inputdata.pActivator, inputdata.pCaller );
+ if ( pEntity )
+ {
+ // Mapmaker wants us to ground attack a specific target
+ m_hGroundAttackTarget = pEntity;
+ }
+ else
+ {
+ StartGroundAttack();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &vGunPosition -
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::UpdateEnemyTarget( void )
+{
+ Vector vGunPosition;
+
+ GetAttachment( "muzzle", vGunPosition );
+
+ // Follow mode
+ Vector enemyPos;
+ bool bTargettingPlayer;
+
+ if ( GetEnemy() != NULL )
+ {
+ CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
+ if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
+ {
+ // Update against a driving target
+ enemyPos = GetEnemy()->WorldSpaceCenter();
+ }
+ else
+ {
+ enemyPos = GetEnemy()->EyePosition();
+ }
+ bTargettingPlayer = GetEnemy()->IsPlayer();
+ }
+ else
+ {
+ enemyPos = m_vecAttackPosition;
+ bTargettingPlayer = false;
+ }
+
+ // Direction towards the enemy
+ Vector targetDir = enemyPos - m_vecAttackPosition;
+ VectorNormalize( targetDir );
+
+ // Direction from the gunship to the enemy
+ Vector enemyDir = enemyPos - vGunPosition;
+ VectorNormalize( enemyDir );
+
+ float lastSpeed = VectorNormalize( m_vecAttackVelocity );
+ QAngle chaseAngles, lastChaseAngles;
+
+ VectorAngles( targetDir, chaseAngles );
+ VectorAngles( m_vecAttackVelocity, lastChaseAngles );
+
+ // Debug info
+ if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
+ {
+ // Final position
+ NDebugOverlay::Cross3D( m_vecAttackPosition, -Vector(2,2,2), Vector(2,2,2), 0, 0, 255, true, 4.0f );
+ }
+
+ float yawDiff = UTIL_AngleDiff( lastChaseAngles[YAW], chaseAngles[YAW] );
+
+ int maxYaw;
+ if ( bTargettingPlayer )
+ {
+ maxYaw = 6;
+ }
+ else
+ {
+ maxYaw = 30;
+ }
+
+ yawDiff = clamp( yawDiff, -maxYaw, maxYaw );
+
+ chaseAngles[PITCH] = 0.0f;
+ chaseAngles[ROLL] = 0.0f;
+
+ bool bMaxHits = ( m_iBurstHits >= GUNSHIP_MAX_HITS_PER_BURST || (GetEnemy() && !GetEnemy()->IsAlive()) );
+
+ if ( bMaxHits )
+ {
+ // We've hit our target. Stop chasing, and return to max speed.
+ chaseAngles[YAW] = lastChaseAngles[YAW];
+ lastSpeed = BASE_STITCH_VELOCITY;
+ }
+ else
+ {
+ // Move towards the target yaw
+ chaseAngles[YAW] = UTIL_AngleMod( lastChaseAngles[YAW] - yawDiff );
+ }
+
+ // If we've hit the target already, or we're not close enough to it, then just stitch along
+ if ( bMaxHits || ( m_vecAttackPosition - enemyPos ).LengthSqr() > (64 * 64) )
+ {
+ AngleVectors( chaseAngles, &targetDir );
+
+ // Update our new velocity
+ m_vecAttackVelocity = targetDir * lastSpeed;
+
+ if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
+ {
+ NDebugOverlay::Line( m_vecAttackPosition, m_vecAttackPosition + (m_vecAttackVelocity * 0.1), 255, 0, 0, true, 4.0f );
+ }
+
+ // Move along that velocity for this step in time
+ m_vecAttackPosition += ( m_vecAttackVelocity * 0.1f );
+ m_vecAttackPosition.z = enemyPos.z;
+ }
+ else
+ {
+ // Otherwise always continue to hit an NPC when close enough
+ m_vecAttackPosition = enemyPos;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Utility function to aim the helicopter gun at the direction
+//------------------------------------------------------------------------------
+bool CNPC_CombineGunship::PoseGunTowardTargetDirection( const Vector &vTargetDir )
+{
+ Vector vecOut;
+ VectorIRotate( vTargetDir, EntityToWorldTransform(), vecOut );
+
+ QAngle angles;
+ VectorAngles(vecOut, angles);
+ angles.y = AngleNormalize( angles.y );
+ angles.x = AngleNormalize( angles.x );
+
+ if (angles.x > m_angGun.x)
+ {
+ m_angGun.x = MIN( angles.x, m_angGun.x + 12 );
+ }
+ if (angles.x < m_angGun.x)
+ {
+ m_angGun.x = MAX( angles.x, m_angGun.x - 12 );
+ }
+ if (angles.y > m_angGun.y)
+ {
+ m_angGun.y = MIN( angles.y, m_angGun.y + 12 );
+ }
+ if (angles.y < m_angGun.y)
+ {
+ m_angGun.y = MAX( angles.y, m_angGun.y - 12 );
+ }
+
+ SetPoseParameter( m_poseWeapon_Pitch, -m_angGun.x );
+ SetPoseParameter( m_poseWeapon_Yaw, m_angGun.y );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Vector
+//-----------------------------------------------------------------------------
+Vector CNPC_CombineGunship::GetMissileTarget( void )
+{
+ return GetEnemy()->GetAbsOrigin();
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Get the target position for the enemy- the position we fire upon.
+// this is often modified by m_flAttackOffset to provide the 'stitching'
+// behavior that's so popular with the kids these days (sjb)
+//
+// Input : vGunPosition - location of gunship's muzzle
+// : pTarget = vector to paste enemy target into.
+// Output :
+//------------------------------------------------------------------------------
+Vector CNPC_CombineGunship::GetEnemyTarget( void )
+{
+ // Make sure we have an enemy
+ if ( GetEnemy() == NULL )
+ return m_vecAttackPosition;
+
+ // If we're locked onto a missile, use special code to try and destroy it
+ if ( IsTargettingMissile() )
+ return GetMissileTarget();
+
+ return m_vecAttackPosition;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &tr -
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::DoImpactEffect( trace_t &tr, int nDamageType )
+{
+ UTIL_ImpactTrace( &tr, nDamageType, "ImpactGunship" );
+
+ // These glow effects don't sort properly, so they're cut for E3 2003 (sjb)
+#if 0
+ CEffectData data;
+
+ data.m_vOrigin = tr.endpos;
+ data.m_vNormal = vec3_origin;
+ data.m_vAngles = vec3_angle;
+
+ DispatchEffect( "GunshipImpact", data );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make the gunship's signature blue tracer!
+// Input : &vecTracerSrc -
+// &tr -
+// iTracerType -
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
+{
+ switch ( iTracerType )
+ {
+ case TRACER_LINE:
+ {
+ float flTracerDist;
+ Vector vecDir;
+ Vector vecEndPos;
+
+ vecDir = tr.endpos - vecTracerSrc;
+
+ flTracerDist = VectorNormalize( vecDir );
+
+ UTIL_Tracer( vecTracerSrc, tr.endpos, 0, TRACER_DONT_USE_ATTACHMENT, 8000, true, "GunshipTracer" );
+ }
+ break;
+
+ default:
+ BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+// &vecDir -
+// *ptr -
+// Output : int
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ // Reflect bullets
+ if ( info.GetDamageType() & DMG_BULLET )
+ {
+ if ( random->RandomInt( 0, 2 ) == 0 )
+ {
+ Vector vecRicochetDir = vecDir * -1;
+
+ vecRicochetDir.x += random->RandomFloat( -0.5, 0.5 );
+ vecRicochetDir.y += random->RandomFloat( -0.5, 0.5 );
+ vecRicochetDir.z += random->RandomFloat( -0.5, 0.5 );
+
+ VectorNormalize( vecRicochetDir );
+
+ Vector end = ptr->endpos + vecRicochetDir * 1024;
+ UTIL_Tracer( ptr->endpos, end, entindex(), TRACER_DONT_USE_ATTACHMENT, 3500 );
+ }
+
+ // If this is from a player, record it so a nearby citizen can respond.
+ if ( info.GetAttacker()->IsPlayer() )
+ {
+ if ( GetCitizenResponse() )
+ {
+ GetCitizenResponse()->AddResponseTrigger( CR_PLAYER_SHOT_GUNSHIP );
+ }
+
+#ifdef HL2_EPISODIC
+ NPCEventResponse()->TriggerEvent( "TLK_CITIZEN_RESPONSE_SHOT_GUNSHIP", false, false );
+#endif
+ }
+
+ return;
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This is necessary to ensure that the game doesn't break if a mapmaker has outputs that
+// must be fired on gunships, and the player switches skill levels
+// midway through a gunship battle.
+// Input : iDamageNumber -
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::FireDamageOutputsUpto( int iDamageNumber )
+{
+ for ( int i = 0; i <= iDamageNumber; i++ )
+ {
+ if ( !m_bDamageOutputsFired[i] )
+ {
+ m_bDamageOutputsFired[i] = true;
+
+ switch ( i )
+ {
+ case 0:
+ //Msg("Fired first\n");
+ m_OnFirstDamage.FireOutput( this, this );
+ break;
+
+ case 1:
+ //Msg("Fired second\n");
+ m_OnSecondDamage.FireOutput( this, this );
+ break;
+
+ case 2:
+ //Msg("Fired third\n");
+ m_OnThirdDamage.FireOutput( this, this );
+ break;
+
+ case 3:
+ //Msg("Fired fourth\n");
+ m_OnFourthDamage.FireOutput( this, this );
+ break;
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// Damage filtering
+//------------------------------------------------------------------------------
+int CNPC_CombineGunship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ // Allow npc_kill to kill me
+ if ( inputInfo.GetDamageType() != DMG_GENERIC )
+ {
+ // Ignore mundane bullet damage.
+ if ( ( inputInfo.GetDamageType() & DMG_BLAST ) == false )
+ return 0;
+
+ // Ignore blasts less than this amount
+ if ( inputInfo.GetDamage() < GUNSHIP_MIN_DAMAGE_THRESHOLD )
+ return 0;
+ }
+
+ // Only take blast damage
+ CTakeDamageInfo info = inputInfo;
+
+ // Make a pain sound
+ if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
+ {
+ EmitSound( "NPC_CombineGunship.Pain" );
+ }
+
+ Vector damageDir = info.GetDamageForce();
+ VectorNormalize( damageDir );
+
+ // Don't get knocked around if I'm ground attacking
+ if ( !m_bIsGroundAttacking )
+ {
+ ApplyAbsVelocityImpulse( damageDir * 200.0f );
+ }
+
+ if ( m_bInvulnerable == false )
+ {
+ // Take a percentage of our health away
+ // Adjust health for damage
+ int iHealthIncrements = sk_gunship_health_increments.GetInt();
+ if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
+ {
+ iHealthIncrements = ceil( iHealthIncrements * 0.5 );
+ }
+ else if ( g_pGameRules->IsSkillLevel( SKILL_HARD ) )
+ {
+ iHealthIncrements = floor( iHealthIncrements * 1.5 );
+ }
+ info.SetDamage( ( GetMaxHealth() / (float)iHealthIncrements ) + 1 );
+
+ // Find out which "stage" we're at in our health
+ int healthIncrement = iHealthIncrements - ( GetHealth() / (float)(( GetMaxHealth() / (float)iHealthIncrements )) );
+ switch ( healthIncrement )
+ {
+ case 1:
+ // If we're on Easy, we're half dead now, so fire the rest of our outputs too
+ // This is done in case the mapmaker's connected those inputs to something important
+ // that has to happen before the gunship dies.
+ if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
+ {
+ FireDamageOutputsUpto( 3 );
+ }
+ else
+ {
+ FireDamageOutputsUpto( 1 );
+ }
+ break;
+
+ default:
+ FireDamageOutputsUpto( healthIncrement );
+ break;
+ }
+
+ // Start smoking when we're almost dead
+ CreateSmokeTrail();
+
+ if ( m_pSmokeTrail )
+ {
+ if ( healthIncrement < 2 )
+ {
+ m_pSmokeTrail->SetLifetime( 8.0 );
+ }
+
+ m_pSmokeTrail->FollowEntity( this, "exhaustl" );
+ }
+
+ // Move with the target
+ Vector gibVelocity = GetAbsVelocity() + (-damageDir * 200.0f);
+
+ // Dump out metal gibs
+ CPVSFilter filter( GetAbsOrigin() );
+ for ( int i = 0; i < 10; i++ )
+ {
+ int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );
+ te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, Vector(40,40,40), gibVelocity, iModelIndex, 400, 1, 2.5, BREAK_METAL );
+ }
+ }
+
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : The proper way to begin the gunship cannon firing at the enemy.
+// Input : iBurstSize - the size of the burst, in rounds.
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::StartCannonBurst( int iBurstSize )
+{
+ m_iBurstSize = iBurstSize;
+ m_iBurstHits = 0;
+
+ m_flTimeNextAttack = gpGlobals->curtime;
+
+ // Start up the cannon sound.
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pCannonSound, 1.0, 0 );
+
+ m_bIsFiring = true;
+
+ // Setup the initial position of the burst
+ if ( GetEnemy() )
+ {
+ // Follow mode
+ Vector enemyPos;
+ UTIL_PredictedPosition( GetEnemy(), 2.0f, &enemyPos );
+
+ QAngle offsetAngles;
+ Vector offsetDir = ( WorldSpaceCenter() - enemyPos );
+ VectorNormalize( offsetDir );
+ VectorAngles( offsetDir, offsetAngles );
+
+ int angleOffset = random->RandomInt( 15, 30 );
+ if ( random->RandomInt( 0, 1 ) )
+ {
+ angleOffset *= -1;
+ }
+ offsetAngles[YAW] += angleOffset;
+ offsetAngles[PITCH] = 0;
+ offsetAngles[ROLL] = 0;
+
+ AngleVectors( offsetAngles, &offsetDir );
+
+ float stitchOffset;
+ float enemyDist = GroundDistToPosition( GetEnemy()->GetAbsOrigin() );
+ if ( enemyDist < ( sk_gunship_burst_dist.GetFloat() + GUNSHIP_STITCH_MIN ) )
+ {
+ stitchOffset = GUNSHIP_STITCH_MIN;
+ }
+ else
+ {
+ stitchOffset = sk_gunship_burst_dist.GetFloat();
+ }
+
+ // Move out to the start of our stitch run
+ m_vecAttackPosition = enemyPos + ( offsetDir * stitchOffset );
+ m_vecAttackPosition.z = enemyPos.z;
+
+ // Point at our target
+ m_vecAttackVelocity = -offsetDir * BASE_STITCH_VELOCITY;
+
+ CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_REACT_TO_SOURCE, enemyPos, 512, 0.2f, this );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : The proper way to cease the gunship cannon firing.
+//------------------------------------------------------------------------------
+void CNPC_CombineGunship::StopCannonBurst( void )
+{
+ m_iBurstHits = 0;
+ m_bIsFiring = false;
+ m_bPreFire = false;
+
+ // Reduce the burst time when we get lower in health
+ float flPerc = (float)GetHealth() / (float)GetMaxHealth();
+ float flDelay = clamp( flPerc * m_flBurstDelay, 0.5, m_flBurstDelay );
+
+ // If we didn't finish the burst, don't wait so long
+ flPerc = 1.0 - (m_iBurstSize / sk_gunship_burst_size.GetFloat());
+ flDelay *= flPerc;
+
+ m_flTimeNextAttack = gpGlobals->curtime + flDelay;
+ m_iBurstSize = 0;
+
+ // Stop the cannon sound.
+ if ( m_pCannonSound != NULL )
+ {
+ CSoundEnvelopeController::GetController().SoundChangeVolume( m_pCannonSound, 0.0, 0.05 );
+ }
+
+ EmitSound( "NPC_CombineGunship.CannonStopSound" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::StopLoopingSounds( void )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ if ( m_pCannonSound )
+ {
+ controller.SoundDestroy( m_pCannonSound );
+ m_pCannonSound = NULL;
+ }
+
+ if ( m_pRotorSound )
+ {
+ controller.SoundDestroy( m_pRotorSound );
+ m_pRotorSound = NULL;
+ }
+
+ if ( m_pAirExhaustSound )
+ {
+ controller.SoundDestroy( m_pAirExhaustSound );
+ m_pAirExhaustSound = NULL;
+ }
+
+ if ( m_pAirBlastSound )
+ {
+ controller.SoundDestroy( m_pAirBlastSound );
+ m_pAirBlastSound = NULL;
+ }
+
+ BaseClass::StopLoopingSounds();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEnemy -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_CombineGunship::IsValidEnemy( CBaseEntity *pEnemy )
+{
+ // Always track missiles
+ if ( pEnemy->IsAlive() && !pEnemy->MyNPCPointer() && FClassnameIs( pEnemy, "rpg_missile" ) )
+ return true;
+
+ // If we're shooting off a burst, don't pick up a new enemy
+ if ( ( m_bIsFiring ) && ( ( GetEnemy() == NULL ) || ( GetEnemy() != pEnemy ) ) )
+ return false;
+
+ return BaseClass::IsValidEnemy( pEnemy );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::GatherEnemyConditions( CBaseEntity *pEnemy )
+{
+ BaseClass::GatherEnemyConditions(pEnemy);
+
+ // If we can't see the enemy for a few seconds, consider him unreachable
+ if ( !HasCondition(COND_SEE_ENEMY) )
+ {
+ if ( gpGlobals->curtime - GetEnemyLastTimeSeen() >= 3.0f )
+ {
+ MarkEnemyAsEluded();
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tells us whether or not we're targetting an incoming missile
+//-----------------------------------------------------------------------------
+bool CNPC_CombineGunship::IsTargettingMissile( void )
+{
+ if ( GetEnemy() == NULL )
+ return false;
+
+ if ( FClassnameIs( GetEnemy(), "rpg_missile" ) == false )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::InputBecomeInvulnerable( inputdata_t &input )
+{
+ m_bInvulnerable = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineGunship::InputBecomeVulnerable( inputdata_t &input )
+{
+ m_bInvulnerable = false;
+}
+
+AI_BEGIN_CUSTOM_NPC( npc_combinegunship, CNPC_CombineGunship )
+
+// DECLARE_TASK( )
+
+ DECLARE_ACTIVITY( ACT_GUNSHIP_PATROL );
+ DECLARE_ACTIVITY( ACT_GUNSHIP_HOVER );
+ DECLARE_ACTIVITY( ACT_GUNSHIP_CRASH );
+
+ //DECLARE_CONDITION( COND_ )
+
+ //=========================================================
+// DEFINE_SCHEDULE
+// (
+// SCHED_DUMMY,
+//
+// " Tasks"
+// " TASK_FACE_ENEMY 0"
+// " "
+// " Interrupts"
+// )
+
+
+AI_END_CUSTOM_NPC()
+