Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3228 lines
94 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "baseanimating.h"
  9. #include "ai_network.h"
  10. #include "ai_default.h"
  11. #include "ai_schedule.h"
  12. #include "ai_hull.h"
  13. #include "ai_node.h"
  14. #include "ai_task.h"
  15. #include "ai_motor.h"
  16. #include "entitylist.h"
  17. #include "basecombatweapon.h"
  18. #include "soundenvelope.h"
  19. #include "gib.h"
  20. #include "gamerules.h"
  21. #include "ammodef.h"
  22. #include "cbasehelicopter.h"
  23. #include "npcevent.h"
  24. #include "ndebugoverlay.h"
  25. #include "decals.h"
  26. #include "explode.h" // temp (sjb)
  27. #include "smoke_trail.h" // temp (sjb)
  28. #include "IEffects.h"
  29. #include "vstdlib/random.h"
  30. #include "engine/IEngineSound.h"
  31. #include "ar2_explosion.h"
  32. #include "te_effect_dispatch.h"
  33. #include "rope.h"
  34. #include "effect_dispatch_data.h"
  35. #include "trains.h"
  36. #include "globals.h"
  37. #include "physics_prop_ragdoll.h"
  38. #include "iservervehicle.h"
  39. #include "soundent.h"
  40. #include "npc_citizen17.h"
  41. #include "physics_saverestore.h"
  42. #include "hl2_shareddefs.h"
  43. #include "props.h"
  44. #include "npc_attackchopper.h"
  45. #include "citadel_effects_shared.h"
  46. #include "eventqueue.h"
  47. #include "beam_flags.h"
  48. #include "ai_eventresponse.h"
  49. // memdbgon must be the last include file in a .cpp file!!!
  50. #include "tier0/memdbgon.h"
  51. #define GUNSHIP_MSG_BIG_SHOT 1
  52. #define GUNSHIP_MSG_STREAKS 2
  53. #define GUNSHIP_NUM_DAMAGE_OUTPUTS 4
  54. extern short g_sModelIndexFireball; // holds the index for the fireball
  55. int g_iGunshipEffectIndex = -1;
  56. #define GUNSHIP_ACCEL_RATE 500
  57. // Spawnflags
  58. #define SF_GUNSHIP_NO_GROUND_ATTACK ( 1 << 12 )
  59. #define SF_GUNSHIP_USE_CHOPPER_MODEL ( 1 << 13 )
  60. ConVar sk_gunship_burst_size("sk_gunship_burst_size", "15" );
  61. ConVar sk_gunship_burst_min("sk_gunship_burst_min", "800" );
  62. ConVar sk_gunship_burst_dist("sk_gunship_burst_dist", "768" );
  63. // Number of times the gunship must be struck by explosive damage
  64. ConVar sk_gunship_health_increments( "sk_gunship_health_increments", "0" );
  65. /*
  66. Wedge's notes:
  67. Gunship should move its head according to flight model when the target is behind the gunship,
  68. or when the target is too far away to shoot at. Otherwise, the head should aim at the target.
  69. Negative angvelocity.y is a RIGHT turn.
  70. Negative angvelocity.x is UP
  71. */
  72. #define GUNSHIP_AP_MUZZLE 5
  73. #define GUNSHIP_MAX_SPEED 1056.0f
  74. #define GUNSHIP_MAX_FIRING_SPEED 200.0f
  75. #define GUNSHIP_MIN_ROCKET_DIST 1000.0f
  76. #define GUNSHIP_MAX_GUN_DIST 2000.0f
  77. #define GUNSHIP_ARRIVE_DIST 128.0f
  78. #define GUNSHIP_HOVER_SPEED 300.0f // play hover animation if moving slower than this.
  79. #define GUNSHIP_AE_THRUST 1
  80. #define GUNSHIP_HEAD_MAX_UP -65
  81. #define GUNSHIP_HEAD_MAX_DOWN 60
  82. #define GUNSHIP_HEAD_MAX_LEFT 60
  83. #define GUNSHIP_HEAD_MAX_RIGHT -60
  84. #define BASE_STITCH_VELOCITY 800 //Units per second
  85. #define MAX_STITCH_VELOCITY 1000 //Units per second
  86. #define GUNSHIP_LEAD_DISTANCE 800.0f
  87. #define GUNSHIP_AVOID_DIST 512.0f
  88. #define GUNSHIP_STITCH_MIN 512.0f
  89. #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
  90. #define MIN_GROUND_ATTACK_DIST 500.0f // Minimum distance a target has to be for the gunship to consider using the ground attack weapon
  91. #define MIN_GROUND_ATTACK_HEIGHT_DIFF 128.0f // Target's position and hit position must be within this threshold vertically
  92. #define GUNSHIP_WASH_ALTITUDE 1024.0f
  93. #define GUNSHIP_MIN_DAMAGE_THRESHOLD 50.0f
  94. #define GUNSHIP_INNER_NAV_DIST 400.0f
  95. #define GUNSHIP_OUTER_NAV_DIST 800.0f
  96. #define GUNSHIP_BELLYBLAST_TARGET_HEIGHT 512.0 // Height above targets that the gunship wants to be when bellyblasting
  97. #define GUNSHIP_MISSILE_MAX_RESPONSE_TIME 0.4
  98. #define GUNSHIP_MAX_HITS_PER_BURST 5
  99. #define GUNSHIP_FLARE_IGNORE_TIME 6.0
  100. //=====================================
  101. // Custom activities
  102. //=====================================
  103. Activity ACT_GUNSHIP_PATROL;
  104. Activity ACT_GUNSHIP_HOVER;
  105. Activity ACT_GUNSHIP_CRASH;
  106. #define GUNSHIP_DEBUG_LEADING 1
  107. #define GUNSHIP_DEBUG_PATH 2
  108. #define GUNSHIP_DEBUG_STITCHING 3
  109. #define GUNSHIP_DEBUG_BELLYBLAST 4
  110. ConVar g_debug_gunship( "g_debug_gunship", "0", FCVAR_CHEAT );
  111. //-----------------------------------------------------------------------------
  112. // Purpose: Dying gunship ragdoll controller
  113. //-----------------------------------------------------------------------------
  114. class CGunshipRagdollMotion : public IMotionEvent
  115. {
  116. DECLARE_SIMPLE_DATADESC();
  117. public:
  118. virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
  119. {
  120. linear = Vector(0,0,400);
  121. angular = Vector(0,600,100);
  122. return SIM_GLOBAL_ACCELERATION;
  123. }
  124. };
  125. BEGIN_SIMPLE_DATADESC( CGunshipRagdollMotion )
  126. END_DATADESC()
  127. //-----------------------------------------------------------------------------
  128. // Purpose:
  129. //-----------------------------------------------------------------------------
  130. class CTargetGunshipCrash : public CPointEntity
  131. {
  132. DECLARE_CLASS( CTargetGunshipCrash, CPointEntity );
  133. public:
  134. DECLARE_DATADESC();
  135. void InputEnable( inputdata_t &inputdata )
  136. {
  137. m_bDisabled = false;
  138. }
  139. void InputDisable( inputdata_t &inputdata )
  140. {
  141. m_bDisabled = true;
  142. }
  143. bool IsDisabled( void )
  144. {
  145. return m_bDisabled;
  146. }
  147. void GunshipCrashedOnTarget( void )
  148. {
  149. m_OnCrashed.FireOutput( this, this );
  150. }
  151. private:
  152. bool m_bDisabled;
  153. COutputEvent m_OnCrashed;
  154. };
  155. LINK_ENTITY_TO_CLASS( info_target_gunshipcrash, CTargetGunshipCrash );
  156. BEGIN_DATADESC( CTargetGunshipCrash )
  157. DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ),
  158. // Inputs
  159. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  160. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  161. // Outputs
  162. DEFINE_OUTPUT( m_OnCrashed, "OnCrashed" ),
  163. END_DATADESC()
  164. //===================================================================
  165. // Gunship - the combine dugongic like attack vehicle.
  166. //===================================================================
  167. class CNPC_CombineGunship : public CBaseHelicopter
  168. {
  169. public:
  170. DECLARE_CLASS( CNPC_CombineGunship, CBaseHelicopter );
  171. CNPC_CombineGunship( void );
  172. ~CNPC_CombineGunship( void );
  173. DECLARE_DATADESC();
  174. DECLARE_SERVERCLASS();
  175. DEFINE_CUSTOM_AI;
  176. void PlayPatrolLoop( void );
  177. void PlayAngryLoop( void );
  178. void Spawn( void );
  179. void Precache( void );
  180. void OnRestore( void );
  181. void PrescheduleThink( void );
  182. void HelicopterPostThink( void );
  183. void StopLoopingSounds( void );
  184. bool IsValidEnemy( CBaseEntity *pEnemy );
  185. void GatherEnemyConditions( CBaseEntity *pEnemy );
  186. void Flight( void );
  187. bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
  188. int OnTakeDamage_Alive( const CTakeDamageInfo &info );
  189. void FireDamageOutputsUpto( int iDamageNumber );
  190. virtual float GetAcceleration( void ) { return 15; }
  191. virtual void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
  192. virtual void DoImpactEffect( trace_t &tr, int nDamageType );
  193. void MoveHead( void );
  194. void UpdateDesiredPosition( void );
  195. void DoCombat( void );
  196. bool ChooseEnemy( void );
  197. void DoMuzzleFlash( void );
  198. void Ping( void );
  199. void FireCannonRound( void );
  200. // Gunship death process
  201. void Event_Killed( const CTakeDamageInfo &info );
  202. void BeginCrash( void ); // I'm going to go to a crash point and die there
  203. void BeginDestruct( void ); // I want to die now, so create my ragdoll
  204. void SelfDestruct( void ); // I'm now fully dead, so remove myself.
  205. void CreateSmokeTrail( void );
  206. bool FindNearestGunshipCrash( void );
  207. int BloodColor( void ) { return DONT_BLEED; }
  208. void GibMonster( void );
  209. void UpdateRotorSoundPitch( int iPitch );
  210. void InitializeRotorSound( void );
  211. void ApplyGeneralDrag( void );
  212. void ApplySidewaysDrag( const Vector &vecRight );
  213. void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
  214. void UpdateEnemyTarget( void );
  215. Vector GetEnemyTarget( void );
  216. Vector GetMissileTarget( void );
  217. float GroundDistToPosition( const Vector &pos );
  218. bool FireGun( void );
  219. bool IsTargettingMissile( void );
  220. Class_T Classify( void ) { return CLASS_COMBINE_GUNSHIP; } // for now
  221. float GetAutoAimRadius() { return 144.0f; }
  222. // Input functions
  223. void InputSetPenetrationDepth( inputdata_t &inputdata );
  224. void InputOmniscientOn( inputdata_t &inputdata );
  225. void InputOmniscientOff( inputdata_t &inputdata );
  226. void InputBlindfireOn( inputdata_t &inputdata );
  227. void InputBlindfireOff( inputdata_t &inputdata );
  228. void InputSelfDestruct( inputdata_t &inputdata );
  229. void InputSetDockingBBox( inputdata_t &inputdata );
  230. void InputSetNormalBBox( inputdata_t &inputdata );
  231. void InputEnableGroundAttack( inputdata_t &inputdata );
  232. void InputDisableGroundAttack( inputdata_t &inputdata );
  233. void InputDoGroundAttack( inputdata_t &inputdata );
  234. //NOTENOTE: I'm rather queasy about adding these, as they can lead to nasty bugs...
  235. void InputBecomeInvulnerable( inputdata_t &inputdata );
  236. void InputBecomeVulnerable( inputdata_t &inputdata );
  237. bool PoseGunTowardTargetDirection( const Vector &vTargetDir );
  238. void StartCannonBurst( int iBurstSize );
  239. void StopCannonBurst( void );
  240. bool CheckGroundAttack( void );
  241. void StartGroundAttack( void );
  242. void StopGroundAttack( bool bDoAttack );
  243. Vector GetGroundAttackHitPosition( void );
  244. void DoGroundAttackExplosion( void );
  245. void DrawRotorWash( float flAltitude, const Vector &vecRotorOrigin );
  246. void ManageWarningBeam( void );
  247. void DoBellyBlastDamage( trace_t &tr, Vector vMins, Vector vMaxs );
  248. // Updates the facing direction
  249. void UpdateFacingDirection( void );
  250. void CreateBellyBlastEnergyCore( void );
  251. protected:
  252. // Because the combine gunship is a leaf class, we can use
  253. // static variables to store this information, and save some memory.
  254. // Should the gunship end up having inheritors, their activate may
  255. // stomp these numbers, in which case you should make these ordinary members
  256. // again.
  257. static int m_poseFlex_Horz, m_poseFlex_Vert, m_posePitch, m_poseYaw, m_poseFin_Accel, m_poseFin_Sway;
  258. static int m_poseWeapon_Pitch, m_poseWeapon_Yaw;
  259. static bool m_sbStaticPoseParamsLoaded;
  260. virtual void PopulatePoseParameters( void );
  261. private:
  262. // Outputs
  263. COutputEvent m_OnFireCannon;
  264. COutputEvent m_OnCrashed;
  265. COutputEvent m_OnFirstDamage; // First damage tick
  266. COutputEvent m_OnSecondDamage;
  267. COutputEvent m_OnThirdDamage;
  268. COutputEvent m_OnFourthDamage;
  269. // Keep track of which damage outputs we've fired. This is necessary
  270. // to ensure that the game doesn't break if a mapmaker has outputs that
  271. // must be fired on gunships, and the player switches skill levels
  272. // midway through a gunship battle.
  273. bool m_bDamageOutputsFired[GUNSHIP_NUM_DAMAGE_OUTPUTS];
  274. float m_flNextGroundAttack; // Time to wait before the next ground attack
  275. bool m_bIsGroundAttacking; // Denotes that we are ground attacking
  276. bool m_bCanGroundAttack; // Denotes whether we can ground attack or not
  277. float m_flGroundAttackTime; // Delay before blast happens from ground attack
  278. CHandle<SmokeTrail> m_pSmokeTrail;
  279. EHANDLE m_hGroundAttackTarget;
  280. CSoundPatch *m_pAirExhaustSound;
  281. CSoundPatch *m_pAirBlastSound;
  282. CSoundPatch *m_pCannonSound;
  283. CBaseEntity *m_pRotorWashModel;
  284. QAngle m_vecAngAcceleration;
  285. float m_flEndDestructTime;
  286. int m_iDoSmokePuff;
  287. int m_iAmmoType;
  288. int m_iBurstSize;
  289. bool m_fBlindfire;
  290. bool m_fOmniscient;
  291. bool m_bIsFiring;
  292. int m_iBurstHits;
  293. bool m_bPreFire;
  294. bool m_bInvulnerable;
  295. float m_flTimeNextPing;
  296. float m_flPenetrationDepth;
  297. float m_flDeltaT;
  298. float m_flTimeNextAttack;
  299. float m_flNextSeeEnemySound;
  300. float m_flNextRocket;
  301. float m_flBurstDelay;
  302. Vector m_vecAttackPosition;
  303. Vector m_vecAttackVelocity;
  304. // Used when the gunships using the chopper model
  305. Vector m_angGun;
  306. // For my death throes
  307. IPhysicsMotionController *m_pCrashingController;
  308. CGunshipRagdollMotion m_crashCallback;
  309. EHANDLE m_hRagdoll;
  310. CHandle<CTargetGunshipCrash> m_hCrashTarget;
  311. float m_flNextGunshipCrashFind;
  312. CHandle<CCitadelEnergyCore> m_hEnergyCore;
  313. CNetworkVector( m_vecHitPos );
  314. // If true, playing patrol loop.
  315. // Else, playing angry.
  316. bool m_fPatrolLoopPlaying;
  317. };
  318. LINK_ENTITY_TO_CLASS( npc_combinegunship, CNPC_CombineGunship );
  319. IMPLEMENT_SERVERCLASS_ST( CNPC_CombineGunship, DT_CombineGunship )
  320. SendPropVector(SENDINFO(m_vecHitPos), -1, SPROP_COORD),
  321. END_SEND_TABLE()
  322. BEGIN_DATADESC( CNPC_CombineGunship )
  323. DEFINE_ENTITYFUNC( FlyTouch ),
  324. DEFINE_FIELD( m_flNextGroundAttack,FIELD_TIME ),
  325. DEFINE_FIELD( m_bIsGroundAttacking,FIELD_BOOLEAN ),
  326. DEFINE_FIELD( m_bCanGroundAttack, FIELD_BOOLEAN ),
  327. DEFINE_FIELD( m_flGroundAttackTime,FIELD_TIME ),
  328. DEFINE_FIELD( m_pRotorWashModel, FIELD_CLASSPTR ),
  329. DEFINE_FIELD( m_pSmokeTrail, FIELD_EHANDLE ),
  330. DEFINE_FIELD( m_hGroundAttackTarget, FIELD_EHANDLE ),
  331. DEFINE_SOUNDPATCH( m_pAirExhaustSound ),
  332. DEFINE_SOUNDPATCH( m_pAirBlastSound ),
  333. DEFINE_SOUNDPATCH( m_pCannonSound ),
  334. DEFINE_FIELD( m_vecAngAcceleration,FIELD_VECTOR ),
  335. DEFINE_FIELD( m_flDeltaT, FIELD_FLOAT ),
  336. DEFINE_FIELD( m_flTimeNextAttack, FIELD_TIME ),
  337. DEFINE_FIELD( m_flNextSeeEnemySound, FIELD_TIME ),
  338. DEFINE_FIELD( m_flEndDestructTime, FIELD_TIME ),
  339. DEFINE_FIELD( m_flNextRocket, FIELD_TIME ),
  340. DEFINE_FIELD( m_iDoSmokePuff, FIELD_INTEGER ),
  341. DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
  342. DEFINE_FIELD( m_iBurstSize, FIELD_INTEGER ),
  343. DEFINE_FIELD( m_flBurstDelay, FIELD_FLOAT ),
  344. DEFINE_FIELD( m_fBlindfire, FIELD_BOOLEAN ),
  345. DEFINE_FIELD( m_fOmniscient, FIELD_BOOLEAN ),
  346. DEFINE_FIELD( m_bIsFiring, FIELD_BOOLEAN ),
  347. DEFINE_FIELD( m_iBurstHits, FIELD_INTEGER ),
  348. DEFINE_FIELD( m_flTimeNextPing, FIELD_TIME ),
  349. DEFINE_FIELD( m_flPenetrationDepth,FIELD_FLOAT ),
  350. DEFINE_FIELD( m_vecAttackPosition, FIELD_VECTOR ),
  351. DEFINE_FIELD( m_vecAttackVelocity, FIELD_VECTOR ),
  352. DEFINE_FIELD( m_angGun, FIELD_VECTOR ),
  353. DEFINE_PHYSPTR( m_pCrashingController ),
  354. DEFINE_EMBEDDED( m_crashCallback ),
  355. DEFINE_FIELD( m_hRagdoll, FIELD_EHANDLE ),
  356. DEFINE_FIELD( m_hCrashTarget, FIELD_EHANDLE ),
  357. DEFINE_FIELD( m_vecHitPos, FIELD_VECTOR ),
  358. DEFINE_FIELD( m_fPatrolLoopPlaying,FIELD_BOOLEAN ),
  359. DEFINE_FIELD( m_bPreFire, FIELD_BOOLEAN ),
  360. DEFINE_FIELD( m_bInvulnerable, FIELD_BOOLEAN ),
  361. DEFINE_FIELD( m_flNextGunshipCrashFind, FIELD_TIME ),
  362. DEFINE_FIELD( m_hEnergyCore, FIELD_EHANDLE ),
  363. DEFINE_ARRAY( m_bDamageOutputsFired, FIELD_BOOLEAN, GUNSHIP_NUM_DAMAGE_OUTPUTS ),
  364. // Function pointers
  365. DEFINE_INPUTFUNC( FIELD_VOID, "OmniscientOn", InputOmniscientOn ),
  366. DEFINE_INPUTFUNC( FIELD_VOID, "OmniscientOff", InputOmniscientOff ),
  367. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPenetrationDepth", InputSetPenetrationDepth ),
  368. DEFINE_INPUTFUNC( FIELD_VOID, "BlindfireOn", InputBlindfireOn ),
  369. DEFINE_INPUTFUNC( FIELD_VOID, "BlindfireOff", InputBlindfireOff ),
  370. DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ),
  371. DEFINE_INPUTFUNC( FIELD_VOID, "SetDockingBBox", InputSetDockingBBox ),
  372. DEFINE_INPUTFUNC( FIELD_VOID, "SetNormalBBox", InputSetNormalBBox ),
  373. DEFINE_INPUTFUNC( FIELD_VOID, "EnableGroundAttack", InputEnableGroundAttack ),
  374. DEFINE_INPUTFUNC( FIELD_VOID, "DisableGroundAttack", InputDisableGroundAttack ),
  375. DEFINE_INPUTFUNC( FIELD_STRING, "DoGroundAttack", InputDoGroundAttack ),
  376. DEFINE_OUTPUT( m_OnFireCannon, "OnFireCannon" ),
  377. DEFINE_OUTPUT( m_OnFirstDamage, "OnFirstDamage" ),
  378. DEFINE_OUTPUT( m_OnSecondDamage, "OnSecondDamage" ),
  379. DEFINE_OUTPUT( m_OnThirdDamage, "OnThirdDamage" ),
  380. DEFINE_OUTPUT( m_OnFourthDamage, "OnFourthDamage" ),
  381. DEFINE_OUTPUT( m_OnCrashed, "OnCrashed" ),
  382. END_DATADESC()
  383. //-----------------------------------------------------------------------------
  384. // Constructor
  385. //-----------------------------------------------------------------------------
  386. CNPC_CombineGunship::CNPC_CombineGunship( void )
  387. {
  388. m_hGroundAttackTarget = NULL;
  389. m_pSmokeTrail = NULL;
  390. m_iAmmoType = -1;
  391. m_pCrashingController = NULL;
  392. m_hRagdoll = NULL;
  393. m_hCrashTarget = NULL;
  394. }
  395. void CNPC_CombineGunship::CreateBellyBlastEnergyCore( void )
  396. {
  397. CCitadelEnergyCore *pCore = static_cast<CCitadelEnergyCore*>( CreateEntityByName( "env_citadel_energy_core" ) );
  398. if ( pCore == NULL )
  399. return;
  400. m_hEnergyCore = pCore;
  401. int iAttachment = LookupAttachment( "BellyGun" );
  402. Vector vOrigin;
  403. QAngle vAngle;
  404. GetAttachment( iAttachment, vOrigin, vAngle );
  405. pCore->SetAbsOrigin( vOrigin );
  406. pCore->SetAbsAngles( vAngle );
  407. DispatchSpawn( pCore );
  408. pCore->Activate();
  409. pCore->SetParent( this, iAttachment );
  410. pCore->SetScale( 4.0f );
  411. }
  412. //------------------------------------------------------------------------------
  413. // Purpose:
  414. //------------------------------------------------------------------------------
  415. void CNPC_CombineGunship::Spawn( void )
  416. {
  417. Precache( );
  418. if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
  419. {
  420. SetModel( "models/combine_helicopter.mdl" );
  421. }
  422. else
  423. {
  424. SetModel( "models/gunship.mdl" );
  425. }
  426. ExtractBbox( SelectHeaviestSequence( ACT_GUNSHIP_PATROL ), m_cullBoxMins, m_cullBoxMaxs );
  427. BaseClass::Spawn();
  428. InitPathingData( GUNSHIP_ARRIVE_DIST, GUNSHIP_MIN_CHASE_DIST_DIFF, sk_gunship_burst_min.GetFloat() );
  429. AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION );
  430. m_takedamage = DAMAGE_YES;
  431. SetHullType(HULL_LARGE_CENTERED);
  432. SetHullSizeNormal();
  433. m_iMaxHealth = m_iHealth = 100;
  434. m_flFieldOfView = -0.707; // 270 degrees
  435. m_fHelicopterFlags |= BITS_HELICOPTER_GUN_ON;
  436. InitBoneControllers();
  437. InitCustomSchedules();
  438. SetActivity( (Activity)ACT_GUNSHIP_PATROL );
  439. SetCollisionGroup( HL2COLLISION_GROUP_GUNSHIP );
  440. m_flMaxSpeed = GUNSHIP_MAX_SPEED;
  441. m_flMaxSpeedFiring = GUNSHIP_MAX_SPEED;
  442. m_flTimeNextAttack = gpGlobals->curtime;
  443. m_flNextSeeEnemySound = gpGlobals->curtime;
  444. // Init the pose parameters
  445. SetPoseParameter( "flex_horz", 0 );
  446. SetPoseParameter( "flex_vert", 0 );
  447. SetPoseParameter( "fin_accel", 0 );
  448. SetPoseParameter( "fin_sway", 0 );
  449. if( m_iAmmoType == -1 )
  450. {
  451. // Since there's no weapon to index the ammo type,
  452. // do it manually here.
  453. m_iAmmoType = GetAmmoDef()->Index("CombineCannon");
  454. }
  455. //!!!HACKHACK
  456. // This tricks the AI code that constantly complains that the gunship has no schedule.
  457. SetSchedule( SCHED_IDLE_STAND );
  458. AddRelationship( "env_flare D_LI 9", NULL );
  459. AddRelationship( "rpg_missile D_HT 99", NULL );
  460. m_flTimeNextPing = gpGlobals->curtime + 2;
  461. m_flPenetrationDepth = 24;
  462. m_flBurstDelay = 2.0f;
  463. // Blindfire and Omniscience default to off
  464. m_fBlindfire = false;
  465. m_fOmniscient = false;
  466. m_bIsFiring = false;
  467. m_bPreFire = false;
  468. m_bInvulnerable = false;
  469. // See if we should start being able to attack
  470. m_bCanGroundAttack = ( m_spawnflags & SF_GUNSHIP_NO_GROUND_ATTACK ) ? false : true;
  471. m_flEndDestructTime = 0;
  472. m_iBurstSize = 0;
  473. m_iBurstHits = 0;
  474. // Do not dissolve
  475. AddEFlags( EFL_NO_DISSOLVE );
  476. for ( int i = 0; i < GUNSHIP_NUM_DAMAGE_OUTPUTS; i++ )
  477. {
  478. m_bDamageOutputsFired[i] = false;
  479. }
  480. CapabilitiesAdd( bits_CAP_SQUAD);
  481. if ( hl2_episodic.GetBool() == true )
  482. {
  483. CreateBellyBlastEnergyCore();
  484. }
  485. // Allows autoaim to help attack the gunship.
  486. if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
  487. {
  488. AddFlag( FL_AIMTARGET );
  489. }
  490. }
  491. //-----------------------------------------------------------------------------
  492. // Purpose: Restore the motion controller
  493. //-----------------------------------------------------------------------------
  494. void CNPC_CombineGunship::OnRestore( void )
  495. {
  496. BaseClass::OnRestore();
  497. if ( m_pCrashingController )
  498. {
  499. m_pCrashingController->SetEventHandler( &m_crashCallback );
  500. }
  501. }
  502. //------------------------------------------------------------------------------
  503. // Purpose:
  504. //------------------------------------------------------------------------------
  505. void CNPC_CombineGunship::Precache( void )
  506. {
  507. if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
  508. {
  509. PrecacheModel( "models/combine_helicopter.mdl" );
  510. Chopper_PrecacheChunks( this );
  511. }
  512. else
  513. {
  514. PrecacheModel("models/gunship.mdl");
  515. }
  516. PrecacheModel("sprites/lgtning.vmt");
  517. PrecacheMaterial( "effects/ar2ground2" );
  518. PrecacheMaterial( "effects/blueblackflash" );
  519. PrecacheScriptSound( "NPC_CombineGunship.SearchPing" );
  520. PrecacheScriptSound( "NPC_CombineGunship.PatrolPing" );
  521. PrecacheScriptSound( "NPC_Strider.Charge" );
  522. PrecacheScriptSound( "NPC_Strider.Shoot" );
  523. PrecacheScriptSound( "NPC_CombineGunship.SeeEnemy" );
  524. PrecacheScriptSound( "NPC_CombineGunship.CannonStartSound" );
  525. PrecacheScriptSound( "NPC_CombineGunship.Explode");
  526. PrecacheScriptSound( "NPC_CombineGunship.Pain" );
  527. PrecacheScriptSound( "NPC_CombineGunship.CannonStopSound" );
  528. PrecacheScriptSound( "NPC_CombineGunship.DyingSound" );
  529. PrecacheScriptSound( "NPC_CombineGunship.CannonSound" );
  530. PrecacheScriptSound( "NPC_CombineGunship.RotorSound" );
  531. PrecacheScriptSound( "NPC_CombineGunship.ExhaustSound" );
  532. PrecacheScriptSound( "NPC_CombineGunship.RotorBlastSound" );
  533. if ( hl2_episodic.GetBool() == true )
  534. {
  535. UTIL_PrecacheOther( "env_citadel_energy_core" );
  536. g_iGunshipEffectIndex = PrecacheModel( "sprites/physbeam.vmt" );
  537. }
  538. PropBreakablePrecacheAll( MAKE_STRING("models/gunship.mdl") );
  539. BaseClass::Precache();
  540. }
  541. //-----------------------------------------------------------------------------
  542. // Purpose: Cache whatever pose parameters we intend to use
  543. //-----------------------------------------------------------------------------
  544. bool CNPC_CombineGunship::m_sbStaticPoseParamsLoaded = false;
  545. int CNPC_CombineGunship::m_poseFlex_Horz = 0;
  546. int CNPC_CombineGunship::m_poseFlex_Vert = 0;
  547. int CNPC_CombineGunship::m_posePitch = 0;
  548. int CNPC_CombineGunship::m_poseYaw = 0;
  549. int CNPC_CombineGunship::m_poseFin_Accel = 0;
  550. int CNPC_CombineGunship::m_poseFin_Sway = 0;
  551. int CNPC_CombineGunship::m_poseWeapon_Pitch = 0;
  552. int CNPC_CombineGunship::m_poseWeapon_Yaw = 0;
  553. void CNPC_CombineGunship::PopulatePoseParameters( void )
  554. {
  555. if (!m_sbStaticPoseParamsLoaded)
  556. {
  557. m_poseFlex_Horz = LookupPoseParameter( "flex_horz");
  558. m_poseFlex_Vert = LookupPoseParameter( "flex_vert" );
  559. m_posePitch = LookupPoseParameter( "pitch" );
  560. m_poseYaw = LookupPoseParameter( "yaw" );
  561. m_poseFin_Accel = LookupPoseParameter( "fin_accel" );
  562. m_poseFin_Sway = LookupPoseParameter( "fin_sway" );
  563. m_poseWeapon_Pitch = LookupPoseParameter( "weapon_pitch" );
  564. m_poseWeapon_Yaw = LookupPoseParameter( "weapon_yaw" );
  565. m_sbStaticPoseParamsLoaded = true;
  566. }
  567. BaseClass::PopulatePoseParameters();
  568. }
  569. //------------------------------------------------------------------------------
  570. // Purpose :
  571. //------------------------------------------------------------------------------
  572. CNPC_CombineGunship::~CNPC_CombineGunship(void)
  573. {
  574. StopLoopingSounds();
  575. if ( m_pCrashingController )
  576. {
  577. physenv->DestroyMotionController( m_pCrashingController );
  578. }
  579. }
  580. //-----------------------------------------------------------------------------
  581. // Purpose:
  582. //-----------------------------------------------------------------------------
  583. void CNPC_CombineGunship::Ping( void )
  584. {
  585. if( IsCrashing() )
  586. return;
  587. if( GetEnemy() != NULL )
  588. {
  589. if( !HasCondition(COND_SEE_ENEMY) && gpGlobals->curtime > m_flTimeNextPing )
  590. {
  591. EmitSound( "NPC_CombineGunship.SearchPing" );
  592. m_flTimeNextPing = gpGlobals->curtime + 3;
  593. }
  594. }
  595. else
  596. {
  597. if( gpGlobals->curtime > m_flTimeNextPing )
  598. {
  599. EmitSound( "NPC_CombineGunship.PatrolPing" );
  600. m_flTimeNextPing = gpGlobals->curtime + 3;
  601. }
  602. }
  603. }
  604. //-----------------------------------------------------------------------------
  605. // Purpose:
  606. // Input : &pos -
  607. // Output : float
  608. //-----------------------------------------------------------------------------
  609. float CNPC_CombineGunship::GroundDistToPosition( const Vector &pos )
  610. {
  611. Vector vecDiff;
  612. VectorSubtract( GetAbsOrigin(), pos, vecDiff );
  613. // Only interested in the 2d dist
  614. vecDiff.z = 0;
  615. return vecDiff.Length();
  616. }
  617. //-----------------------------------------------------------------------------
  618. // Purpose:
  619. //-----------------------------------------------------------------------------
  620. void CNPC_CombineGunship::PlayPatrolLoop( void )
  621. {
  622. m_fPatrolLoopPlaying = true;
  623. /*
  624. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  625. controller.SoundChangeVolume( m_pPatrolSound, 1.0, 1.0 );
  626. controller.SoundChangeVolume( m_pAngrySound, 0.0, 1.0 );
  627. */
  628. }
  629. //-----------------------------------------------------------------------------
  630. // Purpose:
  631. //-----------------------------------------------------------------------------
  632. void CNPC_CombineGunship::PlayAngryLoop( void )
  633. {
  634. m_fPatrolLoopPlaying = false;
  635. /*
  636. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  637. controller.SoundChangeVolume( m_pPatrolSound, 0.0, 1.0 );
  638. controller.SoundChangeVolume( m_pAngrySound, 1.0, 1.0 );
  639. */
  640. }
  641. //-----------------------------------------------------------------------------
  642. //-----------------------------------------------------------------------------
  643. void CNPC_CombineGunship::HelicopterPostThink( void )
  644. {
  645. // After HelicopterThink()
  646. if ( HasCondition( COND_ENEMY_DEAD ) )
  647. {
  648. if ( m_bIsFiring )
  649. {
  650. // Fire more shots at the dead body for effect
  651. if ( m_iBurstSize > 8 )
  652. {
  653. m_iBurstSize = 8;
  654. }
  655. }
  656. // Fade out search sound, fade in patrol sound.
  657. PlayPatrolLoop();
  658. }
  659. }
  660. //-----------------------------------------------------------------------------
  661. // Purpose:
  662. // Output : Vector
  663. //-----------------------------------------------------------------------------
  664. Vector CNPC_CombineGunship::GetGroundAttackHitPosition( void )
  665. {
  666. trace_t tr;
  667. Vector vecShootPos, vecShootDir;
  668. GetAttachment( "BellyGun", vecShootPos, &vecShootDir, NULL, NULL );
  669. AI_TraceLine( vecShootPos, vecShootPos + Vector( 0, 0, -MAX_TRACE_LENGTH ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  670. if ( m_hGroundAttackTarget )
  671. {
  672. return Vector( tr.endpos.x, tr.endpos.y, m_hGroundAttackTarget->WorldSpaceCenter().z );
  673. }
  674. return tr.endpos;
  675. }
  676. //-----------------------------------------------------------------------------
  677. // Purpose:
  678. // Output : Returns true on success, false on failure.
  679. //-----------------------------------------------------------------------------
  680. bool CNPC_CombineGunship::CheckGroundAttack( void )
  681. {
  682. if ( m_bCanGroundAttack == false )
  683. return false;
  684. if ( m_bIsGroundAttacking )
  685. return false;
  686. // Must have an enemy
  687. if ( GetEnemy() == NULL )
  688. return false;
  689. // Must not have done it too recently
  690. if ( m_flNextGroundAttack > gpGlobals->curtime )
  691. return false;
  692. Vector predPos, predDest;
  693. // Find where the enemy is most likely to be in two seconds
  694. UTIL_PredictedPosition( GetEnemy(), 1.0f, &predPos );
  695. UTIL_PredictedPosition( this, 1.0f, &predDest );
  696. Vector predGap = ( predDest - predPos );
  697. predGap.z = 0;
  698. float predDistance = predGap.Length();
  699. // Must be within distance
  700. if ( predDistance > MIN_GROUND_ATTACK_DIST )
  701. return false;
  702. // Can't ground attack missiles
  703. if ( IsTargettingMissile() )
  704. return false;
  705. //FIXME: Check to make sure we're not firing too far above or below the target
  706. if ( fabs( GetGroundAttackHitPosition().z - GetEnemy()->WorldSpaceCenter().z ) > MIN_GROUND_ATTACK_HEIGHT_DIFF )
  707. return false;
  708. //FIXME: Check for ground movement capabilities?
  709. //TODO: Check for friendly-fire
  710. return true;
  711. }
  712. //-----------------------------------------------------------------------------
  713. // Purpose:
  714. //-----------------------------------------------------------------------------
  715. void CNPC_CombineGunship::StartGroundAttack( void )
  716. {
  717. // Mark us as attacking
  718. m_bIsGroundAttacking = true;
  719. m_flGroundAttackTime = gpGlobals->curtime + 3.0f;
  720. // Setup the attack effects
  721. Vector vecShootPos;
  722. GetAttachment( "BellyGun", vecShootPos );
  723. EntityMessageBegin( this, true );
  724. WRITE_BYTE( GUNSHIP_MSG_STREAKS );
  725. WRITE_VEC3COORD( vecShootPos );
  726. MessageEnd();
  727. CPASAttenuationFilter filter2( this, "NPC_Strider.Charge" );
  728. EmitSound( filter2, entindex(), "NPC_Strider.Charge" );
  729. Vector endpos = GetGroundAttackHitPosition();
  730. CSoundEnt::InsertSound ( SOUND_DANGER, endpos, 1024, 0.5f );
  731. if ( hl2_episodic.GetBool() == true )
  732. {
  733. if ( m_hEnergyCore )
  734. {
  735. variant_t value;
  736. value.SetFloat( 3.0f );
  737. g_EventQueue.AddEvent( m_hEnergyCore, "StartCharge", value, 0, this, this );
  738. }
  739. }
  740. }
  741. #define GUNSHIP_BELLY_BLAST_RADIUS 256.0f
  742. #define BELLY_BLAST_MAX_PUNCH 5
  743. //-----------------------------------------------------------------------------
  744. // Purpose:
  745. //-----------------------------------------------------------------------------
  746. void CNPC_CombineGunship::ManageWarningBeam( void )
  747. {
  748. Vector vecSrc, vecShootDir;
  749. GetAttachment( "BellyGun", vecSrc, NULL, NULL, NULL );
  750. trace_t tr;
  751. CTraceFilterSkipTwoEntities filter( m_hGroundAttackTarget, this, COLLISION_GROUP_NONE );
  752. UTIL_TraceLine( vecSrc, m_vecHitPos, MASK_SOLID, &filter, &tr );
  753. int iPunch = 0;
  754. while ( tr.endpos != m_vecHitPos )
  755. {
  756. iPunch++;
  757. if ( iPunch > BELLY_BLAST_MAX_PUNCH )
  758. break;
  759. if ( tr.fraction != 1.0 )
  760. {
  761. if ( tr.m_pEnt )
  762. {
  763. CTakeDamageInfo info( this, this, 1.0f, DMG_ENERGYBEAM );
  764. Vector vTargetDir = tr.m_pEnt->BodyTarget( tr.endpos, false ) - tr.endpos;
  765. VectorNormalize( vTargetDir );
  766. info.SetDamagePosition( tr.endpos + ( tr.plane.normal * 64.0f ) );
  767. info.SetDamageForce( vTargetDir * 100 );
  768. if ( tr.m_pEnt->m_takedamage != DAMAGE_NO )
  769. {
  770. // Deal damage
  771. tr.m_pEnt->TakeDamage( info );
  772. }
  773. }
  774. Vector vDir = m_vecHitPos - vecSrc;
  775. VectorNormalize( vDir );
  776. Vector vStartPunch = tr.endpos + vDir * 1;
  777. UTIL_TraceLine( vStartPunch, m_vecHitPos, MASK_SOLID, &filter, &tr );
  778. if ( tr.startsolid )
  779. {
  780. float flLength = (vStartPunch - tr.endpos).Length();
  781. Vector vEndPunch = vStartPunch + vDir * ( flLength * tr.fractionleftsolid );
  782. UTIL_TraceLine( vEndPunch, m_vecHitPos, MASK_SOLID, &filter, &tr );
  783. trace_t tr2;
  784. UTIL_TraceLine( vEndPunch, vEndPunch - vDir * 2, MASK_SOLID, &filter, &tr2 );
  785. if ( (m_flGroundAttackTime - gpGlobals->curtime) <= 2.0f )
  786. {
  787. g_pEffects->EnergySplash( tr2.endpos + vDir * 8, tr2.plane.normal, true );
  788. }
  789. g_pEffects->Sparks( tr2.endpos, 3.0f - (m_flGroundAttackTime-gpGlobals->curtime), 3.5f - (m_flGroundAttackTime-gpGlobals->curtime), &tr2.plane.normal );
  790. }
  791. }
  792. }
  793. }
  794. //-----------------------------------------------------------------------------
  795. // Purpose:
  796. //-----------------------------------------------------------------------------
  797. void CNPC_CombineGunship::DoBellyBlastDamage( trace_t &tr, Vector vMins, Vector vMaxs )
  798. {
  799. CBaseEntity* pList[100];
  800. if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_BELLYBLAST )
  801. {
  802. NDebugOverlay::Box( tr.endpos, vMins, vMaxs, 255, 255, 0, true, 5.0f );
  803. }
  804. int count = UTIL_EntitiesInBox( pList, 100, tr.endpos + vMins, tr.endpos + vMaxs, 0 );
  805. for ( int i = 0; i < count; i++ )
  806. {
  807. CBaseEntity *pEntity = pList[i];
  808. if ( pEntity == this )
  809. continue;
  810. if ( pEntity->m_takedamage == DAMAGE_NO )
  811. continue;
  812. float damage = 150;
  813. if ( pEntity->IsPlayer() )
  814. {
  815. float damageDist = ( pEntity->GetAbsOrigin() - tr.endpos ).Length();
  816. damage = RemapValClamped( damageDist, 0, 300, 200, 0 );
  817. }
  818. CTakeDamageInfo info( this, this, damage, DMG_DISSOLVE );
  819. Vector vTargetDir = pEntity->BodyTarget( tr.endpos, false ) - tr.endpos;
  820. VectorNormalize( vTargetDir );
  821. info.SetDamagePosition( tr.endpos + ( tr.plane.normal * 64.0f ) );
  822. info.SetDamageForce( vTargetDir * 25000 );
  823. // Deal damage
  824. pEntity->TakeDamage( info );
  825. trace_t groundTrace;
  826. UTIL_TraceLine( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin() - Vector( 0, 0, 256 ), MASK_SOLID, pEntity, COLLISION_GROUP_NONE, &groundTrace );
  827. if ( tr.fraction < 1.0f )
  828. {
  829. CEffectData data;
  830. // Find the floor and add a dissolve explosion at that point
  831. data.m_flRadius = GUNSHIP_BELLY_BLAST_RADIUS * 0.5f;
  832. data.m_vNormal = groundTrace.plane.normal;
  833. data.m_vOrigin = groundTrace.endpos;
  834. DispatchEffect( "AR2Explosion", data );
  835. }
  836. // If the creature was killed, then dissolve it
  837. if ( pEntity->GetHealth() <= 0.0f )
  838. {
  839. if ( pEntity->GetBaseAnimating() != NULL && !pEntity->IsEFlagSet( EFL_NO_DISSOLVE ) )
  840. {
  841. pEntity->GetBaseAnimating()->Dissolve( NULL, gpGlobals->curtime );
  842. }
  843. }
  844. }
  845. }
  846. //-----------------------------------------------------------------------------
  847. // Purpose:
  848. //-----------------------------------------------------------------------------
  849. void CNPC_CombineGunship::DoGroundAttackExplosion( void )
  850. {
  851. // Fire the bullets
  852. Vector vecSrc, vecShootDir;
  853. Vector vecAttachmentOrigin;
  854. GetAttachment( "BellyGun", vecAttachmentOrigin, &vecShootDir, NULL, NULL );
  855. vecSrc = vecAttachmentOrigin;
  856. if ( m_hGroundAttackTarget )
  857. {
  858. vecSrc = m_hGroundAttackTarget->GetAbsOrigin();
  859. }
  860. Vector impactPoint = vecSrc + ( Vector( 0, 0, -1 ) * MAX_TRACE_LENGTH );
  861. trace_t tr;
  862. UTIL_TraceLine( vecSrc, impactPoint, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  863. UTIL_DecalTrace( &tr, "Scorch" );
  864. if ( hl2_episodic.GetBool() == true )
  865. {
  866. g_pEffects->EnergySplash( tr.endpos, tr.plane.normal );
  867. CBroadcastRecipientFilter filter;
  868. te->BeamRingPoint( filter, 0.0,
  869. tr.endpos, //origin
  870. 0, //start radius
  871. GUNSHIP_BELLY_BLAST_RADIUS, //end radius
  872. g_iGunshipEffectIndex, //texture
  873. 0, //halo index
  874. 0, //start frame
  875. 0, //framerate
  876. 0.2, //life
  877. 10, //width
  878. 0, //spread
  879. 0, //amplitude
  880. 255, //r
  881. 255, //g
  882. 255, //b
  883. 50, //a
  884. 0, //speed
  885. FBEAM_FADEOUT
  886. );
  887. }
  888. // Send the effect over
  889. CEffectData data;
  890. // Do an extra effect if we struck the world
  891. if ( tr.m_pEnt && tr.m_pEnt->IsWorld() )
  892. {
  893. data.m_flRadius = GUNSHIP_BELLY_BLAST_RADIUS;
  894. data.m_vNormal = tr.plane.normal;
  895. data.m_vOrigin = tr.endpos;
  896. DispatchEffect( "AR2Explosion", data );
  897. }
  898. float flZLength = vecAttachmentOrigin.z - tr.endpos.z;
  899. Vector vBeamMins = Vector( -16, -16, 0 );
  900. Vector vBeamMaxs = Vector( 16, 16, flZLength );
  901. DoBellyBlastDamage( tr, vBeamMins, vBeamMaxs );
  902. Vector vBlastMins = Vector( -GUNSHIP_BELLY_BLAST_RADIUS, -GUNSHIP_BELLY_BLAST_RADIUS, 0 );
  903. Vector vBlastMaxs = Vector( GUNSHIP_BELLY_BLAST_RADIUS, GUNSHIP_BELLY_BLAST_RADIUS, 96 );
  904. DoBellyBlastDamage( tr, vBlastMins, vBlastMaxs );
  905. }
  906. //-----------------------------------------------------------------------------
  907. // Purpose:
  908. //-----------------------------------------------------------------------------
  909. void CNPC_CombineGunship::StopGroundAttack( bool bDoAttack )
  910. {
  911. if ( !m_bIsGroundAttacking )
  912. return;
  913. // Mark us as no longer attacking
  914. m_bIsGroundAttacking = false;
  915. m_flNextGroundAttack = gpGlobals->curtime + 4.0f;
  916. m_flTimeNextAttack = gpGlobals->curtime + 2.0f;
  917. Vector hitPos = GetGroundAttackHitPosition();
  918. // tell the client side effect to complete
  919. EntityMessageBegin( this, true );
  920. WRITE_BYTE( GUNSHIP_MSG_BIG_SHOT );
  921. WRITE_VEC3COORD( hitPos );
  922. MessageEnd();
  923. if ( hl2_episodic.GetBool() == true )
  924. {
  925. if ( m_hEnergyCore )
  926. {
  927. variant_t value;
  928. value.SetFloat( 1.0f );
  929. g_EventQueue.AddEvent( m_hEnergyCore, "Stop", value, 0, this, this );
  930. }
  931. }
  932. // Only attack if told to
  933. if ( bDoAttack )
  934. {
  935. CPASAttenuationFilter filter2( this, "NPC_Strider.Shoot" );
  936. EmitSound( filter2, entindex(), "NPC_Strider.Shoot");
  937. ApplyAbsVelocityImpulse( Vector( 0, 0, 200.0f ) );
  938. //ExplosionCreate( hitPos, QAngle( 0, 0, 1 ), this, 500, 500, true );
  939. DoGroundAttackExplosion();
  940. }
  941. // If we were attacking a target, revert to our previous target
  942. if ( m_hGroundAttackTarget )
  943. {
  944. m_hGroundAttackTarget = NULL;
  945. if ( GetDestPathTarget() )
  946. {
  947. // Return to our old path
  948. SetupNewCurrentTarget( GetDestPathTarget() );
  949. }
  950. }
  951. }
  952. //-----------------------------------------------------------------------------
  953. // Purpose:
  954. //-----------------------------------------------------------------------------
  955. void CNPC_CombineGunship::DrawRotorWash( float flAltitude, const Vector &vecRotorOrigin )
  956. {
  957. // If we have a ragdoll, we want the wash under that, not me
  958. if ( m_hRagdoll )
  959. {
  960. BaseClass::DrawRotorWash( flAltitude, m_hRagdoll->GetAbsOrigin() );
  961. return;
  962. }
  963. BaseClass::DrawRotorWash( flAltitude, vecRotorOrigin );
  964. }
  965. //------------------------------------------------------------------------------
  966. // Purpose : Override the desired position if your derived helicopter is doing something special
  967. //------------------------------------------------------------------------------
  968. void CNPC_CombineGunship::UpdateDesiredPosition( void )
  969. {
  970. if ( m_hCrashTarget )
  971. {
  972. SetDesiredPosition( m_hCrashTarget->WorldSpaceCenter() + Vector(0,0,128) );
  973. }
  974. else if ( m_hGroundAttackTarget )
  975. {
  976. SetDesiredPosition( m_hGroundAttackTarget->GetAbsOrigin() + Vector(0,0,GUNSHIP_BELLYBLAST_TARGET_HEIGHT) );
  977. }
  978. }
  979. //-----------------------------------------------------------------------------
  980. // Purpose: do all of the stuff related to having an enemy, attacking, etc.
  981. //-----------------------------------------------------------------------------
  982. void CNPC_CombineGunship::DoCombat( void )
  983. {
  984. // Check for enemy change-overs
  985. if ( HasEnemy() )
  986. {
  987. if ( HasCondition( COND_NEW_ENEMY ) )
  988. {
  989. if ( GetEnemy() && GetEnemy()->IsPlayer() && m_flNextSeeEnemySound < gpGlobals->curtime )
  990. {
  991. m_flNextSeeEnemySound = gpGlobals->curtime + 5.0;
  992. if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
  993. {
  994. EmitSound( "NPC_CombineGunship.SeeEnemy" );
  995. }
  996. }
  997. // If we're shooting at a missile, do it immediately!
  998. if ( IsTargettingMissile() )
  999. {
  1000. EmitSound( "NPC_CombineGunship.SeeMissile" );
  1001. // Allow the gunship to attack again immediately
  1002. if ( ( m_flTimeNextAttack > gpGlobals->curtime ) && ( ( m_flTimeNextAttack - gpGlobals->curtime ) > GUNSHIP_MISSILE_MAX_RESPONSE_TIME ) )
  1003. {
  1004. m_flTimeNextAttack = gpGlobals->curtime + GUNSHIP_MISSILE_MAX_RESPONSE_TIME;
  1005. m_iBurstSize = sk_gunship_burst_size.GetInt();
  1006. }
  1007. }
  1008. // Fade in angry sound, fade out patrol sound.
  1009. PlayAngryLoop();
  1010. }
  1011. }
  1012. // Do we have a belly blast target?
  1013. if ( m_hGroundAttackTarget && !m_bIsGroundAttacking )
  1014. {
  1015. // If we're over it, blast. Can't use GetDesiredPosition() because it's not updated yet.
  1016. Vector vecTarget = m_hGroundAttackTarget->GetAbsOrigin() + Vector(0,0,GUNSHIP_BELLYBLAST_TARGET_HEIGHT);
  1017. Vector vecToTarget = (vecTarget - GetAbsOrigin());
  1018. float flDistance = vecToTarget.Length();
  1019. // Get the difference between our velocity & the target's velocity
  1020. Vector vec2DVelocity = GetAbsVelocity();
  1021. Vector vec2DTargetVelocity = m_hGroundAttackTarget->GetAbsVelocity();
  1022. vec2DVelocity.z = vec2DTargetVelocity.z = 0;
  1023. float flVelocityDiff = (vec2DVelocity - vec2DTargetVelocity).Length();
  1024. if ( flDistance < 100 && flVelocityDiff < 200 )
  1025. {
  1026. StartGroundAttack();
  1027. }
  1028. }
  1029. // Update our firing
  1030. if ( m_bIsFiring )
  1031. {
  1032. // Fire if we have rounds remaining in this burst
  1033. if ( ( m_iBurstSize > 0 ) && ( gpGlobals->curtime > m_flTimeNextAttack ) )
  1034. {
  1035. UpdateEnemyTarget();
  1036. FireCannonRound();
  1037. }
  1038. else if ( m_iBurstSize < 1 )
  1039. {
  1040. // We're done firing
  1041. StopCannonBurst();
  1042. if ( IsTargettingMissile() )
  1043. {
  1044. m_flTimeNextAttack = gpGlobals->curtime + 0.5f;
  1045. }
  1046. }
  1047. }
  1048. else
  1049. {
  1050. // If we're not firing, look at the enemy
  1051. if ( GetEnemy() )
  1052. {
  1053. m_vecAttackPosition = GetEnemy()->EyePosition();
  1054. }
  1055. #ifdef BELLYBLAST
  1056. // Check for a ground attack
  1057. if ( CheckGroundAttack() )
  1058. {
  1059. StartGroundAttack();
  1060. }
  1061. #endif
  1062. // See if we're attacking
  1063. if ( m_bIsGroundAttacking )
  1064. {
  1065. m_vecHitPos = GetGroundAttackHitPosition();
  1066. ManageWarningBeam();
  1067. // If our time is up, fire the blast and be done
  1068. if ( m_flGroundAttackTime < gpGlobals->curtime )
  1069. {
  1070. // Fire!
  1071. StopGroundAttack( true );
  1072. }
  1073. }
  1074. }
  1075. // If we're using the chopper model, align the gun towards the target
  1076. if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
  1077. {
  1078. Vector vGunPosition;
  1079. GetAttachment( "gun", vGunPosition );
  1080. Vector vecToAttackPos = (m_vecAttackPosition - vGunPosition);
  1081. PoseGunTowardTargetDirection( vecToAttackPos );
  1082. }
  1083. // Forget flares once I've seen them for a while
  1084. float flDeltaSeen = m_flLastSeen - m_flPrevSeen;
  1085. if ( GetEnemy() != NULL && GetEnemy()->Classify() == CLASS_FLARE && flDeltaSeen > GUNSHIP_FLARE_IGNORE_TIME )
  1086. {
  1087. AddEntityRelationship( GetEnemy(), D_NU, 5 );
  1088. PlayPatrolLoop();
  1089. // Forget the flare now.
  1090. SetEnemy( NULL );
  1091. }
  1092. }
  1093. //-----------------------------------------------------------------------------
  1094. // Purpose:
  1095. // Output : Returns true on success, false on failure.
  1096. //-----------------------------------------------------------------------------
  1097. bool CNPC_CombineGunship::ChooseEnemy( void )
  1098. {
  1099. // If we're firing, don't switch enemies. This stops the gunship occasionally
  1100. // stopping a burst before he's really fired at all, which makes him look indecisive.
  1101. if ( m_bIsFiring )
  1102. return true;
  1103. return BaseClass::ChooseEnemy();
  1104. }
  1105. //-----------------------------------------------------------------------------
  1106. // Purpose: There's a lot of code in here now. We should consider moving
  1107. // helicopters and such to scheduled AI. (sjb)
  1108. //-----------------------------------------------------------------------------
  1109. void CNPC_CombineGunship::MoveHead( void )
  1110. {
  1111. float flYaw = GetPoseParameter( m_poseFlex_Horz );
  1112. float flPitch = GetPoseParameter( m_poseFlex_Vert );
  1113. /*
  1114. This head-turning code will cause the head to POP when switching from looking at the enemy
  1115. to looking according to the flight model. I will fix this later. Right now I'm turning
  1116. the code over to Ken for some aiming fixups. (sjb)
  1117. */
  1118. while( 1 )
  1119. {
  1120. if ( GetEnemy() != NULL )
  1121. {
  1122. Vector vecToEnemy, vecAimDir;
  1123. float flDot;
  1124. Vector vTargetPos, vGunPosition;
  1125. Vector vecTargetOffset;
  1126. QAngle vGunAngles;
  1127. GetAttachment( "muzzle", vGunPosition, vGunAngles );
  1128. vTargetPos = GetEnemyTarget();
  1129. VectorSubtract( vTargetPos, vGunPosition, vecToEnemy );
  1130. VectorNormalize( vecToEnemy );
  1131. // get angles relative to body position
  1132. AngleVectors( GetAbsAngles(), &vecAimDir );
  1133. flDot = DotProduct( vecAimDir, vecToEnemy );
  1134. // Look at Enemy!!
  1135. if ( flDot > 0.3f )
  1136. {
  1137. float flDiff;
  1138. float flDesiredYaw = VecToYaw(vTargetPos - vGunPosition);
  1139. flDiff = UTIL_AngleDiff( flDesiredYaw, vGunAngles.y ) * 0.90;
  1140. flYaw = UTIL_Approach( flYaw + flDiff, flYaw, 5.0 );
  1141. float flDesiredPitch = UTIL_VecToPitch(vTargetPos - vGunPosition);
  1142. flDiff = UTIL_AngleDiff( flDesiredPitch, vGunAngles.x ) * 0.90;
  1143. flPitch = UTIL_Approach( flPitch + flDiff, flPitch, 5.0 );
  1144. break;
  1145. }
  1146. }
  1147. // Look where going!
  1148. #if 1 // old way- look according to rotational velocity
  1149. flYaw = UTIL_Approach( GetLocalAngularVelocity().y, flYaw, 2.0 * 10 * m_flDeltaT );
  1150. flPitch = UTIL_Approach( GetLocalAngularVelocity().x, flPitch, 2.0 * 10 * m_flDeltaT );
  1151. #else // new way- look towards the next waypoint?
  1152. // !!!UNDONE
  1153. #endif
  1154. break;
  1155. }
  1156. // Set the body flexes
  1157. SetPoseParameter( m_poseFlex_Vert, flPitch );
  1158. SetPoseParameter( m_poseFlex_Horz, flYaw );
  1159. }
  1160. //-----------------------------------------------------------------------------
  1161. // Purpose: There's a lot of code in here now. We should consider moving
  1162. // helicopters and such to scheduled AI. (sjb)
  1163. //-----------------------------------------------------------------------------
  1164. void CNPC_CombineGunship::PrescheduleThink( void )
  1165. {
  1166. m_flDeltaT = gpGlobals->curtime - GetLastThink();
  1167. // Are we crashing?
  1168. if ( m_flEndDestructTime && gpGlobals->curtime > m_flEndDestructTime )
  1169. {
  1170. // We're dead, remove ourselves
  1171. SelfDestruct();
  1172. return;
  1173. }
  1174. if( m_lifeState == LIFE_ALIVE )
  1175. {
  1176. // Chopper doesn't ping
  1177. if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
  1178. {
  1179. Ping();
  1180. }
  1181. DoCombat();
  1182. MoveHead();
  1183. }
  1184. else if( m_lifeState == LIFE_DYING )
  1185. {
  1186. // Increase the number of explosions as he gets closer to death
  1187. bool bCreateExplosion = false;
  1188. float flTimeLeft = m_flEndDestructTime - gpGlobals->curtime;
  1189. if ( flTimeLeft > 1.5 )
  1190. {
  1191. bCreateExplosion = (random->RandomInt( 0, 3 ) == 0);
  1192. }
  1193. else
  1194. {
  1195. bCreateExplosion = (random->RandomInt( 0, 2 ) == 0);
  1196. }
  1197. if ( bCreateExplosion )
  1198. {
  1199. Vector explodePoint;
  1200. if ( m_hRagdoll )
  1201. {
  1202. m_hRagdoll->CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );
  1203. }
  1204. else
  1205. {
  1206. CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );
  1207. // Knock the gunship a little, but not if we're trying to fly to a point
  1208. if ( !m_hCrashTarget )
  1209. {
  1210. Vector vecPush = (GetAbsOrigin() - explodePoint);
  1211. VectorNormalize( vecPush );
  1212. ApplyAbsVelocityImpulse( vecPush * 128 );
  1213. }
  1214. }
  1215. ExplosionCreate( explodePoint, QAngle(0,0,1), this, 100, 128, false );
  1216. }
  1217. // Have we reached our crash point?
  1218. if ( m_flNextGunshipCrashFind && !m_hRagdoll )
  1219. {
  1220. // Update nearest crash point. The RPG that killed us may have knocked us
  1221. // closer to a different point than the one we were near when we first died.
  1222. if ( m_flNextGunshipCrashFind < gpGlobals->curtime )
  1223. {
  1224. FindNearestGunshipCrash();
  1225. }
  1226. if ( m_hCrashTarget )
  1227. {
  1228. MoveHead();
  1229. UpdateDesiredPosition();
  1230. // If we're over it, destruct
  1231. Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
  1232. if ( vecToTarget.LengthSqr() < (384 * 384) )
  1233. {
  1234. BeginDestruct();
  1235. m_OnCrashed.FireOutput( this, this );
  1236. m_hCrashTarget->GunshipCrashedOnTarget();
  1237. return;
  1238. }
  1239. }
  1240. }
  1241. }
  1242. BaseClass::PrescheduleThink();
  1243. #ifdef JACOBS_GUNSHIP
  1244. SetPoseParameter( m_posePitch, random->RandomFloat( GUNSHIP_HEAD_MAX_LEFT, GUNSHIP_HEAD_MAX_RIGHT ) );
  1245. SetPoseParameter( m_poseYaw, random->RandomFloat( GUNSHIP_HEAD_MAX_UP, GUNSHIP_HEAD_MAX_DOWN ) );
  1246. #endif
  1247. }
  1248. //------------------------------------------------------------------------------
  1249. // Purpose : If the enemy is in front of the gun, load up a burst.
  1250. // Actual gunfire is handled in PrescheduleThink
  1251. // Input :
  1252. // Output :
  1253. //------------------------------------------------------------------------------
  1254. bool CNPC_CombineGunship::FireGun( void )
  1255. {
  1256. if ( m_lifeState != LIFE_ALIVE )
  1257. return false;
  1258. if ( m_bIsGroundAttacking )
  1259. return false;
  1260. if ( GetEnemy() && !m_bIsFiring && gpGlobals->curtime > m_flTimeNextAttack )
  1261. {
  1262. // We want to decelerate to attack
  1263. if (m_flGoalSpeed > GetMaxSpeedFiring() )
  1264. {
  1265. m_flGoalSpeed = GetMaxSpeedFiring();
  1266. }
  1267. bool bTargetingMissile = IsTargettingMissile();
  1268. if ( !bTargetingMissile && !m_bPreFire )
  1269. {
  1270. m_bPreFire = true;
  1271. m_flTimeNextAttack = gpGlobals->curtime + 0.5f;
  1272. EmitSound( "NPC_CombineGunship.CannonStartSound" );
  1273. return false;
  1274. }
  1275. //TODO: Emit the danger noise and wait until it's finished
  1276. // Don't fire at an occluded enemy unless blindfire is on.
  1277. if ( HasCondition( COND_ENEMY_OCCLUDED ) && ( m_fBlindfire == false ) )
  1278. return false;
  1279. // Don't shoot if the enemy is too close
  1280. if ( !bTargetingMissile && GroundDistToPosition( GetEnemy()->GetAbsOrigin() ) < GUNSHIP_STITCH_MIN )
  1281. return false;
  1282. Vector vecAimDir, vecToEnemy;
  1283. Vector vecMuzzle, vecEnemyTarget;
  1284. GetAttachment( "muzzle", vecMuzzle, &vecAimDir, NULL, NULL );
  1285. vecEnemyTarget = GetEnemyTarget();
  1286. // Aim with the muzzle's attachment point.
  1287. VectorSubtract( vecEnemyTarget, vecMuzzle, vecToEnemy );
  1288. VectorNormalize( vecToEnemy );
  1289. VectorNormalize( vecAimDir );
  1290. if ( DotProduct( vecToEnemy, vecAimDir ) > 0.9 )
  1291. {
  1292. StartCannonBurst( sk_gunship_burst_size.GetInt() );
  1293. return true;
  1294. }
  1295. return false;
  1296. }
  1297. return false;
  1298. }
  1299. //------------------------------------------------------------------------------
  1300. // Purpose: Fire a round from the cannon
  1301. // Notes: Only call this if you have an enemy.
  1302. //------------------------------------------------------------------------------
  1303. void CNPC_CombineGunship::FireCannonRound( void )
  1304. {
  1305. Vector vecPenetrate;
  1306. trace_t tr;
  1307. Vector vecToEnemy, vecEnemyTarget;
  1308. Vector vecMuzzle;
  1309. Vector vecAimDir;
  1310. GetAttachment( "muzzle", vecMuzzle, &vecAimDir );
  1311. vecEnemyTarget = GetEnemyTarget();
  1312. // Aim with the muzzle's attachment point.
  1313. VectorSubtract( vecEnemyTarget, vecMuzzle, vecToEnemy );
  1314. VectorNormalize( vecToEnemy );
  1315. // If the gun is wildly off target, stop firing!
  1316. // FIXME - this should use a vector pointing
  1317. // to the enemy's location PLUS the stitching
  1318. // error! (sjb) !!!BUGBUG
  1319. if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
  1320. {
  1321. QAngle vecAimAngle;
  1322. Vector vForward, vRight, vUp;
  1323. GetAttachment( "muzzle", vecMuzzle, &vForward, &vRight, &vUp );
  1324. AngleVectors( vecAimAngle, &vForward, &vRight, &vUp );
  1325. NDebugOverlay::Line( vecMuzzle, vecEnemyTarget, 255, 255, 0, true, 1.0f );
  1326. NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vForward * 64.0f ), 255, 0, 0, true, 1.0f );
  1327. NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vRight * 32.0f ), 0, 255, 0, true, 1.0f );
  1328. NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vUp * 32.0f ), 0, 0, 255, true, 1.0f );
  1329. }
  1330. // Robin: Check the dotproduct to the enemy, NOT to the offsetted firing angle
  1331. // Fixes problems firing at close enemies, where the enemy is valid but
  1332. // the offset firing stitch isn't.
  1333. Vector vecDotCheck = vecToEnemy;
  1334. if ( GetEnemy() )
  1335. {
  1336. VectorSubtract( GetEnemy()->GetAbsOrigin(), vecMuzzle, vecDotCheck );
  1337. VectorNormalize( vecDotCheck );
  1338. }
  1339. if ( DotProduct( vecDotCheck, vecAimDir ) < 0.8f )
  1340. {
  1341. StopCannonBurst();
  1342. return;
  1343. }
  1344. DoMuzzleFlash();
  1345. m_OnFireCannon.FireOutput( this, this, 0 );
  1346. m_flTimeNextAttack = gpGlobals->curtime + 0.05f;
  1347. float flPrevHealth = 0;
  1348. if ( GetEnemy() )
  1349. {
  1350. flPrevHealth = GetEnemy()->GetHealth();
  1351. }
  1352. // Make sure we hit missiles
  1353. if ( IsTargettingMissile() )
  1354. {
  1355. // Fire a fake shot
  1356. FireBullets( 1, vecMuzzle, vecToEnemy, VECTOR_CONE_5DEGREES, 8192, m_iAmmoType, 1 );
  1357. CBaseEntity *pMissile = GetEnemy();
  1358. Vector missileDir, threatDir;
  1359. AngleVectors( pMissile->GetAbsAngles(), &missileDir );
  1360. threatDir = ( WorldSpaceCenter() - pMissile->GetAbsOrigin() );
  1361. float threatDist = VectorNormalize( threatDir );
  1362. // Check that the target is within some threshold
  1363. if ( ( DotProduct( threatDir, missileDir ) > 0.95f ) && ( threatDist < 1024.0f ) )
  1364. {
  1365. if ( random->RandomInt( 0, 1 ) == 0 )
  1366. {
  1367. CTakeDamageInfo info( this, this, 200, DMG_MISSILEDEFENSE );
  1368. CalculateBulletDamageForce( &info, m_iAmmoType, -threatDir, WorldSpaceCenter() );
  1369. GetEnemy()->TakeDamage( info );
  1370. }
  1371. }
  1372. else
  1373. {
  1374. //FIXME: Some other metric
  1375. }
  1376. }
  1377. else
  1378. {
  1379. m_iBurstSize--;
  1380. // Fire directly at the target
  1381. FireBulletsInfo_t info( 1, vecMuzzle, vecToEnemy, vec3_origin, MAX_COORD_RANGE, m_iAmmoType );
  1382. info.m_iTracerFreq = 1;
  1383. CAmmoDef *pAmmoDef = GetAmmoDef();
  1384. info.m_iPlayerDamage = pAmmoDef->PlrDamage( m_iAmmoType );
  1385. // If we've already hit the player, do 0 damage. This ensures we don't hit the
  1386. // player multiple times during a single burst.
  1387. if ( m_iBurstHits >= GUNSHIP_MAX_HITS_PER_BURST )
  1388. {
  1389. info.m_iPlayerDamage = 1;
  1390. }
  1391. FireBullets( info );
  1392. if ( GetEnemy() && flPrevHealth != GetEnemy()->GetHealth() )
  1393. {
  1394. m_iBurstHits++;
  1395. }
  1396. }
  1397. }
  1398. //-----------------------------------------------------------------------------
  1399. // Purpose:
  1400. //-----------------------------------------------------------------------------
  1401. void CNPC_CombineGunship::DoMuzzleFlash( void )
  1402. {
  1403. BaseClass::DoMuzzleFlash();
  1404. CEffectData data;
  1405. data.m_nAttachmentIndex = LookupAttachment( "muzzle" );
  1406. data.m_nEntIndex = entindex();
  1407. DispatchEffect( "GunshipMuzzleFlash", data );
  1408. }
  1409. //-----------------------------------------------------------------------------
  1410. // Purpose:
  1411. // Output : Returns true on success, false on failure.
  1412. //-----------------------------------------------------------------------------
  1413. bool CNPC_CombineGunship::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
  1414. {
  1415. bool fReturn = BaseClass::FVisible( pEntity, traceMask, ppBlocker );
  1416. if( m_fOmniscient )
  1417. {
  1418. if( !fReturn )
  1419. {
  1420. // Set this condition so that we can check it later and know that the
  1421. // enemy truly is occluded, but the gunship regards it as visible due
  1422. // to omniscience.
  1423. SetCondition( COND_ENEMY_OCCLUDED );
  1424. }
  1425. else
  1426. {
  1427. ClearCondition( COND_ENEMY_OCCLUDED );
  1428. }
  1429. return true;
  1430. }
  1431. if( fReturn )
  1432. {
  1433. ClearCondition( COND_ENEMY_OCCLUDED );
  1434. }
  1435. else
  1436. {
  1437. SetCondition( COND_ENEMY_OCCLUDED );
  1438. }
  1439. return fReturn;
  1440. }
  1441. //-----------------------------------------------------------------------------
  1442. // Purpose: Change the depth that gunship bullets can penetrate through solids
  1443. //-----------------------------------------------------------------------------
  1444. void CNPC_CombineGunship::InputSetPenetrationDepth( inputdata_t &inputdata )
  1445. {
  1446. m_flPenetrationDepth = inputdata.value.Float();
  1447. }
  1448. //-----------------------------------------------------------------------------
  1449. // Purpose: Allow the gunship to sense its enemy's location even when enemy
  1450. // is hidden from sight.
  1451. //-----------------------------------------------------------------------------
  1452. void CNPC_CombineGunship::InputOmniscientOn( inputdata_t &inputdata )
  1453. {
  1454. m_fOmniscient = true;
  1455. }
  1456. //-----------------------------------------------------------------------------
  1457. // Purpose: Returns the gunship to its default requirement that it see the
  1458. // enemy to know its current position
  1459. //-----------------------------------------------------------------------------
  1460. void CNPC_CombineGunship::InputOmniscientOff( inputdata_t &inputdata )
  1461. {
  1462. m_fOmniscient = false;
  1463. }
  1464. //-----------------------------------------------------------------------------
  1465. // Purpose: Allows the gunship to fire at an unseen enemy. The gunship is relying
  1466. // on hitting the target with bullets that will punch through the
  1467. // cover that the enemy is hiding behind. (Such as the Depot lighthouse)
  1468. //-----------------------------------------------------------------------------
  1469. void CNPC_CombineGunship::InputBlindfireOn( inputdata_t &inputdata )
  1470. {
  1471. m_fBlindfire = true;
  1472. }
  1473. //-----------------------------------------------------------------------------
  1474. // Purpose: Returns the gunship to default rules for attacking the enemy. The
  1475. // enemy must be seen to be fired at.
  1476. //-----------------------------------------------------------------------------
  1477. void CNPC_CombineGunship::InputBlindfireOff( inputdata_t &inputdata )
  1478. {
  1479. m_fBlindfire = false;
  1480. }
  1481. //-----------------------------------------------------------------------------
  1482. // Purpose: Set the gunship's paddles flailing!
  1483. //-----------------------------------------------------------------------------
  1484. void CNPC_CombineGunship::Event_Killed( const CTakeDamageInfo &info )
  1485. {
  1486. m_takedamage = DAMAGE_NO;
  1487. StopCannonBurst();
  1488. // Replace the rotor sound with broken engine sound.
  1489. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1490. controller.SoundDestroy( m_pRotorSound );
  1491. // BUGBUG: Isn't this sound just going to get stomped when the base class calls StopLoopingSounds() ??
  1492. CPASAttenuationFilter filter2( this );
  1493. m_pRotorSound = controller.SoundCreate( filter2, entindex(), "NPC_CombineGunship.DyingSound" );
  1494. controller.Play( m_pRotorSound, 1.0, 100 );
  1495. m_OnDeath.FireOutput( info.GetAttacker(), this );
  1496. SendOnKilledGameEvent( info );
  1497. BeginCrash();
  1498. // we deliberately do not call BaseClass::EventKilled
  1499. }
  1500. //-----------------------------------------------------------------------------
  1501. // Purpose:
  1502. //-----------------------------------------------------------------------------
  1503. void CNPC_CombineGunship::BeginCrash( void )
  1504. {
  1505. m_lifeState = LIFE_DYING;
  1506. StopGroundAttack( false );
  1507. // Increase our smoke trail
  1508. CreateSmokeTrail();
  1509. if ( m_pSmokeTrail )
  1510. {
  1511. m_pSmokeTrail->SetLifetime( -1 );
  1512. m_pSmokeTrail->m_StartSize = 64;
  1513. m_pSmokeTrail->m_EndSize = 128;
  1514. m_pSmokeTrail->m_Opacity = 0.5f;
  1515. }
  1516. if ( !FindNearestGunshipCrash() )
  1517. {
  1518. // We couldn't find a crash target, so just die right here.
  1519. BeginDestruct();
  1520. return;
  1521. }
  1522. }
  1523. //-----------------------------------------------------------------------------
  1524. // Purpose:
  1525. //-----------------------------------------------------------------------------
  1526. bool CNPC_CombineGunship::FindNearestGunshipCrash( void )
  1527. {
  1528. // Find the nearest crash point. If we find one, we'll try to fly to it and die.
  1529. // If we can't find one, we'll die right here.
  1530. bool bFoundAnyCrashTargets = false;
  1531. float flNearest = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH;
  1532. CTargetGunshipCrash *pNearest = NULL;
  1533. CBaseEntity *pEnt = NULL;
  1534. while( (pEnt = gEntList.FindEntityByClassname(pEnt, "info_target_gunshipcrash")) != NULL )
  1535. {
  1536. CTargetGunshipCrash *pCrashTarget = assert_cast<CTargetGunshipCrash*>(pEnt);
  1537. if ( pCrashTarget->IsDisabled() )
  1538. continue;
  1539. bFoundAnyCrashTargets = true;
  1540. float flDist = ( pEnt->WorldSpaceCenter() - WorldSpaceCenter() ).LengthSqr();
  1541. if( flDist < flNearest )
  1542. {
  1543. trace_t tr;
  1544. UTIL_TraceLine( WorldSpaceCenter(), pEnt->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
  1545. if( tr.fraction == 1.0 )
  1546. {
  1547. pNearest = pCrashTarget;
  1548. flNearest = flDist;
  1549. }
  1550. else if ( g_debug_gunship.GetInt() )
  1551. {
  1552. NDebugOverlay::Line( WorldSpaceCenter(), tr.endpos, 255,0,0, true, 99);
  1553. }
  1554. }
  1555. }
  1556. if ( !pNearest )
  1557. {
  1558. // If we found a gunship crash, but none near enough, claim we did find one, so that we
  1559. // don't blow up yet. This will give us 3 seconds to attempt to find one before dying.
  1560. if ( !m_hCrashTarget && bFoundAnyCrashTargets )
  1561. {
  1562. m_flNextGunshipCrashFind = gpGlobals->curtime + 0.5;
  1563. m_flEndDestructTime = gpGlobals->curtime + 3.0;
  1564. return true;
  1565. }
  1566. return false;
  1567. }
  1568. // Fly to the crash point and destruct there
  1569. m_hCrashTarget = pNearest;
  1570. m_flNextGunshipCrashFind = gpGlobals->curtime + 0.5;
  1571. m_flEndDestructTime = 0;
  1572. if ( g_debug_gunship.GetInt() )
  1573. {
  1574. NDebugOverlay::Line(GetAbsOrigin(), m_hCrashTarget->GetAbsOrigin(), 0,255,0, true, 0.5);
  1575. NDebugOverlay::Box( m_hCrashTarget->GetAbsOrigin(), -Vector(200,200,200), Vector(200,200,200), 0,255,0, 128, 0.5 );
  1576. }
  1577. return true;
  1578. }
  1579. //-----------------------------------------------------------------------------
  1580. // Purpose: I'm now ready to die. Create my ragdoll & hide myself.
  1581. //-----------------------------------------------------------------------------
  1582. void CNPC_CombineGunship::BeginDestruct( void )
  1583. {
  1584. m_flEndDestructTime = gpGlobals->curtime + 3.0;
  1585. // Clamp velocity
  1586. if( hl2_episodic.GetBool() && GetAbsVelocity().Length() > 700.0f )
  1587. {
  1588. Vector vecVelocity = GetAbsVelocity();
  1589. VectorNormalize( vecVelocity );
  1590. SetAbsVelocity( vecVelocity * 700.0f );
  1591. }
  1592. CTakeDamageInfo info;
  1593. info.SetDamage( 40000 );
  1594. CalculateExplosiveDamageForce( &info, GetAbsVelocity(), GetAbsOrigin() );
  1595. // Don't create a ragdoll if we're going to explode into gibs
  1596. if ( !m_hCrashTarget )
  1597. return;
  1598. // Switch to damaged skin
  1599. m_nSkin = 1;
  1600. if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
  1601. {
  1602. Chopper_BecomeChunks( this );
  1603. SetThink( &CNPC_CombineGunship::SUB_Remove );
  1604. SetNextThink( gpGlobals->curtime + 0.1f );
  1605. AddEffects( EF_NODRAW );
  1606. return;
  1607. }
  1608. // Create the ragdoll
  1609. m_hRagdoll = CreateServerRagdoll( this, 0, info, COLLISION_GROUP_NONE );
  1610. if ( !m_hRagdoll )
  1611. {
  1612. // Failed, just explode
  1613. SelfDestruct();
  1614. return;
  1615. }
  1616. m_hRagdoll->SetName( AllocPooledString( UTIL_VarArgs("%s_ragdoll", STRING(GetEntityName()) ) ) );
  1617. // Tell the smoke trail to follow the ragdoll
  1618. CreateSmokeTrail();
  1619. if ( m_pSmokeTrail )
  1620. {
  1621. // Force the smoke trail to stay on, and tell it to follow the ragdoll
  1622. m_pSmokeTrail->SetLifetime( -1 );
  1623. m_pSmokeTrail->FollowEntity( m_hRagdoll );
  1624. m_pSmokeTrail->m_StartSize = 64;
  1625. m_pSmokeTrail->m_EndSize = 128;
  1626. m_pSmokeTrail->m_Opacity = 0.5f;
  1627. }
  1628. /*
  1629. // ROBIN: Disabled this for now.
  1630. //
  1631. // Create the crashing controller and attach it to the ragdoll physics objects
  1632. m_pCrashingController = physenv->CreateMotionController( &m_crashCallback );
  1633. IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
  1634. int count = m_hRagdoll->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
  1635. for ( int i = 0; i < count; i++ )
  1636. {
  1637. m_pCrashingController->AttachObject( pList[i], false );
  1638. }
  1639. */
  1640. // Hide myself, because the ragdoll's now taken my place
  1641. AddEffects( EF_NODRAW );
  1642. AddSolidFlags( FSOLID_NOT_SOLID );
  1643. }
  1644. //-----------------------------------------------------------------------------
  1645. // Purpose: Create a smoke trail
  1646. //-----------------------------------------------------------------------------
  1647. void CNPC_CombineGunship::CreateSmokeTrail( void )
  1648. {
  1649. if ( m_pSmokeTrail )
  1650. return;
  1651. m_pSmokeTrail = SmokeTrail::CreateSmokeTrail();
  1652. if ( m_pSmokeTrail )
  1653. {
  1654. m_pSmokeTrail->m_SpawnRate = 48;
  1655. m_pSmokeTrail->m_ParticleLifetime = 2.5f;
  1656. m_pSmokeTrail->m_StartColor.Init( 0.25f, 0.25f, 0.25f );
  1657. m_pSmokeTrail->m_EndColor.Init( 0.0, 0.0, 0.0 );
  1658. m_pSmokeTrail->m_StartSize = 24;
  1659. m_pSmokeTrail->m_EndSize = 128;
  1660. m_pSmokeTrail->m_SpawnRadius = 4;
  1661. m_pSmokeTrail->m_MinSpeed = 8;
  1662. m_pSmokeTrail->m_MaxSpeed = 64;
  1663. m_pSmokeTrail->m_Opacity = 0.2f;
  1664. m_pSmokeTrail->SetLifetime( -1 );
  1665. }
  1666. }
  1667. //-----------------------------------------------------------------------------
  1668. // Purpose:
  1669. //-----------------------------------------------------------------------------
  1670. void CNPC_CombineGunship::ApplyGeneralDrag( void )
  1671. {
  1672. Vector vecNewVelocity = GetAbsVelocity();
  1673. // See if we need to stop more quickly
  1674. if ( m_bIsGroundAttacking )
  1675. {
  1676. vecNewVelocity *= 0.95f;
  1677. }
  1678. else
  1679. {
  1680. vecNewVelocity *= 0.995;
  1681. }
  1682. SetAbsVelocity( vecNewVelocity );
  1683. }
  1684. //-----------------------------------------------------------------------------
  1685. // Purpose:
  1686. //-----------------------------------------------------------------------------
  1687. void CNPC_CombineGunship::Flight( void )
  1688. {
  1689. if( GetFlags() & FL_ONGROUND )
  1690. {
  1691. //This would be really bad.
  1692. SetGroundEntity( NULL );
  1693. }
  1694. if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_PATH )
  1695. {
  1696. NDebugOverlay::Line(GetLocalOrigin(), GetDesiredPosition(), 0,0,255, true, 0.1);
  1697. }
  1698. // calc desired acceleration
  1699. float dt = 1.0f;
  1700. Vector accel;
  1701. float accelRate = GUNSHIP_ACCEL_RATE;
  1702. float maxSpeed = GetMaxSpeed();
  1703. if ( m_lifeState == LIFE_DYING && m_hCrashTarget != NULL )
  1704. {
  1705. // Gunship can fly faster to the place where it's supposed to crash, but
  1706. // maintain normal speeds if we haven't found a place to crash.
  1707. accelRate *= 2.0;
  1708. maxSpeed *= 4.0;
  1709. }
  1710. float flCurrentSpeed = GetAbsVelocity().Length();
  1711. float flDist = MIN( flCurrentSpeed + accelRate, maxSpeed );
  1712. Vector deltaPos;
  1713. if ( m_lifeState == LIFE_DYING || m_hGroundAttackTarget )
  1714. {
  1715. // Move directly to the target point
  1716. deltaPos = GetDesiredPosition();
  1717. }
  1718. else
  1719. {
  1720. ComputeActualTargetPosition( flDist, dt, 0.0f, &deltaPos );
  1721. }
  1722. deltaPos -= GetAbsOrigin();
  1723. // calc goal linear accel to hit deltaPos in dt time.
  1724. accel.x = 2.0 * (deltaPos.x - GetAbsVelocity().x * dt) / (dt * dt);
  1725. accel.y = 2.0 * (deltaPos.y - GetAbsVelocity().y * dt) / (dt * dt);
  1726. accel.z = 2.0 * (deltaPos.z - GetAbsVelocity().z * dt + 0.5 * 384 * dt * dt) / (dt * dt);
  1727. float flDistFromPath = 0.0f;
  1728. Vector vecPoint, vecDelta;
  1729. if ( m_lifeState != LIFE_DYING && IsOnPathTrack() )
  1730. {
  1731. // Also, add in a little force to get us closer to our current line segment if we can
  1732. ClosestPointToCurrentPath( &vecPoint );
  1733. VectorSubtract( vecPoint, GetAbsOrigin(), vecDelta );
  1734. flDistFromPath = VectorNormalize( vecDelta );
  1735. if ( flDistFromPath > GUNSHIP_OUTER_NAV_DIST )
  1736. {
  1737. // Strongly constrain to an n unit pipe around the current path
  1738. // by damping out all impulse forces that would push us further from the pipe
  1739. float flAmount = (flDistFromPath - GUNSHIP_OUTER_NAV_DIST) / 200.0f;
  1740. flAmount = clamp( flAmount, 0, 1 );
  1741. VectorMA( accel, flAmount * 200.0f, vecDelta, accel );
  1742. }
  1743. }
  1744. Vector vecAvoidForce;
  1745. CAvoidSphere::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
  1746. accel += vecAvoidForce;
  1747. CAvoidBox::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
  1748. accel += vecAvoidForce;
  1749. if ( m_lifeState != LIFE_DYING || m_hCrashTarget == NULL )
  1750. {
  1751. // don't fall faster than 0.2G or climb faster than 2G
  1752. accel.z = clamp( accel.z, 384 * 0.2, 384 * 2.0 );
  1753. }
  1754. Vector forward, right, up;
  1755. GetVectors( &forward, &right, &up );
  1756. Vector goalUp = accel;
  1757. VectorNormalize( goalUp );
  1758. // calc goal orientation to hit linear accel forces
  1759. float goalPitch = RAD2DEG( asin( DotProduct( forward, goalUp ) ) );
  1760. float goalYaw = UTIL_VecToYaw( m_vecDesiredFaceDir );
  1761. float goalRoll = RAD2DEG( asin( DotProduct( right, goalUp ) ) );
  1762. // clamp goal orientations
  1763. goalPitch = clamp( goalPitch, -45, 60 );
  1764. goalRoll = clamp( goalRoll, -45, 45 );
  1765. // calc angular accel needed to hit goal pitch in dt time.
  1766. dt = 0.6;
  1767. QAngle goalAngAccel;
  1768. goalAngAccel.x = 2.0 * (AngleDiff( goalPitch, AngleNormalize( GetLocalAngles().x ) ) - GetLocalAngularVelocity().x * dt) / (dt * dt);
  1769. goalAngAccel.y = 2.0 * (AngleDiff( goalYaw, AngleNormalize( GetLocalAngles().y ) ) - GetLocalAngularVelocity().y * dt) / (dt * dt);
  1770. goalAngAccel.z = 2.0 * (AngleDiff( goalRoll, AngleNormalize( GetLocalAngles().z ) ) - GetLocalAngularVelocity().z * dt) / (dt * dt);
  1771. goalAngAccel.x = clamp( goalAngAccel.x, -300, 300 );
  1772. //goalAngAccel.y = clamp( goalAngAccel.y, -60, 60 );
  1773. goalAngAccel.y = clamp( goalAngAccel.y, -120, 120 );
  1774. goalAngAccel.z = clamp( goalAngAccel.z, -300, 300 );
  1775. // limit angular accel changes to similate mechanical response times
  1776. dt = 0.1;
  1777. QAngle angAccelAccel;
  1778. angAccelAccel.x = (goalAngAccel.x - m_vecAngAcceleration.x) / dt;
  1779. angAccelAccel.y = (goalAngAccel.y - m_vecAngAcceleration.y) / dt;
  1780. angAccelAccel.z = (goalAngAccel.z - m_vecAngAcceleration.z) / dt;
  1781. angAccelAccel.x = clamp( angAccelAccel.x, -1000, 1000 );
  1782. angAccelAccel.y = clamp( angAccelAccel.y, -1000, 1000 );
  1783. angAccelAccel.z = clamp( angAccelAccel.z, -1000, 1000 );
  1784. m_vecAngAcceleration += angAccelAccel * 0.1;
  1785. // DevMsg( "pitch %6.1f (%6.1f:%6.1f) ", goalPitch, GetLocalAngles().x, m_vecAngVelocity.x );
  1786. // DevMsg( "roll %6.1f (%6.1f:%6.1f) : ", goalRoll, GetLocalAngles().z, m_vecAngVelocity.z );
  1787. // DevMsg( "%6.1f %6.1f %6.1f : ", goalAngAccel.x, goalAngAccel.y, goalAngAccel.z );
  1788. // DevMsg( "%6.0f %6.0f %6.0f\n", angAccelAccel.x, angAccelAccel.y, angAccelAccel.z );
  1789. ApplySidewaysDrag( right );
  1790. ApplyGeneralDrag();
  1791. QAngle angVel = GetLocalAngularVelocity();
  1792. angVel += m_vecAngAcceleration * 0.1;
  1793. //angVel.y = clamp( angVel.y, -60, 60 );
  1794. //angVel.y = clamp( angVel.y, -120, 120 );
  1795. angVel.y = clamp( angVel.y, -120, 120 );
  1796. SetLocalAngularVelocity( angVel );
  1797. m_flForce = m_flForce * 0.8 + (accel.z + fabs( accel.x ) * 0.1 + fabs( accel.y ) * 0.1) * 0.1 * 0.2;
  1798. Vector vecImpulse = m_flForce * up;
  1799. if ( !m_hCrashTarget && m_lifeState == LIFE_DYING && !hl2_episodic.GetBool() )
  1800. {
  1801. // Force gunship to the ground if it doesn't have a specific place to crash.
  1802. // EXCEPT In episodic, where forcing it to the ground means it crashes where the player can't see (attic showdown) (sjb)
  1803. vecImpulse.z = -10;
  1804. }
  1805. else
  1806. {
  1807. vecImpulse.z -= 38.4; // 32ft/sec
  1808. }
  1809. // Find our current velocity
  1810. Vector vecVelDir = GetAbsVelocity();
  1811. VectorNormalize( vecVelDir );
  1812. if ( flDistFromPath > GUNSHIP_INNER_NAV_DIST )
  1813. {
  1814. // Strongly constrain to an n unit pipe around the current path
  1815. // by damping out all impulse forces that would push us further from the pipe
  1816. float flDot = DotProduct( vecImpulse, vecDelta );
  1817. if ( flDot < 0.0f )
  1818. {
  1819. VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
  1820. }
  1821. // Also apply an extra impulse to compensate for the current velocity
  1822. flDot = DotProduct( vecVelDir, vecDelta );
  1823. if ( flDot < 0.0f )
  1824. {
  1825. VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
  1826. }
  1827. }
  1828. // Find our acceleration direction
  1829. Vector vecAccelDir = vecImpulse;
  1830. VectorNormalize( vecAccelDir );
  1831. // Level out our plane of movement
  1832. vecAccelDir.z = 0.0f;
  1833. vecVelDir.z = 0.0f;
  1834. forward.z = 0.0f;
  1835. right.z = 0.0f;
  1836. // Find out how "fast" we're moving in relation to facing and acceleration
  1837. float speed = m_flForce * DotProduct( vecVelDir, vecAccelDir );// * DotProduct( forward, vecVelDir );
  1838. // Apply the acceleration blend to the fins
  1839. float finAccelBlend = SimpleSplineRemapVal( speed, -60, 60, -1, 1 );
  1840. float curFinAccel = GetPoseParameter( m_poseFin_Accel );
  1841. curFinAccel = UTIL_Approach( finAccelBlend, curFinAccel, 0.5f );
  1842. SetPoseParameter( m_poseFin_Accel, curFinAccel );
  1843. speed = m_flForce * DotProduct( vecVelDir, right );
  1844. // Apply the spin sway to the fins
  1845. float finSwayBlend = SimpleSplineRemapVal( speed, -60, 60, -1, 1 );
  1846. float curFinSway = GetPoseParameter( m_poseFin_Sway );
  1847. curFinSway = UTIL_Approach( finSwayBlend, curFinSway, 0.5f );
  1848. SetPoseParameter( m_poseFin_Sway, curFinSway );
  1849. if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_PATH )
  1850. {
  1851. NDebugOverlay::Line(GetLocalOrigin(), GetLocalOrigin() + vecImpulse, 255,0,0, true, 0.1);
  1852. }
  1853. // Add in our velocity pulse for this frame
  1854. ApplyAbsVelocityImpulse( vecImpulse );
  1855. }
  1856. //------------------------------------------------------------------------------
  1857. // Updates the facing direction
  1858. //------------------------------------------------------------------------------
  1859. void CNPC_CombineGunship::UpdateFacingDirection( void )
  1860. {
  1861. if ( GetEnemy() )
  1862. {
  1863. if ( !IsCrashing() && m_flLastSeen + 5 > gpGlobals->curtime )
  1864. {
  1865. // If we've seen the target recently, face the target.
  1866. //Msg( "Facing Target \n" );
  1867. m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
  1868. }
  1869. else
  1870. {
  1871. // Remain facing the way you were facing...
  1872. }
  1873. }
  1874. else
  1875. {
  1876. // Face our desired position.
  1877. if ( GetDesiredPosition().DistToSqr( GetAbsOrigin() ) > 1 )
  1878. {
  1879. m_vecDesiredFaceDir = GetDesiredPosition() - GetAbsOrigin();
  1880. }
  1881. else
  1882. {
  1883. GetVectors( &m_vecDesiredFaceDir, NULL, NULL );
  1884. }
  1885. }
  1886. VectorNormalize( m_vecDesiredFaceDir );
  1887. }
  1888. //------------------------------------------------------------------------------
  1889. // Purpose : Fire up the Gunships 'second' rotor sound. The Search sound.
  1890. // Input :
  1891. // Output :
  1892. //------------------------------------------------------------------------------
  1893. void CNPC_CombineGunship::InitializeRotorSound( void )
  1894. {
  1895. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1896. CPASAttenuationFilter filter( this );
  1897. m_pCannonSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.CannonSound" );
  1898. m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.RotorSound" );
  1899. m_pAirExhaustSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.ExhaustSound" );
  1900. m_pAirBlastSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.RotorBlastSound" );
  1901. controller.Play( m_pCannonSound, 0.0, 100 );
  1902. controller.Play( m_pAirExhaustSound, 0.0, 100 );
  1903. controller.Play( m_pAirBlastSound, 0.0, 100 );
  1904. BaseClass::InitializeRotorSound();
  1905. }
  1906. //------------------------------------------------------------------------------
  1907. // Purpose :
  1908. // Input :
  1909. // Output :
  1910. //------------------------------------------------------------------------------
  1911. void CNPC_CombineGunship::UpdateRotorSoundPitch( int iPitch )
  1912. {
  1913. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1914. // Apply the pitch to both sounds.
  1915. controller.SoundChangePitch( m_pAirExhaustSound, iPitch, 0.1 );
  1916. // FIXME: Doesn't work in multiplayer
  1917. CBaseEntity *pPlayer = UTIL_PlayerByIndex(1);
  1918. if (pPlayer)
  1919. {
  1920. Vector pos;
  1921. Vector up;
  1922. GetAttachment( "rotor", pos, NULL, NULL, &up );
  1923. float flDistance = (pPlayer->WorldSpaceCenter() - pos).Length2DSqr();
  1924. // Fade in exhaust when we're far from the player
  1925. float flVolume = clamp( RemapVal( flDistance, (900*900), (1800*1800), 1, 0 ), 0, 1 );
  1926. controller.SoundChangeVolume( m_pAirExhaustSound, flVolume * GetRotorVolume(), 0.1 );
  1927. // Fade in the blast when it's close to the player (in 2D)
  1928. flVolume = clamp( RemapVal( flDistance, (600*600), (700*700), 1, 0 ), 0, 1 );
  1929. controller.SoundChangeVolume( m_pAirBlastSound, flVolume * GetRotorVolume(), 0.1 );
  1930. }
  1931. BaseClass::UpdateRotorSoundPitch( iPitch );
  1932. }
  1933. //-----------------------------------------------------------------------------
  1934. // Purpose:
  1935. // Input :
  1936. // Output :
  1937. //-----------------------------------------------------------------------------
  1938. void CNPC_CombineGunship::ApplySidewaysDrag( const Vector &vecRight )
  1939. {
  1940. Vector vecVelocity = GetAbsVelocity();
  1941. if( m_lifeState == LIFE_ALIVE )
  1942. {
  1943. vecVelocity.x *= (1.0 - fabs( vecRight.x ) * 0.04);
  1944. vecVelocity.y *= (1.0 - fabs( vecRight.y ) * 0.04);
  1945. vecVelocity.z *= (1.0 - fabs( vecRight.z ) * 0.04);
  1946. }
  1947. else
  1948. {
  1949. vecVelocity.x *= (1.0 - fabs( vecRight.x ) * 0.03);
  1950. vecVelocity.y *= (1.0 - fabs( vecRight.y ) * 0.03);
  1951. vecVelocity.z *= (1.0 - fabs( vecRight.z ) * 0.09);
  1952. }
  1953. SetAbsVelocity( vecVelocity );
  1954. }
  1955. //------------------------------------------------------------------------------
  1956. // Purpose: Explode the gunship.
  1957. //------------------------------------------------------------------------------
  1958. void CNPC_CombineGunship::SelfDestruct( void )
  1959. {
  1960. SetThink( NULL );
  1961. m_lifeState = LIFE_DEAD;
  1962. StopLoopingSounds();
  1963. StopCannonBurst();
  1964. Vector vecVelocity = GetAbsVelocity();
  1965. vecVelocity.z = 0.0; // stop falling.
  1966. SetAbsVelocity( vecVelocity );
  1967. CBaseEntity *pBreakEnt = this;
  1968. // If we've ragdolled, play the explosions on the ragdoll instead
  1969. Vector vecOrigin;
  1970. if ( m_hRagdoll )
  1971. {
  1972. m_hRagdoll->EmitSound( "NPC_CombineGunship.Explode" );
  1973. vecOrigin = m_hRagdoll->GetAbsOrigin();
  1974. pBreakEnt = m_hRagdoll;
  1975. }
  1976. else
  1977. {
  1978. EmitSound( "NPC_CombineGunship.Explode" );
  1979. vecOrigin = GetAbsOrigin();
  1980. }
  1981. // Create some explosions on the gunship body
  1982. Vector vecDelta;
  1983. for( int i = 0 ; i < 6 ; i++ )
  1984. {
  1985. vecDelta = RandomVector( -200,200 );
  1986. ExplosionCreate( vecOrigin + vecDelta, QAngle( -90, 0, 0 ), this, 10, 10, false );
  1987. }
  1988. AR2Explosion *pExplosion = AR2Explosion::CreateAR2Explosion( vecOrigin );
  1989. if ( pExplosion )
  1990. {
  1991. pExplosion->SetLifetime( 10 );
  1992. }
  1993. // If we don't have a crash target, explode into chunks
  1994. if ( !m_hCrashTarget )
  1995. {
  1996. Vector angVelocity;
  1997. QAngleToAngularImpulse( pBreakEnt->GetLocalAngularVelocity(), angVelocity );
  1998. PropBreakableCreateAll( pBreakEnt->GetModelIndex(), pBreakEnt->VPhysicsGetObject(), pBreakEnt->GetAbsOrigin(), pBreakEnt->GetAbsAngles(), pBreakEnt->GetAbsVelocity(), angVelocity, 1.0, 800, COLLISION_GROUP_NPC, pBreakEnt );
  1999. // Throw out some small chunks too
  2000. CPVSFilter filter( GetAbsOrigin() );
  2001. for ( int i = 0; i < 20; i++ )
  2002. {
  2003. Vector gibVelocity = RandomVector(-100,100) * 10;
  2004. int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );
  2005. te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, Vector(40,40,40), gibVelocity, iModelIndex, 400, 1, 2.5, BREAK_METAL );
  2006. }
  2007. if ( m_hRagdoll )
  2008. {
  2009. UTIL_Remove( m_hRagdoll );
  2010. }
  2011. }
  2012. else
  2013. {
  2014. if ( m_pSmokeTrail )
  2015. {
  2016. // If we have a ragdoll, let it smoke for a few more seconds
  2017. if ( m_hRagdoll )
  2018. {
  2019. m_pSmokeTrail->SetLifetime(3.0f);
  2020. }
  2021. else
  2022. {
  2023. m_pSmokeTrail->SetLifetime(0.1f);
  2024. }
  2025. m_pSmokeTrail = NULL;
  2026. }
  2027. }
  2028. UTIL_Remove( this );
  2029. // Record this so a nearby citizen can respond.
  2030. if ( GetCitizenResponse() )
  2031. {
  2032. GetCitizenResponse()->AddResponseTrigger( CR_PLAYER_KILLED_GUNSHIP );
  2033. }
  2034. #ifdef HL2_EPISODIC
  2035. NPCEventResponse()->TriggerEvent( "TLK_CITIZEN_RESPONSE_KILLED_GUNSHIP", false, false );
  2036. #endif
  2037. }
  2038. //------------------------------------------------------------------------------
  2039. // Purpose : Explode the gunship.
  2040. // Input :
  2041. // Output :
  2042. //------------------------------------------------------------------------------
  2043. void CNPC_CombineGunship::InputSelfDestruct( inputdata_t &inputdata )
  2044. {
  2045. BeginCrash();
  2046. }
  2047. //------------------------------------------------------------------------------
  2048. // Purpose : Shrink the gunship's bbox so that it fits in docking bays
  2049. // Input :
  2050. // Output :
  2051. //------------------------------------------------------------------------------
  2052. void CNPC_CombineGunship::InputSetDockingBBox( inputdata_t &inputdata )
  2053. {
  2054. Vector vecSize( 32, 32, 32 );
  2055. UTIL_SetSize( this, vecSize * -1, vecSize );
  2056. }
  2057. //------------------------------------------------------------------------------
  2058. // Purpose : Set the gunship BBox to normal size
  2059. // Input :
  2060. // Output :
  2061. //------------------------------------------------------------------------------
  2062. void CNPC_CombineGunship::InputSetNormalBBox( inputdata_t &inputdata )
  2063. {
  2064. Vector vecBBMin, vecBBMax;
  2065. ExtractBbox( SelectHeaviestSequence( ACT_GUNSHIP_PATROL ), vecBBMin, vecBBMax );
  2066. // Trim the bounding box a bit. It's huge.
  2067. #define GUNSHIP_TRIM_BOX 38
  2068. vecBBMin.x += GUNSHIP_TRIM_BOX;
  2069. vecBBMax.x -= GUNSHIP_TRIM_BOX;
  2070. vecBBMin.y += GUNSHIP_TRIM_BOX;
  2071. vecBBMax.y -= GUNSHIP_TRIM_BOX;
  2072. UTIL_SetSize( this, vecBBMin, vecBBMax );
  2073. }
  2074. //-----------------------------------------------------------------------------
  2075. // Purpose:
  2076. // Input : &inputdata -
  2077. //-----------------------------------------------------------------------------
  2078. void CNPC_CombineGunship::InputEnableGroundAttack( inputdata_t &inputdata )
  2079. {
  2080. m_bCanGroundAttack = true;
  2081. }
  2082. //-----------------------------------------------------------------------------
  2083. // Purpose:
  2084. // Input : &inputdata -
  2085. //-----------------------------------------------------------------------------
  2086. void CNPC_CombineGunship::InputDisableGroundAttack( inputdata_t &inputdata )
  2087. {
  2088. m_bCanGroundAttack = false;
  2089. }
  2090. //-----------------------------------------------------------------------------
  2091. // Purpose:
  2092. // Input : &inputdata -
  2093. //-----------------------------------------------------------------------------
  2094. void CNPC_CombineGunship::InputDoGroundAttack( inputdata_t &inputdata )
  2095. {
  2096. // Was a target node specified?
  2097. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID(), NULL, inputdata.pActivator, inputdata.pCaller );
  2098. if ( pEntity )
  2099. {
  2100. // Mapmaker wants us to ground attack a specific target
  2101. m_hGroundAttackTarget = pEntity;
  2102. }
  2103. else
  2104. {
  2105. StartGroundAttack();
  2106. }
  2107. }
  2108. //-----------------------------------------------------------------------------
  2109. // Purpose:
  2110. // Input : &vGunPosition -
  2111. //-----------------------------------------------------------------------------
  2112. void CNPC_CombineGunship::UpdateEnemyTarget( void )
  2113. {
  2114. Vector vGunPosition;
  2115. GetAttachment( "muzzle", vGunPosition );
  2116. // Follow mode
  2117. Vector enemyPos;
  2118. bool bTargettingPlayer;
  2119. if ( GetEnemy() != NULL )
  2120. {
  2121. CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
  2122. if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
  2123. {
  2124. // Update against a driving target
  2125. enemyPos = GetEnemy()->WorldSpaceCenter();
  2126. }
  2127. else
  2128. {
  2129. enemyPos = GetEnemy()->EyePosition();
  2130. }
  2131. bTargettingPlayer = GetEnemy()->IsPlayer();
  2132. }
  2133. else
  2134. {
  2135. enemyPos = m_vecAttackPosition;
  2136. bTargettingPlayer = false;
  2137. }
  2138. // Direction towards the enemy
  2139. Vector targetDir = enemyPos - m_vecAttackPosition;
  2140. VectorNormalize( targetDir );
  2141. // Direction from the gunship to the enemy
  2142. Vector enemyDir = enemyPos - vGunPosition;
  2143. VectorNormalize( enemyDir );
  2144. float lastSpeed = VectorNormalize( m_vecAttackVelocity );
  2145. QAngle chaseAngles, lastChaseAngles;
  2146. VectorAngles( targetDir, chaseAngles );
  2147. VectorAngles( m_vecAttackVelocity, lastChaseAngles );
  2148. // Debug info
  2149. if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
  2150. {
  2151. // Final position
  2152. NDebugOverlay::Cross3D( m_vecAttackPosition, -Vector(2,2,2), Vector(2,2,2), 0, 0, 255, true, 4.0f );
  2153. }
  2154. float yawDiff = UTIL_AngleDiff( lastChaseAngles[YAW], chaseAngles[YAW] );
  2155. int maxYaw;
  2156. if ( bTargettingPlayer )
  2157. {
  2158. maxYaw = 6;
  2159. }
  2160. else
  2161. {
  2162. maxYaw = 30;
  2163. }
  2164. yawDiff = clamp( yawDiff, -maxYaw, maxYaw );
  2165. chaseAngles[PITCH] = 0.0f;
  2166. chaseAngles[ROLL] = 0.0f;
  2167. bool bMaxHits = ( m_iBurstHits >= GUNSHIP_MAX_HITS_PER_BURST || (GetEnemy() && !GetEnemy()->IsAlive()) );
  2168. if ( bMaxHits )
  2169. {
  2170. // We've hit our target. Stop chasing, and return to max speed.
  2171. chaseAngles[YAW] = lastChaseAngles[YAW];
  2172. lastSpeed = BASE_STITCH_VELOCITY;
  2173. }
  2174. else
  2175. {
  2176. // Move towards the target yaw
  2177. chaseAngles[YAW] = UTIL_AngleMod( lastChaseAngles[YAW] - yawDiff );
  2178. }
  2179. // If we've hit the target already, or we're not close enough to it, then just stitch along
  2180. if ( bMaxHits || ( m_vecAttackPosition - enemyPos ).LengthSqr() > (64 * 64) )
  2181. {
  2182. AngleVectors( chaseAngles, &targetDir );
  2183. // Update our new velocity
  2184. m_vecAttackVelocity = targetDir * lastSpeed;
  2185. if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
  2186. {
  2187. NDebugOverlay::Line( m_vecAttackPosition, m_vecAttackPosition + (m_vecAttackVelocity * 0.1), 255, 0, 0, true, 4.0f );
  2188. }
  2189. // Move along that velocity for this step in time
  2190. m_vecAttackPosition += ( m_vecAttackVelocity * 0.1f );
  2191. m_vecAttackPosition.z = enemyPos.z;
  2192. }
  2193. else
  2194. {
  2195. // Otherwise always continue to hit an NPC when close enough
  2196. m_vecAttackPosition = enemyPos;
  2197. }
  2198. }
  2199. //------------------------------------------------------------------------------
  2200. // Purpose: Utility function to aim the helicopter gun at the direction
  2201. //------------------------------------------------------------------------------
  2202. bool CNPC_CombineGunship::PoseGunTowardTargetDirection( const Vector &vTargetDir )
  2203. {
  2204. Vector vecOut;
  2205. VectorIRotate( vTargetDir, EntityToWorldTransform(), vecOut );
  2206. QAngle angles;
  2207. VectorAngles(vecOut, angles);
  2208. angles.y = AngleNormalize( angles.y );
  2209. angles.x = AngleNormalize( angles.x );
  2210. if (angles.x > m_angGun.x)
  2211. {
  2212. m_angGun.x = MIN( angles.x, m_angGun.x + 12 );
  2213. }
  2214. if (angles.x < m_angGun.x)
  2215. {
  2216. m_angGun.x = MAX( angles.x, m_angGun.x - 12 );
  2217. }
  2218. if (angles.y > m_angGun.y)
  2219. {
  2220. m_angGun.y = MIN( angles.y, m_angGun.y + 12 );
  2221. }
  2222. if (angles.y < m_angGun.y)
  2223. {
  2224. m_angGun.y = MAX( angles.y, m_angGun.y - 12 );
  2225. }
  2226. SetPoseParameter( m_poseWeapon_Pitch, -m_angGun.x );
  2227. SetPoseParameter( m_poseWeapon_Yaw, m_angGun.y );
  2228. return true;
  2229. }
  2230. //-----------------------------------------------------------------------------
  2231. // Purpose:
  2232. // Output : Vector
  2233. //-----------------------------------------------------------------------------
  2234. Vector CNPC_CombineGunship::GetMissileTarget( void )
  2235. {
  2236. return GetEnemy()->GetAbsOrigin();
  2237. }
  2238. //------------------------------------------------------------------------------
  2239. // Purpose : Get the target position for the enemy- the position we fire upon.
  2240. // this is often modified by m_flAttackOffset to provide the 'stitching'
  2241. // behavior that's so popular with the kids these days (sjb)
  2242. //
  2243. // Input : vGunPosition - location of gunship's muzzle
  2244. // : pTarget = vector to paste enemy target into.
  2245. // Output :
  2246. //------------------------------------------------------------------------------
  2247. Vector CNPC_CombineGunship::GetEnemyTarget( void )
  2248. {
  2249. // Make sure we have an enemy
  2250. if ( GetEnemy() == NULL )
  2251. return m_vecAttackPosition;
  2252. // If we're locked onto a missile, use special code to try and destroy it
  2253. if ( IsTargettingMissile() )
  2254. return GetMissileTarget();
  2255. return m_vecAttackPosition;
  2256. }
  2257. //-----------------------------------------------------------------------------
  2258. // Purpose:
  2259. // Input : &tr -
  2260. //-----------------------------------------------------------------------------
  2261. void CNPC_CombineGunship::DoImpactEffect( trace_t &tr, int nDamageType )
  2262. {
  2263. UTIL_ImpactTrace( &tr, nDamageType, "ImpactGunship" );
  2264. // These glow effects don't sort properly, so they're cut for E3 2003 (sjb)
  2265. #if 0
  2266. CEffectData data;
  2267. data.m_vOrigin = tr.endpos;
  2268. data.m_vNormal = vec3_origin;
  2269. data.m_vAngles = vec3_angle;
  2270. DispatchEffect( "GunshipImpact", data );
  2271. #endif
  2272. }
  2273. //-----------------------------------------------------------------------------
  2274. // Purpose: Make the gunship's signature blue tracer!
  2275. // Input : &vecTracerSrc -
  2276. // &tr -
  2277. // iTracerType -
  2278. //-----------------------------------------------------------------------------
  2279. void CNPC_CombineGunship::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
  2280. {
  2281. switch ( iTracerType )
  2282. {
  2283. case TRACER_LINE:
  2284. {
  2285. float flTracerDist;
  2286. Vector vecDir;
  2287. Vector vecEndPos;
  2288. vecDir = tr.endpos - vecTracerSrc;
  2289. flTracerDist = VectorNormalize( vecDir );
  2290. UTIL_Tracer( vecTracerSrc, tr.endpos, 0, TRACER_DONT_USE_ATTACHMENT, 8000, true, "GunshipTracer" );
  2291. }
  2292. break;
  2293. default:
  2294. BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
  2295. break;
  2296. }
  2297. }
  2298. //-----------------------------------------------------------------------------
  2299. // Purpose:
  2300. // Input : &info -
  2301. // &vecDir -
  2302. // *ptr -
  2303. // Output : int
  2304. //-----------------------------------------------------------------------------
  2305. void CNPC_CombineGunship::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  2306. {
  2307. // Reflect bullets
  2308. if ( info.GetDamageType() & DMG_BULLET )
  2309. {
  2310. if ( random->RandomInt( 0, 2 ) == 0 )
  2311. {
  2312. Vector vecRicochetDir = vecDir * -1;
  2313. vecRicochetDir.x += random->RandomFloat( -0.5, 0.5 );
  2314. vecRicochetDir.y += random->RandomFloat( -0.5, 0.5 );
  2315. vecRicochetDir.z += random->RandomFloat( -0.5, 0.5 );
  2316. VectorNormalize( vecRicochetDir );
  2317. Vector end = ptr->endpos + vecRicochetDir * 1024;
  2318. UTIL_Tracer( ptr->endpos, end, entindex(), TRACER_DONT_USE_ATTACHMENT, 3500 );
  2319. }
  2320. // If this is from a player, record it so a nearby citizen can respond.
  2321. if ( info.GetAttacker()->IsPlayer() )
  2322. {
  2323. if ( GetCitizenResponse() )
  2324. {
  2325. GetCitizenResponse()->AddResponseTrigger( CR_PLAYER_SHOT_GUNSHIP );
  2326. }
  2327. #ifdef HL2_EPISODIC
  2328. NPCEventResponse()->TriggerEvent( "TLK_CITIZEN_RESPONSE_SHOT_GUNSHIP", false, false );
  2329. #endif
  2330. }
  2331. return;
  2332. }
  2333. BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
  2334. }
  2335. //-----------------------------------------------------------------------------
  2336. // Purpose: This is necessary to ensure that the game doesn't break if a mapmaker has outputs that
  2337. // must be fired on gunships, and the player switches skill levels
  2338. // midway through a gunship battle.
  2339. // Input : iDamageNumber -
  2340. //-----------------------------------------------------------------------------
  2341. void CNPC_CombineGunship::FireDamageOutputsUpto( int iDamageNumber )
  2342. {
  2343. for ( int i = 0; i <= iDamageNumber; i++ )
  2344. {
  2345. if ( !m_bDamageOutputsFired[i] )
  2346. {
  2347. m_bDamageOutputsFired[i] = true;
  2348. switch ( i )
  2349. {
  2350. case 0:
  2351. //Msg("Fired first\n");
  2352. m_OnFirstDamage.FireOutput( this, this );
  2353. break;
  2354. case 1:
  2355. //Msg("Fired second\n");
  2356. m_OnSecondDamage.FireOutput( this, this );
  2357. break;
  2358. case 2:
  2359. //Msg("Fired third\n");
  2360. m_OnThirdDamage.FireOutput( this, this );
  2361. break;
  2362. case 3:
  2363. //Msg("Fired fourth\n");
  2364. m_OnFourthDamage.FireOutput( this, this );
  2365. break;
  2366. }
  2367. }
  2368. }
  2369. }
  2370. //------------------------------------------------------------------------------
  2371. // Damage filtering
  2372. //------------------------------------------------------------------------------
  2373. int CNPC_CombineGunship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
  2374. {
  2375. // Allow npc_kill to kill me
  2376. if ( inputInfo.GetDamageType() != DMG_GENERIC )
  2377. {
  2378. // Ignore mundane bullet damage.
  2379. if ( ( inputInfo.GetDamageType() & DMG_BLAST ) == false )
  2380. return 0;
  2381. // Ignore blasts less than this amount
  2382. if ( inputInfo.GetDamage() < GUNSHIP_MIN_DAMAGE_THRESHOLD )
  2383. return 0;
  2384. }
  2385. // Only take blast damage
  2386. CTakeDamageInfo info = inputInfo;
  2387. // Make a pain sound
  2388. if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
  2389. {
  2390. EmitSound( "NPC_CombineGunship.Pain" );
  2391. }
  2392. Vector damageDir = info.GetDamageForce();
  2393. VectorNormalize( damageDir );
  2394. // Don't get knocked around if I'm ground attacking
  2395. if ( !m_bIsGroundAttacking )
  2396. {
  2397. ApplyAbsVelocityImpulse( damageDir * 200.0f );
  2398. }
  2399. if ( m_bInvulnerable == false )
  2400. {
  2401. // Take a percentage of our health away
  2402. // Adjust health for damage
  2403. int iHealthIncrements = sk_gunship_health_increments.GetInt();
  2404. if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
  2405. {
  2406. iHealthIncrements = ceil( iHealthIncrements * 0.5 );
  2407. }
  2408. else if ( g_pGameRules->IsSkillLevel( SKILL_HARD ) )
  2409. {
  2410. iHealthIncrements = floor( iHealthIncrements * 1.5 );
  2411. }
  2412. info.SetDamage( ( GetMaxHealth() / (float)iHealthIncrements ) + 1 );
  2413. // Find out which "stage" we're at in our health
  2414. int healthIncrement = iHealthIncrements - ( GetHealth() / (float)(( GetMaxHealth() / (float)iHealthIncrements )) );
  2415. switch ( healthIncrement )
  2416. {
  2417. case 1:
  2418. // If we're on Easy, we're half dead now, so fire the rest of our outputs too
  2419. // This is done in case the mapmaker's connected those inputs to something important
  2420. // that has to happen before the gunship dies.
  2421. if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
  2422. {
  2423. FireDamageOutputsUpto( 3 );
  2424. }
  2425. else
  2426. {
  2427. FireDamageOutputsUpto( 1 );
  2428. }
  2429. break;
  2430. default:
  2431. FireDamageOutputsUpto( healthIncrement );
  2432. break;
  2433. }
  2434. // Start smoking when we're almost dead
  2435. CreateSmokeTrail();
  2436. if ( m_pSmokeTrail )
  2437. {
  2438. if ( healthIncrement < 2 )
  2439. {
  2440. m_pSmokeTrail->SetLifetime( 8.0 );
  2441. }
  2442. m_pSmokeTrail->FollowEntity( this, "exhaustl" );
  2443. }
  2444. // Move with the target
  2445. Vector gibVelocity = GetAbsVelocity() + (-damageDir * 200.0f);
  2446. // Dump out metal gibs
  2447. CPVSFilter filter( GetAbsOrigin() );
  2448. for ( int i = 0; i < 10; i++ )
  2449. {
  2450. int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );
  2451. te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, Vector(40,40,40), gibVelocity, iModelIndex, 400, 1, 2.5, BREAK_METAL );
  2452. }
  2453. }
  2454. return BaseClass::OnTakeDamage_Alive( info );
  2455. }
  2456. //------------------------------------------------------------------------------
  2457. // Purpose : The proper way to begin the gunship cannon firing at the enemy.
  2458. // Input : iBurstSize - the size of the burst, in rounds.
  2459. //------------------------------------------------------------------------------
  2460. void CNPC_CombineGunship::StartCannonBurst( int iBurstSize )
  2461. {
  2462. m_iBurstSize = iBurstSize;
  2463. m_iBurstHits = 0;
  2464. m_flTimeNextAttack = gpGlobals->curtime;
  2465. // Start up the cannon sound.
  2466. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  2467. controller.SoundChangeVolume( m_pCannonSound, 1.0, 0 );
  2468. m_bIsFiring = true;
  2469. // Setup the initial position of the burst
  2470. if ( GetEnemy() )
  2471. {
  2472. // Follow mode
  2473. Vector enemyPos;
  2474. UTIL_PredictedPosition( GetEnemy(), 2.0f, &enemyPos );
  2475. QAngle offsetAngles;
  2476. Vector offsetDir = ( WorldSpaceCenter() - enemyPos );
  2477. VectorNormalize( offsetDir );
  2478. VectorAngles( offsetDir, offsetAngles );
  2479. int angleOffset = random->RandomInt( 15, 30 );
  2480. if ( random->RandomInt( 0, 1 ) )
  2481. {
  2482. angleOffset *= -1;
  2483. }
  2484. offsetAngles[YAW] += angleOffset;
  2485. offsetAngles[PITCH] = 0;
  2486. offsetAngles[ROLL] = 0;
  2487. AngleVectors( offsetAngles, &offsetDir );
  2488. float stitchOffset;
  2489. float enemyDist = GroundDistToPosition( GetEnemy()->GetAbsOrigin() );
  2490. if ( enemyDist < ( sk_gunship_burst_dist.GetFloat() + GUNSHIP_STITCH_MIN ) )
  2491. {
  2492. stitchOffset = GUNSHIP_STITCH_MIN;
  2493. }
  2494. else
  2495. {
  2496. stitchOffset = sk_gunship_burst_dist.GetFloat();
  2497. }
  2498. // Move out to the start of our stitch run
  2499. m_vecAttackPosition = enemyPos + ( offsetDir * stitchOffset );
  2500. m_vecAttackPosition.z = enemyPos.z;
  2501. // Point at our target
  2502. m_vecAttackVelocity = -offsetDir * BASE_STITCH_VELOCITY;
  2503. CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_REACT_TO_SOURCE, enemyPos, 512, 0.2f, this );
  2504. }
  2505. }
  2506. //------------------------------------------------------------------------------
  2507. // Purpose : The proper way to cease the gunship cannon firing.
  2508. //------------------------------------------------------------------------------
  2509. void CNPC_CombineGunship::StopCannonBurst( void )
  2510. {
  2511. m_iBurstHits = 0;
  2512. m_bIsFiring = false;
  2513. m_bPreFire = false;
  2514. // Reduce the burst time when we get lower in health
  2515. float flPerc = (float)GetHealth() / (float)GetMaxHealth();
  2516. float flDelay = clamp( flPerc * m_flBurstDelay, 0.5, m_flBurstDelay );
  2517. // If we didn't finish the burst, don't wait so long
  2518. flPerc = 1.0 - (m_iBurstSize / sk_gunship_burst_size.GetFloat());
  2519. flDelay *= flPerc;
  2520. m_flTimeNextAttack = gpGlobals->curtime + flDelay;
  2521. m_iBurstSize = 0;
  2522. // Stop the cannon sound.
  2523. if ( m_pCannonSound != NULL )
  2524. {
  2525. CSoundEnvelopeController::GetController().SoundChangeVolume( m_pCannonSound, 0.0, 0.05 );
  2526. }
  2527. EmitSound( "NPC_CombineGunship.CannonStopSound" );
  2528. }
  2529. //-----------------------------------------------------------------------------
  2530. // Purpose:
  2531. //-----------------------------------------------------------------------------
  2532. void CNPC_CombineGunship::StopLoopingSounds( void )
  2533. {
  2534. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  2535. if ( m_pCannonSound )
  2536. {
  2537. controller.SoundDestroy( m_pCannonSound );
  2538. m_pCannonSound = NULL;
  2539. }
  2540. if ( m_pRotorSound )
  2541. {
  2542. controller.SoundDestroy( m_pRotorSound );
  2543. m_pRotorSound = NULL;
  2544. }
  2545. if ( m_pAirExhaustSound )
  2546. {
  2547. controller.SoundDestroy( m_pAirExhaustSound );
  2548. m_pAirExhaustSound = NULL;
  2549. }
  2550. if ( m_pAirBlastSound )
  2551. {
  2552. controller.SoundDestroy( m_pAirBlastSound );
  2553. m_pAirBlastSound = NULL;
  2554. }
  2555. BaseClass::StopLoopingSounds();
  2556. }
  2557. //-----------------------------------------------------------------------------
  2558. // Purpose:
  2559. // Input : *pEnemy -
  2560. // Output : Returns true on success, false on failure.
  2561. //-----------------------------------------------------------------------------
  2562. bool CNPC_CombineGunship::IsValidEnemy( CBaseEntity *pEnemy )
  2563. {
  2564. // Always track missiles
  2565. if ( pEnemy->IsAlive() && !pEnemy->MyNPCPointer() && FClassnameIs( pEnemy, "rpg_missile" ) )
  2566. return true;
  2567. // If we're shooting off a burst, don't pick up a new enemy
  2568. if ( ( m_bIsFiring ) && ( ( GetEnemy() == NULL ) || ( GetEnemy() != pEnemy ) ) )
  2569. return false;
  2570. return BaseClass::IsValidEnemy( pEnemy );
  2571. }
  2572. //-----------------------------------------------------------------------------
  2573. // Purpose:
  2574. //-----------------------------------------------------------------------------
  2575. void CNPC_CombineGunship::GatherEnemyConditions( CBaseEntity *pEnemy )
  2576. {
  2577. BaseClass::GatherEnemyConditions(pEnemy);
  2578. // If we can't see the enemy for a few seconds, consider him unreachable
  2579. if ( !HasCondition(COND_SEE_ENEMY) )
  2580. {
  2581. if ( gpGlobals->curtime - GetEnemyLastTimeSeen() >= 3.0f )
  2582. {
  2583. MarkEnemyAsEluded();
  2584. }
  2585. }
  2586. }
  2587. //-----------------------------------------------------------------------------
  2588. // Purpose: Tells us whether or not we're targetting an incoming missile
  2589. //-----------------------------------------------------------------------------
  2590. bool CNPC_CombineGunship::IsTargettingMissile( void )
  2591. {
  2592. if ( GetEnemy() == NULL )
  2593. return false;
  2594. if ( FClassnameIs( GetEnemy(), "rpg_missile" ) == false )
  2595. return false;
  2596. return true;
  2597. }
  2598. //-----------------------------------------------------------------------------
  2599. // Purpose:
  2600. //-----------------------------------------------------------------------------
  2601. void CNPC_CombineGunship::InputBecomeInvulnerable( inputdata_t &input )
  2602. {
  2603. m_bInvulnerable = true;
  2604. }
  2605. //-----------------------------------------------------------------------------
  2606. // Purpose:
  2607. //-----------------------------------------------------------------------------
  2608. void CNPC_CombineGunship::InputBecomeVulnerable( inputdata_t &input )
  2609. {
  2610. m_bInvulnerable = false;
  2611. }
  2612. AI_BEGIN_CUSTOM_NPC( npc_combinegunship, CNPC_CombineGunship )
  2613. // DECLARE_TASK( )
  2614. DECLARE_ACTIVITY( ACT_GUNSHIP_PATROL );
  2615. DECLARE_ACTIVITY( ACT_GUNSHIP_HOVER );
  2616. DECLARE_ACTIVITY( ACT_GUNSHIP_CRASH );
  2617. //DECLARE_CONDITION( COND_ )
  2618. //=========================================================
  2619. // DEFINE_SCHEDULE
  2620. // (
  2621. // SCHED_DUMMY,
  2622. //
  2623. // " Tasks"
  2624. // " TASK_FACE_ENEMY 0"
  2625. // " "
  2626. // " Interrupts"
  2627. // )
  2628. AI_END_CUSTOM_NPC()