aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_attackchopper.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/hl2/npc_attackchopper.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/hl2/npc_attackchopper.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_attackchopper.cpp12234
1 files changed, 6117 insertions, 6117 deletions
diff --git a/mp/src/game/server/hl2/npc_attackchopper.cpp b/mp/src/game/server/hl2/npc_attackchopper.cpp
index 1df3395e..0687c915 100644
--- a/mp/src/game/server/hl2/npc_attackchopper.cpp
+++ b/mp/src/game/server/hl2/npc_attackchopper.cpp
@@ -1,6117 +1,6117 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//===========================================================================//
-
-#include "cbase.h"
-#include "ai_network.h"
-#include "ai_default.h"
-#include "ai_schedule.h"
-#include "ai_hull.h"
-#include "ai_node.h"
-#include "ai_task.h"
-#include "entitylist.h"
-#include "basecombatweapon.h"
-#include "soundenvelope.h"
-#include "gib.h"
-#include "gamerules.h"
-#include "ammodef.h"
-#include "grenade_homer.h"
-#include "cbasehelicopter.h"
-#include "engine/IEngineSound.h"
-#include "IEffects.h"
-#include "globals.h"
-#include "explode.h"
-#include "movevars_shared.h"
-#include "smoke_trail.h"
-#include "ar2_explosion.h"
-#include "collisionutils.h"
-#include "props.h"
-#include "EntityFlame.h"
-#include "decals.h"
-#include "effect_dispatch_data.h"
-#include "te_effect_dispatch.h"
-#include "ai_spotlight.h"
-#include "vphysics/constraints.h"
-#include "physics_saverestore.h"
-#include "ai_memory.h"
-#include "npc_attackchopper.h"
-
-#ifdef HL2_EPISODIC
-#include "physics_bone_follower.h"
-#endif // HL2_EPISODIC
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-// -------------------------------------
-// Bone controllers
-// -------------------------------------
-#define CHOPPER_DRONE_NAME "models/combine_helicopter/helicopter_bomb01.mdl"
-#define CHOPPER_MODEL_NAME "models/combine_helicopter.mdl"
-#define CHOPPER_MODEL_CORPSE_NAME "models/combine_helicopter_broken.mdl"
-#define CHOPPER_RED_LIGHT_SPRITE "sprites/redglow1.vmt"
-
-#define CHOPPER_MAX_SMALL_CHUNKS 1
-#define CHOPPER_MAX_CHUNKS 3
-static const char *s_pChunkModelName[CHOPPER_MAX_CHUNKS] =
-{
- "models/gibs/helicopter_brokenpiece_01.mdl",
- "models/gibs/helicopter_brokenpiece_02.mdl",
- "models/gibs/helicopter_brokenpiece_03.mdl",
-};
-
-#define BOMB_SKIN_LIGHT_ON 1
-#define BOMB_SKIN_LIGHT_OFF 0
-
-
-#define HELICOPTER_CHUNK_COCKPIT "models/gibs/helicopter_brokenpiece_04_cockpit.mdl"
-#define HELICOPTER_CHUNK_TAIL "models/gibs/helicopter_brokenpiece_05_tailfan.mdl"
-#define HELICOPTER_CHUNK_BODY "models/gibs/helicopter_brokenpiece_06_body.mdl"
-
-
-#define CHOPPER_MAX_SPEED (60 * 17.6f)
-#define CHOPPER_MAX_FIRING_SPEED 250.0f
-#define CHOPPER_MAX_GUN_DIST 2000.0f
-
-#define CHOPPER_ACCEL_RATE 500
-#define CHOPPER_ACCEL_RATE_BOOST 1500
-
-#define DEFAULT_FREE_KNOWLEDGE_DURATION 5.0f
-
-// -------------------------------------
-// Pathing data
-#define CHOPPER_LEAD_DISTANCE 800.0f
-#define CHOPPER_MIN_CHASE_DIST_DIFF 128.0f // Distance threshold used to determine when a target has moved enough to update our navigation to it
-#define CHOPPER_MIN_AGGRESSIVE_CHASE_DIST_DIFF 64.0f
-#define CHOPPER_AVOID_DIST 512.0f
-#define CHOPPER_ARRIVE_DIST 128.0f
-
-#define CHOPPER_MAX_CIRCLE_OF_DEATH_FOLLOW_SPEED 450.0f
-#define CHOPPER_MIN_CIRCLE_OF_DEATH_RADIUS 150.0f
-#define CHOPPER_MAX_CIRCLE_OF_DEATH_RADIUS 350.0f
-
-#define CHOPPER_BOMB_DROP_COUNT 6
-
-// Bullrush
-#define CHOPPER_BULLRUSH_MODE_DISTANCE g_helicopter_bullrush_distance.GetFloat()
-#define CHOPPER_BULLRUSH_ENEMY_BOMB_DISTANCE g_helicopter_bullrush_bomb_enemy_distance.GetFloat()
-#define CHOPPER_BULLRUSH_ENEMY_BOMB_TIME g_helicopter_bullrush_bomb_time.GetFloat()
-#define CHOPPER_BULLRUSH_ENEMY_BOMB_SPEED g_helicopter_bullrush_bomb_speed.GetFloat()
-#define CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET g_helicopter_bullrush_shoot_height.GetFloat()
-
-#define CHOPPER_GUN_CHARGE_TIME g_helicopter_chargetime.GetFloat()
-#define CHOPPER_GUN_IDLE_TIME g_helicopter_idletime.GetFloat()
-#define CHOPPER_GUN_MAX_FIRING_DIST g_helicopter_maxfiringdist.GetFloat()
-
-#define BULLRUSH_IDLE_PLAYER_FIRE_TIME 6.0f
-
-#define DRONE_SPEED sk_helicopter_drone_speed.GetFloat()
-
-#define SF_HELICOPTER_LOUD_ROTOR_SOUND 0x00010000
-#define SF_HELICOPTER_ELECTRICAL_DRONE 0x00020000
-#define SF_HELICOPTER_LIGHTS 0x00040000
-#define SF_HELICOPTER_IGNORE_AVOID_FORCES 0x00080000
-#define SF_HELICOPTER_AGGRESSIVE 0x00100000
-#define SF_HELICOPTER_LONG_SHADOW 0x00200000
-
-#define CHOPPER_SLOW_BOMB_SPEED 250
-
-#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED 250
-#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_SQ (CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED * CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED)
-
-#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2 450
-#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2_SQ (CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2 * CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2)
-
-// CVars
-ConVar sk_helicopter_health( "sk_helicopter_health","5600");
-ConVar sk_helicopter_firingcone( "sk_helicopter_firingcone","20.0", 0, "The angle in degrees of the cone in which the shots will be fired" );
-ConVar sk_helicopter_burstcount( "sk_helicopter_burstcount","12", 0, "How many shot bursts to fire after charging up. The bigger the number, the longer the firing is" );
-ConVar sk_helicopter_roundsperburst( "sk_helicopter_roundsperburst","5", 0, "How many shots to fire in a single burst" );
-
-ConVar sk_helicopter_grenadedamage( "sk_helicopter_grenadedamage","25.0", 0, "The amount of damage the helicopter grenade deals." );
-ConVar sk_helicopter_grenaderadius( "sk_helicopter_grenaderadius","275.0", 0, "The damage radius of the helicopter grenade." );
-ConVar sk_helicopter_grenadeforce( "sk_helicopter_grenadeforce","55000.0", 0, "The physics force that the helicopter grenade exerts." );
-ConVar sk_helicopter_grenade_puntscale( "sk_helicopter_grenade_puntscale","1.5", 0, "When physpunting a chopper's grenade, scale its velocity by this much." );
-
-// Number of bomb hits it takes to kill a chopper on each skill level.
-ConVar sk_helicopter_num_bombs1("sk_helicopter_num_bombs1", "3");
-ConVar sk_helicopter_num_bombs2("sk_helicopter_num_bombs2", "5");
-ConVar sk_helicopter_num_bombs3("sk_helicopter_num_bombs3", "5");
-
-ConVar sk_npc_dmg_helicopter_to_plr( "sk_npc_dmg_helicopter_to_plr","3", 0, "Damage helicopter shots deal to the player" );
-ConVar sk_npc_dmg_helicopter( "sk_npc_dmg_helicopter","6", 0, "Damage helicopter shots deal to everything but the player" );
-
-ConVar sk_helicopter_drone_speed( "sk_helicopter_drone_speed","450.0", 0, "How fast does the zapper drone move?" );
-
-ConVar g_helicopter_chargetime( "g_helicopter_chargetime","2.0", 0, "How much time we have to wait (on average) between the time we start hearing the charging sound + the chopper fires" );
-ConVar g_helicopter_bullrush_distance("g_helicopter_bullrush_distance", "5000");
-ConVar g_helicopter_bullrush_bomb_enemy_distance("g_helicopter_bullrush_bomb_enemy_distance", "0");
-ConVar g_helicopter_bullrush_bomb_time("g_helicopter_bullrush_bomb_time", "10");
-ConVar g_helicopter_idletime( "g_helicopter_idletime","3.0", 0, "How much time we have to wait (on average) after we fire before we can charge up again" );
-ConVar g_helicopter_maxfiringdist( "g_helicopter_maxfiringdist","2500.0", 0, "The maximum distance the player can be from the chopper before it stops firing" );
-ConVar g_helicopter_bullrush_bomb_speed( "g_helicopter_bullrush_bomb_speed","850.0", 0, "The maximum distance the player can be from the chopper before it stops firing" );
-ConVar g_helicopter_bullrush_shoot_height( "g_helicopter_bullrush_shoot_height","650.0", 0, "The maximum distance the player can be from the chopper before it stops firing" );
-ConVar g_helicopter_bullrush_mega_bomb_health( "g_helicopter_bullrush_mega_bomb_health","0.25", 0, "Fraction of the health of the chopper before it mega-bombs" );
-
-ConVar g_helicopter_bomb_danger_radius( "g_helicopter_bomb_danger_radius", "120" );
-
-Activity ACT_HELICOPTER_DROP_BOMB;
-Activity ACT_HELICOPTER_CRASHING;
-
-static const char *s_pBlinkLightThinkContext = "BlinkLights";
-static const char *s_pSpotlightThinkContext = "SpotlightThink";
-static const char *s_pRampSoundContext = "RampSound";
-static const char *s_pWarningBlinkerContext = "WarningBlinker";
-static const char *s_pAnimateThinkContext = "Animate";
-
-#define CHOPPER_LIGHT_BLINK_TIME 1.0f
-#define CHOPPER_LIGHT_BLINK_TIME_SHORT 0.1f
-
-#define BOMB_LIFETIME 2.5f // Don't access this directly. Call GetBombLifetime();
-#define BOMB_RAMP_SOUND_TIME 1.0f
-
-enum
-{
- MAX_HELICOPTER_LIGHTS = 3,
-};
-
-enum
-{
- SF_GRENADE_HELICOPTER_MEGABOMB = 0x1,
-};
-
-#define GRENADE_HELICOPTER_MODEL "models/combine_helicopter/helicopter_bomb01.mdl"
-
-LINK_ENTITY_TO_CLASS( info_target_helicopter_crash, CPointEntity );
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-static inline float ClampSplineRemapVal( float flValue, float flMinValue, float flMaxValue, float flOutMin, float flOutMax )
-{
- Assert( flMinValue <= flMaxValue );
- float flClampedVal = clamp( flValue, flMinValue, flMaxValue );
- return SimpleSplineRemapVal( flClampedVal, flMinValue, flMaxValue, flOutMin, flOutMax );
-}
-
-
-//-----------------------------------------------------------------------------
-// The bombs the attack helicopter drops
-//-----------------------------------------------------------------------------
-enum
-{
- SKIN_REGULAR,
- SKIN_DUD,
-};
-
-class CGrenadeHelicopter : public CBaseGrenade
-{
- DECLARE_DATADESC();
-
-public:
- DECLARE_CLASS( CGrenadeHelicopter, CBaseGrenade );
-
- virtual void Precache( );
- virtual void Spawn( );
- virtual void UpdateOnRemove();
- virtual void OnEntityEvent( EntityEvent_t event, void *pEventData );
- virtual void PhysicsSimulate( void );
- virtual float GetShakeAmplitude( void ) { return 25.0; }
- virtual float GetShakeRadius( void ) { return sk_helicopter_grenaderadius.GetFloat() * 2; }
- virtual int OnTakeDamage( const CTakeDamageInfo &info );
- virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
- void SetExplodeOnContact( bool bExplode ) { m_bExplodeOnContact = bExplode; }
-
- virtual QAngle PreferredCarryAngles( void ) { return QAngle( -12, 98, 55 ); }
- virtual bool HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer ) { return true; }
-
- float GetBombLifetime();
-
-#ifdef HL2_EPISODIC
- virtual void OnPhysGunPickup(CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
- virtual void OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason );
- virtual Class_T Classify( void ) { return CLASS_MISSILE; }
- void SetCollisionObject( CBaseEntity *pEntity ) { m_hCollisionObject = pEntity; }
- void SendMissEvent();
- bool IsThrownByPlayer();
-
- virtual bool ShouldPuntUseLaunchForces( PhysGunForce_t reason ) { return ( reason == PHYSGUN_FORCE_LAUNCHED ); }
- virtual Vector PhysGunLaunchVelocity( const Vector &forward, float flMass );
-
- void InputExplodeIn( inputdata_t &inputdata );
-#endif // HL2_EPISODIC
-
-private:
- // Pow!
- void DoExplosion( const Vector &vecOrigin, const Vector &vecVelocity );
- void ExplodeThink();
- void RampSoundThink();
- void WarningBlinkerThink();
- void StopWarningBlinker();
- void AnimateThink();
- void ExplodeConcussion( CBaseEntity *pOther );
- void BecomeActive();
- void ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity );
-
- bool m_bActivated;
- bool m_bExplodeOnContact;
- CSoundPatch *m_pWarnSound;
-
- EHANDLE m_hWarningSprite;
- bool m_bBlinkerAtTop;
-
-
-#ifdef HL2_EPISODIC
- float m_flLifetime;
- EHANDLE m_hCollisionObject; // Pointer to object we re-enable collisions with when picked up
- bool m_bPickedUp;
- float m_flBlinkFastTime;
- COutputEvent m_OnPhysGunOnlyPickup;
-#endif // HL2_EPISODIC
-};
-
-
-//-----------------------------------------------------------------------------
-// The bombs the attack helicopter drops
-//-----------------------------------------------------------------------------
-class CBombDropSensor : public CBaseEntity
-{
- DECLARE_DATADESC();
-
-public:
- DECLARE_CLASS( CBombDropSensor, CBaseEntity );
-
- void Spawn();
-
- // Drop a bomb at a particular location
- void InputDropBomb( inputdata_t &inputdata );
- void InputDropBombStraightDown( inputdata_t &inputdata );
- void InputDropBombAtTarget( inputdata_t &inputdata );
- void InputDropBombAtTargetAlways( inputdata_t &inputdata );
- void InputDropBombDelay( inputdata_t &inputdata );
-};
-
-//-----------------------------------------------------------------------------
-// This entity is used to create boxes that the helicopter can't bomb in
-//-----------------------------------------------------------------------------
-class CBombSuppressor : public CBaseEntity
-{
- DECLARE_DATADESC();
-
-public:
- DECLARE_CLASS( CBombSuppressor, CBaseEntity );
-
- virtual void Spawn( );
- virtual void Activate();
- virtual void UpdateOnRemove();
-
- static bool CanBomb( const Vector &vecPosition );
-
-private:
- typedef CHandle<CBombSuppressor> BombSuppressorHandle_t;
- static CUtlVector< BombSuppressorHandle_t > s_BombSuppressors;
-};
-
- enum
- {
- CHUNK_COCKPIT,
- CHUNK_BODY,
- CHUNK_TAIL
- };
-
-//-----------------------------------------------------------------------------
-// This entity is used for helicopter gibs with specific properties
-//-----------------------------------------------------------------------------
-class CHelicopterChunk : public CBaseAnimating
-{
- DECLARE_DATADESC();
-
-public:
- DECLARE_CLASS( CHelicopterChunk, CBaseAnimating );
-
- virtual void Spawn( void );
- virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
-
- static CHelicopterChunk *CreateHelicopterChunk( const Vector &vecPos, const QAngle &vecAngles, const Vector &vecVelocity, const char *pszModelName, int chunkID );
-
- int m_nChunkID;
-
- CHandle<CHelicopterChunk> m_hMaster;
- IPhysicsConstraint *m_pTailConstraint;
- IPhysicsConstraint *m_pCockpitConstraint;
-
-protected:
-
- void CollisionCallback( CHelicopterChunk *pCaller );
-
- void FallThink( void );
-
- bool m_bLanded;
-};
-
-//-----------------------------------------------------------------------------
-// The attack helicopter
-//-----------------------------------------------------------------------------
-class CNPC_AttackHelicopter : public CBaseHelicopter
-{
-public:
- DECLARE_CLASS( CNPC_AttackHelicopter, CBaseHelicopter );
- DECLARE_DATADESC();
- DEFINE_CUSTOM_AI;
-
- CNPC_AttackHelicopter();
- ~CNPC_AttackHelicopter();
-
- virtual void Precache( void );
- virtual void Spawn( void );
- virtual void Activate( void );
- virtual bool CreateComponents();
- virtual int ObjectCaps();
-
-#ifdef HL2_EPISODIC
- virtual bool CreateVPhysics( void );
-#endif // HL2_EPISODIC
-
- virtual void UpdateOnRemove();
- virtual void StopLoopingSounds();
-
- int BloodColor( void ) { return DONT_BLEED; }
- Class_T Classify ( void ) { return CLASS_COMBINE_GUNSHIP; }
- virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
- virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
- virtual int OnTakeDamage( const CTakeDamageInfo &info );
-
- // Shot spread
- virtual Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget );
-
- // More Enemy visibility check
- virtual bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
-
- // Think!
- virtual void PrescheduleThink( void );
-
- // Purpose: Set the gunship's paddles flailing!
- virtual void Event_Killed( const CTakeDamageInfo &info );
-
- // Drop a bomb at a particular location
- void InputDropBomb( inputdata_t &inputdata );
- void InputDropBombStraightDown( inputdata_t &inputdata );
- void InputDropBombAtTarget( inputdata_t &inputdata );
- void InputDropBombAtTargetAlways( inputdata_t &inputdata );
- void InputDropBombAtTargetInternal( inputdata_t &inputdata, bool bCheckFairness );
- void InputDropBombDelay( inputdata_t &inputdata );
- void InputStartCarpetBombing( inputdata_t &inputdata );
- void InputStopCarpetBombing( inputdata_t &inputdata );
-
- virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
- virtual const char *GetTracerType( void );
-
- virtual void DoImpactEffect( trace_t &tr, int nDamageType );
- virtual void DoMuzzleFlash( void );
-
- // FIXME: Work this back into the base class
- virtual bool ShouldUseFixedPatrolLogic() { return true; }
-
-protected:
-
- int m_poseWeapon_Pitch, m_poseWeapon_Yaw, m_poseRudder;
- virtual void PopulatePoseParameters( void );
-
-private:
- enum GunState_t
- {
- GUN_STATE_IDLE = 0,
- GUN_STATE_CHARGING,
- GUN_STATE_FIRING,
- };
-
- // Gets the max speed of the helicopter
- virtual float GetMaxSpeed();
- virtual float GetMaxSpeedFiring();
-
- // Startup the chopper
- virtual void Startup();
-
- void InitializeRotorSound( void );
-
- // Weaponry
- bool FireGun( void );
-
- // Movement:
- virtual void Flight( void );
-
- // Changes the main thinking method of helicopters
- virtual void Hunt( void );
-
- // For scripted times where it *has* to shoot
- void InputResetIdleTime( inputdata_t &inputdata );
- void InputSetHealthFraction( inputdata_t &inputdata );
- void InputStartBombExplodeOnContact( inputdata_t &inputdata );
- void InputStopBombExplodeOnContact( inputdata_t &inputdata );
-
- void InputEnableAlwaysTransition( inputdata_t &inputdata );
- void InputDisableAlwaysTransition( inputdata_t &inputdata );
- void InputOutsideTransition( inputdata_t &inputdata );
- void InputSetOutsideTransitionTarget( inputdata_t &inputdata );
-
- // Turns off the gun
- void InputGunOff( inputdata_t &inputdata );
-
- // Vehicle attack modes
- void InputStartBombingVehicle( inputdata_t &inputdata );
- void InputStartTrailingVehicle( inputdata_t &inputdata );
- void InputStartDefaultBehavior( inputdata_t &inputdata );
- void InputStartAlwaysLeadingVehicle( inputdata_t &inputdata );
-
- // Deadly shooting, tex!
- void InputEnableDeadlyShooting( inputdata_t &inputdata );
- void InputDisableDeadlyShooting( inputdata_t &inputdata );
- void InputStartNormalShooting( inputdata_t &inputdata );
- void InputStartLongCycleShooting( inputdata_t &inputdata );
- void InputStartContinuousShooting( inputdata_t &inputdata );
- void InputStartFastShooting( inputdata_t &inputdata );
-
- int GetShootingMode( );
- bool IsDeadlyShooting();
-
- // Bombing suppression
- void InputEnableBombing( inputdata_t &inputdata );
- void InputDisableBombing( inputdata_t &inputdata );
-
- // Visibility tests
- void InputDisablePathVisibilityTests( inputdata_t &inputdata );
- void InputEnablePathVisibilityTests( inputdata_t &inputdata );
-
- // Death, etc.
- void InputSelfDestruct( inputdata_t &inputdata );
-
- // Enemy visibility check
- CBaseEntity *FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos );
-
- // Special path navigation when we explicitly want to follow a path
- void UpdateFollowPathNavigation();
-
- // Find interesting nearby things to shoot
- int BuildMissTargetList( int nCount, CBaseEntity **ppMissCandidates );
-
- // Shoot when the player's your enemy :
- void ShootAtPlayer( const Vector &vBasePos, const Vector &vGunDir );
-
- // Shoot when the player's your enemy + he's driving a vehicle
- void ShootAtVehicle( const Vector &vBasePos, const Vector &vGunDir );
-
- // Shoot where we're facing
- void ShootAtFacingDirection( const Vector &vBasePos, const Vector &vGunDir, bool bFirstShotAccurate );
-
- // Updates the facing direction
- void UpdateFacingDirection( const Vector &vecActualDesiredPosition );
-
- // Various states of the helicopter firing...
- bool PoseGunTowardTargetDirection( const Vector &vTargetDir );
-
- // Compute the position to fire at (vehicle + non-vehicle case)
- void ComputeFireAtPosition( Vector *pVecActualTargetPosition );
- void ComputeVehicleFireAtPosition( Vector *pVecActualTargetPosition );
-
- // Various states of the helicopter firing...
- bool DoGunIdle( const Vector &vecGunDir, const Vector &vTargetDir );
- bool DoGunCharging( );
- bool DoGunFiring( const Vector &vBasePos, const Vector &vGunDir, const Vector &vecFireAtPosition );
- void FireElectricityGun( );
-
- // Chooses a point within the circle of death to fire in
- void PickDirectionToCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition, Vector *pResult );
-
- // Gets a vehicle the enemy is in (if any)
- CBaseEntity *GetEnemyVehicle();
-
- // Updates the perpendicular path distance for the chopper
- float UpdatePerpPathDistance( float flMaxPathOffset );
-
- // Purpose :
- void UpdateEnemyLeading( void );
-
- // Drop those bombs!
- void DropBombs( );
-
- // Should we drop those bombs?
- bool ShouldDropBombs( void );
-
- // Returns the max firing distance
- float GetMaxFiringDistance();
-
- // Make sure we don't hit too many times
- void FireBullets( const FireBulletsInfo_t &info );
-
- // Is it "fair" to drop this bomb?
- bool IsBombDropFair( const Vector &vecBombStartPos, const Vector &vecVelocity );
-
- // Actually drops the bomb
- void CreateBomb( bool bCheckForFairness = true, Vector *pVecVelocity = NULL, bool bMegaBomb = false );
- CGrenadeHelicopter *SpawnBombEntity( const Vector &vecPos, const Vector &vecVelocity ); // Spawns the bomb entity and sets it up
-
- // Deliberately aims as close as possible w/o hitting
- void AimCloseToTargetButMiss( CBaseEntity *pTarget, float flMinDist, float flMaxDist, const Vector &shootOrigin, Vector *pResult );
-
- // Pops a shot inside the circle of death using the burst rules
- void ShootInsideCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition );
-
- // How easy is the target to hit?
- void UpdateTargetHittability();
-
- // Add a smoke trail since we've taken more damage
- void AddSmokeTrail( const Vector &vecPos );
-
- // Destroy all smoke trails
- void DestroySmokeTrails();
-
- // Creates the breakable husk of an attack chopper
- void CreateChopperHusk();
-
- // Pow!
- void ExplodeAndThrowChunk( const Vector &vecExplosionPos );
-
- // Drop a corpse!
- void DropCorpse( int nDamage );
-
- // Should we trigger a damage effect?
- bool ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const;
-
- // Become indestructible
- void InputBecomeIndestructible( inputdata_t &inputdata );
-
- // Purpose :
- float CreepTowardEnemy( float flSpeed, float flMinSpeed, float flMaxSpeed, float flMinDist, float flMaxDist );
-
- // Start bullrush
- void InputStartBullrushBehavior( inputdata_t &inputdata );
-
- void GetMaxSpeedAndAccel( float *pMaxSpeed, float *pAccelRate );
- void ComputeAngularVelocity( const Vector &vecGoalUp, const Vector &vecFacingDirection );
- void ComputeVelocity( const Vector &deltaPos, float flAdditionalHeight, float flMinDistFromSegment, float flMaxDistFromSegment, Vector *pVecAccel );
- void FlightDirectlyOverhead( void );
-
- // Methods related to computing leading distance
- float ComputeBombingLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle );
- float ComputeBullrushLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle );
-
- bool IsCarpetBombing() { return m_bIsCarpetBombing == true; }
-
- // Update the bullrush state
- void UpdateBullrushState( void );
-
- // Whether to shoot at or bomb an idle player
- bool ShouldBombIdlePlayer( void );
-
- // Different bomb-dropping behavior
- void BullrushBombs( );
-
- // Switch to idle
- void SwitchToBullrushIdle( void );
-
- // Secondary mode
- void SetSecondaryMode( int nMode, bool bRetainTime = false );
- bool IsInSecondaryMode( int nMode );
- float SecondaryModeTime( ) const;
-
- // Should the chopper shoot the idle player?
- bool ShouldShootIdlePlayerInBullrush();
-
- // Shutdown shooting during bullrush
- void ShutdownGunDuringBullrush( );
-
- // Updates the enemy
- virtual float EnemySearchDistance( );
-
- // Prototype zapper
- bool IsValidZapTarget( CBaseEntity *pTarget );
- void CreateZapBeam( const Vector &vecTargetPos );
- void CreateEntityZapEffect( CBaseEntity *pEnt );
-
- // Blink lights
- void BlinkLightsThink();
-
- // Spotlights
- void SpotlightThink();
- void SpotlightStartup();
- void SpotlightShutdown();
-
- CBaseEntity *GetCrashPoint() { return m_hCrashPoint.Get(); }
-
-private:
- enum
- {
- ATTACK_MODE_DEFAULT = 0,
- ATTACK_MODE_BOMB_VEHICLE,
- ATTACK_MODE_TRAIL_VEHICLE,
- ATTACK_MODE_ALWAYS_LEAD_VEHICLE,
- ATTACK_MODE_BULLRUSH_VEHICLE,
- };
-
- enum
- {
- MAX_SMOKE_TRAILS = 5,
- MAX_EXPLOSIONS = 13,
- MAX_CORPSES = 2,
- };
-
- enum
- {
- BULLRUSH_MODE_WAIT_FOR_ENEMY = 0,
- BULLRUSH_MODE_GET_DISTANCE,
- BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED,
- BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER,
- BULLRUSH_MODE_SHOOT_GUN,
- BULLRUSH_MODE_MEGA_BOMB,
- BULLRUSH_MODE_SHOOT_IDLE_PLAYER,
- };
-
- enum
- {
- SHOOT_MODE_DEFAULT = 0,
- SHOOT_MODE_LONG_CYCLE,
- SHOOT_MODE_CONTINUOUS,
- SHOOT_MODE_FAST,
- };
-
-#ifdef HL2_EPISODIC
- void InitBoneFollowers( void );
- CBoneFollowerManager m_BoneFollowerManager;
-#endif // HL2_EPISODIC
-
- CAI_Spotlight m_Spotlight;
- Vector m_angGun;
- QAngle m_vecAngAcceleration;
- int m_iAmmoType;
- float m_flLastCorpseFall;
- GunState_t m_nGunState;
- float m_flChargeTime;
- float m_flIdleTimeDelay;
- int m_nRemainingBursts;
- int m_nGrenadeCount;
- float m_flPathOffset;
- float m_flAcrossTime;
- float m_flCurrPathOffset;
- int m_nBurstHits;
- int m_nMaxBurstHits;
- float m_flCircleOfDeathRadius;
- int m_nAttackMode;
- float m_flInputDropBombTime;
- CHandle<CBombDropSensor> m_hSensor;
- float m_flAvoidMetric;
- AngularImpulse m_vecLastAngVelocity;
- CHandle<CBaseEntity> m_hSmokeTrail[MAX_SMOKE_TRAILS];
- int m_nSmokeTrailCount;
- bool m_bIndestructible;
- float m_flGracePeriod;
- bool m_bBombsExplodeOnContact;
- bool m_bNonCombat;
-
- int m_nNearShots;
- int m_nMaxNearShots;
-
- // Bomb dropping attachments
- int m_nGunTipAttachment;
- int m_nGunBaseAttachment;
- int m_nBombAttachment;
- int m_nSpotlightAttachment;
- float m_flLastFastTime;
-
- // Secondary modes
- int m_nSecondaryMode;
- float m_flSecondaryModeStartTime;
-
- // Bullrush behavior
- bool m_bRushForward;
- float m_flBullrushAdditionalHeight;
- int m_nBullrushBombMode;
- float m_flNextBullrushBombTime;
- float m_flNextMegaBombHealth;
-
- // Shooting method
- int m_nShootingMode;
- bool m_bDeadlyShooting;
-
- // Bombing suppression
- bool m_bBombingSuppressed;
-
- // Blinking lights
- CHandle<CSprite> m_hLights[MAX_HELICOPTER_LIGHTS];
- bool m_bShortBlink;
-
- // Path behavior
- bool m_bIgnorePathVisibilityTests;
-
- // Teleport
- bool m_bAlwaysTransition;
- string_t m_iszTransitionTarget;
-
- // Special attacks
- bool m_bIsCarpetBombing;
-
- // Fun damage effects
- float m_flGoalRollDmg;
- float m_flGoalYawDmg;
-
- // Sounds
- CSoundPatch *m_pGunFiringSound;
-
- // Outputs
- COutputInt m_OnHealthChanged;
- COutputEvent m_OnShotDown;
-
- // Crashing
- EHANDLE m_hCrashPoint;
-};
-
-#ifdef HL2_EPISODIC
-static const char *pFollowerBoneNames[] =
-{
- "Chopper.Body"
-};
-#endif // HL2_EPISODIC
-
-LINK_ENTITY_TO_CLASS( npc_helicopter, CNPC_AttackHelicopter );
-
-BEGIN_DATADESC( CNPC_AttackHelicopter )
-
- DEFINE_ENTITYFUNC( FlyTouch ),
-
- DEFINE_EMBEDDED( m_Spotlight ),
-#ifdef HL2_EPISODIC
- DEFINE_EMBEDDED( m_BoneFollowerManager ),
-#endif
- DEFINE_FIELD( m_angGun, FIELD_VECTOR ),
- DEFINE_FIELD( m_vecAngAcceleration, FIELD_VECTOR ),
- DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
- DEFINE_FIELD( m_flLastCorpseFall, FIELD_TIME ),
- DEFINE_FIELD( m_nGunState, FIELD_INTEGER ),
- DEFINE_FIELD( m_flChargeTime, FIELD_TIME ),
- DEFINE_FIELD( m_flIdleTimeDelay, FIELD_FLOAT ),
- DEFINE_FIELD( m_nRemainingBursts, FIELD_INTEGER ),
- DEFINE_FIELD( m_nGrenadeCount, FIELD_INTEGER ),
- DEFINE_FIELD( m_flPathOffset, FIELD_FLOAT ),
- DEFINE_FIELD( m_flAcrossTime, FIELD_TIME ),
- DEFINE_FIELD( m_flCurrPathOffset, FIELD_FLOAT ),
- DEFINE_FIELD( m_nBurstHits, FIELD_INTEGER ),
- DEFINE_FIELD( m_nMaxBurstHits, FIELD_INTEGER ),
- DEFINE_FIELD( m_flCircleOfDeathRadius, FIELD_FLOAT ),
- DEFINE_FIELD( m_nAttackMode, FIELD_INTEGER ),
- DEFINE_FIELD( m_flInputDropBombTime, FIELD_TIME ),
- DEFINE_FIELD( m_hSensor, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flAvoidMetric, FIELD_FLOAT ),
- DEFINE_FIELD( m_vecLastAngVelocity, FIELD_VECTOR ),
- DEFINE_AUTO_ARRAY( m_hSmokeTrail, FIELD_EHANDLE ),
- DEFINE_FIELD( m_nSmokeTrailCount, FIELD_INTEGER ),
- DEFINE_FIELD( m_nNearShots, FIELD_INTEGER ),
- DEFINE_FIELD( m_nMaxNearShots, FIELD_INTEGER ),
-// DEFINE_FIELD( m_nGunTipAttachment, FIELD_INTEGER ),
-// DEFINE_FIELD( m_nGunBaseAttachment, FIELD_INTEGER ),
-// DEFINE_FIELD( m_nBombAttachment, FIELD_INTEGER ),
-// DEFINE_FIELD( m_nSpotlightAttachment, FIELD_INTEGER ),
- DEFINE_FIELD( m_flLastFastTime, FIELD_TIME ),
- DEFINE_FIELD( m_nSecondaryMode, FIELD_INTEGER ),
- DEFINE_FIELD( m_flSecondaryModeStartTime, FIELD_TIME ),
- DEFINE_FIELD( m_bRushForward, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flBullrushAdditionalHeight, FIELD_FLOAT ),
- DEFINE_FIELD( m_nBullrushBombMode, FIELD_INTEGER ),
- DEFINE_FIELD( m_flNextBullrushBombTime, FIELD_TIME ),
- DEFINE_FIELD( m_flNextMegaBombHealth, FIELD_FLOAT ),
- DEFINE_FIELD( m_nShootingMode, FIELD_INTEGER ),
- DEFINE_FIELD( m_bDeadlyShooting, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bBombingSuppressed, FIELD_BOOLEAN ),
- DEFINE_SOUNDPATCH( m_pGunFiringSound ),
- DEFINE_AUTO_ARRAY( m_hLights, FIELD_EHANDLE ),
- DEFINE_FIELD( m_bIgnorePathVisibilityTests, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bShortBlink, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bIndestructible, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bBombsExplodeOnContact, FIELD_BOOLEAN ),
-
- DEFINE_KEYFIELD( m_bAlwaysTransition, FIELD_BOOLEAN, "AlwaysTransition" ),
- DEFINE_KEYFIELD( m_iszTransitionTarget, FIELD_STRING, "TransitionTarget" ),
- DEFINE_FIELD( m_bIsCarpetBombing, FIELD_BOOLEAN ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableAlwaysTransition", InputEnableAlwaysTransition ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableAlwaysTransition", InputDisableAlwaysTransition ),
- DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
- DEFINE_INPUTFUNC( FIELD_STRING, "SetTransitionTarget", InputSetOutsideTransitionTarget ),
-
- DEFINE_KEYFIELD( m_flGracePeriod, FIELD_FLOAT, "GracePeriod" ),
- DEFINE_KEYFIELD( m_flMaxSpeed, FIELD_FLOAT, "PatrolSpeed" ),
- DEFINE_KEYFIELD( m_bNonCombat, FIELD_BOOLEAN, "NonCombat" ),
-
- DEFINE_FIELD( m_hCrashPoint, FIELD_EHANDLE ),
-
- DEFINE_INPUTFUNC( FIELD_VOID, "ResetIdleTime", InputResetIdleTime ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartAlwaysLeadingVehicle", InputStartAlwaysLeadingVehicle ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartBombingVehicle", InputStartBombingVehicle ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartTrailingVehicle", InputStartTrailingVehicle ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartDefaultBehavior", InputStartDefaultBehavior ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartBullrushBehavior", InputStartBullrushBehavior ),
-
- DEFINE_INPUTFUNC( FIELD_VOID, "DropBomb", InputDropBomb ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DropBombStraightDown", InputDropBombStraightDown ),
- DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTargetAlways", InputDropBombAtTargetAlways ),
- DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTarget", InputDropBombAtTarget ),
- DEFINE_INPUTFUNC( FIELD_FLOAT, "DropBombDelay", InputDropBombDelay ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartCarpetBombing", InputStartCarpetBombing ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StopCarpetBombing", InputStopCarpetBombing ),
- DEFINE_INPUTFUNC( FIELD_VOID, "BecomeIndestructible", InputBecomeIndestructible ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableDeadlyShooting", InputEnableDeadlyShooting ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableDeadlyShooting", InputDisableDeadlyShooting ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartNormalShooting", InputStartNormalShooting ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartLongCycleShooting", InputStartLongCycleShooting ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartContinuousShooting", InputStartContinuousShooting ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartFastShooting", InputStartFastShooting ),
- DEFINE_INPUTFUNC( FIELD_VOID, "GunOff", InputGunOff ),
- DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthFraction", InputSetHealthFraction ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StartBombExplodeOnContact", InputStartBombExplodeOnContact ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StopBombExplodeOnContact", InputStopBombExplodeOnContact ),
-
- DEFINE_INPUTFUNC( FIELD_VOID, "DisablePathVisibilityTests", InputDisablePathVisibilityTests ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnablePathVisibilityTests", InputEnablePathVisibilityTests ),
-
- DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ),
-
- DEFINE_THINKFUNC( BlinkLightsThink ),
- DEFINE_THINKFUNC( SpotlightThink ),
-
- DEFINE_OUTPUT( m_OnHealthChanged, "OnHealthChanged" ),
- DEFINE_OUTPUT( m_OnShotDown, "OnShotDown" ),
-
-END_DATADESC()
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-CNPC_AttackHelicopter::CNPC_AttackHelicopter() :
- m_bNonCombat( false ),
- m_flGracePeriod( 2.0f ),
- m_bBombsExplodeOnContact( false )
-{
- m_flMaxSpeed = 0;
-}
-
-CNPC_AttackHelicopter::~CNPC_AttackHelicopter(void)
-{
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Shuts down looping sounds when we are killed in combat or deleted.
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::StopLoopingSounds()
-{
- BaseClass::StopLoopingSounds();
-
- if ( m_pGunFiringSound )
- {
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundDestroy( m_pGunFiringSound );
- m_pGunFiringSound = NULL;
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-void Chopper_PrecacheChunks( CBaseEntity *pChopper )
-{
- for ( int i = 0; i < CHOPPER_MAX_CHUNKS; ++i )
- {
- pChopper->PrecacheModel( s_pChunkModelName[i] );
- }
-
- pChopper->PrecacheModel( HELICOPTER_CHUNK_COCKPIT );
- pChopper->PrecacheModel( HELICOPTER_CHUNK_TAIL );
- pChopper->PrecacheModel( HELICOPTER_CHUNK_BODY );
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::Precache( void )
-{
- BaseClass::Precache();
-
- if ( !HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
- {
- PrecacheModel( CHOPPER_MODEL_NAME );
- }
- else
- {
- PrecacheModel( CHOPPER_DRONE_NAME );
- }
-
- PrecacheModel( CHOPPER_RED_LIGHT_SPRITE );
- //PrecacheModel( CHOPPER_MODEL_CORPSE_NAME );
-
- // If we're never going to engage in combat, we don't need to load these assets!
- if ( m_bNonCombat == false )
- {
- UTIL_PrecacheOther( "grenade_helicopter" );
- UTIL_PrecacheOther( "env_fire_trail" );
- Chopper_PrecacheChunks( this );
- PrecacheModel("models/combine_soldier.mdl");
- }
-
- PrecacheScriptSound("NPC_AttackHelicopter.ChargeGun");
- if ( HasSpawnFlags( SF_HELICOPTER_LOUD_ROTOR_SOUND ) )
- {
- PrecacheScriptSound("NPC_AttackHelicopter.RotorsLoud");
- }
- else
- {
- PrecacheScriptSound("NPC_AttackHelicopter.Rotors");
- }
- PrecacheScriptSound( "NPC_AttackHelicopter.DropMine" );
- PrecacheScriptSound( "NPC_AttackHelicopter.BadlyDamagedAlert" );
- PrecacheScriptSound( "NPC_AttackHelicopter.CrashingAlarm1" );
- PrecacheScriptSound( "NPC_AttackHelicopter.MegabombAlert" );
-
- PrecacheScriptSound( "NPC_AttackHelicopter.RotorBlast" );
- PrecacheScriptSound( "NPC_AttackHelicopter.EngineFailure" );
- PrecacheScriptSound( "NPC_AttackHelicopter.FireGun" );
- PrecacheScriptSound( "NPC_AttackHelicopter.Crash" );
- PrecacheScriptSound( "HelicopterBomb.HardImpact" );
-
- PrecacheScriptSound( "ReallyLoudSpark" );
- PrecacheScriptSound( "NPC_AttackHelicopterGrenade.Ping" );
-}
-
-int CNPC_AttackHelicopter::ObjectCaps()
-{
- int caps = BaseClass::ObjectCaps();
- if ( m_bAlwaysTransition )
- caps |= FCAP_NOTIFY_ON_TRANSITION;
- return caps;
-}
-
-void CNPC_AttackHelicopter::InputOutsideTransition( inputdata_t &inputdata )
-{
- CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, m_iszTransitionTarget );
-
- if ( pEnt )
- {
- Vector teleportLocation = pEnt->GetAbsOrigin();
- QAngle teleportAngles = pEnt->GetAbsAngles();
- Teleport( &teleportLocation, &teleportAngles, &vec3_origin );
- Teleported();
- }
- else
- {
- DevMsg( 2, "NPC \"%s\" failed to find a suitable transition a point\n", STRING(GetEntityName()) );
- }
-}
-
-void CNPC_AttackHelicopter::InputSetOutsideTransitionTarget( inputdata_t &inputdata )
-{
- m_iszTransitionTarget = MAKE_STRING( inputdata.value.String() );
-}
-
-
-//-----------------------------------------------------------------------------
-// Create components
-//-----------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::CreateComponents()
-{
- if ( !BaseClass::CreateComponents() )
- return false;
-
- m_Spotlight.Init( this, AI_SPOTLIGHT_NO_DLIGHTS, 45.0f, 500.0f );
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose :
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::Spawn( void )
-{
- Precache( );
-
- m_bIndestructible = false;
- m_bDeadlyShooting = false;
- m_bBombingSuppressed = false;
- m_bIgnorePathVisibilityTests = false;
-
- if ( !HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
- {
- SetModel( CHOPPER_MODEL_NAME );
- }
- else
- {
- SetModel( CHOPPER_DRONE_NAME );
- }
-
- ExtractBbox( SelectHeaviestSequence( ACT_IDLE ), m_cullBoxMins, m_cullBoxMaxs );
- GetEnemies()->SetFreeKnowledgeDuration( DEFAULT_FREE_KNOWLEDGE_DURATION );
-
- float flLoadedSpeed = m_flMaxSpeed;
- BaseClass::Spawn();
-
- float flChaseDist = HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) ?
- CHOPPER_MIN_AGGRESSIVE_CHASE_DIST_DIFF : CHOPPER_MIN_CHASE_DIST_DIFF;
- InitPathingData( CHOPPER_ARRIVE_DIST, flChaseDist, CHOPPER_AVOID_DIST );
- SetFarthestPathDist( GetMaxFiringDistance() );
-
- m_takedamage = DAMAGE_YES;
- m_nGunState = GUN_STATE_IDLE;
- SetHullType( HULL_LARGE_CENTERED );
-
- SetHullSizeNormal();
-
-#ifdef HL2_EPISODIC
- CreateVPhysics();
-#endif // HL2_EPISODIC
-
- SetPauseState( PAUSE_NO_PAUSE );
-
- m_iMaxHealth = m_iHealth = sk_helicopter_health.GetInt();
-
- m_flMaxSpeed = flLoadedSpeed;
- if ( m_flMaxSpeed <= 0 )
- {
- m_flMaxSpeed = CHOPPER_MAX_SPEED;
- }
- m_flNextMegaBombHealth = m_iMaxHealth - m_iMaxHealth * g_helicopter_bullrush_mega_bomb_health.GetFloat();
-
- m_nGrenadeCount = CHOPPER_BOMB_DROP_COUNT;
-
- m_flFieldOfView = -1.0; // 360 degrees
- m_flIdleTimeDelay = 0.0f;
- m_iAmmoType = GetAmmoDef()->Index("HelicopterGun");
-
- InitBoneControllers();
-
- m_fHelicopterFlags = BITS_HELICOPTER_GUN_ON;
- m_bSuppressSound = false;
-
- m_flAcrossTime = -1.0f;
- m_flPathOffset = 0.0f;
- m_flCurrPathOffset = 0.0f;
- m_nAttackMode = ATTACK_MODE_DEFAULT;
- m_flInputDropBombTime = gpGlobals->curtime;
- SetActivity( ACT_IDLE );
-
- int nBombAttachment = LookupAttachment("bomb");
- m_hSensor = static_cast<CBombDropSensor*>(CreateEntityByName( "npc_helicoptersensor" ));
- m_hSensor->Spawn();
- m_hSensor->SetParent( this, nBombAttachment );
- m_hSensor->SetLocalOrigin( vec3_origin );
- m_hSensor->SetLocalAngles( vec3_angle );
- m_hSensor->SetOwnerEntity( this );
-
- AddFlag( FL_AIMTARGET );
-
- m_hCrashPoint.Set( NULL );
-}
-
-#ifdef HL2_EPISODIC
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::CreateVPhysics( void )
-{
- InitBoneFollowers();
- return BaseClass::CreateVPhysics();
-}
-#endif // HL2_EPISODIC
-
-//------------------------------------------------------------------------------
-// Startup the chopper
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::Startup()
-{
- BaseClass::Startup();
-
- if ( HasSpawnFlags( SF_HELICOPTER_LIGHTS ) )
- {
- for ( int i = 0; i < MAX_HELICOPTER_LIGHTS; ++i )
- {
- // See if there's an attachment for this smoke trail
- char buf[32];
- Q_snprintf( buf, 32, "Light_Red%d", i );
- int nAttachment = LookupAttachment( buf );
- if ( nAttachment == 0 )
- {
- m_hLights[i] = NULL;
- continue;
- }
-
- m_hLights[i] = CSprite::SpriteCreate( CHOPPER_RED_LIGHT_SPRITE, vec3_origin, false );
- if ( !m_hLights[i] )
- continue;
-
- m_hLights[i]->SetParent( this, nAttachment );
- m_hLights[i]->SetLocalOrigin( vec3_origin );
- m_hLights[i]->SetLocalVelocity( vec3_origin );
- m_hLights[i]->SetMoveType( MOVETYPE_NONE );
- m_hLights[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 200, kRenderFxNone );
- m_hLights[i]->SetScale( 1.0f );
- m_hLights[i]->TurnOn();
- }
-
- SetContextThink( &CNPC_AttackHelicopter::BlinkLightsThink, gpGlobals->curtime + CHOPPER_LIGHT_BLINK_TIME_SHORT, s_pBlinkLightThinkContext );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Startup the chopper
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::BlinkLightsThink()
-{
- bool bIsOn = false;
- for ( int i = 0; i < MAX_HELICOPTER_LIGHTS; ++i )
- {
- if ( !m_hLights[i] )
- continue;
-
- if ( m_hLights[i]->GetScale() > 0.1f )
- {
- m_hLights[i]->SetScale( 0.1f, CHOPPER_LIGHT_BLINK_TIME_SHORT );
- }
- else
- {
- m_hLights[i]->SetScale( 0.5f, 0.0f );
- bIsOn = true;
- }
- }
-
- float flTime;
- if ( bIsOn )
- {
- flTime = CHOPPER_LIGHT_BLINK_TIME_SHORT;
- }
- else
- {
- flTime = m_bShortBlink ? CHOPPER_LIGHT_BLINK_TIME_SHORT : CHOPPER_LIGHT_BLINK_TIME;
- m_bShortBlink = !m_bShortBlink;
- }
-
- SetContextThink( &CNPC_AttackHelicopter::BlinkLightsThink, gpGlobals->curtime + flTime, s_pBlinkLightThinkContext );
-}
-
-
-//------------------------------------------------------------------------------
-// Start up spotlights
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::SpotlightStartup()
-{
- if ( !HasSpawnFlags( SF_HELICOPTER_LIGHTS ) )
- return;
-
- Vector vecForward;
- Vector vecOrigin;
- GetAttachment( m_nSpotlightAttachment, vecOrigin, &vecForward );
- m_Spotlight.SpotlightCreate( m_nSpotlightAttachment, vecForward );
- SpotlightThink();
-}
-
-
-//------------------------------------------------------------------------------
-// Shutdown spotlights
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::SpotlightShutdown()
-{
- m_Spotlight.SpotlightDestroy();
- SetContextThink( NULL, gpGlobals->curtime, s_pSpotlightThinkContext );
-}
-
-
-//------------------------------------------------------------------------------
-// Spotlights
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::SpotlightThink()
-{
- // NOTE: This function should deal with all deactivation cases
- if ( m_lifeState != LIFE_ALIVE )
- {
- SpotlightShutdown();
- return;
- }
-
- switch( m_nAttackMode )
- {
- case ATTACK_MODE_BULLRUSH_VEHICLE:
- {
- switch ( m_nSecondaryMode )
- {
- case BULLRUSH_MODE_SHOOT_GUN:
- {
- Vector vecForward;
- Vector vecOrigin;
- GetAttachment( m_nSpotlightAttachment, vecOrigin, &vecForward );
- m_Spotlight.SetSpotlightTargetDirection( vecForward );
- }
- break;
-
- case BULLRUSH_MODE_SHOOT_IDLE_PLAYER:
- if ( GetEnemy() )
- {
- m_Spotlight.SetSpotlightTargetPos( GetEnemy()->WorldSpaceCenter() );
- }
- break;
-
- default:
- SpotlightShutdown();
- return;
- }
- }
- break;
-
- default:
- SpotlightShutdown();
- return;
- }
-
- m_Spotlight.Update();
- SetContextThink( &CNPC_AttackHelicopter::SpotlightThink, gpGlobals->curtime + TICK_INTERVAL, s_pSpotlightThinkContext );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Always transition along with the player
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputEnableAlwaysTransition( inputdata_t &inputdata )
-{
- m_bAlwaysTransition = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Stop always transitioning along with the player
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputDisableAlwaysTransition( inputdata_t &inputdata )
-{
- m_bAlwaysTransition = false;
-}
-
-//------------------------------------------------------------------------------
-// On Remove
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::UpdateOnRemove()
-{
- BaseClass::UpdateOnRemove();
- StopLoopingSounds();
- UTIL_Remove(m_hSensor);
- DestroySmokeTrails();
- for ( int i = 0; i < MAX_HELICOPTER_LIGHTS; ++i )
- {
- if ( m_hLights[i] )
- {
- UTIL_Remove( m_hLights[i] );
- m_hLights[i] = NULL;
- }
- }
-
-#ifdef HL2_EPISODIC
- m_BoneFollowerManager.DestroyBoneFollowers();
-#endif // HL2_EPISODIC
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::Activate( void )
-{
- BaseClass::Activate();
- m_nGunBaseAttachment = LookupAttachment("gun");
- m_nGunTipAttachment = LookupAttachment("muzzle");
- m_nBombAttachment = LookupAttachment("bomb");
- m_nSpotlightAttachment = LookupAttachment("spotlight");
-
- if ( HasSpawnFlags( SF_HELICOPTER_LONG_SHADOW ) )
- {
- SetShadowCastDistance( 2048 );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-const char *CNPC_AttackHelicopter::GetTracerType( void )
-{
- return "HelicopterTracer";
-}
-
-
-//-----------------------------------------------------------------------------
-// Allows the shooter to change the impact effect of his bullets
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::DoImpactEffect( trace_t &tr, int nDamageType )
-{
- UTIL_ImpactTrace( &tr, nDamageType, "HelicopterImpact" );
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose : Create our rotor sound
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InitializeRotorSound( void )
-{
- if ( !m_pRotorSound )
- {
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- CPASAttenuationFilter filter( this );
-
- if ( HasSpawnFlags( SF_HELICOPTER_LOUD_ROTOR_SOUND ) )
- {
- m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.RotorsLoud" );
- }
- else
- {
- m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.Rotors" );
- }
-
- m_pRotorBlast = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.RotorBlast" );
- m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.FireGun" );
- controller.Play( m_pGunFiringSound, 0.0, 100 );
- }
- else
- {
- Assert(m_pRotorSound);
- Assert(m_pRotorBlast);
- Assert(m_pGunFiringSound);
- }
-
-
- BaseClass::InitializeRotorSound();
-}
-
-
-//------------------------------------------------------------------------------
-// Gets the max speed of the helicopter
-//------------------------------------------------------------------------------
-float CNPC_AttackHelicopter::GetMaxSpeed()
-{
- if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
- return DRONE_SPEED;
-
- if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && IsInSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED ) )
- return CHOPPER_BULLRUSH_ENEMY_BOMB_SPEED;
-
- if ( !GetEnemyVehicle() )
- return BaseClass::GetMaxSpeed();
-
- return 3000.0f;
-}
-
-float CNPC_AttackHelicopter::GetMaxSpeedFiring()
-{
- if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
- return DRONE_SPEED;
-
- if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && IsInSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED ) )
- return CHOPPER_BULLRUSH_ENEMY_BOMB_SPEED;
-
- if ( !GetEnemyVehicle() )
- return BaseClass::GetMaxSpeedFiring();
-
- return 3000.0f;
-}
-
-
-//------------------------------------------------------------------------------
-// Returns the max firing distance
-//------------------------------------------------------------------------------
-float CNPC_AttackHelicopter::GetMaxFiringDistance()
-{
- if ( !GetEnemyVehicle() )
- return CHOPPER_GUN_MAX_FIRING_DIST;
-
- return 8000.0f;
-}
-
-
-//------------------------------------------------------------------------------
-// Updates the enemy
-//------------------------------------------------------------------------------
-float CNPC_AttackHelicopter::EnemySearchDistance( )
-{
- return 6000.0f;
-}
-
-
-//------------------------------------------------------------------------------
-// Leading behaviors
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputStartBombingVehicle( inputdata_t &inputdata )
-{
- m_nAttackMode = ATTACK_MODE_BOMB_VEHICLE;
- SetLeadingDistance( 1500.0f );
-}
-
-void CNPC_AttackHelicopter::InputStartTrailingVehicle( inputdata_t &inputdata )
-{
- m_nAttackMode = ATTACK_MODE_TRAIL_VEHICLE;
- SetLeadingDistance( -1500.0f );
-}
-
-void CNPC_AttackHelicopter::InputStartDefaultBehavior( inputdata_t &inputdata )
-{
- m_nAttackMode = ATTACK_MODE_DEFAULT;
-}
-
-void CNPC_AttackHelicopter::InputStartAlwaysLeadingVehicle( inputdata_t &inputdata )
-{
- m_nAttackMode = ATTACK_MODE_ALWAYS_LEAD_VEHICLE;
- SetLeadingDistance( 0.0f );
-}
-
-void CNPC_AttackHelicopter::InputStartBullrushBehavior( inputdata_t &inputdata )
-{
- if ( m_nAttackMode != ATTACK_MODE_BULLRUSH_VEHICLE )
- {
- m_nAttackMode = ATTACK_MODE_BULLRUSH_VEHICLE;
- SetSecondaryMode( BULLRUSH_MODE_WAIT_FOR_ENEMY );
- SetLeadingDistance( 0.0f );
- }
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputStartCarpetBombing( inputdata_t &inputdata )
-{
- m_bIsCarpetBombing = true;
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputStopCarpetBombing( inputdata_t &inputdata )
-{
- m_bIsCarpetBombing = false;
-}
-
-//------------------------------------------------------------------------------
-// Become indestructible
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputBecomeIndestructible( inputdata_t &inputdata )
-{
- m_bIndestructible = true;
-}
-
-
-//------------------------------------------------------------------------------
-// Deadly shooting, tex!
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputEnableDeadlyShooting( inputdata_t &inputdata )
-{
- m_bDeadlyShooting = true;
-}
-
-void CNPC_AttackHelicopter::InputDisableDeadlyShooting( inputdata_t &inputdata )
-{
- m_bDeadlyShooting = false;
-}
-
-void CNPC_AttackHelicopter::InputStartNormalShooting( inputdata_t &inputdata )
-{
- m_nShootingMode = SHOOT_MODE_DEFAULT;
-}
-
-void CNPC_AttackHelicopter::InputStartLongCycleShooting( inputdata_t &inputdata )
-{
- m_nShootingMode = SHOOT_MODE_LONG_CYCLE;
-}
-
-void CNPC_AttackHelicopter::InputStartContinuousShooting( inputdata_t &inputdata )
-{
- m_nShootingMode = SHOOT_MODE_CONTINUOUS;
-}
-
-void CNPC_AttackHelicopter::InputStartFastShooting( inputdata_t &inputdata )
-{
- m_nShootingMode = SHOOT_MODE_FAST;
-}
-
-//------------------------------------------------------------------------------
-// Deadly shooting, tex!
-//------------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::IsDeadlyShooting()
-{
- if ( m_bDeadlyShooting )
- return true;
-
- if (( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) )
- {
- return (!GetEnemyVehicle()) && GetEnemy() && GetEnemy()->IsPlayer();
- }
-
- return false;
-}
-
-int CNPC_AttackHelicopter::GetShootingMode( )
-{
- if ( IsDeadlyShooting() )
- return SHOOT_MODE_LONG_CYCLE;
-
- if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
- return SHOOT_MODE_CONTINUOUS;
-
- return m_nShootingMode;
-}
-
-
-//-----------------------------------------------------------------------------
-// Bombing suppression
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputEnableBombing( inputdata_t &inputdata )
-{
- m_bBombingSuppressed = false;
-}
-
-void CNPC_AttackHelicopter::InputDisableBombing( inputdata_t &inputdata )
-{
- m_bBombingSuppressed = true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Visibility tests
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputDisablePathVisibilityTests( inputdata_t &inputdata )
-{
- m_bIgnorePathVisibilityTests = true;
- GetEnemies()->SetUnforgettable( GetEnemy(), true );
-}
-
-void CNPC_AttackHelicopter::InputEnablePathVisibilityTests( inputdata_t &inputdata )
-{
- m_bIgnorePathVisibilityTests = false;
- GetEnemies()->SetUnforgettable( GetEnemy(), false );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputSelfDestruct( inputdata_t &inputdata )
-{
- m_lifeState = LIFE_ALIVE; // Force to die properly.
- CTakeDamageInfo info( this, this, Vector(0, 0, 1), WorldSpaceCenter(), GetMaxHealth(), CLASS_MISSILE );
- TakeDamage( info );
-}
-
-//-----------------------------------------------------------------------------
-// For scripted times where it *has* to shoot
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputSetHealthFraction( inputdata_t &inputdata )
-{
- // Sets the health fraction, no damage effects
- if ( inputdata.value.Float() > 0 )
- {
- SetHealth( GetMaxHealth() * inputdata.value.Float() * 0.01f );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &inputdata -
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputStartBombExplodeOnContact( inputdata_t &inputdata )
-{
- m_bBombsExplodeOnContact = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &inputdata -
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputStopBombExplodeOnContact( inputdata_t &inputdata )
-{
- m_bBombsExplodeOnContact = false;
-}
-
-//------------------------------------------------------------------------------
-// For scripted times where it *has* to shoot
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputResetIdleTime( inputdata_t &inputdata )
-{
- if ( m_nGunState == GUN_STATE_IDLE )
- {
- m_flNextAttack = gpGlobals->curtime;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// This trace filter ignores all breakables + physics props
-//-----------------------------------------------------------------------------
-class CTraceFilterChopper : public CTraceFilterSimple
-{
- DECLARE_CLASS( CTraceFilterChopper, CTraceFilterSimple );
-
-public:
- CTraceFilterChopper( const IHandleEntity *passentity, int collisionGroup );
- virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask );
-
-private:
- const IHandleEntity *m_pPassEnt;
- int m_collisionGroup;
-};
-
-CTraceFilterChopper::CTraceFilterChopper( const IHandleEntity *passentity, int collisionGroup ) :
- CTraceFilterSimple( passentity, collisionGroup )
-{
-}
-
-bool CTraceFilterChopper::ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
-{
- CBaseEntity *pEnt = static_cast<IServerUnknown*>(pServerEntity)->GetBaseEntity();
- if ( pEnt )
- {
- if ( FClassnameIs( pEnt, "func_breakable" ) ||
- FClassnameIs( pEnt, "func_physbox" ) ||
- FClassnameIs( pEnt, "prop_physics" ) ||
- FClassnameIs( pEnt, "physics_prop" ) )
- {
- return false;
- }
- }
-
- return BaseClass::ShouldHitEntity( pServerEntity, contentsMask );
-}
-
-
-//-----------------------------------------------------------------------------
-// Enemy visibility check
-//-----------------------------------------------------------------------------
-CBaseEntity *CNPC_AttackHelicopter::FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos )
-{
- if ( m_bIgnorePathVisibilityTests )
- return NULL;
-
- CTraceFilterChopper chopperFilter( this, COLLISION_GROUP_NONE );
-
- trace_t tr;
- AI_TraceHull( vecViewPoint, vecTargetPos, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT, &chopperFilter, &tr );
-
- if ( tr.fraction != 1.0f )
- {
- Assert( tr.m_pEnt );
- }
-
- return (tr.fraction != 1.0f) ? tr.m_pEnt : NULL;
-}
-
-
-//-----------------------------------------------------------------------------
-// More Enemy visibility check
-//-----------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
-{
- if ( pEntity->GetFlags() & FL_NOTARGET )
- return false;
-
-#if 0
- // FIXME: only block LOS through opaque water
- // don't look through water
- if ((m_nWaterLevel != 3 && pEntity->m_nWaterLevel == 3)
- || (m_nWaterLevel == 3 && pEntity->m_nWaterLevel == 0))
- return false;
-#endif
-
- Vector vecLookerOrigin = EyePosition();//look through the caller's 'eyes'
- Vector vecTargetOrigin = pEntity->EyePosition();
-
- CTraceFilterChopper chopperFilter( this, COLLISION_GROUP_NONE );
-
- trace_t tr;
- UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, traceMask, &chopperFilter, &tr);
-
- if (tr.fraction != 1.0)
- {
- // Got line of sight!
- if ( tr.m_pEnt == pEntity )
- return true;
-
- // Got line of sight on the vehicle the player is driving!
- if ( pEntity && pEntity->IsPlayer() )
- {
- CBasePlayer *pPlayer = assert_cast<CBasePlayer*>( pEntity );
- if ( tr.m_pEnt == pPlayer->GetVehicleEntity() )
- return true;
- }
-
- if (ppBlocker)
- {
- *ppBlocker = tr.m_pEnt;
- }
- return false;// Line of sight is not established
- }
-
- return true;// line of sight is valid.
-}
-
-
-//------------------------------------------------------------------------------
-// Shot spread
-//------------------------------------------------------------------------------
-#define PLAYER_TIGHTEN_FACTOR 0.75f
-Vector CNPC_AttackHelicopter::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
-{
- float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * PLAYER_TIGHTEN_FACTOR * 0.5f * (3.14f / 180.0f) );
- Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
- return vecSpread;
-}
-
-
-//------------------------------------------------------------------------------
-// Find interesting nearby things to shoot
-//------------------------------------------------------------------------------
-int CNPC_AttackHelicopter::BuildMissTargetList( int nCount, CBaseEntity **ppMissCandidates )
-{
- int numMissCandidates = 0;
-
- CBaseEntity *pEnts[256];
- Vector radius( 150, 150, 150 );
- const Vector &vecSource = GetEnemy()->WorldSpaceCenter();
-
- int numEnts = UTIL_EntitiesInBox( pEnts, 256, vecSource - radius, vecSource+radius, 0 );
-
- for ( int i = 0; i < numEnts; i++ )
- {
- if ( pEnts[i] == NULL )
- continue;
-
- if ( numMissCandidates >= nCount )
- break;
-
- // Miss candidates cannot include the player or his vehicle
- if ( pEnts[i] == GetEnemyVehicle() || pEnts[i] == GetEnemy() )
- continue;
-
- // See if it's a good target candidate
- if ( FClassnameIs( pEnts[i], "prop_dynamic" ) ||
- FClassnameIs( pEnts[i], "prop_physics" ) ||
- FClassnameIs( pEnts[i], "physics_prop" ) )
- {
- ppMissCandidates[numMissCandidates++] = pEnts[i];
- }
- }
-
- return numMissCandidates;
-}
-
-
-//------------------------------------------------------------------------------
-// Gets a vehicle the enemy is in (if any)
-//------------------------------------------------------------------------------
-CBaseEntity *CNPC_AttackHelicopter::GetEnemyVehicle()
-{
- if ( !GetEnemy() )
- return NULL;
-
- if ( !GetEnemy()->IsPlayer() )
- return NULL;
-
- return static_cast<CBasePlayer*>(GetEnemy())->GetVehicleEntity();
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::ShootAtPlayer( const Vector &vBasePos, const Vector &vGunDir )
-{
- // Fire one shots per round right at the player, using usual rules
- FireBulletsInfo_t info;
- info.m_vecSrc = vBasePos;
- info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
- info.m_flDistance = MAX_COORD_RANGE;
- info.m_iAmmoType = m_iAmmoType;
- info.m_iTracerFreq = 1;
- info.m_vecDirShooting = GetActualShootTrajectory( vBasePos );
- info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
-
- DoMuzzleFlash();
-
- QAngle vGunAng;
- VectorAngles( vGunDir, vGunAng );
-
- FireBullets( info );
-
- // Fire the rest of the bullets at objects around the player
- CBaseEntity *ppNearbyTargets[16];
- int nActualTargets = BuildMissTargetList( 16, ppNearbyTargets );
-
- // Randomly sort it...
- int i;
- for ( i = 0; i < nActualTargets; ++i )
- {
- int nSwap = random->RandomInt( 0, nActualTargets - 1 );
- V_swap( ppNearbyTargets[i], ppNearbyTargets[nSwap] );
- }
-
- // Just shoot where we're facing
- float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * 0.5f * (3.14f / 180.0f) );
- Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
-
- // How many times should we hit the player this time?
- int nDesiredHitCount = (int)(((float)( m_nMaxBurstHits - m_nBurstHits ) / (float)m_nRemainingBursts) + 0.5f);
- int nNearbyTargetCount = 0;
- int nPlayerShotCount = 0;
- for ( i = sk_helicopter_roundsperburst.GetInt() - 1; --i >= 0; )
- {
- // Find something interesting around the enemy to shoot instead of just missing.
- if ( nActualTargets > nNearbyTargetCount )
- {
- // FIXME: Constrain to the firing cone?
- ppNearbyTargets[nNearbyTargetCount]->CollisionProp()->RandomPointInBounds( Vector(.25, .25, .25), Vector(.75, .75, .75), &info.m_vecDirShooting );
- info.m_vecDirShooting -= vBasePos;
- VectorNormalize( info.m_vecDirShooting );
- info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
- info.m_flDistance = MAX_COORD_RANGE;
- info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
-
- FireBullets( info );
-
- ++nNearbyTargetCount;
- continue;
- }
-
- if ( GetEnemy() && ( nPlayerShotCount < nDesiredHitCount ))
- {
- GetEnemy()->CollisionProp()->RandomPointInBounds( Vector(0, 0, 0), Vector(1, 1, 1), &info.m_vecDirShooting );
- info.m_vecDirShooting -= vBasePos;
- VectorNormalize( info.m_vecDirShooting );
- info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
- info.m_flDistance = MAX_COORD_RANGE;
- info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
- FireBullets( info );
- ++nPlayerShotCount;
- continue;
- }
-
- // Nothing nearby; just fire randomly...
- info.m_vecDirShooting = vGunDir;
- info.m_vecSpread = vecSpread;
- info.m_flDistance = 8192;
- info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
-
- FireBullets( info );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Chooses a point within the circle of death to fire in
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::PickDirectionToCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition, Vector *pResult )
-{
- *pResult = vecFireAtPosition;
- float x, y;
- do
- {
- x = random->RandomFloat( -1.0f, 1.0f );
- y = random->RandomFloat( -1.0f, 1.0f );
- } while ( (x * x + y * y) > 1.0f );
-
- pResult->x += x * m_flCircleOfDeathRadius;
- pResult->y += y * m_flCircleOfDeathRadius;
-
- *pResult -= vBasePos;
- VectorNormalize( *pResult );
-}
-
-
-//-----------------------------------------------------------------------------
-// Deliberately aims as close as possible w/o hitting
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::AimCloseToTargetButMiss( CBaseEntity *pTarget, float flMinDist, float flMaxDist, const Vector &shootOrigin, Vector *pResult )
-{
- Vector vecDirection;
- VectorSubtract( pTarget->WorldSpaceCenter(), shootOrigin, vecDirection );
- float flDist = VectorNormalize( vecDirection );
- float flRadius = pTarget->BoundingRadius() + random->RandomFloat( flMinDist, flMaxDist );
-
- float flMinRadius = flRadius;
- if ( flDist > flRadius )
- {
- flMinRadius = flDist * flRadius / sqrt( flDist * flDist - flRadius * flRadius );
- }
-
- // Choose random points in a plane perpendicular to the shoot origin.
- Vector vecRandomDir;
- vecRandomDir.Random( -1.0f, 1.0f );
- VectorMA( vecRandomDir, -DotProduct( vecDirection, vecRandomDir ), vecDirection, vecRandomDir );
- VectorNormalize( vecRandomDir );
- vecRandomDir *= flMinRadius;
- vecRandomDir += pTarget->WorldSpaceCenter();
-
- VectorSubtract( vecRandomDir, shootOrigin, *pResult );
- VectorNormalize( *pResult );
-}
-
-
-//-----------------------------------------------------------------------------
-// Make sure we don't hit too many times
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::FireBullets( const FireBulletsInfo_t &info )
-{
- // Use this to count the number of hits in a burst
- bool bIsPlayer = GetEnemy() && GetEnemy()->IsPlayer();
- if ( !bIsPlayer )
- {
- BaseClass::FireBullets( info );
- return;
- }
-
- if ( !GetEnemyVehicle() && !IsDeadlyShooting() )
- {
- if ( m_nBurstHits >= m_nMaxBurstHits )
- {
- FireBulletsInfo_t actualInfo = info;
- actualInfo.m_pAdditionalIgnoreEnt = GetEnemy();
- BaseClass::FireBullets( actualInfo );
- return;
- }
- }
-
- CBasePlayer *pPlayer = assert_cast<CBasePlayer*>(GetEnemy());
-
- int nPrevHealth = pPlayer->GetHealth();
- int nPrevArmor = pPlayer->ArmorValue();
-
- BaseClass::FireBullets( info );
-
- if (( pPlayer->GetHealth() < nPrevHealth ) || ( pPlayer->ArmorValue() < nPrevArmor ))
- {
- ++m_nBurstHits;
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::ShootInsideCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition )
-{
- Vector vecFireDirection;
- if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
- {
- PickDirectionToCircleOfDeath( vBasePos, vecFireAtPosition, &vecFireDirection );
- }
- else if ( ( m_nNearShots < m_nMaxNearShots ) || !GetEnemyVehicle() )
- {
- if ( ( m_nBurstHits < m_nMaxBurstHits ) || !GetEnemy() )
- {
- ++m_nNearShots;
- PickDirectionToCircleOfDeath( vBasePos, vecFireAtPosition, &vecFireDirection );
- }
- else
- {
- m_nNearShots += 6;
- AimCloseToTargetButMiss( GetEnemy(), 20.0f, 50.0f, vBasePos, &vecFireDirection );
- }
- }
- else
- {
- AimCloseToTargetButMiss( GetEnemyVehicle(), 10.0f, 80.0f, vBasePos, &vecFireDirection );
- }
-
- FireBulletsInfo_t info( 1, vBasePos, vecFireDirection, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
- info.m_iTracerFreq = 1;
- info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
-
- FireBullets( info );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::DoMuzzleFlash( void )
-{
- BaseClass::DoMuzzleFlash();
-
- CEffectData data;
-
- data.m_nAttachmentIndex = LookupAttachment( "muzzle" );
- data.m_nEntIndex = entindex();
- DispatchEffect( "ChopperMuzzleFlash", data );
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-#define HIT_VEHICLE_SPEED_MIN 200.0f
-#define HIT_VEHICLE_SPEED_MAX 500.0f
-
-void CNPC_AttackHelicopter::ShootAtVehicle( const Vector &vBasePos, const Vector &vecFireAtPosition )
-{
- int nShotsRemaining = sk_helicopter_roundsperburst.GetInt();
-
- DoMuzzleFlash();
-
- // Do special code against episodic drivers
- if ( hl2_episodic.GetBool() )
- {
- Vector vecVelocity;
- GetEnemyVehicle()->GetVelocity( &vecVelocity, NULL );
-
- float flSpeed = clamp( vecVelocity.Length(), 0.0f, 400.0f );
- float flRange = RemapVal( flSpeed, 0.0f, 400.0f, 0.05f, 1.0f );
-
- // Alter each shot's trajectory based on our speed
- for ( int i = 0; i < nShotsRemaining; i++ )
- {
- Vector vecShotDir;
-
- // If they're at a dead stand-still, just hit them
- if ( flRange <= 0.1f )
- {
- VectorSubtract( GetEnemy()->EyePosition(), vBasePos, vecShotDir );
-
- Vector vecOffset;
- vecOffset.Random( -40.0f, 40.0f );
- vecShotDir += vecOffset;
- VectorNormalize( vecShotDir );
- }
- else
- {
- // Aim in a cone around them
- AimCloseToTargetButMiss( GetEnemy(), (3*12) * flRange, (10*12) * flRange, vBasePos, &vecShotDir );
- }
-
- FireBulletsInfo_t info( 1, vBasePos, vecShotDir, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
- info.m_iTracerFreq = 1;
- FireBullets( info );
- }
-
- // We opt out of the rest of the function
- // FIXME: Should we emulate the below functionality and have half the bullets attempt to miss admirably? -- jdw
- return;
- }
-
- // Pop one at the player based on how fast he's going
- if ( m_nBurstHits < m_nMaxBurstHits )
- {
- Vector vecDir;
- VectorSubtract( GetEnemy()->EyePosition(), vBasePos, vecDir );
-
- Vector vecOffset;
- vecOffset.Random( -5.0f, 5.0f );
- vecDir += vecOffset;
- VectorNormalize( vecDir );
-
- FireBulletsInfo_t info( 1, vBasePos, vecDir, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
- info.m_iTracerFreq = 1;
- FireBullets( info );
- --nShotsRemaining;
- }
-
- // Fire half of the bullets within the circle of death, the other half at interesting things
- int i;
- int nFireInCircle = nShotsRemaining >> 1;
- nShotsRemaining -= nFireInCircle;
- for ( i = 0; i < nFireInCircle; ++i )
- {
- ShootInsideCircleOfDeath( vBasePos, vecFireAtPosition );
- }
-
- // Fire the rest of the bullets at objects around the enemy
- CBaseEntity *ppNearbyTargets[16];
- int nActualTargets = BuildMissTargetList( 16, ppNearbyTargets );
-
- // Randomly sort it...
- for ( i = 0; i < nActualTargets; ++i )
- {
- int nSwap = random->RandomInt( 0, nActualTargets - 1 );
- V_swap( ppNearbyTargets[i], ppNearbyTargets[nSwap] );
- }
-
- // Just shoot where we're facing
- float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * 0.5f * (3.14f / 180.0f) );
- Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
-
- for ( i = nShotsRemaining; --i >= 0; )
- {
- // Find something interesting around the enemy to shoot instead of just missing.
- if ( nActualTargets > i )
- {
- Vector vecFireDirection;
- ppNearbyTargets[i]->CollisionProp()->RandomPointInBounds( Vector(.25, .25, .25), Vector(.75, .75, .75), &vecFireDirection );
- vecFireDirection -= vBasePos;
- VectorNormalize( vecFireDirection );
-
- // FIXME: Constrain to the firing cone?
-
- // I put in all the default arguments simply so I could guarantee the first shot of one of the bursts always hits
- FireBulletsInfo_t info( 1, vBasePos, vecFireDirection, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
- info.m_iTracerFreq = 1;
- FireBullets( info );
- }
- else
- {
- ShootInsideCircleOfDeath( vBasePos, vecFireAtPosition );
- }
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Various states of the helicopter firing...
-//------------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::PoseGunTowardTargetDirection( const Vector &vTargetDir )
-{
- Vector vecOut;
- VectorIRotate( vTargetDir, EntityToWorldTransform(), vecOut );
-
- QAngle angles;
- VectorAngles(vecOut, angles);
-
- if (angles.y > 180)
- {
- angles.y = angles.y - 360;
- }
- else if (angles.y < -180)
- {
- angles.y = angles.y + 360;
- }
- if (angles.x > 180)
- {
- angles.x = angles.x - 360;
- }
- else if (angles.x < -180)
- {
- angles.x = angles.x + 360;
- }
-
- if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && !IsInSecondaryMode(BULLRUSH_MODE_SHOOT_IDLE_PLAYER) && GetEnemy())
- {
- if ( GetEnemyVehicle() )
- {
- angles.x = clamp( angles.x, -12.0f, 0.0f );
- angles.y = clamp( angles.y, -10.0f, 10.0f );
- }
- else
- {
- angles.x = clamp( angles.x, -10.0f, 10.0f );
- angles.y = clamp( angles.y, -10.0f, 10.0f );
- }
- }
-
- if (angles.x > m_angGun.x)
- {
- m_angGun.x = MIN( angles.x, m_angGun.x + 12 );
- }
- if (angles.x < m_angGun.x)
- {
- m_angGun.x = MAX( angles.x, m_angGun.x - 12 );
- }
- if (angles.y > m_angGun.y)
- {
- m_angGun.y = MIN( angles.y, m_angGun.y + 12 );
- }
- if (angles.y < m_angGun.y)
- {
- m_angGun.y = MAX( angles.y, m_angGun.y - 12 );
- }
-
- SetPoseParameter( m_poseWeapon_Pitch, -m_angGun.x );
- SetPoseParameter( m_poseWeapon_Yaw, m_angGun.y );
-
- return true;
-}
-
-
-//------------------------------------------------------------------------------
-// Compute the enemy position (non-vehicle case)
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::ComputeFireAtPosition( Vector *pVecActualTargetPosition )
-{
- // Deal with various leading behaviors...
- *pVecActualTargetPosition = m_vecTargetPosition;
-}
-
-
-//------------------------------------------------------------------------------
-// Compute the enemy position (non-vehicle case)
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::ComputeVehicleFireAtPosition( Vector *pVecActualTargetPosition )
-{
- CBaseEntity *pVehicle = GetEnemyVehicle();
-
- // Make sure the circle of death doesn't move more than N units
- // This will cause the target to have to maintain a large enough speed
- *pVecActualTargetPosition = pVehicle->BodyTarget( GetAbsOrigin(), false );
-
-// NDebugOverlay::Box( *pVecActualTargetPosition,
-// Vector(-m_flCircleOfDeathRadius, -m_flCircleOfDeathRadius, 0),
-// Vector(m_flCircleOfDeathRadius, m_flCircleOfDeathRadius, 0),
-// 0, 0, 255, false, 0.1f );
-}
-
-
-//------------------------------------------------------------------------------
-// Here's what we do when we're looking for a target
-//------------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::DoGunIdle( const Vector &vGunDir, const Vector &vTargetDir )
-{
- // When bullrushing, skip the idle
- if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) &&
- ( IsInSecondaryMode( BULLRUSH_MODE_SHOOT_GUN ) || IsInSecondaryMode(BULLRUSH_MODE_SHOOT_IDLE_PLAYER) ) )
- {
- EmitSound( "NPC_AttackHelicopter.ChargeGun" );
- m_flChargeTime = gpGlobals->curtime + CHOPPER_GUN_CHARGE_TIME;
- m_nGunState = GUN_STATE_CHARGING;
- m_flCircleOfDeathRadius = CHOPPER_MAX_CIRCLE_OF_DEATH_RADIUS;
- return true;
- }
-
- // Can't continually fire....
- if (m_flNextAttack > gpGlobals->curtime)
- return false;
-
- // Don't fire if we're too far away, or if the enemy isn't in front of us
- if (!GetEnemy())
- return false;
-
- float flMaxDistSqr = GetMaxFiringDistance();
- flMaxDistSqr *= flMaxDistSqr;
-
- float flDistSqr = WorldSpaceCenter().DistToSqr( GetEnemy()->WorldSpaceCenter() );
- if (flDistSqr > flMaxDistSqr)
- return false;
-
- // If he's mostly within the cone, shoot away!
- float flChargeCone = sk_helicopter_firingcone.GetFloat() * 0.5f;
- if ( flChargeCone < 15.0f )
- {
- flChargeCone = 15.0f;
- }
-
- float flCosConeDegrees = cos( flChargeCone * (3.14f / 180.0f) );
- float fDotPr = DotProduct( vGunDir, vTargetDir );
- if (fDotPr < flCosConeDegrees)
- return false;
-
- // Fast shooting doesn't charge up
- if( m_nShootingMode == SHOOT_MODE_FAST )
- {
- m_flChargeTime = gpGlobals->curtime;
- m_nGunState = GUN_STATE_CHARGING;
- m_flAvoidMetric = 0.0f;
- m_vecLastAngVelocity.Init( 0, 0, 0 );
- }
- else
- {
- EmitSound( "NPC_AttackHelicopter.ChargeGun" );
- float flChargeTime = CHOPPER_GUN_CHARGE_TIME;
- float flVariance = flChargeTime * 0.1f;
- m_flChargeTime = gpGlobals->curtime + random->RandomFloat(flChargeTime - flVariance, flChargeTime + flVariance);
- m_nGunState = GUN_STATE_CHARGING;
- m_flAvoidMetric = 0.0f;
- m_vecLastAngVelocity.Init( 0, 0, 0 );
- }
-
- return true;
-}
-
-
-//------------------------------------------------------------------------------
-// How easy is the target to hit?
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::UpdateTargetHittability()
-{
- // This simply is a measure of how much juking is going on.
- // Along with how much steering is happening.
- if ( GetEnemyVehicle() )
- {
- Vector vecVelocity;
- AngularImpulse vecAngVelocity;
- GetEnemyVehicle()->GetVelocity( &vecVelocity, &vecAngVelocity );
-
- float flDist = fabs( vecAngVelocity.z - m_vecLastAngVelocity.z );
- m_flAvoidMetric += flDist;
- m_vecLastAngVelocity = vecAngVelocity;
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Here's what we do when we're getting ready to fire
-//------------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::DoGunCharging( )
-{
- // Update the target hittability, which will indicate how many hits we'll accept.
- UpdateTargetHittability();
-
- if ( m_flChargeTime > gpGlobals->curtime )
- return false;
-
- m_nGunState = GUN_STATE_FIRING;
-
- if ( HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) )
- {
- SetPauseState( PAUSE_AT_NEXT_LOS_POSITION );
- }
-
- int nHitFactor = 1;
- switch( GetShootingMode() )
- {
- case SHOOT_MODE_DEFAULT:
- case SHOOT_MODE_FAST:
- {
- int nBurstCount = sk_helicopter_burstcount.GetInt();
- m_nRemainingBursts = random->RandomInt( nBurstCount, 2.0 * nBurstCount );
- m_flIdleTimeDelay = 0.1f * ( m_nRemainingBursts - nBurstCount );
- }
- break;
-
- case SHOOT_MODE_LONG_CYCLE:
- {
- m_nRemainingBursts = 60;
- m_flIdleTimeDelay = 0.0f;
- nHitFactor = 2;
- }
- break;
-
- case SHOOT_MODE_CONTINUOUS:
- if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
- {
- // We're relying on the special aiming behavior for bullrushing to just randomly deal damage
- m_nRemainingBursts = 1;
- m_flIdleTimeDelay = 0.0f;
- }
- else
- {
- m_nRemainingBursts = 0;
- m_flIdleTimeDelay = 0.0f;
- nHitFactor = 1000;
- }
- break;
- }
-
- if ( !GetEnemyVehicle() )
- {
- m_nMaxBurstHits = !IsDeadlyShooting() ? random->RandomInt( 6, 9 ) : 200;
- m_nMaxNearShots = 10000;
- }
- else
- {
- Vector vecVelocity;
- GetEnemyVehicle()->GetVelocity( &vecVelocity, NULL );
- float flSpeed = vecVelocity.Length();
- flSpeed = clamp( flSpeed, 150.0f, 600.0f );
- flSpeed = RemapVal( flSpeed, 150.0f, 600.0f, 0.0f, 1.0f );
- float flAvoid = clamp( m_flAvoidMetric, 100.0f, 400.0f );
- flAvoid = RemapVal( flAvoid, 100.0f, 400.0f, 0.0f, 1.0f );
-
- float flTotal = 0.5f * ( flSpeed + flAvoid );
- int nHitCount = (int)(RemapVal( flTotal, 0.0f, 1.0f, 7, -0.5 ) + 0.5f);
-
- int nMin = nHitCount >= 1 ? nHitCount - 1 : 0;
- m_nMaxBurstHits = random->RandomInt( nMin, nHitCount + 1 );
-
- int nNearShots = (int)(RemapVal( flTotal, 0.0f, 1.0f, 70, 5 ) + 0.5f);
- int nMinNearShots = nNearShots >= 5 ? nNearShots - 5 : 0;
- m_nMaxNearShots = random->RandomInt( nMinNearShots, nNearShots + 5 );
-
- // Set up the circle of death parameters at this point
- m_flCircleOfDeathRadius = SimpleSplineRemapVal( flTotal, 0.0f, 1.0f,
- CHOPPER_MIN_CIRCLE_OF_DEATH_RADIUS, CHOPPER_MAX_CIRCLE_OF_DEATH_RADIUS );
- }
-
- m_nMaxBurstHits *= nHitFactor;
- m_nMaxNearShots *= nHitFactor;
-
- m_nBurstHits = 0;
- m_nNearShots = 0;
- return true;
-}
-
-
-//------------------------------------------------------------------------------
-// Shoot where we're facing
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::ShootAtFacingDirection( const Vector &vBasePos, const Vector &vGunDir, bool bFirstShotAccurate )
-{
- // Just shoot where we're facing
- float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * 0.5f * (3.14f / 180.0f) );
- Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
-
- int nShotCount = sk_helicopter_roundsperburst.GetInt();
- if ( bFirstShotAccurate && GetEnemy() )
- {
- // Check to see if the enemy is within his firing cone
- if ( GetEnemy() )
- {
- // Find the closest point to the gunDir
- const Vector &vecCenter = GetEnemy()->WorldSpaceCenter();
-
- float t;
- Vector vNearPoint;
- Vector vEndPoint;
- VectorMA( vBasePos, 1024.0f, vGunDir, vEndPoint );
- CalcClosestPointOnLine( vecCenter, vBasePos, vEndPoint, vNearPoint, &t );
- if ( t > 0.0f )
- {
- Vector vecDelta;
- VectorSubtract( vecCenter, vBasePos, vecDelta );
- float flDist = VectorNormalize( vecDelta );
- float flPerpDist = vecCenter.DistTo( vNearPoint );
- float flSinAngle = flPerpDist / flDist;
- if ( flSinAngle <= flSinConeDegrees )
- {
- FireBulletsInfo_t info( 1, vBasePos, vecDelta, VECTOR_CONE_PRECALCULATED, 8192, m_iAmmoType );
- info.m_iTracerFreq = 1;
- FireBullets( info );
- --nShotCount;
- }
- }
- }
- }
-
-#ifdef HL2_EPISODIC
- if( GetEnemy() != NULL )
- {
- CSoundEnt::InsertSound( SOUND_DANGER, GetEnemy()->WorldSpaceCenter(), 180.0f, 0.5f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
- }
-#endif//HL2_EPISODIC
-
- DoMuzzleFlash();
-
- FireBulletsInfo_t info( nShotCount, vBasePos, vGunDir, vecSpread, 8192, m_iAmmoType );
- info.m_iTracerFreq = 1;
- FireBullets( info );
-}
-
-
-//-----------------------------------------------------------------------------
-// Can we zap it?
-//-----------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::IsValidZapTarget( CBaseEntity *pTarget )
-{
- // Don't use the player or vehicle as a zap target, we'll do that ourselves.
- if ( pTarget->IsPlayer() || pTarget->GetServerVehicle() )
- return false;
-
- if ( pTarget == this )
- return false;
-
- if ( !pTarget->IsSolid() )
- return false;
-
- Assert( pTarget );
- IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
- int count = pTarget->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
- for ( int i = 0; i < count; i++ )
- {
- int material = pList[i]->GetMaterialIndex();
- const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material );
-
- // Is flesh or metal? Go for it!
- if ( pSurfaceData->game.material == CHAR_TEX_METAL ||
- pSurfaceData->game.material == CHAR_TEX_FLESH ||
- pSurfaceData->game.material == CHAR_TEX_VENT ||
- pSurfaceData->game.material == CHAR_TEX_GRATE ||
- pSurfaceData->game.material == CHAR_TEX_COMPUTER ||
- pSurfaceData->game.material == CHAR_TEX_BLOODYFLESH ||
- pSurfaceData->game.material == CHAR_TEX_ALIENFLESH )
- {
- return true;
- }
- }
- return false;
-}
-
-
-//------------------------------------------------------------------------------
-// Effects
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::CreateZapBeam( const Vector &vecTargetPos )
-{
- CEffectData data;
- data.m_nEntIndex = entindex();
- data.m_nAttachmentIndex = 0; // m_nGunTipAttachment;
- data.m_vOrigin = vecTargetPos;
- data.m_flScale = 5;
- DispatchEffect( "TeslaZap", data );
-}
-
-void CNPC_AttackHelicopter::CreateEntityZapEffect( CBaseEntity *pEnt )
-{
- CEffectData data;
- data.m_nEntIndex = pEnt->entindex();
- data.m_flMagnitude = 10;
- data.m_flScale = 1.0f;
- DispatchEffect( "TeslaHitboxes", data );
-}
-
-
-//------------------------------------------------------------------------------
-// Here's what we do when we *are* firing
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::FireElectricityGun( )
-{
- if ( m_flNextAttack > gpGlobals->curtime )
- return;
-
- EmitSound( "ReallyLoudSpark" );
-
- CBaseEntity *ppEnts[256];
- Vector vecCenter = WorldSpaceCenter();
- float flRadius = 500.0f;
- vecCenter.z -= flRadius * 0.8f;
- int nEntCount = UTIL_EntitiesInSphere( ppEnts, 256, vecCenter, flRadius, 0 );
- CBaseEntity *ppCandidates[256];
- int nCandidateCount = 0;
- int i;
- for ( i = 0; i < nEntCount; i++ )
- {
- if ( ppEnts[i] == NULL )
- continue;
-
- // Zap metal or flesh things.
- if ( !IsValidZapTarget( ppEnts[i] ) )
- continue;
-
- ppCandidates[ nCandidateCount++ ] = ppEnts[i];
- }
-
- // First, put a bolt in front of the player, at random
- float flDist = 1024;
- if ( GetEnemy() )
- {
- Vector vecDelta;
- Vector2DSubtract( GetEnemy()->WorldSpaceCenter().AsVector2D(), WorldSpaceCenter().AsVector2D(), vecDelta.AsVector2D() );
- vecDelta.z = 0.0f;
-
- flDist = VectorNormalize( vecDelta );
- Vector vecPerp( -vecDelta.y, vecDelta.x, 0.0f );
- int nBoltCount = (int)(ClampSplineRemapVal( flDist, 256.0f, 1024.0f, 8, 0 ) + 0.5f);
-
- for ( i = 0; i < nBoltCount; ++i )
- {
- Vector vecTargetPt = GetEnemy()->WorldSpaceCenter();
- VectorMA( vecTargetPt, random->RandomFloat( flDist + 100, flDist + 500 ), vecDelta, vecTargetPt );
- VectorMA( vecTargetPt, random->RandomFloat( -500, 500 ), vecPerp, vecTargetPt );
- vecTargetPt.z += random->RandomFloat( -500, 500 );
- CreateZapBeam( vecTargetPt );
- }
- }
-
- // Next, choose the number of bolts...
- int nBoltCount = random->RandomInt( 8, 16 );
- for ( i = 0; i < nBoltCount; ++i )
- {
- if ( (nCandidateCount > 0) && random->RandomFloat( 0.0f, 1.0f ) < 0.6f )
- {
- --nCandidateCount;
-
- Vector vecTarget;
- ppCandidates[nCandidateCount]->CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecTarget );
- CreateZapBeam( vecTarget );
- CreateEntityZapEffect( ppCandidates[nCandidateCount] );
- }
- else
- {
- // Select random point *on* sphere
- Vector vecTargetPt;
- float flEffectRadius = random->RandomFloat( flRadius * 1.2, flRadius * 1.5f );
- float flTheta = random->RandomFloat( 0.0f, 2.0f * M_PI );
- float flPhi = random->RandomFloat( -0.5f * M_PI, 0.5f * M_PI );
- vecTargetPt.x = cos(flTheta) * cos(flPhi);
- vecTargetPt.y = sin(flTheta) * cos(flPhi);
- vecTargetPt.z = sin(flPhi);
- vecTargetPt *= flEffectRadius;
- vecTargetPt += vecCenter;
-
- CreateZapBeam( vecTargetPt );
- }
- }
-
- // Finally, put a bolt right at the player, at random
- float flHitRatio = ClampSplineRemapVal( flDist, 128.0f, 512.0f, 0.75f, 0.0f );
- if ( random->RandomFloat( 0.0f, 1.0f ) < flHitRatio )
- {
- if ( GetEnemyVehicle() )
- {
- Vector vecTarget;
- GetEnemyVehicle()->CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecTarget );
- CreateZapBeam( vecTarget );
- CreateEntityZapEffect( GetEnemyVehicle() );
-
- CTakeDamageInfo info( this, this, 5, DMG_SHOCK );
- GetEnemy()->TakeDamage( info );
- }
- else if ( GetEnemy() )
- {
- Vector vecTarget;
- GetEnemy()->CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecTarget );
- CreateZapBeam( vecTarget );
-
- CTakeDamageInfo info( this, this, 5, DMG_SHOCK );
- GetEnemy()->TakeDamage( info );
- }
- }
-
- m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 0.3f, 1.0f );
-}
-
-
-//------------------------------------------------------------------------------
-// Here's what we do when we *are* firing
-//------------------------------------------------------------------------------
-#define INTERVAL_BETWEEN_HITS 4
-
-bool CNPC_AttackHelicopter::DoGunFiring( const Vector &vBasePos, const Vector &vGunDir, const Vector &vecFireAtPosition )
-{
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- float flVolume = controller.SoundGetVolume( m_pGunFiringSound );
- if ( flVolume != 1.0f )
- {
- controller.SoundChangeVolume( m_pGunFiringSound, 1.0, 0.01f );
- }
-
- if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && ( IsInSecondaryMode( BULLRUSH_MODE_SHOOT_GUN ) ) )
- {
- ShootAtFacingDirection( vBasePos, vGunDir, m_nRemainingBursts == 0 );
- }
- else if ( GetEnemyVehicle() )
- {
- ShootAtVehicle( vBasePos, vecFireAtPosition );
- }
- else if ( GetEnemy() && GetEnemy()->IsPlayer() )
- {
- if ( !IsDeadlyShooting() )
- {
- ShootAtPlayer( vBasePos, vGunDir );
- }
- else
- {
- ShootAtFacingDirection( vBasePos, vGunDir, true );
- }
- }
- else
- {
- ShootAtFacingDirection( vBasePos, vGunDir, false );
- }
-
- if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
- {
- if ( --m_nRemainingBursts < 0 )
- {
- m_nRemainingBursts = INTERVAL_BETWEEN_HITS;
- }
- return true;
- }
-
- --m_nRemainingBursts;
- if ( m_nRemainingBursts > 0 )
- return true;
-
- controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.01f );
- float flIdleTime = CHOPPER_GUN_IDLE_TIME;
- float flVariance = flIdleTime * 0.1f;
- m_flNextAttack = gpGlobals->curtime + m_flIdleTimeDelay + random->RandomFloat(flIdleTime - flVariance, flIdleTime + flVariance);
- m_nGunState = GUN_STATE_IDLE;
- SetPauseState( PAUSE_NO_PAUSE );
- return true;
-}
-
-
-//------------------------------------------------------------------------------
-// Is it "fair" to drop this bomb?
-//------------------------------------------------------------------------------
-#define MIN_BOMB_DISTANCE_SQR ( 600.0f * 600.0f )
-
-bool CNPC_AttackHelicopter::IsBombDropFair( const Vector &vecBombStartPos, const Vector &vecBombVelocity )
-{
- if ( (m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE) && IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) )
- return true;
-
- // Can happen if you're noclipping around
- if ( !GetEnemy() )
- return false;
-
- // If the player is moving slowly, it's fair
- if ( GetEnemy()->GetSmoothedVelocity().LengthSqr() < ( CHOPPER_SLOW_BOMB_SPEED * CHOPPER_SLOW_BOMB_SPEED ) )
- return true;
-
- // Skip out if we're right above or behind the player.. that's unfair
- if ( GetEnemy() && GetEnemy()->IsPlayer() )
- {
- // How much time will it take to fall?
- // dx = 0.5 * a * t^2
- Vector vecTarget = GetEnemy()->BodyTarget( GetAbsOrigin(), false );
- float dz = vecBombStartPos.z - vecTarget.z;
- float dt = (dz > 0.0f) ? sqrt( 2 * dz / GetCurrentGravity() ) : 0.0f;
-
- // Where will the enemy be in that time?
- Vector vecEnemyVel = GetEnemy()->GetSmoothedVelocity();
- VectorMA( vecTarget, dt, vecEnemyVel, vecTarget );
-
- // Where will the bomb be in that time?
- Vector vecBomb;
- VectorMA( vecBombStartPos, dt, vecBombVelocity, vecBomb );
-
- float flEnemySpeed = vecEnemyVel.LengthSqr();
- flEnemySpeed = clamp( flEnemySpeed, 200.0f, 500.0f );
- float flDistFactorSq = RemapVal( flEnemySpeed, 200.0f, 500.0f, 0.3f, 1.0f );
- flDistFactorSq *= flDistFactorSq;
-
- // If it's too close, then we're not doing it.
- if ( vecBomb.AsVector2D().DistToSqr( vecTarget.AsVector2D() ) < (flDistFactorSq * MIN_BOMB_DISTANCE_SQR) )
- return false;
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Create the bomb entity and set it up
-// Input : &vecPos - Position to spawn at
-// &vecVelocity - velocity to spawn with
-//-----------------------------------------------------------------------------
-CGrenadeHelicopter *CNPC_AttackHelicopter::SpawnBombEntity( const Vector &vecPos, const Vector &vecVelocity )
-{
- // Create the grenade and set it up
- CGrenadeHelicopter *pGrenade = static_cast<CGrenadeHelicopter*>(CreateEntityByName( "grenade_helicopter" ));
- pGrenade->SetAbsOrigin( vecPos );
- pGrenade->SetOwnerEntity( this );
- pGrenade->SetThrower( this );
- pGrenade->SetAbsVelocity( vecVelocity );
- DispatchSpawn( pGrenade );
- pGrenade->SetExplodeOnContact( m_bBombsExplodeOnContact );
-
-#ifdef HL2_EPISODIC
- // Disable collisions with the owner's bone followers while we drop
- physfollower_t *pFollower = m_BoneFollowerManager.GetBoneFollower( 0 );
- if ( pFollower )
- {
- CBaseEntity *pBoneFollower = pFollower->hFollower;
- PhysDisableEntityCollisions( pBoneFollower, pGrenade );
- pGrenade->SetCollisionObject( pBoneFollower );
- }
-#endif // HL2_EPISODIC
-
- return pGrenade;
-}
-
-//------------------------------------------------------------------------------
-// Actually drops the bomb
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::CreateBomb( bool bCheckForFairness, Vector *pVecVelocity, bool bMegaBomb )
-{
- if ( m_bBombingSuppressed )
- return;
-
- Vector vTipPos;
- GetAttachment( m_nBombAttachment, vTipPos );
-
- if ( !CBombSuppressor::CanBomb( vTipPos ) )
- return;
-
- // Compute velocity
- Vector vecActualVelocity;
- if ( !pVecVelocity )
- {
- Vector vecAcross;
- vecActualVelocity = GetAbsVelocity();
- CrossProduct( vecActualVelocity, Vector( 0, 0, 1 ), vecAcross );
- VectorNormalize( vecAcross );
- vecAcross *= random->RandomFloat( 10.0f, 30.0f );
- vecAcross *= random->RandomFloat( 0.0f, 1.0f ) < 0.5f ? 1.0f : -1.0f;
-
- // Blat out z component of velocity if it's moving upward....
- if ( vecActualVelocity.z > 0 )
- {
- vecActualVelocity.z = 0.0f;
- }
-
- vecActualVelocity += vecAcross;
- }
- else
- {
- vecActualVelocity = *pVecVelocity;
- }
-
- if ( bCheckForFairness )
- {
- if ( !IsBombDropFair( vTipPos, vecActualVelocity ) )
- return;
- }
-
- AddGesture( (Activity)ACT_HELICOPTER_DROP_BOMB );
- EmitSound( "NPC_AttackHelicopter.DropMine" );
-
- // Make the bomb and send it off
- CGrenadeHelicopter *pGrenade = SpawnBombEntity( vTipPos, vecActualVelocity );
- if ( pGrenade && bMegaBomb )
- {
- pGrenade->AddSpawnFlags( SF_GRENADE_HELICOPTER_MEGABOMB );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Drop a bomb at a particular location
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputDropBomb( inputdata_t &inputdata )
-{
- if ( m_flInputDropBombTime > gpGlobals->curtime )
- return;
-
- // Prevent two triggers from being hit the same frame
- m_flInputDropBombTime = gpGlobals->curtime + 0.01f;
-
- CreateBomb( );
-
- // If we're in the middle of a bomb dropping schedule, wait to drop another bomb.
- if ( ShouldDropBombs() )
- {
- m_flNextAttack = gpGlobals->curtime + 0.5f + random->RandomFloat( 0.3f, 0.6f );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Drops a bomb straight downwards
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputDropBombStraightDown( inputdata_t &inputdata )
-{
- if ( m_flInputDropBombTime > gpGlobals->curtime )
- return;
-
- // Prevent two triggers from being hit the same frame
- m_flInputDropBombTime = gpGlobals->curtime + 0.01f;
-
- Vector vTipPos;
- GetAttachment( m_nBombAttachment, vTipPos );
-
- // Make the bomb drop straight down
- SpawnBombEntity( vTipPos, vec3_origin );
-
- // If we're in the middle of a bomb dropping schedule, wait to drop another bomb.
- if ( ShouldDropBombs() )
- {
- m_flNextAttack = gpGlobals->curtime + 0.5f + random->RandomFloat( 0.3f, 0.6f );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Drop a bomb at a particular location
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputDropBombAtTargetInternal( inputdata_t &inputdata, bool bCheckFairness )
-{
- if ( m_flInputDropBombTime > gpGlobals->curtime )
- return;
-
- // Prevent two triggers from being hit the same frame
- m_flInputDropBombTime = gpGlobals->curtime + 0.01f;
-
- // Find our specified target
- string_t strBombTarget = MAKE_STRING( inputdata.value.String() );
- CBaseEntity *pBombEnt = gEntList.FindEntityByName( NULL, strBombTarget );
- if ( pBombEnt == NULL )
- {
- Warning( "%s: Could not find bomb drop target '%s'!\n", GetClassname(), STRING( strBombTarget ) );
- return;
- }
-
- Vector vTipPos;
- GetAttachment( m_nBombAttachment, vTipPos );
-
- // Compute the time it would take to fall to the target
- Vector vecTarget = pBombEnt->BodyTarget( GetAbsOrigin(), false );
- float dz = vTipPos.z - vecTarget.z;
- if ( dz <= 0.0f )
- {
- Warning("Bomb target %s is above the chopper!\n", STRING( strBombTarget ) );
- return;
- }
- float dt = sqrt( 2 * dz / GetCurrentGravity() );
-
- // Compute the velocity that would make it happen
- Vector vecVelocity;
- VectorSubtract( vecTarget, vTipPos, vecVelocity );
- vecVelocity /= dt;
- vecVelocity.z = 0.0f;
-
- if ( bCheckFairness )
- {
- if ( !IsBombDropFair( vTipPos, vecVelocity ) )
- return;
- }
-
- // Make the bomb and send it off
- SpawnBombEntity( vTipPos, vecVelocity );
-
- // If we're in the middle of a bomb dropping schedule, wait to drop another bomb.
- if ( ShouldDropBombs() )
- {
- m_flNextAttack = gpGlobals->curtime + 1.5f + random->RandomFloat( 0.1f, 0.2f );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Drop a bomb at a particular location
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputDropBombAtTargetAlways( inputdata_t &inputdata )
-{
- InputDropBombAtTargetInternal( inputdata, false );
-}
-
-
-//------------------------------------------------------------------------------
-// Drop a bomb at a particular location
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputDropBombAtTarget( inputdata_t &inputdata )
-{
- InputDropBombAtTargetInternal( inputdata, true );
-}
-
-
-//------------------------------------------------------------------------------
-// Drop a bomb at a particular location
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputDropBombDelay( inputdata_t &inputdata )
-{
- m_flInputDropBombTime = gpGlobals->curtime + inputdata.value.Float();
-
- if ( ShouldDropBombs() )
- {
- m_flNextAttack = m_flInputDropBombTime;
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Drop those bombs!
-//------------------------------------------------------------------------------
-#define MAX_BULLRUSH_BOMB_DISTANCE_SQR ( 3072.0f * 3072.0f )
-
-void CNPC_AttackHelicopter::DropBombs( )
-{
- // Can't continually fire....
- if (m_flNextAttack > gpGlobals->curtime)
- return;
-
- // Otherwise, behave as normal.
- if ( m_nAttackMode != ATTACK_MODE_BULLRUSH_VEHICLE )
- {
- if ( GetEnemy() && GetEnemy()->IsPlayer() )
- {
- if ( GetEnemy()->GetSmoothedVelocity().LengthSqr() > ( CHOPPER_SLOW_BOMB_SPEED * CHOPPER_SLOW_BOMB_SPEED ) )
- {
- // Don't drop bombs if you are behind the player, unless the player is moving slowly
- float flLeadingDistSq = GetLeadingDistance() * 0.75f;
- flLeadingDistSq *= flLeadingDistSq;
-
- Vector vecPoint;
- ClosestPointToCurrentPath( &vecPoint );
- if ( vecPoint.AsVector2D().DistToSqr( GetDesiredPosition().AsVector2D() ) > flLeadingDistSq )
- return;
- }
- }
- }
- else
- {
- // Skip out if we're bullrushing but too far from the player
- if ( GetEnemy() )
- {
- if ( GetEnemy()->GetAbsOrigin().AsVector2D().DistToSqr( GetAbsOrigin().AsVector2D() ) > MAX_BULLRUSH_BOMB_DISTANCE_SQR )
- return;
- }
- }
-
- CreateBomb( );
-
- m_flNextAttack = gpGlobals->curtime + 0.5f + random->RandomFloat( 0.3f, 0.6f );
-
- if ( (m_nAttackMode != ATTACK_MODE_BULLRUSH_VEHICLE) )
- {
- if ( --m_nGrenadeCount <= 0 )
- {
- m_nGrenadeCount = CHOPPER_BOMB_DROP_COUNT;
- m_flNextAttack += random->RandomFloat( 1.5f, 3.0f );
- }
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Should we drop those bombs?
-//------------------------------------------------------------------------------
-#define BOMB_GRACE_PERIOD 1.5f
-#define BOMB_MIN_SPEED 150.0
-
-bool CNPC_AttackHelicopter::ShouldDropBombs( void )
-{
- if ( IsCarpetBombing() )
- return true;
-
- if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
- {
- // Distance determines whether or not we should do this
- if ((m_nSecondaryMode == BULLRUSH_MODE_SHOOT_IDLE_PLAYER) && (SecondaryModeTime() >= BULLRUSH_IDLE_PLAYER_FIRE_TIME))
- return ShouldBombIdlePlayer();
-
- return (( m_nSecondaryMode == BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED ) || ( m_nSecondaryMode == BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER ));
- }
-
- if (!IsLeading() || !GetEnemyVehicle())
- return false;
-
- if (( m_nAttackMode != ATTACK_MODE_BOMB_VEHICLE ) && ( m_nAttackMode != ATTACK_MODE_ALWAYS_LEAD_VEHICLE ))
- return false;
-
- if ( m_nGunState != GUN_STATE_IDLE )
- return false;
-
- // This is for bombing. If you get hit, give a grace period to get back to speed
- float flSpeedSqr = GetEnemy()->GetSmoothedVelocity().LengthSqr();
- if ( flSpeedSqr >= BOMB_MIN_SPEED * BOMB_MIN_SPEED )
- {
- m_flLastFastTime = gpGlobals->curtime;
- }
- else
- {
- if ( ( gpGlobals->curtime - m_flLastFastTime ) < BOMB_GRACE_PERIOD )
- return false;
- }
-
- float flSpeedAlongPath = TargetSpeedAlongPath();
- if ( m_nAttackMode == ATTACK_MODE_BOMB_VEHICLE )
- return ( flSpeedAlongPath > -BOMB_MIN_SPEED );
-
- // This is for ALWAYS_LEAD
- if ( fabs(flSpeedAlongPath) < 50.0f )
- return false;
-
- float flLeadingDist = ComputeDistanceToLeadingPosition( );
- flLeadingDist = GetLeadingDistance() - flLeadingDist;
- if ( flSpeedAlongPath < 0.0f )
- {
- return flLeadingDist < 300.0f;
- }
- else
- {
- return flLeadingDist > -300.0f;
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Different bomb-dropping behavior
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::BullrushBombs( )
-{
- if ( gpGlobals->curtime < m_flNextBullrushBombTime )
- return;
-
- if ( m_nBullrushBombMode & 0x1 )
- {
- CreateBomb( false, NULL, true );
- }
- else
- {
- Vector vecAcross;
- Vector vecVelocity = GetAbsVelocity();
- CrossProduct( vecVelocity, Vector( 0, 0, 1 ), vecAcross );
- VectorNormalize( vecAcross );
- vecAcross *= random->RandomFloat( 300.0f, 500.0f );
-
- // Blat out z component of velocity if it's moving upward....
- if ( vecVelocity.z > 0 )
- {
- vecVelocity.z = 0.0f;
- }
- vecVelocity += vecAcross;
- CreateBomb( false, &vecVelocity, true );
-
- VectorMA( vecVelocity, -2.0f, vecAcross, vecVelocity );
- CreateBomb( false, &vecVelocity, true );
- }
-
- m_nBullrushBombMode = !m_nBullrushBombMode;
- m_flNextBullrushBombTime = gpGlobals->curtime + 0.2f;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Turn the gun off
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InputGunOff( inputdata_t &inputdata )
-{
- BaseClass::InputGunOff( inputdata );
-
- if ( m_pGunFiringSound )
- {
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.01f );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Fire that gun baby!
-//------------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::FireGun( void )
-{
- // Do the test electricity gun
- if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
- {
- FireElectricityGun( );
- return true;
- }
-
- // HACK: CBaseHelicopter ignores this, and fire forever at the last place it saw the player. Why?
- if (( m_nGunState == GUN_STATE_IDLE ) && ( m_nAttackMode != ATTACK_MODE_BULLRUSH_VEHICLE ) && !IsCarpetBombing() )
- {
- if ( (m_flLastSeen + 1 <= gpGlobals->curtime) || (m_flPrevSeen + m_flGracePeriod > gpGlobals->curtime) )
- return false;
- }
-
- if ( IsCarpetBombing() )
- {
- BullrushBombs();
- return false;
- }
-
- if ( ShouldDropBombs() )
- {
- DropBombs( );
- return false;
- }
-
- // Drop those bullrush bombs when shooting...
- if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
- {
- if ( IsInSecondaryMode( BULLRUSH_MODE_MEGA_BOMB ) )
- {
- BullrushBombs( );
- return false;
- }
-
- // Don't fire if we're bullrushing and we're getting distance
- if ( !IsInSecondaryMode( BULLRUSH_MODE_SHOOT_GUN ) && !IsInSecondaryMode(BULLRUSH_MODE_SHOOT_IDLE_PLAYER) )
- return false;
-
- // If we're in the grace period on this mode, then don't fire
- if ( IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) && (SecondaryModeTime() < BULLRUSH_IDLE_PLAYER_FIRE_TIME) )
- {
- // Stop our gun sound
- if ( m_nGunState != GUN_STATE_IDLE )
- {
- ShutdownGunDuringBullrush();
- }
-
- return false;
- }
- }
-
- // Get gun attachment points
- Vector vBasePos;
- GetAttachment( m_nGunBaseAttachment, vBasePos );
-
- // Aim perfectly while idle, but after charging, the gun don't move so fast.
- Vector vecFireAtPosition;
- if ( !GetEnemyVehicle() || (m_nGunState == GUN_STATE_IDLE) )
- {
- ComputeFireAtPosition( &vecFireAtPosition );
- }
- else
- {
- ComputeVehicleFireAtPosition( &vecFireAtPosition );
- }
-
- Vector vTargetDir = vecFireAtPosition - vBasePos;
- VectorNormalize( vTargetDir );
-
- // Makes the model of the gun point to where we're aiming.
- if ( !PoseGunTowardTargetDirection( vTargetDir ) )
- return false;
-
- // Are we charging?
- if ( m_nGunState == GUN_STATE_CHARGING )
- {
- if ( !DoGunCharging( ) )
- return false;
- }
-
- Vector vTipPos;
- GetAttachment( m_nGunTipAttachment, vTipPos );
-
- Vector vGunDir = vTipPos - vBasePos;
- VectorNormalize( vGunDir );
-
- // Are we firing?
- if ( m_nGunState == GUN_STATE_FIRING )
- {
- return DoGunFiring( vTipPos, vGunDir, vecFireAtPosition );
- }
-
- return DoGunIdle( vGunDir, vTargetDir );
-}
-
-
-//-----------------------------------------------------------------------------
-// Should we trigger a damage effect?
-//-----------------------------------------------------------------------------
-inline bool CNPC_AttackHelicopter::ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const
-{
- int nPrevRange = (int)( ((float)nPrevHealth / (float)GetMaxHealth()) * nEffectCount );
- int nRange = (int)( ((float)GetHealth() / (float)GetMaxHealth()) * nEffectCount );
- return ( nRange != nPrevRange );
-}
-
-
-//-----------------------------------------------------------------------------
-// Add a smoke trail since we've taken more damage
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::AddSmokeTrail( const Vector &vecPos )
-{
- if ( m_nSmokeTrailCount == MAX_SMOKE_TRAILS )
- return;
-
- // See if there's an attachment for this smoke trail
- int nAttachment = LookupAttachment( UTIL_VarArgs( "damage%d", m_nSmokeTrailCount ) );
-
- if ( nAttachment == 0 )
- return;
-
- // The final smoke trail is a flaming engine
- if ( m_nSmokeTrailCount == 0 || m_nSmokeTrailCount % 2 )
- {
- CFireTrail *pFireTrail = CFireTrail::CreateFireTrail();
-
- if ( pFireTrail == NULL )
- return;
-
- m_hSmokeTrail[m_nSmokeTrailCount] = pFireTrail;
-
- pFireTrail->FollowEntity( this, UTIL_VarArgs( "damage%d", m_nSmokeTrailCount ) );
- pFireTrail->SetParent( this, nAttachment );
- pFireTrail->SetLocalOrigin( vec3_origin );
- pFireTrail->SetMoveType( MOVETYPE_NONE );
- pFireTrail->SetLifetime( -1 );
- }
- else
- {
- SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
- if( !pSmokeTrail )
- return;
-
- m_hSmokeTrail[m_nSmokeTrailCount] = pSmokeTrail;
-
- pSmokeTrail->m_SpawnRate = 48;
- pSmokeTrail->m_ParticleLifetime = 0.5f;
- pSmokeTrail->m_StartColor.Init(0.15, 0.15, 0.15);
- pSmokeTrail->m_EndColor.Init(0.0, 0.0, 0.0);
- pSmokeTrail->m_StartSize = 24;
- pSmokeTrail->m_EndSize = 80;
- pSmokeTrail->m_SpawnRadius = 8;
- pSmokeTrail->m_Opacity = 0.2;
- pSmokeTrail->m_MinSpeed = 16;
- pSmokeTrail->m_MaxSpeed = 64;
- pSmokeTrail->SetLifetime(-1);
- pSmokeTrail->SetParent( this, nAttachment );
- pSmokeTrail->SetLocalOrigin( vec3_origin );
- pSmokeTrail->SetMoveType( MOVETYPE_NONE );
- }
-
- m_nSmokeTrailCount++;
-}
-
-
-//-----------------------------------------------------------------------------
-// Destroy all smoke trails
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::DestroySmokeTrails()
-{
- for ( int i = m_nSmokeTrailCount; --i >= 0; )
- {
- UTIL_Remove( m_hSmokeTrail[i] );
- m_hSmokeTrail[i] = NULL;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &vecChunkPos -
-//-----------------------------------------------------------------------------
-void Chopper_CreateChunk( CBaseEntity *pChopper, const Vector &vecChunkPos, const QAngle &vecChunkAngles, const char *pszChunkName, bool bSmall )
-{
- // Drop a flaming, smoking chunk.
- CGib *pChunk = CREATE_ENTITY( CGib, "gib" );
- pChunk->Spawn( pszChunkName );
- pChunk->SetBloodColor( DONT_BLEED );
-
- pChunk->SetAbsOrigin( vecChunkPos );
- pChunk->SetAbsAngles( vecChunkAngles );
-
- pChunk->SetOwnerEntity( pChopper );
-
- if ( bSmall )
- {
- pChunk->m_lifeTime = random->RandomFloat( 0.5f, 1.0f );
- pChunk->SetSolidFlags( FSOLID_NOT_SOLID );
- pChunk->SetSolid( SOLID_BBOX );
- pChunk->AddEffects( EF_NODRAW );
- pChunk->SetGravity( UTIL_ScaleForGravity( 400 ) );
- }
- else
- {
- pChunk->m_lifeTime = 5.0f;
- }
-
- pChunk->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
-
- // Set the velocity
- Vector vecVelocity;
- AngularImpulse angImpulse;
-
- QAngle angles;
- angles.x = random->RandomFloat( -70, 20 );
- angles.y = random->RandomFloat( 0, 360 );
- angles.z = 0.0f;
- AngleVectors( angles, &vecVelocity );
-
- vecVelocity *= random->RandomFloat( 550, 800 );
- vecVelocity += pChopper->GetAbsVelocity();
-
- angImpulse = RandomAngularImpulse( -180, 180 );
-
- pChunk->SetAbsVelocity( vecVelocity );
-
- if ( bSmall == false )
- {
- IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
-
- if ( pPhysicsObject )
- {
- pPhysicsObject->EnableMotion( true );
- pPhysicsObject->SetVelocity(&vecVelocity, &angImpulse );
- }
- }
-
- CFireTrail *pFireTrail = CFireTrail::CreateFireTrail();
-
- if ( pFireTrail == NULL )
- return;
-
- pFireTrail->FollowEntity( pChunk, "" );
- pFireTrail->SetParent( pChunk, 0 );
- pFireTrail->SetLocalOrigin( vec3_origin );
- pFireTrail->SetMoveType( MOVETYPE_NONE );
- pFireTrail->SetLifetime( pChunk->m_lifeTime );
-}
-
-//------------------------------------------------------------------------------
-// Pow!
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::ExplodeAndThrowChunk( const Vector &vecExplosionPos )
-{
- CEffectData data;
- data.m_vOrigin = vecExplosionPos;
- DispatchEffect( "HelicopterMegaBomb", data );
-
- EmitSound( "BaseExplosionEffect.Sound" );
-
- UTIL_ScreenShake( vecExplosionPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
-
- if(GetCrashPoint() != NULL)
- {
- // Make it clear that I'm done for.
- ExplosionCreate( vecExplosionPos, QAngle(0,0,1), this, 100, 128, false );
- }
-
- if ( random->RandomInt( 0, 4 ) )
- {
- for ( int i = 0; i < 2; i++ )
- {
- Chopper_CreateChunk( this, vecExplosionPos, RandomAngle(0, 360), g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ), true );
- }
- }
- else
- {
- Chopper_CreateChunk( this, vecExplosionPos, RandomAngle(0, 360), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_SMALL_CHUNKS - 1 )], false );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Drop a corpse!
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::DropCorpse( int nDamage )
-{
- // Don't drop another corpse if the next guy's not out on the gun yet
- if ( m_flLastCorpseFall > gpGlobals->curtime )
- return;
-
- // Clamp damage to prevent ridiculous ragdoll velocity
- if( nDamage > 250.0f )
- nDamage = 250.0f;
-
- m_flLastCorpseFall = gpGlobals->curtime + 3.0;
-
- // Spawn a ragdoll combine guard
- float forceScale = nDamage * 75 * 4;
- Vector vecForceVector = RandomVector(-1,1);
- vecForceVector.z = 0.5;
- vecForceVector *= forceScale;
-
- CBaseEntity *pGib = CreateRagGib( "models/combine_soldier.mdl", GetAbsOrigin(), GetAbsAngles(), vecForceVector );
- if ( pGib )
- {
- pGib->SetOwnerEntity( this );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
-{
- // Take no damage from trace attacks unless it's blast damage. RadiusDamage() sometimes calls
- // TraceAttack() as a means for delivering blast damage. Usually when the explosive penetrates
- // the target. (RPG missiles do this sometimes).
- if ( ( info.GetDamageType() & DMG_AIRBOAT ) ||
- ( info.GetInflictor()->Classify() == CLASS_MISSILE ) ||
- ( info.GetAttacker()->Classify() == CLASS_MISSILE ) )
- {
- BaseClass::BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int CNPC_AttackHelicopter::OnTakeDamage( const CTakeDamageInfo &info )
-{
- // We don't take blast damage from anything but the airboat or missiles (or myself!)
- if( info.GetInflictor() != this )
- {
- if ( ( ( info.GetDamageType() & DMG_AIRBOAT ) == 0 ) &&
- ( info.GetInflictor()->Classify() != CLASS_MISSILE ) &&
- ( info.GetAttacker()->Classify() != CLASS_MISSILE ) )
- return 0;
- }
-
- if ( m_bIndestructible )
- {
- if ( GetHealth() < info.GetDamage() )
- return 0;
- }
-
- // helicopter takes extra damage from its own grenades
- CGrenadeHelicopter *pGren = dynamic_cast<CGrenadeHelicopter *>(info.GetInflictor());
- if ( pGren && info.GetAttacker() && info.GetAttacker()->IsPlayer() )
- {
- CTakeDamageInfo fudgedInfo = info;
-
- float damage;
- if( g_pGameRules->IsSkillLevel(SKILL_EASY) )
- {
- damage = GetMaxHealth() / sk_helicopter_num_bombs1.GetFloat();
- }
- else if( g_pGameRules->IsSkillLevel(SKILL_HARD) )
- {
- damage = GetMaxHealth() / sk_helicopter_num_bombs3.GetFloat();
- }
- else // Medium, or unspecified
- {
- damage = GetMaxHealth() / sk_helicopter_num_bombs2.GetFloat();
- }
- damage = ceilf( damage );
- fudgedInfo.SetDamage( damage );
- fudgedInfo.SetMaxDamage( damage );
-
- return BaseClass::OnTakeDamage( fudgedInfo );
- }
-
- return BaseClass::OnTakeDamage( info );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Take damage from trace attacks if they hit the gunner
-//-----------------------------------------------------------------------------
-int CNPC_AttackHelicopter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
-{
- int nPrevHealth = GetHealth();
-
- if ( ( info.GetInflictor() != NULL ) && ( info.GetInflictor()->GetOwnerEntity() != NULL ) && ( info.GetInflictor()->GetOwnerEntity() == this ) )
- {
- // Don't take damage from my own bombs. (Unless the player grabbed them and threw them back)
- return 0;
- }
-
- // Chain
- int nRetVal = BaseClass::OnTakeDamage_Alive( info );
-
- if( info.GetDamageType() & DMG_BLAST )
- {
- // Apply a force push that makes us look like we're reacting to the damage
- Vector damageDir = info.GetDamageForce();
- VectorNormalize( damageDir );
- ApplyAbsVelocityImpulse( damageDir * 500.0f );
-
- // Knock the helicopter off of the level, too.
- Vector vecRight, vecForce;
- float flDot;
- GetVectors( NULL, &vecRight, NULL );
- vecForce = info.GetDamageForce();
- VectorNormalize( vecForce );
-
- flDot = DotProduct( vecForce, vecRight );
-
- m_flGoalRollDmg = random->RandomFloat( 10, 30 );
-
- if( flDot <= 0.0f )
- {
- // Missile hit the right side.
- m_flGoalRollDmg *= -1;
- }
- }
-
- // Spawn damage effects
- if ( nPrevHealth != GetHealth() )
- {
- // Give the badly damaged call to say we're going to mega bomb soon
- if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
- {
- if (( nPrevHealth > m_flNextMegaBombHealth ) && (GetHealth() <= m_flNextMegaBombHealth) )
- {
- EmitSound( "NPC_AttackHelicopter.BadlyDamagedAlert" );
- }
- }
-
- if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_SMOKE_TRAILS ) )
- {
- AddSmokeTrail( info.GetDamagePosition() );
- }
-
- if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_CORPSES ) )
- {
- if ( nPrevHealth != GetMaxHealth() )
- {
- DropCorpse( info.GetDamage() );
- }
- }
-
- if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) )
- {
- ExplodeAndThrowChunk( info.GetDamagePosition() );
- }
-
- int nPrevPercent = (int)(100.0f * nPrevHealth / GetMaxHealth());
- int nCurrPercent = (int)(100.0f * GetHealth() / GetMaxHealth());
- if (( (nPrevPercent + 9) / 10 ) != ( (nCurrPercent + 9) / 10 ))
- {
- m_OnHealthChanged.Set( nCurrPercent, this, this );
- }
- }
-
- return nRetVal;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void Chopper_BecomeChunks( CBaseEntity *pChopper )
-{
- QAngle vecChunkAngles = pChopper->GetAbsAngles();
- Vector vecForward, vecUp;
- pChopper->GetVectors( &vecForward, NULL, &vecUp );
-
-#ifdef HL2_EPISODIC
- CNPC_AttackHelicopter *pAttackHelicopter;
- pAttackHelicopter = dynamic_cast<CNPC_AttackHelicopter*>(pChopper);
- if( pAttackHelicopter != NULL )
- {
- // New for EP2, we may be tailspinning, (crashing) and playing an animation that is spinning
- // our root bone, which means our model is not facing the way our entity is facing. So we have
- // to do some attachment point math to get the proper angles to use for computing the relative
- // positions of the gibs. The attachment points called DAMAGE0 is properly oriented and attached
- // to the chopper body so we can use its angles.
- int iAttach = pAttackHelicopter->LookupAttachment( "damage0" );
- Vector vecAttachPos;
-
- if( iAttach > -1 )
- {
- pAttackHelicopter->GetAttachment(iAttach, vecAttachPos, vecChunkAngles );
- AngleVectors( vecChunkAngles, &vecForward, NULL, &vecUp );
- }
- }
-#endif//HL2_EPISODIC
-
-
- Vector vecChunkPos = pChopper->GetAbsOrigin();
-
- Vector vecRight(0,0,0);
-
- if( hl2_episodic.GetBool() )
- {
- // We need to get a right hand vector to toss the cockpit and tail pieces
- // so their motion looks like a continuation of the tailspin animation
- // that the chopper plays before crashing.
- pChopper->GetVectors( NULL, &vecRight, NULL );
- }
-
- // Body
- CHelicopterChunk *pBodyChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, pChopper->GetAbsVelocity(), HELICOPTER_CHUNK_BODY, CHUNK_BODY );
- Chopper_CreateChunk( pChopper, vecChunkPos, RandomAngle( 0, 360 ), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_CHUNKS - 1 )], false );
-
- vecChunkPos = pChopper->GetAbsOrigin() + ( vecForward * 100.0f ) + ( vecUp * -38.0f );
-
- // Cockpit
- CHelicopterChunk *pCockpitChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, pChopper->GetAbsVelocity() + vecRight * -800.0f, HELICOPTER_CHUNK_COCKPIT, CHUNK_COCKPIT );
- Chopper_CreateChunk( pChopper, vecChunkPos, RandomAngle( 0, 360 ), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_CHUNKS - 1 )], false );
-
- pCockpitChunk->m_hMaster = pBodyChunk;
-
- vecChunkPos = pChopper->GetAbsOrigin() + ( vecForward * -175.0f );
-
- // Tail
- CHelicopterChunk *pTailChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, pChopper->GetAbsVelocity() + vecRight * 800.0f, HELICOPTER_CHUNK_TAIL, CHUNK_TAIL );
- Chopper_CreateChunk( pChopper, vecChunkPos, RandomAngle( 0, 360 ), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_CHUNKS - 1 )], false );
-
- pTailChunk->m_hMaster = pBodyChunk;
-
- // Constrain all the pieces together loosely
- IPhysicsObject *pBodyObject = pBodyChunk->VPhysicsGetObject();
- Assert( pBodyObject );
-
- IPhysicsObject *pCockpitObject = pCockpitChunk->VPhysicsGetObject();
- Assert( pCockpitObject );
-
- IPhysicsObject *pTailObject = pTailChunk->VPhysicsGetObject();
- Assert( pTailObject );
-
- IPhysicsConstraintGroup *pGroup = NULL;
-
- // Create the constraint
- constraint_fixedparams_t fixed;
- fixed.Defaults();
- fixed.InitWithCurrentObjectState( pBodyObject, pTailObject );
- fixed.constraint.Defaults();
-
- pBodyChunk->m_pTailConstraint = physenv->CreateFixedConstraint( pBodyObject, pTailObject, pGroup, fixed );
-
- fixed.Defaults();
- fixed.InitWithCurrentObjectState( pBodyObject, pCockpitObject );
- fixed.constraint.Defaults();
-
- pBodyChunk->m_pCockpitConstraint = physenv->CreateFixedConstraint( pBodyObject, pCockpitObject, pGroup, fixed );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Start us crashing
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::Event_Killed( const CTakeDamageInfo &info )
-{
- if( m_lifeState == LIFE_ALIVE )
- {
- m_OnShotDown.FireOutput( this, this );
- }
-
- m_lifeState = LIFE_DYING;
-
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.1f );
-
- if( GetCrashPoint() == NULL )
- {
- CBaseEntity *pCrashPoint = gEntList.FindEntityByClassname( NULL, "info_target_helicopter_crash" );
- if( pCrashPoint != NULL )
- {
- m_hCrashPoint.Set( pCrashPoint );
- SetDesiredPosition( pCrashPoint->GetAbsOrigin() );
-
- // Start the failing engine sound
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundDestroy( m_pRotorSound );
-
- CPASAttenuationFilter filter( this );
- m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.EngineFailure" );
- controller.Play( m_pRotorSound, 1.0, 100 );
-
- // Tailspin!!
- SetActivity( ACT_HELICOPTER_CRASHING );
-
- // Intentionally returning with m_lifeState set to LIFE_DYING
- return;
- }
- }
-
- Chopper_BecomeChunks( this );
- StopLoopingSounds();
-
- m_lifeState = LIFE_DEAD;
-
- EmitSound( "NPC_CombineGunship.Explode" );
-
- SetThink( &CNPC_AttackHelicopter::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 0.1f );
-
- AddEffects( EF_NODRAW );
-
- // Makes the slower rotors fade back in
- SetStartupTime( gpGlobals->curtime + 99.0f );
-
- m_iHealth = 0;
- m_takedamage = DAMAGE_NO;
-
- m_OnDeath.FireOutput( info.GetAttacker(), this );
-}
-
-//------------------------------------------------------------------------------
-// Creates the breakable husk of an attack chopper
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::CreateChopperHusk()
-{
- // We're embedded into the ground
- CBaseEntity *pCorpse = CreateEntityByName( "prop_physics" );
- pCorpse->SetAbsOrigin( GetAbsOrigin() );
- pCorpse->SetAbsAngles( GetAbsAngles() );
- pCorpse->SetModel( CHOPPER_MODEL_CORPSE_NAME );
- pCorpse->AddSpawnFlags( SF_PHYSPROP_MOTIONDISABLED );
- pCorpse->Spawn();
- pCorpse->SetMoveType( MOVETYPE_NONE );
-}
-
-//-----------------------------------------------------------------------------
-// Think!
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::PrescheduleThink( void )
-{
- if ( m_flGoalRollDmg != 0.0f )
- {
- m_flGoalRollDmg = UTIL_Approach( 0, m_flGoalRollDmg, 2.0f );
- }
-
- switch( m_lifeState )
- {
- case LIFE_DYING:
- {
- if( GetCrashPoint() != NULL )
- {
- // Stay on this, no matter what.
- SetDesiredPosition( GetCrashPoint()->WorldSpaceCenter() );
- }
-
- if ( random->RandomInt( 0, 4 ) == 0 )
- {
- Vector explodePoint;
- CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );
-
- ExplodeAndThrowChunk( explodePoint );
- }
- }
- break;
- }
-
- BaseClass::PrescheduleThink();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-float CNPC_AttackHelicopter::UpdatePerpPathDistance( float flMaxPathOffset )
-{
- if ( !IsLeading() || !GetEnemy() )
- {
- m_flCurrPathOffset = 0.0f;
- return 0.0f;
- }
-
- float flNewPathOffset = TargetDistanceToPath();
-
- // Make bomb dropping more interesting
- if ( ShouldDropBombs() )
- {
- float flSpeedAlongPath = TargetSpeedAlongPath();
-
- if ( flSpeedAlongPath > 10.0f )
- {
- float flLeadTime = GetLeadingDistance() / flSpeedAlongPath;
- flLeadTime = clamp( flLeadTime, 0.0f, 2.0f );
- flNewPathOffset += 0.25 * flLeadTime * TargetSpeedAcrossPath();
- }
-
- flSpeedAlongPath = clamp( flSpeedAlongPath, 100.0f, 500.0f );
- float flSinHeight = SimpleSplineRemapVal( flSpeedAlongPath, 100.0f, 500.0f, 0.0f, 200.0f );
- flNewPathOffset += flSinHeight * sin( 2.0f * M_PI * (gpGlobals->curtime / 6.0f) );
- }
-
- if ( (flMaxPathOffset != 0.0f) && (flNewPathOffset > flMaxPathOffset) )
- {
- flNewPathOffset = flMaxPathOffset;
- }
-
- float flMaxChange = 1000.0f * (gpGlobals->curtime - GetLastThink());
- if ( fabs( flNewPathOffset - m_flCurrPathOffset ) < flMaxChange )
- {
- m_flCurrPathOffset = flNewPathOffset;
- }
- else
- {
- float flSign = (m_flCurrPathOffset < flNewPathOffset) ? 1.0f : -1.0f;
- m_flCurrPathOffset += flSign * flMaxChange;
- }
-
- return m_flCurrPathOffset;
-}
-
-
-//-----------------------------------------------------------------------------
-// Computes the max speed + acceleration:
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::GetMaxSpeedAndAccel( float *pMaxSpeed, float *pAccelRate )
-{
- *pAccelRate = CHOPPER_ACCEL_RATE;
- *pMaxSpeed = GetMaxSpeed();
- if ( GetEnemyVehicle() )
- {
- *pAccelRate *= 9.0f;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Computes the acceleration:
-//-----------------------------------------------------------------------------
-#define HELICOPTER_GRAVITY 384
-#define HELICOPTER_DT 0.1f
-#define HELICOPTER_MIN_DZ_DAMP -500.0f
-#define HELICOPTER_MAX_DZ_DAMP -1000.0f
-#define HELICOPTER_FORCE_BLEND 0.8f
-#define HELICOPTER_FORCE_BLEND_VEHICLE 0.2f
-
-void CNPC_AttackHelicopter::ComputeVelocity( const Vector &vecTargetPosition,
- float flAdditionalHeight, float flMinDistFromSegment, float flMaxDistFromSegment, Vector *pVecAccel )
-{
- Vector deltaPos;
- VectorSubtract( vecTargetPosition, GetAbsOrigin(), deltaPos );
-
- // calc goal linear accel to hit deltaPos in dt time.
- // This is solving the equation xf = 0.5 * a * dt^2 + vo * dt + xo
- float dt = 1.0f;
- pVecAccel->x = 2.0f * (deltaPos.x - GetAbsVelocity().x * dt) / (dt * dt);
- pVecAccel->y = 2.0f * (deltaPos.y - GetAbsVelocity().y * dt) / (dt * dt);
- pVecAccel->z = 2.0f * (deltaPos.z - GetAbsVelocity().z * dt) / (dt * dt) + HELICOPTER_GRAVITY;
-
- float flDistFromPath = 0.0f;
- Vector vecPoint, vecDelta;
- if ( flMaxDistFromSegment != 0.0f )
- {
- // Also, add in a little force to get us closer to our current line segment if we can
- ClosestPointToCurrentPath( &vecPoint );
-
- if ( flAdditionalHeight != 0.0f )
- {
- Vector vecEndPoint, vecClosest;
- vecEndPoint = vecPoint;
- vecEndPoint.z += flAdditionalHeight;
- CalcClosestPointOnLineSegment( GetAbsOrigin(), vecPoint, vecEndPoint, vecClosest );
- vecPoint = vecClosest;
- }
-
- VectorSubtract( vecPoint, GetAbsOrigin(), vecDelta );
- flDistFromPath = VectorNormalize( vecDelta );
- if ( flDistFromPath > flMaxDistFromSegment )
- {
- // Strongly constrain to an n unit pipe around the current path
- // by damping out all impulse forces that would push us further from the pipe
- float flAmount = (flDistFromPath - flMaxDistFromSegment) / 200.0f;
- flAmount = clamp( flAmount, 0, 1 );
- VectorMA( *pVecAccel, flAmount * 200.0f, vecDelta, *pVecAccel );
- }
- }
-
- // Apply avoidance forces
- if ( !HasSpawnFlags( SF_HELICOPTER_IGNORE_AVOID_FORCES ) )
- {
- Vector vecAvoidForce;
- CAvoidSphere::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
- *pVecAccel += vecAvoidForce;
- CAvoidBox::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
- *pVecAccel += vecAvoidForce;
- }
-
- // don't fall faster than 0.2G or climb faster than 2G
- pVecAccel->z = clamp( pVecAccel->z, HELICOPTER_GRAVITY * 0.2f, HELICOPTER_GRAVITY * 2.0f );
-
- // The lift factor owing to horizontal movement
- float flHorizLiftFactor = fabs( pVecAccel->x ) * 0.10f + fabs( pVecAccel->y ) * 0.10f;
-
- // If we're way above the path, dampen horizontal lift factor
- float flNewHorizLiftFactor = clamp( deltaPos.z, HELICOPTER_MAX_DZ_DAMP, HELICOPTER_MIN_DZ_DAMP );
- flNewHorizLiftFactor = SimpleSplineRemapVal( flNewHorizLiftFactor, HELICOPTER_MIN_DZ_DAMP, HELICOPTER_MAX_DZ_DAMP, flHorizLiftFactor, 2.5f * (HELICOPTER_GRAVITY * 0.2) );
- float flDampening = (flNewHorizLiftFactor != 0.0f) ? (flNewHorizLiftFactor / flHorizLiftFactor) : 1.0f;
- if ( flDampening < 1.0f )
- {
- pVecAccel->x *= flDampening;
- pVecAccel->y *= flDampening;
- flHorizLiftFactor = flNewHorizLiftFactor;
- }
-
- Vector forward, right, up;
- GetVectors( &forward, &right, &up );
-
- // First, attenuate the current force
- float flForceBlend = GetEnemyVehicle() ? HELICOPTER_FORCE_BLEND_VEHICLE : HELICOPTER_FORCE_BLEND;
- m_flForce *= flForceBlend;
-
- // Now add force based on our acceleration factors
- m_flForce += ( pVecAccel->z + flHorizLiftFactor ) * HELICOPTER_DT * (1.0f - flForceBlend);
-
- // The force is always *locally* upward based; we pitch + roll the chopper to get movement
- Vector vecImpulse;
- VectorMultiply( up, m_flForce, vecImpulse );
-
- // NOTE: These have to be done *before* the additional path distance drag forces are applied below
- ApplySidewaysDrag( right );
- ApplyGeneralDrag();
-
- // If LIFE_DYING, maintain control as long as we're flying to a crash point.
- if ( m_lifeState != LIFE_DYING || (m_lifeState == LIFE_DYING && GetCrashPoint() != NULL) )
- {
- vecImpulse.z += -HELICOPTER_GRAVITY * HELICOPTER_DT;
-
- if ( flMinDistFromSegment != 0.0f && ( flDistFromPath > flMinDistFromSegment ) )
- {
- Vector vecVelDir = GetAbsVelocity();
-
- // Strongly constrain to an n unit pipe around the current path
- // by damping out all impulse forces that would push us further from the pipe
- float flDot = DotProduct( vecImpulse, vecDelta );
- if ( flDot < 0.0f )
- {
- VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
- }
-
- // Also apply an extra impulse to compensate for the current velocity
- flDot = DotProduct( vecVelDir, vecDelta );
- if ( flDot < 0.0f )
- {
- VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
- }
- }
- }
- else
- {
- // No more upward lift...
- vecImpulse.z = -HELICOPTER_GRAVITY * HELICOPTER_DT;
-
- // Damp the horizontal impulses; we should pretty much be falling ballistically
- vecImpulse.x *= 0.1f;
- vecImpulse.y *= 0.1f;
- }
-
- // Add in our velocity pulse for this frame
- ApplyAbsVelocityImpulse( vecImpulse );
-}
-
-
-
-//-----------------------------------------------------------------------------
-// Computes the max speed + acceleration:
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::ComputeAngularVelocity( const Vector &vecGoalUp, const Vector &vecFacingDirection )
-{
- QAngle goalAngAccel;
- if ( m_lifeState != LIFE_DYING || (m_lifeState == LIFE_DYING && GetCrashPoint() != NULL) )
- {
- Vector forward, right, up;
- GetVectors( &forward, &right, &up );
-
- Vector goalUp = vecGoalUp;
- VectorNormalize( goalUp );
-
- // calc goal orientation to hit linear accel forces
- float goalPitch = RAD2DEG( asin( DotProduct( forward, goalUp ) ) );
- float goalYaw = UTIL_VecToYaw( vecFacingDirection );
- float goalRoll = RAD2DEG( asin( DotProduct( right, goalUp ) ) + m_flGoalRollDmg );
- goalPitch *= 0.75f;
-
- // clamp goal orientations
- goalPitch = clamp( goalPitch, -30, 45 );
- goalRoll = clamp( goalRoll, -45, 45 );
-
- // calc angular accel needed to hit goal pitch in dt time.
- float dt = 0.6;
- goalAngAccel.x = 2.0 * (AngleDiff( goalPitch, AngleNormalize( GetAbsAngles().x ) ) - GetLocalAngularVelocity().x * dt) / (dt * dt);
- goalAngAccel.y = 2.0 * (AngleDiff( goalYaw, AngleNormalize( GetAbsAngles().y ) ) - GetLocalAngularVelocity().y * dt) / (dt * dt);
- goalAngAccel.z = 2.0 * (AngleDiff( goalRoll, AngleNormalize( GetAbsAngles().z ) ) - GetLocalAngularVelocity().z * dt) / (dt * dt);
-
- goalAngAccel.x = clamp( goalAngAccel.x, -300, 300 );
- //goalAngAccel.y = clamp( goalAngAccel.y, -60, 60 );
- goalAngAccel.y = clamp( goalAngAccel.y, -120, 120 );
- goalAngAccel.z = clamp( goalAngAccel.z, -300, 300 );
- }
- else
- {
- goalAngAccel.x = 0;
- goalAngAccel.y = random->RandomFloat( 50, 120 );
- goalAngAccel.z = 0;
- }
-
- // limit angular accel changes to similate mechanical response times
- QAngle angAccelAccel;
- float dt = 0.1;
- angAccelAccel.x = (goalAngAccel.x - m_vecAngAcceleration.x) / dt;
- angAccelAccel.y = (goalAngAccel.y - m_vecAngAcceleration.y) / dt;
- angAccelAccel.z = (goalAngAccel.z - m_vecAngAcceleration.z) / dt;
-
- angAccelAccel.x = clamp( angAccelAccel.x, -1000, 1000 );
- angAccelAccel.y = clamp( angAccelAccel.y, -1000, 1000 );
- angAccelAccel.z = clamp( angAccelAccel.z, -1000, 1000 );
-
- // DevMsg( "pitch %6.1f (%6.1f:%6.1f) ", goalPitch, GetLocalAngles().x, m_vecAngVelocity.x );
- // DevMsg( "roll %6.1f (%6.1f:%6.1f) : ", goalRoll, GetLocalAngles().z, m_vecAngVelocity.z );
- // DevMsg( "%6.1f %6.1f %6.1f : ", goalAngAccel.x, goalAngAccel.y, goalAngAccel.z );
- // DevMsg( "%6.0f %6.0f %6.0f\n", angAccelAccel.x, angAccelAccel.y, angAccelAccel.z );
-
- m_vecAngAcceleration += angAccelAccel * 0.1;
-
- QAngle angVel = GetLocalAngularVelocity();
- angVel += m_vecAngAcceleration * 0.1;
- angVel.y = clamp( angVel.y, -120, 120 );
-
- // Fix up pitch and yaw to tend toward small values
- if ( m_lifeState == LIFE_DYING && GetCrashPoint() == NULL )
- {
- float flPitchDiff = random->RandomFloat( -5, 5 ) - GetAbsAngles().x;
- angVel.x = flPitchDiff * 0.1f;
- float flRollDiff = random->RandomFloat( -5, 5 ) - GetAbsAngles().z;
- angVel.z = flRollDiff * 0.1f;
- }
-
- SetLocalAngularVelocity( angVel );
-
- float flAmt = clamp( angVel.y, -30, 30 );
- float flRudderPose = RemapVal( flAmt, -30, 30, 45, -45 );
- SetPoseParameter( "rudder", flRudderPose );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::FlightDirectlyOverhead( void )
-{
- Vector vecTargetPosition = m_vecTargetPosition;
- CBaseEntity *pEnemy = GetEnemy();
- if ( HasEnemy() && FVisible( pEnemy ) )
- {
- if ( GetEnemy()->IsPlayer() )
- {
- CBaseEntity *pEnemyVehicle = assert_cast<CBasePlayer*>(GetEnemy())->GetVehicleEntity();
- if ( pEnemyVehicle )
- {
- Vector vecEnemyVel = pEnemyVehicle->GetSmoothedVelocity();
- Vector vecRelativePosition;
- VectorSubtract( GetAbsOrigin(), pEnemyVehicle->GetAbsOrigin(), vecRelativePosition );
- float flDist = VectorNormalize( vecRelativePosition );
- float flEnemySpeed = VectorNormalize( vecEnemyVel );
- float flDot = DotProduct( vecRelativePosition, vecEnemyVel );
- float flSpeed = GetMaxSpeed() * 0.3f; //GetAbsVelocity().Length();
-
- float a = flSpeed * flSpeed - flEnemySpeed * flEnemySpeed;
- float b = 2.0f * flEnemySpeed * flDist * flDot;
- float c = - flDist * flDist;
-
- float flDiscrim = b * b - 4 * a * c;
- if ( flDiscrim >= 0 )
- {
- float t = ( -b + sqrt( flDiscrim ) ) / (2 * a);
- t = clamp( t, 0.0f, 4.0f );
- VectorMA( pEnemyVehicle->GetAbsOrigin(), t * flEnemySpeed, vecEnemyVel, vecTargetPosition );
- }
- }
- }
- }
-
-// if ( GetCurrentPathTargetPosition() )
-// {
-// vecTargetPosition.z = GetCurrentPathTargetPosition()->z;
-// }
-
- NDebugOverlay::Cross3D( vecTargetPosition, -Vector(32,32,32), Vector(32,32,32), 0, 0, 255, true, 0.1f );
-
- UpdateFacingDirection( vecTargetPosition );
-
- Vector accel;
- ComputeVelocity( vecTargetPosition, 0.0f, 0.0f, 0.0f, &accel );
- ComputeAngularVelocity( accel, m_vecDesiredFaceDir );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::Flight( void )
-{
- if( GetFlags() & FL_ONGROUND )
- {
- // This would be really bad.
- SetGroundEntity( NULL );
- }
-
- // Determine the distances we must lie from the path
- float flMaxPathOffset = MaxDistanceFromCurrentPath();
- float flPerpDist = UpdatePerpPathDistance( flMaxPathOffset );
-
- float flMinDistFromSegment, flMaxDistFromSegment;
- if ( !IsLeading() )
- {
- flMinDistFromSegment = 0.0f;
- flMaxDistFromSegment = 0.0f;
- }
- else
- {
- flMinDistFromSegment = fabs(flPerpDist) + 100.0f;
- flMaxDistFromSegment = fabs(flPerpDist) + 200.0f;
- if ( flMaxPathOffset != 0.0 )
- {
- if ( flMaxDistFromSegment > flMaxPathOffset - 100.0f )
- {
- flMaxDistFromSegment = flMaxPathOffset - 100.0f;
- }
-
- if ( flMinDistFromSegment > flMaxPathOffset - 200.0f )
- {
- flMinDistFromSegment = flMaxPathOffset - 200.0f;
- }
- }
- }
-
- float maxSpeed, accelRate;
- GetMaxSpeedAndAccel( &maxSpeed, &accelRate );
-
- Vector vecTargetPosition;
- float flCurrentSpeed = GetAbsVelocity().Length();
- float flDist = MIN( flCurrentSpeed + accelRate, maxSpeed );
- float dt = 1.0f;
- ComputeActualTargetPosition( flDist, dt, flPerpDist, &vecTargetPosition );
-
- // Raise high in the air when doing the shooting attack
- float flAdditionalHeight = 0.0f;
- if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
- {
- flAdditionalHeight = clamp( m_flBullrushAdditionalHeight, 0.0f, flMaxPathOffset );
- vecTargetPosition.z += flAdditionalHeight;
- }
-
- Vector accel;
- UpdateFacingDirection( vecTargetPosition );
- ComputeVelocity( vecTargetPosition, flAdditionalHeight, flMinDistFromSegment, flMaxDistFromSegment, &accel );
- ComputeAngularVelocity( accel, m_vecDesiredFaceDir );
-}
-
-
-//------------------------------------------------------------------------------
-// Updates the facing direction
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::UpdateFacingDirection( const Vector &vecActualDesiredPosition )
-{
- bool bIsBullrushing = ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE );
-
- bool bSeenTargetRecently = HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) || ( m_flLastSeen + 5 > gpGlobals->curtime );
- if ( GetEnemy() && !bIsBullrushing )
- {
- if ( !IsLeading() )
- {
- if( IsCarpetBombing() && hl2_episodic.GetBool() )
- {
- m_vecDesiredFaceDir = vecActualDesiredPosition - GetAbsOrigin();
- }
- else if ( !IsCrashing() && bSeenTargetRecently )
- {
- // If we've seen the target recently, face the target.
- m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
- }
- else
- {
- // Remain facing the way you were facing...
- }
- }
- else
- {
- if ( ShouldDropBombs() || IsCarpetBombing() )
- {
- m_vecDesiredFaceDir = vecActualDesiredPosition - GetAbsOrigin();
- }
- else
- {
- m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
- }
- }
- }
- else
- {
- // Face our desired position
- float flDistSqr = vecActualDesiredPosition.AsVector2D().DistToSqr( GetAbsOrigin().AsVector2D() );
- if ( flDistSqr <= 50 * 50 )
- {
- if (( flDistSqr > 1 * 1 ) && bSeenTargetRecently && IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) )
- {
- m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
- m_vecDesiredFaceDir.z = 0.0f;
- }
- else
- {
- GetVectors( &m_vecDesiredFaceDir, NULL, NULL );
- }
- }
- else
- {
- m_vecDesiredFaceDir = vecActualDesiredPosition - GetAbsOrigin();
- }
- }
- VectorNormalize( m_vecDesiredFaceDir );
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-#define ENEMY_CREEP_RATE 400
-float CNPC_AttackHelicopter::CreepTowardEnemy( float flSpeed, float flMinSpeed, float flMaxSpeed, float flMinDist, float flMaxDist )
-{
- float dt = gpGlobals->curtime - GetLastThink();
- float flEnemyCreepDist = ENEMY_CREEP_RATE * dt;
-
- // When the player is slow, creep toward him within a second or two
- float flLeadingDist = ClampSplineRemapVal( flSpeed, flMinSpeed, flMaxSpeed, flMinDist, flMaxDist );
- float flCurrentDist = GetLeadingDistance( );
- if ( fabs(flLeadingDist - flCurrentDist) > flEnemyCreepDist )
- {
- float flSign = ( flLeadingDist < flCurrentDist ) ? -1.0f : 1.0f;
- flLeadingDist = flCurrentDist + flSign * flEnemyCreepDist;
- }
-
- return flLeadingDist;
-}
-
-
-#define MIN_ENEMY_SPEED 300
-
-
-//------------------------------------------------------------------------------
-// Computes how far to lead the player when bombing
-//------------------------------------------------------------------------------
-float CNPC_AttackHelicopter::ComputeBombingLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle )
-{
- if ( ( flSpeed <= MIN_ENEMY_SPEED ) && bEnemyInVehicle )
- {
- return CreepTowardEnemy( flSpeed, 0.0f, MIN_ENEMY_SPEED, 0.0f, 1000.0f );
- }
-
- return ClampSplineRemapVal( flSpeedAlongPath, 200.0f, 600.0f, 1000.0f, 2000.0f );
-}
-
-
-//------------------------------------------------------------------------------
-// Computes how far to lead the player when bullrushing
-//------------------------------------------------------------------------------
-float CNPC_AttackHelicopter::ComputeBullrushLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle )
-{
- switch ( m_nSecondaryMode )
- {
- case BULLRUSH_MODE_WAIT_FOR_ENEMY:
- return 0.0f;
-
- case BULLRUSH_MODE_GET_DISTANCE:
- return m_bRushForward ? -CHOPPER_BULLRUSH_MODE_DISTANCE : CHOPPER_BULLRUSH_MODE_DISTANCE;
-
- case BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER:
-// return m_bRushForward ? 1500.0f : -1500.0f;
- return ComputeBombingLeadingDistance( flSpeed, flSpeedAlongPath, bEnemyInVehicle );
-
- case BULLRUSH_MODE_SHOOT_IDLE_PLAYER:
- return 0.0f;
-
- case BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED:
- return m_bRushForward ? 7000 : -7000;
-
- case BULLRUSH_MODE_MEGA_BOMB:
- return m_bRushForward ? CHOPPER_BULLRUSH_MODE_DISTANCE : -CHOPPER_BULLRUSH_MODE_DISTANCE;
-
- case BULLRUSH_MODE_SHOOT_GUN:
- {
- float flLeadDistance = 1000.f - CHOPPER_BULLRUSH_ENEMY_BOMB_DISTANCE;
- return m_bRushForward ? flLeadDistance : -flLeadDistance;
- }
- }
-
- Assert(0);
- return 0.0f;
-}
-
-
-//------------------------------------------------------------------------------
-// Secondary mode
-//------------------------------------------------------------------------------
-inline void CNPC_AttackHelicopter::SetSecondaryMode( int nMode, bool bRetainTime )
-{
- m_nSecondaryMode = nMode;
- if (!bRetainTime)
- {
- m_flSecondaryModeStartTime = gpGlobals->curtime;
- }
-}
-
-inline bool CNPC_AttackHelicopter::IsInSecondaryMode( int nMode )
-{
- return m_nSecondaryMode == nMode;
-}
-
-inline float CNPC_AttackHelicopter::SecondaryModeTime( ) const
-{
- return gpGlobals->curtime - m_flSecondaryModeStartTime;
-}
-
-
-//------------------------------------------------------------------------------
-// Switch to idle
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::SwitchToBullrushIdle( void )
-{
- // Put us directly into idle gun state (we're in firing state)
- m_flNextAttack = gpGlobals->curtime;
- m_nGunState = GUN_STATE_IDLE;
- m_nRemainingBursts = 0;
- m_flBullrushAdditionalHeight = 0.0f;
- SetPauseState( PAUSE_NO_PAUSE );
-
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.1f );
-}
-
-
-//------------------------------------------------------------------------------
-// Should the chopper shoot the idle player?
-//------------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::ShouldShootIdlePlayerInBullrush()
-{
- // Once he starts shooting, then don't stop until the player is moving pretty fast
- float flSpeedSqr = IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) ? CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2_SQ : CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_SQ;
- return ( GetEnemy() && GetEnemy()->GetSmoothedVelocity().LengthSqr() <= flSpeedSqr );
-}
-
-
-//------------------------------------------------------------------------------
-// Shutdown shooting during bullrush
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::ShutdownGunDuringBullrush( )
-{
- // Put us directly into idle gun state (we're in firing state)
- m_flNextAttack = gpGlobals->curtime;
- m_nGunState = GUN_STATE_IDLE;
- m_nRemainingBursts = 0;
- SetPauseState( PAUSE_NO_PAUSE );
-
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.1f );
-}
-
-#define HELICOPTER_MIN_IDLE_BOMBING_DIST 350.0f
-#define HELICOPTER_MIN_IDLE_BOMBING_SPEED 350.0f
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_AttackHelicopter::ShouldBombIdlePlayer( void )
-{
- // Must be settled over a position and not moving too quickly to do this
- if ( GetAbsVelocity().LengthSqr() > Square(HELICOPTER_MIN_IDLE_BOMBING_SPEED) )
- return false;
-
- // Must be within a certain range of the target
- float flDistToTargetSqr = (GetEnemy()->WorldSpaceCenter() - GetAbsOrigin()).Length2DSqr();
-
- if ( flDistToTargetSqr < Square(HELICOPTER_MIN_IDLE_BOMBING_DIST) )
- return true;
-
- // Can't bomb this
- return false;
-}
-
-//------------------------------------------------------------------------------
-// Update the bullrush state
-//------------------------------------------------------------------------------
-#define BULLRUSH_GOAL_TOLERANCE 200
-#define BULLRUSH_BOMB_MAX_DISTANCE 3500
-
-void CNPC_AttackHelicopter::UpdateBullrushState( void )
-{
- if ( !GetEnemy() || IsInForcedMove() )
- {
- if ( !IsInSecondaryMode( BULLRUSH_MODE_WAIT_FOR_ENEMY ) )
- {
- SwitchToBullrushIdle();
- SetSecondaryMode( BULLRUSH_MODE_WAIT_FOR_ENEMY );
- }
- }
-
- switch( m_nSecondaryMode )
- {
- case BULLRUSH_MODE_WAIT_FOR_ENEMY:
- {
- m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
- if ( GetEnemy() && !IsInForcedMove() )
- {
- // This forces us to not start trying checking positions
- // until we have been on the path for a little while
- if ( SecondaryModeTime() > 0.3f )
- {
- float flDistanceToGoal = ComputeDistanceToTargetPosition();
- Vector vecPathDir;
- CurrentPathDirection( &vecPathDir );
- bool bMovingForward = DotProduct2D( GetAbsVelocity().AsVector2D(), vecPathDir.AsVector2D() ) >= 0.0f;
- if ( flDistanceToGoal * (bMovingForward ? 1.0f : -1.0f) > 1000 )
- {
- m_bRushForward = bMovingForward;
- SetSecondaryMode( BULLRUSH_MODE_SHOOT_GUN );
- SpotlightStartup();
- }
- else
- {
- m_bRushForward = !bMovingForward;
- SetSecondaryMode( BULLRUSH_MODE_GET_DISTANCE );
- }
- }
- }
- else
- {
- m_flSecondaryModeStartTime = gpGlobals->curtime;
- }
- }
- break;
-
- case BULLRUSH_MODE_GET_DISTANCE:
- {
- m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
-
- float flDistanceToGoal = ComputeDistanceToTargetPosition();
- if ( m_bRushForward )
- {
- if ( flDistanceToGoal < (CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
- break;
- }
- else
- {
- if ( flDistanceToGoal > -(CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
- break;
- }
-
- if ( GetHealth() <= m_flNextMegaBombHealth )
- {
- m_flNextMegaBombHealth -= GetMaxHealth() * g_helicopter_bullrush_mega_bomb_health.GetFloat();
- m_flNextBullrushBombTime = gpGlobals->curtime;
- SetSecondaryMode( BULLRUSH_MODE_MEGA_BOMB );
- EmitSound( "NPC_AttackHelicopter.MegabombAlert" );
- }
- else
- {
- SetSecondaryMode( BULLRUSH_MODE_SHOOT_GUN );
- SpotlightStartup();
- }
- }
- break;
-
- case BULLRUSH_MODE_MEGA_BOMB:
- {
- m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
-
- float flDistanceToGoal = ComputeDistanceToTargetPosition();
- if ( m_bRushForward )
- {
- if ( flDistanceToGoal > -(CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
- break;
- }
- else
- {
- if ( flDistanceToGoal < (CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
- break;
- }
-
- m_bRushForward = !m_bRushForward;
- SetSecondaryMode( BULLRUSH_MODE_GET_DISTANCE );
- }
- break;
-
- case BULLRUSH_MODE_SHOOT_GUN:
- {
- // When shooting, stop when we cross the player's position
- // Then start bombing. Use the fixed speed version if we're too far
- // from the enemy or if he's travelling in the opposite direction.
- // Otherwise, do the standard bombing behavior for a while.
- float flDistanceToGoal = ComputeDistanceToTargetPosition();
-
- float flShootingHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
- float flSwitchToBombDist = CHOPPER_BULLRUSH_ENEMY_BOMB_DISTANCE;
- float flDropDownDist = 2000.0f;
- if ( m_bRushForward )
- {
- m_flBullrushAdditionalHeight = ClampSplineRemapVal( flDistanceToGoal,
- flSwitchToBombDist, flSwitchToBombDist + flDropDownDist, 0.0f, flShootingHeight );
- if ( flDistanceToGoal > flSwitchToBombDist )
- break;
- }
- else
- {
- m_flBullrushAdditionalHeight = ClampSplineRemapVal( flDistanceToGoal,
- -flSwitchToBombDist - flDropDownDist, -flSwitchToBombDist, flShootingHeight, 0.0f );
- if ( flDistanceToGoal < -flSwitchToBombDist )
- break;
- }
-
- if ( ShouldShootIdlePlayerInBullrush() )
- {
- SetSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER );
- }
- else
- {
- ShutdownGunDuringBullrush( );
- SetSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED );
- }
- }
- break;
-
- case BULLRUSH_MODE_SHOOT_IDLE_PLAYER:
- {
- // Shut down our gun if we're switching to bombing
- if ( ShouldBombIdlePlayer() )
- {
- // Must not already be shutdown
- if (( m_nGunState != GUN_STATE_IDLE ) && (SecondaryModeTime() >= BULLRUSH_IDLE_PLAYER_FIRE_TIME))
- {
- ShutdownGunDuringBullrush( );
- }
- }
-
-// m_nBurstHits = 0;
- m_flCircleOfDeathRadius = ClampSplineRemapVal( SecondaryModeTime(), BULLRUSH_IDLE_PLAYER_FIRE_TIME, BULLRUSH_IDLE_PLAYER_FIRE_TIME + 5.0f, 256.0f, 64.0f );
- m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
- if ( !ShouldShootIdlePlayerInBullrush() )
- {
- ShutdownGunDuringBullrush( );
- SetSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED );
- }
- }
- break;
-
- case BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER:
- {
- m_flBullrushAdditionalHeight = 0.0f;
- float flDistanceToGoal = ComputeDistanceToTargetPosition();
- if ( fabs( flDistanceToGoal ) > 2000.0f )
- {
- SetSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED, true );
- break;
- }
- }
- // FALL THROUGH!!
-
- case BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED:
- {
- float flDistanceToGoal = ComputeDistanceToTargetPosition();
-
- m_flBullrushAdditionalHeight = 0.0f;
- if (( SecondaryModeTime() >= CHOPPER_BULLRUSH_ENEMY_BOMB_TIME ) || ( flDistanceToGoal > BULLRUSH_BOMB_MAX_DISTANCE ))
- {
- m_bRushForward = !m_bRushForward;
- SetSecondaryMode( BULLRUSH_MODE_GET_DISTANCE );
- }
- }
- break;
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::UpdateEnemyLeading( void )
-{
- bool bEnemyInVehicle = true;
- CBaseEntity *pTarget = GetEnemyVehicle();
- if ( !pTarget )
- {
- bEnemyInVehicle = false;
- if ( (m_nAttackMode == ATTACK_MODE_DEFAULT) || !GetEnemy() )
- {
- EnableLeading( false );
- return;
- }
-
- pTarget = GetEnemy();
- }
-
- EnableLeading( true );
-
- float flLeadingDist = 0.0f;
- float flSpeedAlongPath = TargetSpeedAlongPath();
- float flSpeed = pTarget->GetSmoothedVelocity().Length();
-
- // Do the test electricity gun
- if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
- {
- if ( flSpeedAlongPath < 200.0f )
- {
- flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, 0.0f, 200.0f, 100.0f, -200.0f );
- }
- else
- {
- flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, 200.0f, 600.0f, -200.0f, -500.0f );
- }
- SetLeadingDistance( flLeadingDist );
- return;
- }
-
- switch( m_nAttackMode )
- {
- case ATTACK_MODE_BULLRUSH_VEHICLE:
- flLeadingDist = ComputeBullrushLeadingDistance( flSpeed, flSpeedAlongPath, bEnemyInVehicle );
- break;
-
- case ATTACK_MODE_ALWAYS_LEAD_VEHICLE:
- if (( flSpeed <= MIN_ENEMY_SPEED ) && (bEnemyInVehicle) )
- {
- flLeadingDist = CreepTowardEnemy( flSpeed, 0.0f, MIN_ENEMY_SPEED, 0.0f, 1000.0f );
- }
- else
- {
- if ( flSpeedAlongPath > 0.0f )
- {
- flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, 200.0f, 600.0f, 1000.0f, 2000.0f );
- }
- else
- {
- flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, -600.0f, -200.0f, -2000.0f, -1000.0f );
- }
- }
- break;
-
- case ATTACK_MODE_BOMB_VEHICLE:
- flLeadingDist = ComputeBombingLeadingDistance( flSpeed, flSpeedAlongPath, bEnemyInVehicle );
- break;
-
- case ATTACK_MODE_DEFAULT:
- case ATTACK_MODE_TRAIL_VEHICLE:
- if (( flSpeed <= MIN_ENEMY_SPEED ) && (bEnemyInVehicle))
- {
- flLeadingDist = CreepTowardEnemy( flSpeed, 150.0f, MIN_ENEMY_SPEED, 500.0f, -1000.0f );
- }
- else
- {
- flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, -600.0f, -200.0f, -2500.0f, -1000.0f );
- }
- break;
- }
-
- SetLeadingDistance( flLeadingDist );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pInfo -
-// bAlways -
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
-{
- // Are we already marked for transmission?
- if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
- return;
-
- BaseClass::SetTransmit( pInfo, bAlways );
-
- // Make our smoke trails always come with us
- for ( int i = 0; i < m_nSmokeTrailCount; i++ )
- {
- m_hSmokeTrail[i]->SetTransmit( pInfo, bAlways );
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-void CNPC_AttackHelicopter::Hunt( void )
-{
- if ( m_lifeState == LIFE_DEAD )
- {
- return;
- }
-
- if ( m_lifeState == LIFE_DYING )
- {
- Flight();
- UpdatePlayerDopplerShift( );
- return;
- }
-
- // FIXME: Hack to allow us to change the firing distance
- SetFarthestPathDist( GetMaxFiringDistance() );
-
- UpdateEnemy();
-
- // Give free knowledge of the enemy position if the chopper is "aggressive"
- if ( HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) && GetEnemy() )
- {
- m_vecTargetPosition = GetEnemy()->WorldSpaceCenter();
- }
-
- // Test for state transitions when in bullrush mode
- if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
- {
- UpdateBullrushState();
- }
-
- UpdateEnemyLeading();
-
- UpdateTrackNavigation( );
-
- Flight();
-
- UpdatePlayerDopplerShift( );
-
- FireWeapons();
-
- if ( !(m_fHelicopterFlags & BITS_HELICOPTER_GUN_ON) )
- {
- // !!!HACKHACK This is a fairly unsavoury hack that allows the attack
- // chopper to continue to carpet bomb even with the gun turned off
- // (Normally the chopper will carpet bomb inside FireGun(), but FireGun()
- // doesn't get called by the above call to FireWeapons() if the gun is turned off)
- // Adding this little exception here lets me avoid going into the CBaseHelicopter and
- // making some functions virtual that don't want to be virtual.
- if ( IsCarpetBombing() )
- {
- BullrushBombs();
- }
- }
-
-#ifdef HL2_EPISODIC
- // Update our bone followers
- m_BoneFollowerManager.UpdateBoneFollowers(this);
-#endif // HL2_EPISODIC
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Cache whatever pose parameters we intend to use
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::PopulatePoseParameters( void )
-{
- m_poseWeapon_Pitch = LookupPoseParameter("weapon_pitch");
- m_poseWeapon_Yaw = LookupPoseParameter("weapon_yaw");
- m_poseRudder = LookupPoseParameter("rudder");
-
- BaseClass::PopulatePoseParameters();
-}
-
-#ifdef HL2_EPISODIC
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_AttackHelicopter::InitBoneFollowers( void )
-{
- // Don't do this if we're already loaded
- if ( m_BoneFollowerManager.GetNumBoneFollowers() != 0 )
- return;
-
- // Init our followers
- m_BoneFollowerManager.InitBoneFollowers( this, ARRAYSIZE(pFollowerBoneNames), pFollowerBoneNames );
-}
-#endif // HL2_EPISODIC
-
-//-----------------------------------------------------------------------------
-// Where are how should we avoid?
-//-----------------------------------------------------------------------------
-AI_BEGIN_CUSTOM_NPC( npc_helicopter, CNPC_AttackHelicopter )
-
-// DECLARE_TASK( )
-
- DECLARE_ACTIVITY( ACT_HELICOPTER_DROP_BOMB );
- DECLARE_ACTIVITY( ACT_HELICOPTER_CRASHING );
-
-// DECLARE_CONDITION( COND_ )
-
- //=========================================================
-// DEFINE_SCHEDULE
-// (
-// SCHED_DUMMY,
-//
-// " Tasks"
-// " TASK_FACE_ENEMY 0"
-// " "
-// " Interrupts"
-// )
-
-
-AI_END_CUSTOM_NPC()
-
-
-
-//------------------------------------------------------------------------------
-//
-// A sensor used to drop bombs only in the correct points
-//
-//------------------------------------------------------------------------------
-LINK_ENTITY_TO_CLASS( npc_helicoptersensor, CBombDropSensor );
-
-BEGIN_DATADESC( CBombDropSensor )
-
- DEFINE_INPUTFUNC( FIELD_VOID, "DropBomb", InputDropBomb ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DropBombStraightDown", InputDropBombStraightDown ),
- DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTargetAlways", InputDropBombAtTargetAlways ),
- DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTarget", InputDropBombAtTarget ),
- DEFINE_INPUTFUNC( FIELD_FLOAT, "DropBombDelay", InputDropBombDelay ),
-
-END_DATADESC()
-
-
-void CBombDropSensor::Spawn()
-{
- BaseClass::Spawn();
- UTIL_SetSize(this, Vector(-30,-30,-30), Vector(30,30,30) );
- SetSolid(SOLID_BBOX);
-
- // Shots pass through
- SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
-}
-
-// Drop a bomb at a particular location
-void CBombDropSensor::InputDropBomb( inputdata_t &inputdata )
-{
- inputdata_t myVersion = inputdata;
- myVersion.pActivator = this;
- assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBomb( myVersion );
-}
-
-void CBombDropSensor::InputDropBombStraightDown( inputdata_t &inputdata )
-{
- inputdata_t myVersion = inputdata;
- myVersion.pActivator = this;
- assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombStraightDown( myVersion );
-}
-
-void CBombDropSensor::InputDropBombAtTarget( inputdata_t &inputdata )
-{
- inputdata_t myVersion = inputdata;
- myVersion.pActivator = this;
- assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombAtTarget( myVersion );
-}
-
-void CBombDropSensor::InputDropBombAtTargetAlways( inputdata_t &inputdata )
-{
- inputdata_t myVersion = inputdata;
- myVersion.pActivator = this;
- assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombAtTargetAlways( myVersion );
-}
-
-void CBombDropSensor::InputDropBombDelay( inputdata_t &inputdata )
-{
- inputdata_t myVersion = inputdata;
- myVersion.pActivator = this;
- assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombDelay( myVersion );
-}
-
-//------------------------------------------------------------------------------
-//
-// The bombs the helicopter drops on the player
-//
-//------------------------------------------------------------------------------
-
-//------------------------------------------------------------------------------
-// Save/load
-//------------------------------------------------------------------------------
-
-LINK_ENTITY_TO_CLASS( grenade_helicopter, CGrenadeHelicopter );
-
-BEGIN_DATADESC( CGrenadeHelicopter )
-
- DEFINE_FIELD( m_bActivated, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bExplodeOnContact, FIELD_BOOLEAN ),
- DEFINE_SOUNDPATCH( m_pWarnSound ),
-
- DEFINE_FIELD( m_hWarningSprite, FIELD_EHANDLE ),
- DEFINE_FIELD( m_bBlinkerAtTop, FIELD_BOOLEAN ),
-
-#ifdef HL2_EPISODIC
- DEFINE_FIELD( m_flLifetime, FIELD_FLOAT ),
- DEFINE_FIELD( m_hCollisionObject, FIELD_EHANDLE ),
- DEFINE_FIELD( m_bPickedUp, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flBlinkFastTime, FIELD_TIME ),
-
- DEFINE_INPUTFUNC( FIELD_FLOAT, "ExplodeIn", InputExplodeIn ),
-
- DEFINE_OUTPUT( m_OnPhysGunOnlyPickup, "OnPhysGunOnlyPickup" ),
-#endif // HL2_EPISODIC
-
- DEFINE_THINKFUNC( ExplodeThink ),
- DEFINE_THINKFUNC( AnimateThink ),
- DEFINE_THINKFUNC( RampSoundThink ),
- DEFINE_THINKFUNC( WarningBlinkerThink ),
- DEFINE_ENTITYFUNC( ExplodeConcussion ),
-
-END_DATADESC()
-
-#define SF_HELICOPTER_GRENADE_DUD (1<<16) // Will not become active on impact, only when launched via physcannon
-
-//------------------------------------------------------------------------------
-// Precache
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::Precache( void )
-{
- BaseClass::Precache( );
- PrecacheModel( GRENADE_HELICOPTER_MODEL );
-
- PrecacheScriptSound( "ReallyLoudSpark" );
- PrecacheScriptSound( "NPC_AttackHelicopterGrenade.Ping" );
- PrecacheScriptSound( "NPC_AttackHelicopterGrenade.PingCaptured" );
- PrecacheScriptSound( "NPC_AttackHelicopterGrenade.HardImpact" );
-}
-
-
-//------------------------------------------------------------------------------
-// Spawn
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::Spawn( void )
-{
- Precache();
-
- // point sized, solid, bouncing
- SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
- SetModel( GRENADE_HELICOPTER_MODEL );
-
- if ( HasSpawnFlags( SF_HELICOPTER_GRENADE_DUD ) )
- {
- m_nSkin = (int)SKIN_DUD;
- }
-
- if ( !HasSpawnFlags( SF_GRENADE_HELICOPTER_MEGABOMB ) )
- {
- IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, GetSolidFlags(), false );
- SetMoveType( MOVETYPE_VPHYSICS );
-
- Vector vecAbsVelocity = GetAbsVelocity();
- pPhysicsObject->AddVelocity( &vecAbsVelocity, NULL );
- }
- else
- {
- SetSolid( SOLID_BBOX );
- SetCollisionBounds( Vector( -12.5, -12.5, -12.5 ), Vector( 12.5, 12.5, 12.5 ) );
- VPhysicsInitShadow( false, false );
- SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
- SetElasticity( 0.5f );
- AddEffects( EF_NOSHADOW );
- }
-
- // We're always being dropped beneath the helicopter; need to not
- // be affected by the rotor wash
- AddEFlags( EFL_NO_ROTORWASH_PUSH );
-
- // contact grenades arc lower
- QAngle angles;
- VectorAngles(GetAbsVelocity(), angles );
- SetLocalAngles( angles );
-
- SetThink( NULL );
-
- // Tumble in air
- QAngle vecAngVel( random->RandomFloat ( -100, -500 ), 0, 0 );
- SetLocalAngularVelocity( vecAngVel );
-
- // Explode on contact
- SetTouch( &CGrenadeHelicopter::ExplodeConcussion );
-
- // use a lower gravity for grenades to make them easier to see
- SetGravity( UTIL_ScaleForGravity( 400 ) );
-
-#ifdef HL2_EPISODIC
- m_bPickedUp = false;
- m_flLifetime = BOMB_LIFETIME * 2.0;
-#endif // HL2_EPISODIC
-
- if ( hl2_episodic.GetBool() )
- {
- // Disallow this, we'd rather deal with them as physobjects
- m_takedamage = DAMAGE_NO;
- }
- else
- {
- // Allow player to blow this puppy up in the air
- m_takedamage = DAMAGE_YES;
- }
-
- m_bActivated = false;
- m_pWarnSound = NULL;
- m_bExplodeOnContact = false;
-
- m_flDamage = sk_helicopter_grenadedamage.GetFloat();
-
- g_pNotify->AddEntity( this, this );
-
- if( hl2_episodic.GetBool() )
- {
- SetContextThink( &CGrenadeHelicopter::AnimateThink, gpGlobals->curtime, s_pAnimateThinkContext );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// On Remve
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::UpdateOnRemove()
-{
- if( m_pWarnSound )
- {
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundDestroy( m_pWarnSound );
- }
- g_pNotify->ClearEntity( this );
- BaseClass::UpdateOnRemove();
-}
-
-
-#ifdef HL2_EPISODIC
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::InputExplodeIn( inputdata_t &inputdata )
-{
- m_flLifetime = inputdata.value.Float();
-
- if ( HasSpawnFlags( SF_HELICOPTER_GRENADE_DUD ) )
- {
- // We are a dud no more!
- RemoveSpawnFlags( SF_HELICOPTER_GRENADE_DUD );
- m_nSkin = (int)SKIN_REGULAR;
- }
-
- m_bActivated = false;
- BecomeActive();
-}
-#endif
-
-
-//------------------------------------------------------------------------------
-// Activate!
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::BecomeActive()
-{
- if ( m_bActivated )
- return;
-
- if ( IsMarkedForDeletion() )
- return;
-
- m_bActivated = true;
-
- bool bMegaBomb = HasSpawnFlags(SF_GRENADE_HELICOPTER_MEGABOMB);
-
- SetThink( &CGrenadeHelicopter::ExplodeThink );
-
- if ( hl2_episodic.GetBool() )
- {
- if ( HasSpawnFlags( SF_HELICOPTER_GRENADE_DUD ) == false )
- {
- SetNextThink( gpGlobals->curtime + GetBombLifetime() );
- }
- else
- {
- // NOTE: A dud will not explode after a set time, only when launched!
- SetThink( NULL );
- return;
- }
- }
- else
- {
- SetNextThink( gpGlobals->curtime + GetBombLifetime() );
- }
-
- if ( !bMegaBomb )
- {
- SetContextThink( &CGrenadeHelicopter::RampSoundThink, gpGlobals->curtime + GetBombLifetime() - BOMB_RAMP_SOUND_TIME, s_pRampSoundContext );
-
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- CReliableBroadcastRecipientFilter filter;
- m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopterGrenade.Ping" );
- controller.Play( m_pWarnSound, 1.0, PITCH_NORM );
- }
-
- SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + (GetBombLifetime() - 2.0f), s_pWarningBlinkerContext );
-
-#ifdef HL2_EPISODIC
- m_flBlinkFastTime = gpGlobals->curtime + GetBombLifetime() - 1.0f;
-#endif//HL2_EPISODIC
-}
-
-
-//------------------------------------------------------------------------------
-// Pow!
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::RampSoundThink( )
-{
- if ( m_pWarnSound )
- {
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundChangePitch( m_pWarnSound, 140, BOMB_RAMP_SOUND_TIME );
- }
-
- SetContextThink( NULL, gpGlobals->curtime, s_pRampSoundContext );
-}
-
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::WarningBlinkerThink()
-{
-#ifndef HL2_EPISODIC
- return;
-#endif
-
-/*
- if( !m_hWarningSprite.Get() )
- {
- Vector up;
- GetVectors( NULL, NULL, &up );
-
- // Light isn't on, so create the sprite.
- m_hWarningSprite = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetAbsOrigin() + up * 10.0f, false );
- CSprite *pSprite = (CSprite *)m_hWarningSprite.Get();
-
- if( pSprite != NULL )
- {
- pSprite->SetParent( this, LookupAttachment("top") );
- pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
- pSprite->SetScale( 0.35, 0.0 );
- }
-
- m_bBlinkerAtTop = true;
-
- ResetSequence( LookupActivity( "ACT_ARM" ) );
- }
- else
-*/
- {
- // Just flip it to the other attachment.
- if( m_bBlinkerAtTop )
- {
- //m_hWarningSprite->SetParentAttachment( "SetParentAttachment", "bottom", false );
- m_nSkin = (int)SKIN_REGULAR;
- m_bBlinkerAtTop = false;
- }
- else
- {
- //m_hWarningSprite->SetParentAttachment( "SetParentAttachment", "top", false );
- m_nSkin = (int)SKIN_DUD;
- m_bBlinkerAtTop = true;
- }
- }
-
- // Frighten people
- CSoundEnt::InsertSound ( SOUND_DANGER, WorldSpaceCenter(), g_helicopter_bomb_danger_radius.GetFloat(), 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
-
-#ifdef HL2_EPISODIC
- if( gpGlobals->curtime >= m_flBlinkFastTime )
- {
- SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + 0.1f, s_pWarningBlinkerContext );
- }
- else
- {
- SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + 0.2f, s_pWarningBlinkerContext );
- }
-#endif//HL2_EPISODIC
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::StopWarningBlinker()
-{
- if( m_hWarningSprite.Get() )
- {
- UTIL_Remove( m_hWarningSprite.Get() );
- m_hWarningSprite.Set( NULL );
- }
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::AnimateThink()
-{
- StudioFrameAdvance();
- SetContextThink( &CGrenadeHelicopter::AnimateThink, gpGlobals->curtime + 0.1f, s_pAnimateThinkContext );
-}
-
-//------------------------------------------------------------------------------
-// Entity events... these are events targetted to a particular entity
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::OnEntityEvent( EntityEvent_t event, void *pEventData )
-{
- BaseClass::OnEntityEvent( event, pEventData );
-
- if ( event == ENTITY_EVENT_WATER_TOUCH )
- {
- BecomeActive();
- }
-}
-
-
-//------------------------------------------------------------------------------
-// If we hit water, then stop
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::PhysicsSimulate( void )
-{
- Vector vecPrevPosition = GetAbsOrigin();
-
- BaseClass::PhysicsSimulate();
-
- if (!m_bActivated && (GetMoveType() != MOVETYPE_VPHYSICS))
- {
- if ( GetWaterLevel() > 1 )
- {
- SetAbsVelocity( vec3_origin );
- SetMoveType( MOVETYPE_NONE );
- BecomeActive();
- }
-
- // Stuck condition, can happen pretty often
- if ( vecPrevPosition == GetAbsOrigin() )
- {
- SetAbsVelocity( vec3_origin );
- SetMoveType( MOVETYPE_NONE );
- BecomeActive();
- }
- }
-}
-
-
-//------------------------------------------------------------------------------
-// If we hit something, start the timer
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
-{
- BaseClass::VPhysicsCollision( index, pEvent );
- BecomeActive();
-
-#ifndef HL2_EPISODIC // in ep2, don't do this here, do it in Touch()
- if ( m_bExplodeOnContact )
- {
- Vector vecVelocity;
- GetVelocity( &vecVelocity, NULL );
- DoExplosion( GetAbsOrigin(), vecVelocity );
- }
-#endif
-
-
- if( hl2_episodic.GetBool() )
- {
- float flImpactSpeed = pEvent->preVelocity->Length();
- if( flImpactSpeed > 400.0f && pEvent->pEntities[ 1 ]->IsWorld() )
- {
- EmitSound( "NPC_AttackHelicopterGrenade.HardImpact" );
- }
- }
-}
-
-
-#if HL2_EPISODIC
-//------------------------------------------------------------------------------
-// double launch velocity for ep2_outland_08
-//------------------------------------------------------------------------------
-Vector CGrenadeHelicopter::PhysGunLaunchVelocity( const Vector &forward, float flMass )
-{
- // return ( striderbuster_shot_velocity.GetFloat() * forward );
-
- return BaseClass::PhysGunLaunchVelocity(forward,flMass) * sk_helicopter_grenaderadius.GetFloat();
-}
-#endif
-
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-float CGrenadeHelicopter::GetBombLifetime()
-{
-#if HL2_EPISODIC
- return m_flLifetime;
-#else
- return BOMB_LIFETIME;
-#endif
-}
-
-
-//------------------------------------------------------------------------------
-// Pow!
-//------------------------------------------------------------------------------
-int CGrenadeHelicopter::OnTakeDamage( const CTakeDamageInfo &info )
-{
- // We don't take blast damage
- if ( info.GetDamageType() & DMG_BLAST )
- return 0;
-
- return BaseClass::OnTakeDamage( info );
-}
-
-
-//------------------------------------------------------------------------------
-// Pow!
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::DoExplosion( const Vector &vecOrigin, const Vector &vecVelocity )
-{
- ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity() ? GetOwnerEntity() : this, sk_helicopter_grenadedamage.GetFloat(),
- sk_helicopter_grenaderadius.GetFloat(), (SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODECAL|SF_ENVEXPLOSION_NOFIREBALL|SF_ENVEXPLOSION_NOPARTICLES),
- sk_helicopter_grenadeforce.GetFloat(), this );
-
- if ( GetShakeAmplitude() )
- {
- UTIL_ScreenShake( GetAbsOrigin(), GetShakeAmplitude(), 150.0, 1.0, GetShakeRadius(), SHAKE_START );
- }
-
- CEffectData data;
-
- // If we're under water do a water explosion
- if ( GetWaterLevel() != 0 && (GetWaterType() & CONTENTS_WATER) )
- {
- data.m_vOrigin = WorldSpaceCenter();
- data.m_flMagnitude = 128;
- data.m_flScale = 128;
- data.m_fFlags = 0;
- DispatchEffect( "WaterSurfaceExplosion", data );
- }
- else
- {
- // Otherwise do a normal explosion
- data.m_vOrigin = GetAbsOrigin();
- DispatchEffect( "HelicopterMegaBomb", data );
- }
-
- UTIL_Remove( this );
-}
-
-
-//------------------------------------------------------------------------------
-// I think I Pow!
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::ExplodeThink(void)
-{
-#ifdef HL2_EPISODIC
- // remember if we were thrown by player, we can only determine this prior to explosion
- bool bIsThrownByPlayer = IsThrownByPlayer();
- int iHealthBefore = 0;
- // get the health of the helicopter we came from prior to our explosion
- CNPC_AttackHelicopter *pOwner = dynamic_cast<CNPC_AttackHelicopter *>( GetOriginalThrower() );
- if ( pOwner )
- {
- iHealthBefore = pOwner->GetHealth();
- }
-#endif // HL2_EPISODIC
-
- Vector vecVelocity;
- GetVelocity( &vecVelocity, NULL );
- DoExplosion( GetAbsOrigin(), vecVelocity );
-
-#ifdef HL2_EPISODIC
- // if we were thrown by player, look at health of helicopter after explosion and determine if we damaged it
- if ( bIsThrownByPlayer && pOwner && ( iHealthBefore > 0 ) )
- {
- int iHealthAfter = pOwner->GetHealth();
- if ( iHealthAfter == iHealthBefore )
- {
- // The player threw us, we exploded due to timer, and we did not damage the helicopter that fired us. Send a miss event
- SendMissEvent();
- }
- }
-#endif // HL2_EPISODIC
-
-}
-
-
-//------------------------------------------------------------------------------
-// I think I Pow!
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity )
-{
- ResolveFlyCollisionBounce( trace, vecVelocity, 0.1f );
-}
-
-
-//------------------------------------------------------------------------------
-// Contact grenade, explode when it touches something
-//------------------------------------------------------------------------------
-void CGrenadeHelicopter::ExplodeConcussion( CBaseEntity *pOther )
-{
- if ( !pOther->IsSolid() )
- return;
-
- if ( !m_bExplodeOnContact )
- {
- if ( pOther->IsWorld() )
- return;
-
- if ( hl2_episodic.GetBool() )
- {
- // Don't hit anything other than vehicles
- if ( pOther->GetCollisionGroup() != COLLISION_GROUP_VEHICLE )
- return;
- }
- }
-
-#ifdef HL2_EPISODIC
- CBaseEntity *pEntityHit = pOther;
- if ( pEntityHit->ClassMatches( "phys_bone_follower" ) && pEntityHit->GetOwnerEntity() )
- {
- pEntityHit = pEntityHit->GetOwnerEntity();
- }
- if ( ( CLASS_COMBINE_GUNSHIP != pEntityHit->Classify() ) || !pEntityHit->ClassMatches( "npc_helicopter" ) )
- {
- // We hit something other than a helicopter. If the player threw us, send a miss event
- if ( IsThrownByPlayer() )
- {
- SendMissEvent();
- }
- }
-#endif // HL2_EPISODIC
-
- Vector vecVelocity;
- GetVelocity( &vecVelocity, NULL );
- DoExplosion( GetAbsOrigin(), vecVelocity );
-}
-
-
-#ifdef HL2_EPISODIC
-//-----------------------------------------------------------------------------
-// Purpose: The bomb will act differently when picked up by the player
-//-----------------------------------------------------------------------------
-void CGrenadeHelicopter::OnPhysGunPickup(CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
-{
- if ( reason == PICKED_UP_BY_CANNON )
- {
- if ( !m_bPickedUp )
- {
- if( m_hWarningSprite.Get() != NULL )
- {
- UTIL_Remove( m_hWarningSprite );
- m_hWarningSprite.Set(NULL);
- }
-
- // Turn on
- BecomeActive();
-
- // Change the warning sound to a captured sound.
- SetContextThink( &CGrenadeHelicopter::RampSoundThink, gpGlobals->curtime + GetBombLifetime() - BOMB_RAMP_SOUND_TIME, s_pRampSoundContext );
-
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundDestroy( m_pWarnSound );
-
- CReliableBroadcastRecipientFilter filter;
- m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopterGrenade.PingCaptured" );
- controller.Play( m_pWarnSound, 1.0, PITCH_NORM );
-
- // Reset our counter so the player has more time
- SetThink( &CGrenadeHelicopter::ExplodeThink );
- SetNextThink( gpGlobals->curtime + GetBombLifetime() );
-
- SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + GetBombLifetime() - 2.0f, s_pWarningBlinkerContext );
-
-#ifdef HL2_EPISODIC
- m_nSkin = (int)SKIN_REGULAR;
- m_flBlinkFastTime = gpGlobals->curtime + GetBombLifetime() - 1.0f;
-#endif//HL2_EPISODIC
-
- // Stop us from sparing damage to the helicopter that dropped us
- SetOwnerEntity( pPhysGunUser );
- PhysEnableEntityCollisions( this, m_hCollisionObject );
-
- // Don't do this again!
- m_bPickedUp = true;
-
- m_OnPhysGunOnlyPickup.FireOutput( pPhysGunUser, this );
- }
- }
-
- BaseClass::OnPhysGunPickup( pPhysGunUser, reason );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CGrenadeHelicopter::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason )
-{
- if ( reason == LAUNCHED_BY_CANNON )
- {
- // Enable world touches.
- unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
- VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
-
- // Explode on contact
- SetTouch( &CGrenadeHelicopter::ExplodeConcussion );
- m_bExplodeOnContact = true;
-
- }
-
- BaseClass::OnPhysGunDrop( pPhysGunUser, reason );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns if the player threw this grenade w/phys gun
-//-----------------------------------------------------------------------------
-bool CGrenadeHelicopter::IsThrownByPlayer()
-{
- // if player is the owner and we're set to explode on contact, then the player threw this grenade.
- return ( ( GetOwnerEntity() == UTIL_GetLocalPlayer() ) && m_bExplodeOnContact );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: If player threw this grenade, sends a miss event
-//-----------------------------------------------------------------------------
-void CGrenadeHelicopter::SendMissEvent()
-{
- // send a miss event
- IGameEvent *event = gameeventmanager->CreateEvent( "helicopter_grenade_punt_miss" );
- if ( event )
- {
- gameeventmanager->FireEvent( event );
- }
-}
-
-#endif // HL2_EPISODIC
-
-//-----------------------------------------------------------------------------
-//
-// This entity is used to create little force spheres that the helicopters should avoid.
-//
-//-----------------------------------------------------------------------------
-CUtlVector< CAvoidSphere::AvoidSphereHandle_t > CAvoidSphere::s_AvoidSpheres;
-
-#define SF_AVOIDSPHERE_AVOID_BELOW 0x00010000
-
-LINK_ENTITY_TO_CLASS( npc_heli_avoidsphere, CAvoidSphere );
-
-BEGIN_DATADESC( CAvoidSphere )
-
- DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
-
-END_DATADESC()
-
-
-//-----------------------------------------------------------------------------
-// Creates an avoidance sphere
-//-----------------------------------------------------------------------------
-CBaseEntity *CreateHelicopterAvoidanceSphere( CBaseEntity *pParent, int nAttachment, float flRadius, bool bAvoidBelow )
-{
- CAvoidSphere *pSphere = static_cast<CAvoidSphere*>(CreateEntityByName( "npc_heli_avoidsphere" ));
- pSphere->Init( flRadius );
- if ( bAvoidBelow )
- {
- pSphere->AddSpawnFlags( SF_AVOIDSPHERE_AVOID_BELOW );
- }
- pSphere->Spawn();
- pSphere->SetParent( pParent, nAttachment );
- pSphere->SetLocalOrigin( vec3_origin );
- pSphere->SetLocalAngles( vec3_angle );
- pSphere->SetOwnerEntity( pParent );
- return pSphere;
-}
-
-
-//-----------------------------------------------------------------------------
-// Init
-//-----------------------------------------------------------------------------
-void CAvoidSphere::Init( float flRadius )
-{
- m_flRadius = flRadius;
-}
-
-
-//-----------------------------------------------------------------------------
-// Spawn, remove
-//-----------------------------------------------------------------------------
-void CAvoidSphere::Activate( )
-{
- BaseClass::Activate();
- s_AvoidSpheres.AddToTail( this );
-}
-
-void CAvoidSphere::UpdateOnRemove( )
-{
- s_AvoidSpheres.FindAndRemove( this );
- BaseClass::UpdateOnRemove();
-}
-
-
-//-----------------------------------------------------------------------------
-// Where are how should we avoid?
-//-----------------------------------------------------------------------------
-void CAvoidSphere::ComputeAvoidanceForces( CBaseEntity *pEntity, float flEntityRadius,
- float flAvoidTime, Vector *pVecAvoidForce )
-{
- pVecAvoidForce->Init( );
-
- Vector vecEntityDelta;
- VectorMultiply( pEntity->GetAbsVelocity(), flAvoidTime, vecEntityDelta );
- Vector vecEntityCenter = pEntity->WorldSpaceCenter();
-
- for ( int i = s_AvoidSpheres.Count(); --i >= 0; )
- {
- CAvoidSphere *pSphere = s_AvoidSpheres[i].Get();
- const Vector &vecAvoidCenter = pSphere->WorldSpaceCenter();
-
- // NOTE: This test can be thought of sweeping a sphere through space
- // and seeing if it intersects the avoidance sphere
- float flTotalRadius = flEntityRadius + pSphere->m_flRadius;
- float t1, t2;
- if ( !IntersectRayWithSphere( vecEntityCenter, vecEntityDelta,
- vecAvoidCenter, flTotalRadius, &t1, &t2 ) )
- {
- continue;
- }
-
- // NOTE: The point of closest approach is at the average t value
- Vector vecClosestApproach;
- float flAverageT = (t1 + t2) * 0.5f;
- VectorMA( vecEntityCenter, flAverageT, vecEntityDelta, vecClosestApproach );
-
- // Add velocity to make it be pushed out away from the sphere center
- // without totally counteracting its velocity.
- Vector vecDir;
- VectorSubtract( vecClosestApproach, vecAvoidCenter, vecDir );
- float flZDist = vecDir.z;
- float flDist = VectorNormalize( vecDir );
- float flDistToTravel;
- if ( flDist < 0.01f )
- {
- flDist = 0.01f;
- vecDir.Init( 0, 0, 1 );
- flDistToTravel = flTotalRadius;
- }
- else
- {
- // make the chopper always avoid *above*
- // That means if a force would be applied to push the chopper down,
- // figure out a new distance to travel that would push the chopper up.
- if ( flZDist < 0.0f && !pSphere->HasSpawnFlags(SF_AVOIDSPHERE_AVOID_BELOW) )
- {
- Vector vecExitPoint;
- vecDir.z = -vecDir.z;
- VectorMA( vecAvoidCenter, flTotalRadius, vecDir, vecExitPoint );
- VectorSubtract( vecExitPoint, vecClosestApproach, vecDir );
- flDistToTravel = VectorNormalize( vecDir );
- }
- else
- {
- Assert( flDist <= flTotalRadius );
- flDistToTravel = flTotalRadius - flDist;
- }
- }
-
- // The actual force amount is easy to think about:
- // We need to change the position by dx over a time dt, so dv = dx/dt
- // But so it doesn't explode, lets clamp t1 to a not-unreasonable time
- if ( t1 < 0.25f )
- {
- t1 = 0.25f;
- }
-
- float flForce = 1.25f * flDistToTravel / t1;
- vecDir *= flForce;
-
- *pVecAvoidForce += vecDir;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//
-// This entity is used to create little force boxes that the helicopters should avoid.
-//
-//-----------------------------------------------------------------------------
-CUtlVector< CAvoidBox::AvoidBoxHandle_t > CAvoidBox::s_AvoidBoxes;
-
-#define SF_AVOIDBOX_AVOID_BELOW 0x00010000
-
-LINK_ENTITY_TO_CLASS( npc_heli_avoidbox, CAvoidBox );
-
-BEGIN_DATADESC( CAvoidBox )
-END_DATADESC()
-
-
-//-----------------------------------------------------------------------------
-// Spawn, remove
-//-----------------------------------------------------------------------------
-void CAvoidBox::Spawn( )
-{
- SetModel( STRING( GetModelName() ) );
- SetSolid( SOLID_BSP );
- AddSolidFlags( FSOLID_NOT_SOLID );
- AddEffects( EF_NODRAW );
-}
-
-void CAvoidBox::Activate( )
-{
- BaseClass::Activate();
- s_AvoidBoxes.AddToTail( this );
-}
-
-void CAvoidBox::UpdateOnRemove( )
-{
- s_AvoidBoxes.FindAndRemove( this );
- BaseClass::UpdateOnRemove();
-}
-
-
-//-----------------------------------------------------------------------------
-// Where are how should we avoid?
-//-----------------------------------------------------------------------------
-void CAvoidBox::ComputeAvoidanceForces( CBaseEntity *pEntity, float flEntityRadius, float flAvoidTime, Vector *pVecAvoidForce )
-{
- pVecAvoidForce->Init( );
-
- Vector vecEntityDelta, vecEntityEnd;
- VectorMultiply( pEntity->GetAbsVelocity(), flAvoidTime, vecEntityDelta );
- Vector vecEntityCenter = pEntity->WorldSpaceCenter();
- VectorAdd( vecEntityCenter, vecEntityDelta, vecEntityEnd );
-
- Vector vecVelDir = pEntity->GetAbsVelocity();
- VectorNormalize( vecVelDir );
-
- for ( int i = s_AvoidBoxes.Count(); --i >= 0; )
- {
- CAvoidBox *pBox = s_AvoidBoxes[i].Get();
-
- const Vector &vecAvoidCenter = pBox->WorldSpaceCenter();
-
- // NOTE: This test can be thought of sweeping a sphere through space
- // and seeing if it intersects the avoidance box
- float flTotalRadius = flEntityRadius + pBox->BoundingRadius();
- float t1, t2;
- if ( !IntersectInfiniteRayWithSphere( vecEntityCenter, vecEntityDelta,
- vecAvoidCenter, flTotalRadius, &t1, &t2 ) )
- {
- continue;
- }
-
- if (( t2 < 0.0f ) || ( t1 > 1.0f ))
- continue;
-
- // Unlike the avoid spheres, we also need to make sure the ray intersects the box
- Vector vecLocalCenter, vecLocalDelta;
- pBox->CollisionProp()->WorldToCollisionSpace( vecEntityCenter, &vecLocalCenter );
- pBox->CollisionProp()->WorldDirectionToCollisionSpace( vecEntityDelta, &vecLocalDelta );
-
- Vector vecBoxMin( -flEntityRadius, -flEntityRadius, -flEntityRadius );
- Vector vecBoxMax( flEntityRadius, flEntityRadius, flEntityRadius );
- vecBoxMin += pBox->CollisionProp()->OBBMins();
- vecBoxMax += pBox->CollisionProp()->OBBMaxs();
-
- trace_t tr;
- if ( !IntersectRayWithBox( vecLocalCenter, vecLocalDelta, vecBoxMin, vecBoxMax, 0.0f, &tr ) )
- continue;
-
- // NOTE: The point of closest approach is at the average t value
- Vector vecClosestApproach;
- float flAverageT = (t1 + t2) * 0.5f;
- VectorMA( vecEntityCenter, flAverageT, vecEntityDelta, vecClosestApproach );
-
- // Add velocity to make it be pushed out away from the sphere center
- // without totally counteracting its velocity.
- Vector vecDir;
- VectorSubtract( vecClosestApproach, vecAvoidCenter, vecDir );
-
- // Limit unnecessary sideways motion
- if ( ( tr.plane.type != 3 ) || ( tr.plane.normal[2] > 0.0f ) )
- {
- vecDir.x *= 0.1f;
- vecDir.y *= 0.1f;
- }
-
- float flZDist = vecDir.z;
- float flDist = VectorNormalize( vecDir );
- float flDistToTravel;
- if ( flDist < 10.0f )
- {
- flDist = 10.0f;
- vecDir.Init( 0, 0, 1 );
- flDistToTravel = flTotalRadius;
- }
- else
- {
- // make the chopper always avoid *above*
- // That means if a force would be applied to push the chopper down,
- // figure out a new distance to travel that would push the chopper up.
- if ( flZDist < 0.0f && !pBox->HasSpawnFlags(SF_AVOIDSPHERE_AVOID_BELOW) )
- {
- Vector vecExitPoint;
- vecDir.z = -vecDir.z;
- VectorMA( vecAvoidCenter, flTotalRadius, vecDir, vecExitPoint );
- VectorSubtract( vecExitPoint, vecClosestApproach, vecDir );
- flDistToTravel = VectorNormalize( vecDir );
- }
- else
- {
- Assert( flDist <= flTotalRadius );
- flDistToTravel = flTotalRadius - flDist;
- }
- }
-
- // The actual force amount is easy to think about:
- // We need to change the position by dx over a time dt, so dv = dx/dt
- // But so it doesn't explode, lets clamp t1 to a not-unreasonable time
- if ( t1 < 0.25f )
- {
- t1 = 0.25f;
- }
-
- float flForce = 1.5f * flDistToTravel / t1;
- vecDir *= flForce;
-
- *pVecAvoidForce += vecDir;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//
-// This entity is used to create little force boxes that the helicopters should avoid.
-//
-//-----------------------------------------------------------------------------
-CUtlVector< CBombSuppressor::BombSuppressorHandle_t > CBombSuppressor::s_BombSuppressors;
-
-LINK_ENTITY_TO_CLASS( npc_heli_nobomb, CBombSuppressor );
-
-BEGIN_DATADESC( CBombSuppressor )
-END_DATADESC()
-
-
-//-----------------------------------------------------------------------------
-// Spawn, remove
-//-----------------------------------------------------------------------------
-void CBombSuppressor::Spawn( )
-{
- SetModel( STRING( GetModelName() ) );
- SetSolid( SOLID_BSP );
- AddSolidFlags( FSOLID_NOT_SOLID );
- AddEffects( EF_NODRAW );
-}
-
-void CBombSuppressor::Activate( )
-{
- BaseClass::Activate();
- s_BombSuppressors.AddToTail( this );
-}
-
-void CBombSuppressor::UpdateOnRemove( )
-{
- s_BombSuppressors.FindAndRemove( this );
- BaseClass::UpdateOnRemove();
-}
-
-
-//-----------------------------------------------------------------------------
-// Where are how should we avoid?
-//-----------------------------------------------------------------------------
-bool CBombSuppressor::CanBomb( const Vector &vecPosition )
-{
- for ( int i = s_BombSuppressors.Count(); --i >= 0; )
- {
- CBombSuppressor *pBox = s_BombSuppressors[i].Get();
- if ( pBox->CollisionProp()->IsPointInBounds( vecPosition ) )
- return false;
- }
-
- return true;
-}
-
-LINK_ENTITY_TO_CLASS( helicopter_chunk, CHelicopterChunk );
-
-BEGIN_DATADESC( CHelicopterChunk )
-
- DEFINE_THINKFUNC( FallThink ),
-
- DEFINE_FIELD( m_bLanded, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_hMaster, FIELD_EHANDLE ),
- DEFINE_FIELD( m_nChunkID, FIELD_INTEGER ),
- DEFINE_PHYSPTR( m_pTailConstraint ),
- DEFINE_PHYSPTR( m_pCockpitConstraint ),
-
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHelicopterChunk::Spawn( void )
-{
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CHelicopterChunk::FallThink( void )
-{
- if ( m_bLanded )
- {
- SetThink( NULL );
- return;
- }
-
- if ( random->RandomInt( 0, 8 ) == 0 )
- {
- CEffectData data;
- data.m_vOrigin = GetAbsOrigin() + RandomVector( -64, 64 );
- DispatchEffect( "HelicopterMegaBomb", data );
-
- EmitSound( "BaseExplosionEffect.Sound" );
- }
-
- SetNextThink( gpGlobals->curtime + 0.1f );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : index -
-// *pEvent -
-//-----------------------------------------------------------------------------
-void CHelicopterChunk::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
-{
- BaseClass::VPhysicsCollision( index, pEvent );
-
- if ( m_bLanded == false )
- {
- int otherIndex = !index;
- CBaseEntity *pOther = pEvent->pEntities[otherIndex];
- if ( !pOther )
- return;
-
- if ( pOther->IsWorld() )
- {
- CollisionCallback( this );
-
- m_bLanded = true;
- SetThink( NULL );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pCaller -
-//-----------------------------------------------------------------------------
-void CHelicopterChunk::CollisionCallback( CHelicopterChunk *pCaller )
-{
- if ( m_bLanded )
- return;
-
- if ( m_hMaster != NULL )
- {
- m_hMaster->CollisionCallback( this );
- }
- else
- {
- // Break our other constraints
- if ( m_pTailConstraint )
- {
- physenv->DestroyConstraint( m_pTailConstraint );
- m_pTailConstraint = NULL;
- }
-
- if ( m_pCockpitConstraint )
- {
- physenv->DestroyConstraint( m_pCockpitConstraint );
- m_pCockpitConstraint = NULL;
- }
-
- // Add a dust cloud
- AR2Explosion *pExplosion = AR2Explosion::CreateAR2Explosion( GetAbsOrigin() );
-
- if ( pExplosion != NULL )
- {
- pExplosion->SetLifetime( 10 );
- }
-
- // Make a loud noise
- EmitSound( "NPC_AttackHelicopter.Crash" );
-
- m_bLanded = true;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &vecPos -
-// &vecAngles -
-// &vecVelocity -
-// *pszModelName -
-// Output : CHelicopterChunk
-//-----------------------------------------------------------------------------
-CHelicopterChunk *CHelicopterChunk::CreateHelicopterChunk( const Vector &vecPos, const QAngle &vecAngles, const Vector &vecVelocity, const char *pszModelName, int chunkID )
-{
- // Drop a flaming, smoking chunk.
- CHelicopterChunk *pChunk = CREATE_ENTITY( CHelicopterChunk, "helicopter_chunk" );
-
- if ( pChunk == NULL )
- return NULL;
-
- pChunk->Spawn();
-
- pChunk->SetAbsOrigin( vecPos );
- pChunk->SetAbsAngles( vecAngles );
-
- pChunk->SetModel( pszModelName );
-
- pChunk->m_nChunkID = chunkID;
- pChunk->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE );
-
- IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
-
- // Set the velocity
- if ( pPhysicsObject )
- {
- pPhysicsObject->EnableMotion( true );
- Vector vecChunkVelocity;
- AngularImpulse angImpulse;
-
- vecChunkVelocity = vecVelocity;
- angImpulse = vec3_origin;
-
- pPhysicsObject->SetVelocity(&vecChunkVelocity, &angImpulse );
- }
-
- pChunk->SetThink( &CHelicopterChunk::FallThink );
- pChunk->SetNextThink( gpGlobals->curtime + 0.1f );
-
- pChunk->m_bLanded = false;
-
- SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
- pSmokeTrail->FollowEntity( pChunk, "damage" );
-
- pSmokeTrail->m_SpawnRate = 4;
- pSmokeTrail->m_ParticleLifetime = 2.0f;
-
- pSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f );
- pSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 );
-
- pSmokeTrail->m_StartSize = 32;
- pSmokeTrail->m_EndSize = 64;
- pSmokeTrail->m_SpawnRadius= 8;
- pSmokeTrail->m_MinSpeed = 0;
- pSmokeTrail->m_MaxSpeed = 8;
- pSmokeTrail->m_Opacity = 0.35f;
-
- CFireTrail *pFireTrail = CFireTrail::CreateFireTrail();
-
- if ( pFireTrail == NULL )
- return pChunk;
-
- pFireTrail->FollowEntity( pChunk, "damage" );
- pFireTrail->SetParent( pChunk, 1 );
- pFireTrail->SetLocalOrigin( vec3_origin );
- pFireTrail->SetMoveType( MOVETYPE_NONE );
- pFireTrail->SetLifetime( 10.0f );
-
- return pChunk;
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//===========================================================================//
+
+#include "cbase.h"
+#include "ai_network.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_node.h"
+#include "ai_task.h"
+#include "entitylist.h"
+#include "basecombatweapon.h"
+#include "soundenvelope.h"
+#include "gib.h"
+#include "gamerules.h"
+#include "ammodef.h"
+#include "grenade_homer.h"
+#include "cbasehelicopter.h"
+#include "engine/IEngineSound.h"
+#include "IEffects.h"
+#include "globals.h"
+#include "explode.h"
+#include "movevars_shared.h"
+#include "smoke_trail.h"
+#include "ar2_explosion.h"
+#include "collisionutils.h"
+#include "props.h"
+#include "EntityFlame.h"
+#include "decals.h"
+#include "effect_dispatch_data.h"
+#include "te_effect_dispatch.h"
+#include "ai_spotlight.h"
+#include "vphysics/constraints.h"
+#include "physics_saverestore.h"
+#include "ai_memory.h"
+#include "npc_attackchopper.h"
+
+#ifdef HL2_EPISODIC
+#include "physics_bone_follower.h"
+#endif // HL2_EPISODIC
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// -------------------------------------
+// Bone controllers
+// -------------------------------------
+#define CHOPPER_DRONE_NAME "models/combine_helicopter/helicopter_bomb01.mdl"
+#define CHOPPER_MODEL_NAME "models/combine_helicopter.mdl"
+#define CHOPPER_MODEL_CORPSE_NAME "models/combine_helicopter_broken.mdl"
+#define CHOPPER_RED_LIGHT_SPRITE "sprites/redglow1.vmt"
+
+#define CHOPPER_MAX_SMALL_CHUNKS 1
+#define CHOPPER_MAX_CHUNKS 3
+static const char *s_pChunkModelName[CHOPPER_MAX_CHUNKS] =
+{
+ "models/gibs/helicopter_brokenpiece_01.mdl",
+ "models/gibs/helicopter_brokenpiece_02.mdl",
+ "models/gibs/helicopter_brokenpiece_03.mdl",
+};
+
+#define BOMB_SKIN_LIGHT_ON 1
+#define BOMB_SKIN_LIGHT_OFF 0
+
+
+#define HELICOPTER_CHUNK_COCKPIT "models/gibs/helicopter_brokenpiece_04_cockpit.mdl"
+#define HELICOPTER_CHUNK_TAIL "models/gibs/helicopter_brokenpiece_05_tailfan.mdl"
+#define HELICOPTER_CHUNK_BODY "models/gibs/helicopter_brokenpiece_06_body.mdl"
+
+
+#define CHOPPER_MAX_SPEED (60 * 17.6f)
+#define CHOPPER_MAX_FIRING_SPEED 250.0f
+#define CHOPPER_MAX_GUN_DIST 2000.0f
+
+#define CHOPPER_ACCEL_RATE 500
+#define CHOPPER_ACCEL_RATE_BOOST 1500
+
+#define DEFAULT_FREE_KNOWLEDGE_DURATION 5.0f
+
+// -------------------------------------
+// Pathing data
+#define CHOPPER_LEAD_DISTANCE 800.0f
+#define CHOPPER_MIN_CHASE_DIST_DIFF 128.0f // Distance threshold used to determine when a target has moved enough to update our navigation to it
+#define CHOPPER_MIN_AGGRESSIVE_CHASE_DIST_DIFF 64.0f
+#define CHOPPER_AVOID_DIST 512.0f
+#define CHOPPER_ARRIVE_DIST 128.0f
+
+#define CHOPPER_MAX_CIRCLE_OF_DEATH_FOLLOW_SPEED 450.0f
+#define CHOPPER_MIN_CIRCLE_OF_DEATH_RADIUS 150.0f
+#define CHOPPER_MAX_CIRCLE_OF_DEATH_RADIUS 350.0f
+
+#define CHOPPER_BOMB_DROP_COUNT 6
+
+// Bullrush
+#define CHOPPER_BULLRUSH_MODE_DISTANCE g_helicopter_bullrush_distance.GetFloat()
+#define CHOPPER_BULLRUSH_ENEMY_BOMB_DISTANCE g_helicopter_bullrush_bomb_enemy_distance.GetFloat()
+#define CHOPPER_BULLRUSH_ENEMY_BOMB_TIME g_helicopter_bullrush_bomb_time.GetFloat()
+#define CHOPPER_BULLRUSH_ENEMY_BOMB_SPEED g_helicopter_bullrush_bomb_speed.GetFloat()
+#define CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET g_helicopter_bullrush_shoot_height.GetFloat()
+
+#define CHOPPER_GUN_CHARGE_TIME g_helicopter_chargetime.GetFloat()
+#define CHOPPER_GUN_IDLE_TIME g_helicopter_idletime.GetFloat()
+#define CHOPPER_GUN_MAX_FIRING_DIST g_helicopter_maxfiringdist.GetFloat()
+
+#define BULLRUSH_IDLE_PLAYER_FIRE_TIME 6.0f
+
+#define DRONE_SPEED sk_helicopter_drone_speed.GetFloat()
+
+#define SF_HELICOPTER_LOUD_ROTOR_SOUND 0x00010000
+#define SF_HELICOPTER_ELECTRICAL_DRONE 0x00020000
+#define SF_HELICOPTER_LIGHTS 0x00040000
+#define SF_HELICOPTER_IGNORE_AVOID_FORCES 0x00080000
+#define SF_HELICOPTER_AGGRESSIVE 0x00100000
+#define SF_HELICOPTER_LONG_SHADOW 0x00200000
+
+#define CHOPPER_SLOW_BOMB_SPEED 250
+
+#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED 250
+#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_SQ (CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED * CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED)
+
+#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2 450
+#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2_SQ (CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2 * CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2)
+
+// CVars
+ConVar sk_helicopter_health( "sk_helicopter_health","5600");
+ConVar sk_helicopter_firingcone( "sk_helicopter_firingcone","20.0", 0, "The angle in degrees of the cone in which the shots will be fired" );
+ConVar sk_helicopter_burstcount( "sk_helicopter_burstcount","12", 0, "How many shot bursts to fire after charging up. The bigger the number, the longer the firing is" );
+ConVar sk_helicopter_roundsperburst( "sk_helicopter_roundsperburst","5", 0, "How many shots to fire in a single burst" );
+
+ConVar sk_helicopter_grenadedamage( "sk_helicopter_grenadedamage","25.0", 0, "The amount of damage the helicopter grenade deals." );
+ConVar sk_helicopter_grenaderadius( "sk_helicopter_grenaderadius","275.0", 0, "The damage radius of the helicopter grenade." );
+ConVar sk_helicopter_grenadeforce( "sk_helicopter_grenadeforce","55000.0", 0, "The physics force that the helicopter grenade exerts." );
+ConVar sk_helicopter_grenade_puntscale( "sk_helicopter_grenade_puntscale","1.5", 0, "When physpunting a chopper's grenade, scale its velocity by this much." );
+
+// Number of bomb hits it takes to kill a chopper on each skill level.
+ConVar sk_helicopter_num_bombs1("sk_helicopter_num_bombs1", "3");
+ConVar sk_helicopter_num_bombs2("sk_helicopter_num_bombs2", "5");
+ConVar sk_helicopter_num_bombs3("sk_helicopter_num_bombs3", "5");
+
+ConVar sk_npc_dmg_helicopter_to_plr( "sk_npc_dmg_helicopter_to_plr","3", 0, "Damage helicopter shots deal to the player" );
+ConVar sk_npc_dmg_helicopter( "sk_npc_dmg_helicopter","6", 0, "Damage helicopter shots deal to everything but the player" );
+
+ConVar sk_helicopter_drone_speed( "sk_helicopter_drone_speed","450.0", 0, "How fast does the zapper drone move?" );
+
+ConVar g_helicopter_chargetime( "g_helicopter_chargetime","2.0", 0, "How much time we have to wait (on average) between the time we start hearing the charging sound + the chopper fires" );
+ConVar g_helicopter_bullrush_distance("g_helicopter_bullrush_distance", "5000");
+ConVar g_helicopter_bullrush_bomb_enemy_distance("g_helicopter_bullrush_bomb_enemy_distance", "0");
+ConVar g_helicopter_bullrush_bomb_time("g_helicopter_bullrush_bomb_time", "10");
+ConVar g_helicopter_idletime( "g_helicopter_idletime","3.0", 0, "How much time we have to wait (on average) after we fire before we can charge up again" );
+ConVar g_helicopter_maxfiringdist( "g_helicopter_maxfiringdist","2500.0", 0, "The maximum distance the player can be from the chopper before it stops firing" );
+ConVar g_helicopter_bullrush_bomb_speed( "g_helicopter_bullrush_bomb_speed","850.0", 0, "The maximum distance the player can be from the chopper before it stops firing" );
+ConVar g_helicopter_bullrush_shoot_height( "g_helicopter_bullrush_shoot_height","650.0", 0, "The maximum distance the player can be from the chopper before it stops firing" );
+ConVar g_helicopter_bullrush_mega_bomb_health( "g_helicopter_bullrush_mega_bomb_health","0.25", 0, "Fraction of the health of the chopper before it mega-bombs" );
+
+ConVar g_helicopter_bomb_danger_radius( "g_helicopter_bomb_danger_radius", "120" );
+
+Activity ACT_HELICOPTER_DROP_BOMB;
+Activity ACT_HELICOPTER_CRASHING;
+
+static const char *s_pBlinkLightThinkContext = "BlinkLights";
+static const char *s_pSpotlightThinkContext = "SpotlightThink";
+static const char *s_pRampSoundContext = "RampSound";
+static const char *s_pWarningBlinkerContext = "WarningBlinker";
+static const char *s_pAnimateThinkContext = "Animate";
+
+#define CHOPPER_LIGHT_BLINK_TIME 1.0f
+#define CHOPPER_LIGHT_BLINK_TIME_SHORT 0.1f
+
+#define BOMB_LIFETIME 2.5f // Don't access this directly. Call GetBombLifetime();
+#define BOMB_RAMP_SOUND_TIME 1.0f
+
+enum
+{
+ MAX_HELICOPTER_LIGHTS = 3,
+};
+
+enum
+{
+ SF_GRENADE_HELICOPTER_MEGABOMB = 0x1,
+};
+
+#define GRENADE_HELICOPTER_MODEL "models/combine_helicopter/helicopter_bomb01.mdl"
+
+LINK_ENTITY_TO_CLASS( info_target_helicopter_crash, CPointEntity );
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+static inline float ClampSplineRemapVal( float flValue, float flMinValue, float flMaxValue, float flOutMin, float flOutMax )
+{
+ Assert( flMinValue <= flMaxValue );
+ float flClampedVal = clamp( flValue, flMinValue, flMaxValue );
+ return SimpleSplineRemapVal( flClampedVal, flMinValue, flMaxValue, flOutMin, flOutMax );
+}
+
+
+//-----------------------------------------------------------------------------
+// The bombs the attack helicopter drops
+//-----------------------------------------------------------------------------
+enum
+{
+ SKIN_REGULAR,
+ SKIN_DUD,
+};
+
+class CGrenadeHelicopter : public CBaseGrenade
+{
+ DECLARE_DATADESC();
+
+public:
+ DECLARE_CLASS( CGrenadeHelicopter, CBaseGrenade );
+
+ virtual void Precache( );
+ virtual void Spawn( );
+ virtual void UpdateOnRemove();
+ virtual void OnEntityEvent( EntityEvent_t event, void *pEventData );
+ virtual void PhysicsSimulate( void );
+ virtual float GetShakeAmplitude( void ) { return 25.0; }
+ virtual float GetShakeRadius( void ) { return sk_helicopter_grenaderadius.GetFloat() * 2; }
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
+ void SetExplodeOnContact( bool bExplode ) { m_bExplodeOnContact = bExplode; }
+
+ virtual QAngle PreferredCarryAngles( void ) { return QAngle( -12, 98, 55 ); }
+ virtual bool HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer ) { return true; }
+
+ float GetBombLifetime();
+
+#ifdef HL2_EPISODIC
+ virtual void OnPhysGunPickup(CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
+ virtual void OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason );
+ virtual Class_T Classify( void ) { return CLASS_MISSILE; }
+ void SetCollisionObject( CBaseEntity *pEntity ) { m_hCollisionObject = pEntity; }
+ void SendMissEvent();
+ bool IsThrownByPlayer();
+
+ virtual bool ShouldPuntUseLaunchForces( PhysGunForce_t reason ) { return ( reason == PHYSGUN_FORCE_LAUNCHED ); }
+ virtual Vector PhysGunLaunchVelocity( const Vector &forward, float flMass );
+
+ void InputExplodeIn( inputdata_t &inputdata );
+#endif // HL2_EPISODIC
+
+private:
+ // Pow!
+ void DoExplosion( const Vector &vecOrigin, const Vector &vecVelocity );
+ void ExplodeThink();
+ void RampSoundThink();
+ void WarningBlinkerThink();
+ void StopWarningBlinker();
+ void AnimateThink();
+ void ExplodeConcussion( CBaseEntity *pOther );
+ void BecomeActive();
+ void ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity );
+
+ bool m_bActivated;
+ bool m_bExplodeOnContact;
+ CSoundPatch *m_pWarnSound;
+
+ EHANDLE m_hWarningSprite;
+ bool m_bBlinkerAtTop;
+
+
+#ifdef HL2_EPISODIC
+ float m_flLifetime;
+ EHANDLE m_hCollisionObject; // Pointer to object we re-enable collisions with when picked up
+ bool m_bPickedUp;
+ float m_flBlinkFastTime;
+ COutputEvent m_OnPhysGunOnlyPickup;
+#endif // HL2_EPISODIC
+};
+
+
+//-----------------------------------------------------------------------------
+// The bombs the attack helicopter drops
+//-----------------------------------------------------------------------------
+class CBombDropSensor : public CBaseEntity
+{
+ DECLARE_DATADESC();
+
+public:
+ DECLARE_CLASS( CBombDropSensor, CBaseEntity );
+
+ void Spawn();
+
+ // Drop a bomb at a particular location
+ void InputDropBomb( inputdata_t &inputdata );
+ void InputDropBombStraightDown( inputdata_t &inputdata );
+ void InputDropBombAtTarget( inputdata_t &inputdata );
+ void InputDropBombAtTargetAlways( inputdata_t &inputdata );
+ void InputDropBombDelay( inputdata_t &inputdata );
+};
+
+//-----------------------------------------------------------------------------
+// This entity is used to create boxes that the helicopter can't bomb in
+//-----------------------------------------------------------------------------
+class CBombSuppressor : public CBaseEntity
+{
+ DECLARE_DATADESC();
+
+public:
+ DECLARE_CLASS( CBombSuppressor, CBaseEntity );
+
+ virtual void Spawn( );
+ virtual void Activate();
+ virtual void UpdateOnRemove();
+
+ static bool CanBomb( const Vector &vecPosition );
+
+private:
+ typedef CHandle<CBombSuppressor> BombSuppressorHandle_t;
+ static CUtlVector< BombSuppressorHandle_t > s_BombSuppressors;
+};
+
+ enum
+ {
+ CHUNK_COCKPIT,
+ CHUNK_BODY,
+ CHUNK_TAIL
+ };
+
+//-----------------------------------------------------------------------------
+// This entity is used for helicopter gibs with specific properties
+//-----------------------------------------------------------------------------
+class CHelicopterChunk : public CBaseAnimating
+{
+ DECLARE_DATADESC();
+
+public:
+ DECLARE_CLASS( CHelicopterChunk, CBaseAnimating );
+
+ virtual void Spawn( void );
+ virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
+
+ static CHelicopterChunk *CreateHelicopterChunk( const Vector &vecPos, const QAngle &vecAngles, const Vector &vecVelocity, const char *pszModelName, int chunkID );
+
+ int m_nChunkID;
+
+ CHandle<CHelicopterChunk> m_hMaster;
+ IPhysicsConstraint *m_pTailConstraint;
+ IPhysicsConstraint *m_pCockpitConstraint;
+
+protected:
+
+ void CollisionCallback( CHelicopterChunk *pCaller );
+
+ void FallThink( void );
+
+ bool m_bLanded;
+};
+
+//-----------------------------------------------------------------------------
+// The attack helicopter
+//-----------------------------------------------------------------------------
+class CNPC_AttackHelicopter : public CBaseHelicopter
+{
+public:
+ DECLARE_CLASS( CNPC_AttackHelicopter, CBaseHelicopter );
+ DECLARE_DATADESC();
+ DEFINE_CUSTOM_AI;
+
+ CNPC_AttackHelicopter();
+ ~CNPC_AttackHelicopter();
+
+ virtual void Precache( void );
+ virtual void Spawn( void );
+ virtual void Activate( void );
+ virtual bool CreateComponents();
+ virtual int ObjectCaps();
+
+#ifdef HL2_EPISODIC
+ virtual bool CreateVPhysics( void );
+#endif // HL2_EPISODIC
+
+ virtual void UpdateOnRemove();
+ virtual void StopLoopingSounds();
+
+ int BloodColor( void ) { return DONT_BLEED; }
+ Class_T Classify ( void ) { return CLASS_COMBINE_GUNSHIP; }
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+
+ // Shot spread
+ virtual Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget );
+
+ // More Enemy visibility check
+ virtual bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
+
+ // Think!
+ virtual void PrescheduleThink( void );
+
+ // Purpose: Set the gunship's paddles flailing!
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+
+ // Drop a bomb at a particular location
+ void InputDropBomb( inputdata_t &inputdata );
+ void InputDropBombStraightDown( inputdata_t &inputdata );
+ void InputDropBombAtTarget( inputdata_t &inputdata );
+ void InputDropBombAtTargetAlways( inputdata_t &inputdata );
+ void InputDropBombAtTargetInternal( inputdata_t &inputdata, bool bCheckFairness );
+ void InputDropBombDelay( inputdata_t &inputdata );
+ void InputStartCarpetBombing( inputdata_t &inputdata );
+ void InputStopCarpetBombing( inputdata_t &inputdata );
+
+ virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
+ virtual const char *GetTracerType( void );
+
+ virtual void DoImpactEffect( trace_t &tr, int nDamageType );
+ virtual void DoMuzzleFlash( void );
+
+ // FIXME: Work this back into the base class
+ virtual bool ShouldUseFixedPatrolLogic() { return true; }
+
+protected:
+
+ int m_poseWeapon_Pitch, m_poseWeapon_Yaw, m_poseRudder;
+ virtual void PopulatePoseParameters( void );
+
+private:
+ enum GunState_t
+ {
+ GUN_STATE_IDLE = 0,
+ GUN_STATE_CHARGING,
+ GUN_STATE_FIRING,
+ };
+
+ // Gets the max speed of the helicopter
+ virtual float GetMaxSpeed();
+ virtual float GetMaxSpeedFiring();
+
+ // Startup the chopper
+ virtual void Startup();
+
+ void InitializeRotorSound( void );
+
+ // Weaponry
+ bool FireGun( void );
+
+ // Movement:
+ virtual void Flight( void );
+
+ // Changes the main thinking method of helicopters
+ virtual void Hunt( void );
+
+ // For scripted times where it *has* to shoot
+ void InputResetIdleTime( inputdata_t &inputdata );
+ void InputSetHealthFraction( inputdata_t &inputdata );
+ void InputStartBombExplodeOnContact( inputdata_t &inputdata );
+ void InputStopBombExplodeOnContact( inputdata_t &inputdata );
+
+ void InputEnableAlwaysTransition( inputdata_t &inputdata );
+ void InputDisableAlwaysTransition( inputdata_t &inputdata );
+ void InputOutsideTransition( inputdata_t &inputdata );
+ void InputSetOutsideTransitionTarget( inputdata_t &inputdata );
+
+ // Turns off the gun
+ void InputGunOff( inputdata_t &inputdata );
+
+ // Vehicle attack modes
+ void InputStartBombingVehicle( inputdata_t &inputdata );
+ void InputStartTrailingVehicle( inputdata_t &inputdata );
+ void InputStartDefaultBehavior( inputdata_t &inputdata );
+ void InputStartAlwaysLeadingVehicle( inputdata_t &inputdata );
+
+ // Deadly shooting, tex!
+ void InputEnableDeadlyShooting( inputdata_t &inputdata );
+ void InputDisableDeadlyShooting( inputdata_t &inputdata );
+ void InputStartNormalShooting( inputdata_t &inputdata );
+ void InputStartLongCycleShooting( inputdata_t &inputdata );
+ void InputStartContinuousShooting( inputdata_t &inputdata );
+ void InputStartFastShooting( inputdata_t &inputdata );
+
+ int GetShootingMode( );
+ bool IsDeadlyShooting();
+
+ // Bombing suppression
+ void InputEnableBombing( inputdata_t &inputdata );
+ void InputDisableBombing( inputdata_t &inputdata );
+
+ // Visibility tests
+ void InputDisablePathVisibilityTests( inputdata_t &inputdata );
+ void InputEnablePathVisibilityTests( inputdata_t &inputdata );
+
+ // Death, etc.
+ void InputSelfDestruct( inputdata_t &inputdata );
+
+ // Enemy visibility check
+ CBaseEntity *FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos );
+
+ // Special path navigation when we explicitly want to follow a path
+ void UpdateFollowPathNavigation();
+
+ // Find interesting nearby things to shoot
+ int BuildMissTargetList( int nCount, CBaseEntity **ppMissCandidates );
+
+ // Shoot when the player's your enemy :
+ void ShootAtPlayer( const Vector &vBasePos, const Vector &vGunDir );
+
+ // Shoot when the player's your enemy + he's driving a vehicle
+ void ShootAtVehicle( const Vector &vBasePos, const Vector &vGunDir );
+
+ // Shoot where we're facing
+ void ShootAtFacingDirection( const Vector &vBasePos, const Vector &vGunDir, bool bFirstShotAccurate );
+
+ // Updates the facing direction
+ void UpdateFacingDirection( const Vector &vecActualDesiredPosition );
+
+ // Various states of the helicopter firing...
+ bool PoseGunTowardTargetDirection( const Vector &vTargetDir );
+
+ // Compute the position to fire at (vehicle + non-vehicle case)
+ void ComputeFireAtPosition( Vector *pVecActualTargetPosition );
+ void ComputeVehicleFireAtPosition( Vector *pVecActualTargetPosition );
+
+ // Various states of the helicopter firing...
+ bool DoGunIdle( const Vector &vecGunDir, const Vector &vTargetDir );
+ bool DoGunCharging( );
+ bool DoGunFiring( const Vector &vBasePos, const Vector &vGunDir, const Vector &vecFireAtPosition );
+ void FireElectricityGun( );
+
+ // Chooses a point within the circle of death to fire in
+ void PickDirectionToCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition, Vector *pResult );
+
+ // Gets a vehicle the enemy is in (if any)
+ CBaseEntity *GetEnemyVehicle();
+
+ // Updates the perpendicular path distance for the chopper
+ float UpdatePerpPathDistance( float flMaxPathOffset );
+
+ // Purpose :
+ void UpdateEnemyLeading( void );
+
+ // Drop those bombs!
+ void DropBombs( );
+
+ // Should we drop those bombs?
+ bool ShouldDropBombs( void );
+
+ // Returns the max firing distance
+ float GetMaxFiringDistance();
+
+ // Make sure we don't hit too many times
+ void FireBullets( const FireBulletsInfo_t &info );
+
+ // Is it "fair" to drop this bomb?
+ bool IsBombDropFair( const Vector &vecBombStartPos, const Vector &vecVelocity );
+
+ // Actually drops the bomb
+ void CreateBomb( bool bCheckForFairness = true, Vector *pVecVelocity = NULL, bool bMegaBomb = false );
+ CGrenadeHelicopter *SpawnBombEntity( const Vector &vecPos, const Vector &vecVelocity ); // Spawns the bomb entity and sets it up
+
+ // Deliberately aims as close as possible w/o hitting
+ void AimCloseToTargetButMiss( CBaseEntity *pTarget, float flMinDist, float flMaxDist, const Vector &shootOrigin, Vector *pResult );
+
+ // Pops a shot inside the circle of death using the burst rules
+ void ShootInsideCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition );
+
+ // How easy is the target to hit?
+ void UpdateTargetHittability();
+
+ // Add a smoke trail since we've taken more damage
+ void AddSmokeTrail( const Vector &vecPos );
+
+ // Destroy all smoke trails
+ void DestroySmokeTrails();
+
+ // Creates the breakable husk of an attack chopper
+ void CreateChopperHusk();
+
+ // Pow!
+ void ExplodeAndThrowChunk( const Vector &vecExplosionPos );
+
+ // Drop a corpse!
+ void DropCorpse( int nDamage );
+
+ // Should we trigger a damage effect?
+ bool ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const;
+
+ // Become indestructible
+ void InputBecomeIndestructible( inputdata_t &inputdata );
+
+ // Purpose :
+ float CreepTowardEnemy( float flSpeed, float flMinSpeed, float flMaxSpeed, float flMinDist, float flMaxDist );
+
+ // Start bullrush
+ void InputStartBullrushBehavior( inputdata_t &inputdata );
+
+ void GetMaxSpeedAndAccel( float *pMaxSpeed, float *pAccelRate );
+ void ComputeAngularVelocity( const Vector &vecGoalUp, const Vector &vecFacingDirection );
+ void ComputeVelocity( const Vector &deltaPos, float flAdditionalHeight, float flMinDistFromSegment, float flMaxDistFromSegment, Vector *pVecAccel );
+ void FlightDirectlyOverhead( void );
+
+ // Methods related to computing leading distance
+ float ComputeBombingLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle );
+ float ComputeBullrushLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle );
+
+ bool IsCarpetBombing() { return m_bIsCarpetBombing == true; }
+
+ // Update the bullrush state
+ void UpdateBullrushState( void );
+
+ // Whether to shoot at or bomb an idle player
+ bool ShouldBombIdlePlayer( void );
+
+ // Different bomb-dropping behavior
+ void BullrushBombs( );
+
+ // Switch to idle
+ void SwitchToBullrushIdle( void );
+
+ // Secondary mode
+ void SetSecondaryMode( int nMode, bool bRetainTime = false );
+ bool IsInSecondaryMode( int nMode );
+ float SecondaryModeTime( ) const;
+
+ // Should the chopper shoot the idle player?
+ bool ShouldShootIdlePlayerInBullrush();
+
+ // Shutdown shooting during bullrush
+ void ShutdownGunDuringBullrush( );
+
+ // Updates the enemy
+ virtual float EnemySearchDistance( );
+
+ // Prototype zapper
+ bool IsValidZapTarget( CBaseEntity *pTarget );
+ void CreateZapBeam( const Vector &vecTargetPos );
+ void CreateEntityZapEffect( CBaseEntity *pEnt );
+
+ // Blink lights
+ void BlinkLightsThink();
+
+ // Spotlights
+ void SpotlightThink();
+ void SpotlightStartup();
+ void SpotlightShutdown();
+
+ CBaseEntity *GetCrashPoint() { return m_hCrashPoint.Get(); }
+
+private:
+ enum
+ {
+ ATTACK_MODE_DEFAULT = 0,
+ ATTACK_MODE_BOMB_VEHICLE,
+ ATTACK_MODE_TRAIL_VEHICLE,
+ ATTACK_MODE_ALWAYS_LEAD_VEHICLE,
+ ATTACK_MODE_BULLRUSH_VEHICLE,
+ };
+
+ enum
+ {
+ MAX_SMOKE_TRAILS = 5,
+ MAX_EXPLOSIONS = 13,
+ MAX_CORPSES = 2,
+ };
+
+ enum
+ {
+ BULLRUSH_MODE_WAIT_FOR_ENEMY = 0,
+ BULLRUSH_MODE_GET_DISTANCE,
+ BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED,
+ BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER,
+ BULLRUSH_MODE_SHOOT_GUN,
+ BULLRUSH_MODE_MEGA_BOMB,
+ BULLRUSH_MODE_SHOOT_IDLE_PLAYER,
+ };
+
+ enum
+ {
+ SHOOT_MODE_DEFAULT = 0,
+ SHOOT_MODE_LONG_CYCLE,
+ SHOOT_MODE_CONTINUOUS,
+ SHOOT_MODE_FAST,
+ };
+
+#ifdef HL2_EPISODIC
+ void InitBoneFollowers( void );
+ CBoneFollowerManager m_BoneFollowerManager;
+#endif // HL2_EPISODIC
+
+ CAI_Spotlight m_Spotlight;
+ Vector m_angGun;
+ QAngle m_vecAngAcceleration;
+ int m_iAmmoType;
+ float m_flLastCorpseFall;
+ GunState_t m_nGunState;
+ float m_flChargeTime;
+ float m_flIdleTimeDelay;
+ int m_nRemainingBursts;
+ int m_nGrenadeCount;
+ float m_flPathOffset;
+ float m_flAcrossTime;
+ float m_flCurrPathOffset;
+ int m_nBurstHits;
+ int m_nMaxBurstHits;
+ float m_flCircleOfDeathRadius;
+ int m_nAttackMode;
+ float m_flInputDropBombTime;
+ CHandle<CBombDropSensor> m_hSensor;
+ float m_flAvoidMetric;
+ AngularImpulse m_vecLastAngVelocity;
+ CHandle<CBaseEntity> m_hSmokeTrail[MAX_SMOKE_TRAILS];
+ int m_nSmokeTrailCount;
+ bool m_bIndestructible;
+ float m_flGracePeriod;
+ bool m_bBombsExplodeOnContact;
+ bool m_bNonCombat;
+
+ int m_nNearShots;
+ int m_nMaxNearShots;
+
+ // Bomb dropping attachments
+ int m_nGunTipAttachment;
+ int m_nGunBaseAttachment;
+ int m_nBombAttachment;
+ int m_nSpotlightAttachment;
+ float m_flLastFastTime;
+
+ // Secondary modes
+ int m_nSecondaryMode;
+ float m_flSecondaryModeStartTime;
+
+ // Bullrush behavior
+ bool m_bRushForward;
+ float m_flBullrushAdditionalHeight;
+ int m_nBullrushBombMode;
+ float m_flNextBullrushBombTime;
+ float m_flNextMegaBombHealth;
+
+ // Shooting method
+ int m_nShootingMode;
+ bool m_bDeadlyShooting;
+
+ // Bombing suppression
+ bool m_bBombingSuppressed;
+
+ // Blinking lights
+ CHandle<CSprite> m_hLights[MAX_HELICOPTER_LIGHTS];
+ bool m_bShortBlink;
+
+ // Path behavior
+ bool m_bIgnorePathVisibilityTests;
+
+ // Teleport
+ bool m_bAlwaysTransition;
+ string_t m_iszTransitionTarget;
+
+ // Special attacks
+ bool m_bIsCarpetBombing;
+
+ // Fun damage effects
+ float m_flGoalRollDmg;
+ float m_flGoalYawDmg;
+
+ // Sounds
+ CSoundPatch *m_pGunFiringSound;
+
+ // Outputs
+ COutputInt m_OnHealthChanged;
+ COutputEvent m_OnShotDown;
+
+ // Crashing
+ EHANDLE m_hCrashPoint;
+};
+
+#ifdef HL2_EPISODIC
+static const char *pFollowerBoneNames[] =
+{
+ "Chopper.Body"
+};
+#endif // HL2_EPISODIC
+
+LINK_ENTITY_TO_CLASS( npc_helicopter, CNPC_AttackHelicopter );
+
+BEGIN_DATADESC( CNPC_AttackHelicopter )
+
+ DEFINE_ENTITYFUNC( FlyTouch ),
+
+ DEFINE_EMBEDDED( m_Spotlight ),
+#ifdef HL2_EPISODIC
+ DEFINE_EMBEDDED( m_BoneFollowerManager ),
+#endif
+ DEFINE_FIELD( m_angGun, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecAngAcceleration, FIELD_VECTOR ),
+ DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flLastCorpseFall, FIELD_TIME ),
+ DEFINE_FIELD( m_nGunState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flChargeTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flIdleTimeDelay, FIELD_FLOAT ),
+ DEFINE_FIELD( m_nRemainingBursts, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nGrenadeCount, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flPathOffset, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flAcrossTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flCurrPathOffset, FIELD_FLOAT ),
+ DEFINE_FIELD( m_nBurstHits, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nMaxBurstHits, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flCircleOfDeathRadius, FIELD_FLOAT ),
+ DEFINE_FIELD( m_nAttackMode, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flInputDropBombTime, FIELD_TIME ),
+ DEFINE_FIELD( m_hSensor, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flAvoidMetric, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecLastAngVelocity, FIELD_VECTOR ),
+ DEFINE_AUTO_ARRAY( m_hSmokeTrail, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_nSmokeTrailCount, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nNearShots, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nMaxNearShots, FIELD_INTEGER ),
+// DEFINE_FIELD( m_nGunTipAttachment, FIELD_INTEGER ),
+// DEFINE_FIELD( m_nGunBaseAttachment, FIELD_INTEGER ),
+// DEFINE_FIELD( m_nBombAttachment, FIELD_INTEGER ),
+// DEFINE_FIELD( m_nSpotlightAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flLastFastTime, FIELD_TIME ),
+ DEFINE_FIELD( m_nSecondaryMode, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flSecondaryModeStartTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bRushForward, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flBullrushAdditionalHeight, FIELD_FLOAT ),
+ DEFINE_FIELD( m_nBullrushBombMode, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flNextBullrushBombTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextMegaBombHealth, FIELD_FLOAT ),
+ DEFINE_FIELD( m_nShootingMode, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bDeadlyShooting, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bBombingSuppressed, FIELD_BOOLEAN ),
+ DEFINE_SOUNDPATCH( m_pGunFiringSound ),
+ DEFINE_AUTO_ARRAY( m_hLights, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bIgnorePathVisibilityTests, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bShortBlink, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bIndestructible, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bBombsExplodeOnContact, FIELD_BOOLEAN ),
+
+ DEFINE_KEYFIELD( m_bAlwaysTransition, FIELD_BOOLEAN, "AlwaysTransition" ),
+ DEFINE_KEYFIELD( m_iszTransitionTarget, FIELD_STRING, "TransitionTarget" ),
+ DEFINE_FIELD( m_bIsCarpetBombing, FIELD_BOOLEAN ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableAlwaysTransition", InputEnableAlwaysTransition ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableAlwaysTransition", InputDisableAlwaysTransition ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetTransitionTarget", InputSetOutsideTransitionTarget ),
+
+ DEFINE_KEYFIELD( m_flGracePeriod, FIELD_FLOAT, "GracePeriod" ),
+ DEFINE_KEYFIELD( m_flMaxSpeed, FIELD_FLOAT, "PatrolSpeed" ),
+ DEFINE_KEYFIELD( m_bNonCombat, FIELD_BOOLEAN, "NonCombat" ),
+
+ DEFINE_FIELD( m_hCrashPoint, FIELD_EHANDLE ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "ResetIdleTime", InputResetIdleTime ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartAlwaysLeadingVehicle", InputStartAlwaysLeadingVehicle ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartBombingVehicle", InputStartBombingVehicle ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartTrailingVehicle", InputStartTrailingVehicle ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartDefaultBehavior", InputStartDefaultBehavior ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartBullrushBehavior", InputStartBullrushBehavior ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "DropBomb", InputDropBomb ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DropBombStraightDown", InputDropBombStraightDown ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTargetAlways", InputDropBombAtTargetAlways ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTarget", InputDropBombAtTarget ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "DropBombDelay", InputDropBombDelay ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartCarpetBombing", InputStartCarpetBombing ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StopCarpetBombing", InputStopCarpetBombing ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "BecomeIndestructible", InputBecomeIndestructible ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableDeadlyShooting", InputEnableDeadlyShooting ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableDeadlyShooting", InputDisableDeadlyShooting ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartNormalShooting", InputStartNormalShooting ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartLongCycleShooting", InputStartLongCycleShooting ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartContinuousShooting", InputStartContinuousShooting ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartFastShooting", InputStartFastShooting ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "GunOff", InputGunOff ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthFraction", InputSetHealthFraction ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartBombExplodeOnContact", InputStartBombExplodeOnContact ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StopBombExplodeOnContact", InputStopBombExplodeOnContact ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisablePathVisibilityTests", InputDisablePathVisibilityTests ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnablePathVisibilityTests", InputEnablePathVisibilityTests ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ),
+
+ DEFINE_THINKFUNC( BlinkLightsThink ),
+ DEFINE_THINKFUNC( SpotlightThink ),
+
+ DEFINE_OUTPUT( m_OnHealthChanged, "OnHealthChanged" ),
+ DEFINE_OUTPUT( m_OnShotDown, "OnShotDown" ),
+
+END_DATADESC()
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+CNPC_AttackHelicopter::CNPC_AttackHelicopter() :
+ m_bNonCombat( false ),
+ m_flGracePeriod( 2.0f ),
+ m_bBombsExplodeOnContact( false )
+{
+ m_flMaxSpeed = 0;
+}
+
+CNPC_AttackHelicopter::~CNPC_AttackHelicopter(void)
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Shuts down looping sounds when we are killed in combat or deleted.
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::StopLoopingSounds()
+{
+ BaseClass::StopLoopingSounds();
+
+ if ( m_pGunFiringSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundDestroy( m_pGunFiringSound );
+ m_pGunFiringSound = NULL;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+void Chopper_PrecacheChunks( CBaseEntity *pChopper )
+{
+ for ( int i = 0; i < CHOPPER_MAX_CHUNKS; ++i )
+ {
+ pChopper->PrecacheModel( s_pChunkModelName[i] );
+ }
+
+ pChopper->PrecacheModel( HELICOPTER_CHUNK_COCKPIT );
+ pChopper->PrecacheModel( HELICOPTER_CHUNK_TAIL );
+ pChopper->PrecacheModel( HELICOPTER_CHUNK_BODY );
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::Precache( void )
+{
+ BaseClass::Precache();
+
+ if ( !HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
+ {
+ PrecacheModel( CHOPPER_MODEL_NAME );
+ }
+ else
+ {
+ PrecacheModel( CHOPPER_DRONE_NAME );
+ }
+
+ PrecacheModel( CHOPPER_RED_LIGHT_SPRITE );
+ //PrecacheModel( CHOPPER_MODEL_CORPSE_NAME );
+
+ // If we're never going to engage in combat, we don't need to load these assets!
+ if ( m_bNonCombat == false )
+ {
+ UTIL_PrecacheOther( "grenade_helicopter" );
+ UTIL_PrecacheOther( "env_fire_trail" );
+ Chopper_PrecacheChunks( this );
+ PrecacheModel("models/combine_soldier.mdl");
+ }
+
+ PrecacheScriptSound("NPC_AttackHelicopter.ChargeGun");
+ if ( HasSpawnFlags( SF_HELICOPTER_LOUD_ROTOR_SOUND ) )
+ {
+ PrecacheScriptSound("NPC_AttackHelicopter.RotorsLoud");
+ }
+ else
+ {
+ PrecacheScriptSound("NPC_AttackHelicopter.Rotors");
+ }
+ PrecacheScriptSound( "NPC_AttackHelicopter.DropMine" );
+ PrecacheScriptSound( "NPC_AttackHelicopter.BadlyDamagedAlert" );
+ PrecacheScriptSound( "NPC_AttackHelicopter.CrashingAlarm1" );
+ PrecacheScriptSound( "NPC_AttackHelicopter.MegabombAlert" );
+
+ PrecacheScriptSound( "NPC_AttackHelicopter.RotorBlast" );
+ PrecacheScriptSound( "NPC_AttackHelicopter.EngineFailure" );
+ PrecacheScriptSound( "NPC_AttackHelicopter.FireGun" );
+ PrecacheScriptSound( "NPC_AttackHelicopter.Crash" );
+ PrecacheScriptSound( "HelicopterBomb.HardImpact" );
+
+ PrecacheScriptSound( "ReallyLoudSpark" );
+ PrecacheScriptSound( "NPC_AttackHelicopterGrenade.Ping" );
+}
+
+int CNPC_AttackHelicopter::ObjectCaps()
+{
+ int caps = BaseClass::ObjectCaps();
+ if ( m_bAlwaysTransition )
+ caps |= FCAP_NOTIFY_ON_TRANSITION;
+ return caps;
+}
+
+void CNPC_AttackHelicopter::InputOutsideTransition( inputdata_t &inputdata )
+{
+ CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, m_iszTransitionTarget );
+
+ if ( pEnt )
+ {
+ Vector teleportLocation = pEnt->GetAbsOrigin();
+ QAngle teleportAngles = pEnt->GetAbsAngles();
+ Teleport( &teleportLocation, &teleportAngles, &vec3_origin );
+ Teleported();
+ }
+ else
+ {
+ DevMsg( 2, "NPC \"%s\" failed to find a suitable transition a point\n", STRING(GetEntityName()) );
+ }
+}
+
+void CNPC_AttackHelicopter::InputSetOutsideTransitionTarget( inputdata_t &inputdata )
+{
+ m_iszTransitionTarget = MAKE_STRING( inputdata.value.String() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Create components
+//-----------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::CreateComponents()
+{
+ if ( !BaseClass::CreateComponents() )
+ return false;
+
+ m_Spotlight.Init( this, AI_SPOTLIGHT_NO_DLIGHTS, 45.0f, 500.0f );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose :
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::Spawn( void )
+{
+ Precache( );
+
+ m_bIndestructible = false;
+ m_bDeadlyShooting = false;
+ m_bBombingSuppressed = false;
+ m_bIgnorePathVisibilityTests = false;
+
+ if ( !HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
+ {
+ SetModel( CHOPPER_MODEL_NAME );
+ }
+ else
+ {
+ SetModel( CHOPPER_DRONE_NAME );
+ }
+
+ ExtractBbox( SelectHeaviestSequence( ACT_IDLE ), m_cullBoxMins, m_cullBoxMaxs );
+ GetEnemies()->SetFreeKnowledgeDuration( DEFAULT_FREE_KNOWLEDGE_DURATION );
+
+ float flLoadedSpeed = m_flMaxSpeed;
+ BaseClass::Spawn();
+
+ float flChaseDist = HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) ?
+ CHOPPER_MIN_AGGRESSIVE_CHASE_DIST_DIFF : CHOPPER_MIN_CHASE_DIST_DIFF;
+ InitPathingData( CHOPPER_ARRIVE_DIST, flChaseDist, CHOPPER_AVOID_DIST );
+ SetFarthestPathDist( GetMaxFiringDistance() );
+
+ m_takedamage = DAMAGE_YES;
+ m_nGunState = GUN_STATE_IDLE;
+ SetHullType( HULL_LARGE_CENTERED );
+
+ SetHullSizeNormal();
+
+#ifdef HL2_EPISODIC
+ CreateVPhysics();
+#endif // HL2_EPISODIC
+
+ SetPauseState( PAUSE_NO_PAUSE );
+
+ m_iMaxHealth = m_iHealth = sk_helicopter_health.GetInt();
+
+ m_flMaxSpeed = flLoadedSpeed;
+ if ( m_flMaxSpeed <= 0 )
+ {
+ m_flMaxSpeed = CHOPPER_MAX_SPEED;
+ }
+ m_flNextMegaBombHealth = m_iMaxHealth - m_iMaxHealth * g_helicopter_bullrush_mega_bomb_health.GetFloat();
+
+ m_nGrenadeCount = CHOPPER_BOMB_DROP_COUNT;
+
+ m_flFieldOfView = -1.0; // 360 degrees
+ m_flIdleTimeDelay = 0.0f;
+ m_iAmmoType = GetAmmoDef()->Index("HelicopterGun");
+
+ InitBoneControllers();
+
+ m_fHelicopterFlags = BITS_HELICOPTER_GUN_ON;
+ m_bSuppressSound = false;
+
+ m_flAcrossTime = -1.0f;
+ m_flPathOffset = 0.0f;
+ m_flCurrPathOffset = 0.0f;
+ m_nAttackMode = ATTACK_MODE_DEFAULT;
+ m_flInputDropBombTime = gpGlobals->curtime;
+ SetActivity( ACT_IDLE );
+
+ int nBombAttachment = LookupAttachment("bomb");
+ m_hSensor = static_cast<CBombDropSensor*>(CreateEntityByName( "npc_helicoptersensor" ));
+ m_hSensor->Spawn();
+ m_hSensor->SetParent( this, nBombAttachment );
+ m_hSensor->SetLocalOrigin( vec3_origin );
+ m_hSensor->SetLocalAngles( vec3_angle );
+ m_hSensor->SetOwnerEntity( this );
+
+ AddFlag( FL_AIMTARGET );
+
+ m_hCrashPoint.Set( NULL );
+}
+
+#ifdef HL2_EPISODIC
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::CreateVPhysics( void )
+{
+ InitBoneFollowers();
+ return BaseClass::CreateVPhysics();
+}
+#endif // HL2_EPISODIC
+
+//------------------------------------------------------------------------------
+// Startup the chopper
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::Startup()
+{
+ BaseClass::Startup();
+
+ if ( HasSpawnFlags( SF_HELICOPTER_LIGHTS ) )
+ {
+ for ( int i = 0; i < MAX_HELICOPTER_LIGHTS; ++i )
+ {
+ // See if there's an attachment for this smoke trail
+ char buf[32];
+ Q_snprintf( buf, 32, "Light_Red%d", i );
+ int nAttachment = LookupAttachment( buf );
+ if ( nAttachment == 0 )
+ {
+ m_hLights[i] = NULL;
+ continue;
+ }
+
+ m_hLights[i] = CSprite::SpriteCreate( CHOPPER_RED_LIGHT_SPRITE, vec3_origin, false );
+ if ( !m_hLights[i] )
+ continue;
+
+ m_hLights[i]->SetParent( this, nAttachment );
+ m_hLights[i]->SetLocalOrigin( vec3_origin );
+ m_hLights[i]->SetLocalVelocity( vec3_origin );
+ m_hLights[i]->SetMoveType( MOVETYPE_NONE );
+ m_hLights[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 200, kRenderFxNone );
+ m_hLights[i]->SetScale( 1.0f );
+ m_hLights[i]->TurnOn();
+ }
+
+ SetContextThink( &CNPC_AttackHelicopter::BlinkLightsThink, gpGlobals->curtime + CHOPPER_LIGHT_BLINK_TIME_SHORT, s_pBlinkLightThinkContext );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Startup the chopper
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::BlinkLightsThink()
+{
+ bool bIsOn = false;
+ for ( int i = 0; i < MAX_HELICOPTER_LIGHTS; ++i )
+ {
+ if ( !m_hLights[i] )
+ continue;
+
+ if ( m_hLights[i]->GetScale() > 0.1f )
+ {
+ m_hLights[i]->SetScale( 0.1f, CHOPPER_LIGHT_BLINK_TIME_SHORT );
+ }
+ else
+ {
+ m_hLights[i]->SetScale( 0.5f, 0.0f );
+ bIsOn = true;
+ }
+ }
+
+ float flTime;
+ if ( bIsOn )
+ {
+ flTime = CHOPPER_LIGHT_BLINK_TIME_SHORT;
+ }
+ else
+ {
+ flTime = m_bShortBlink ? CHOPPER_LIGHT_BLINK_TIME_SHORT : CHOPPER_LIGHT_BLINK_TIME;
+ m_bShortBlink = !m_bShortBlink;
+ }
+
+ SetContextThink( &CNPC_AttackHelicopter::BlinkLightsThink, gpGlobals->curtime + flTime, s_pBlinkLightThinkContext );
+}
+
+
+//------------------------------------------------------------------------------
+// Start up spotlights
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::SpotlightStartup()
+{
+ if ( !HasSpawnFlags( SF_HELICOPTER_LIGHTS ) )
+ return;
+
+ Vector vecForward;
+ Vector vecOrigin;
+ GetAttachment( m_nSpotlightAttachment, vecOrigin, &vecForward );
+ m_Spotlight.SpotlightCreate( m_nSpotlightAttachment, vecForward );
+ SpotlightThink();
+}
+
+
+//------------------------------------------------------------------------------
+// Shutdown spotlights
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::SpotlightShutdown()
+{
+ m_Spotlight.SpotlightDestroy();
+ SetContextThink( NULL, gpGlobals->curtime, s_pSpotlightThinkContext );
+}
+
+
+//------------------------------------------------------------------------------
+// Spotlights
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::SpotlightThink()
+{
+ // NOTE: This function should deal with all deactivation cases
+ if ( m_lifeState != LIFE_ALIVE )
+ {
+ SpotlightShutdown();
+ return;
+ }
+
+ switch( m_nAttackMode )
+ {
+ case ATTACK_MODE_BULLRUSH_VEHICLE:
+ {
+ switch ( m_nSecondaryMode )
+ {
+ case BULLRUSH_MODE_SHOOT_GUN:
+ {
+ Vector vecForward;
+ Vector vecOrigin;
+ GetAttachment( m_nSpotlightAttachment, vecOrigin, &vecForward );
+ m_Spotlight.SetSpotlightTargetDirection( vecForward );
+ }
+ break;
+
+ case BULLRUSH_MODE_SHOOT_IDLE_PLAYER:
+ if ( GetEnemy() )
+ {
+ m_Spotlight.SetSpotlightTargetPos( GetEnemy()->WorldSpaceCenter() );
+ }
+ break;
+
+ default:
+ SpotlightShutdown();
+ return;
+ }
+ }
+ break;
+
+ default:
+ SpotlightShutdown();
+ return;
+ }
+
+ m_Spotlight.Update();
+ SetContextThink( &CNPC_AttackHelicopter::SpotlightThink, gpGlobals->curtime + TICK_INTERVAL, s_pSpotlightThinkContext );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Always transition along with the player
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputEnableAlwaysTransition( inputdata_t &inputdata )
+{
+ m_bAlwaysTransition = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop always transitioning along with the player
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputDisableAlwaysTransition( inputdata_t &inputdata )
+{
+ m_bAlwaysTransition = false;
+}
+
+//------------------------------------------------------------------------------
+// On Remove
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::UpdateOnRemove()
+{
+ BaseClass::UpdateOnRemove();
+ StopLoopingSounds();
+ UTIL_Remove(m_hSensor);
+ DestroySmokeTrails();
+ for ( int i = 0; i < MAX_HELICOPTER_LIGHTS; ++i )
+ {
+ if ( m_hLights[i] )
+ {
+ UTIL_Remove( m_hLights[i] );
+ m_hLights[i] = NULL;
+ }
+ }
+
+#ifdef HL2_EPISODIC
+ m_BoneFollowerManager.DestroyBoneFollowers();
+#endif // HL2_EPISODIC
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::Activate( void )
+{
+ BaseClass::Activate();
+ m_nGunBaseAttachment = LookupAttachment("gun");
+ m_nGunTipAttachment = LookupAttachment("muzzle");
+ m_nBombAttachment = LookupAttachment("bomb");
+ m_nSpotlightAttachment = LookupAttachment("spotlight");
+
+ if ( HasSpawnFlags( SF_HELICOPTER_LONG_SHADOW ) )
+ {
+ SetShadowCastDistance( 2048 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CNPC_AttackHelicopter::GetTracerType( void )
+{
+ return "HelicopterTracer";
+}
+
+
+//-----------------------------------------------------------------------------
+// Allows the shooter to change the impact effect of his bullets
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::DoImpactEffect( trace_t &tr, int nDamageType )
+{
+ UTIL_ImpactTrace( &tr, nDamageType, "HelicopterImpact" );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : Create our rotor sound
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InitializeRotorSound( void )
+{
+ if ( !m_pRotorSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ CPASAttenuationFilter filter( this );
+
+ if ( HasSpawnFlags( SF_HELICOPTER_LOUD_ROTOR_SOUND ) )
+ {
+ m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.RotorsLoud" );
+ }
+ else
+ {
+ m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.Rotors" );
+ }
+
+ m_pRotorBlast = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.RotorBlast" );
+ m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.FireGun" );
+ controller.Play( m_pGunFiringSound, 0.0, 100 );
+ }
+ else
+ {
+ Assert(m_pRotorSound);
+ Assert(m_pRotorBlast);
+ Assert(m_pGunFiringSound);
+ }
+
+
+ BaseClass::InitializeRotorSound();
+}
+
+
+//------------------------------------------------------------------------------
+// Gets the max speed of the helicopter
+//------------------------------------------------------------------------------
+float CNPC_AttackHelicopter::GetMaxSpeed()
+{
+ if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
+ return DRONE_SPEED;
+
+ if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && IsInSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED ) )
+ return CHOPPER_BULLRUSH_ENEMY_BOMB_SPEED;
+
+ if ( !GetEnemyVehicle() )
+ return BaseClass::GetMaxSpeed();
+
+ return 3000.0f;
+}
+
+float CNPC_AttackHelicopter::GetMaxSpeedFiring()
+{
+ if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
+ return DRONE_SPEED;
+
+ if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && IsInSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED ) )
+ return CHOPPER_BULLRUSH_ENEMY_BOMB_SPEED;
+
+ if ( !GetEnemyVehicle() )
+ return BaseClass::GetMaxSpeedFiring();
+
+ return 3000.0f;
+}
+
+
+//------------------------------------------------------------------------------
+// Returns the max firing distance
+//------------------------------------------------------------------------------
+float CNPC_AttackHelicopter::GetMaxFiringDistance()
+{
+ if ( !GetEnemyVehicle() )
+ return CHOPPER_GUN_MAX_FIRING_DIST;
+
+ return 8000.0f;
+}
+
+
+//------------------------------------------------------------------------------
+// Updates the enemy
+//------------------------------------------------------------------------------
+float CNPC_AttackHelicopter::EnemySearchDistance( )
+{
+ return 6000.0f;
+}
+
+
+//------------------------------------------------------------------------------
+// Leading behaviors
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputStartBombingVehicle( inputdata_t &inputdata )
+{
+ m_nAttackMode = ATTACK_MODE_BOMB_VEHICLE;
+ SetLeadingDistance( 1500.0f );
+}
+
+void CNPC_AttackHelicopter::InputStartTrailingVehicle( inputdata_t &inputdata )
+{
+ m_nAttackMode = ATTACK_MODE_TRAIL_VEHICLE;
+ SetLeadingDistance( -1500.0f );
+}
+
+void CNPC_AttackHelicopter::InputStartDefaultBehavior( inputdata_t &inputdata )
+{
+ m_nAttackMode = ATTACK_MODE_DEFAULT;
+}
+
+void CNPC_AttackHelicopter::InputStartAlwaysLeadingVehicle( inputdata_t &inputdata )
+{
+ m_nAttackMode = ATTACK_MODE_ALWAYS_LEAD_VEHICLE;
+ SetLeadingDistance( 0.0f );
+}
+
+void CNPC_AttackHelicopter::InputStartBullrushBehavior( inputdata_t &inputdata )
+{
+ if ( m_nAttackMode != ATTACK_MODE_BULLRUSH_VEHICLE )
+ {
+ m_nAttackMode = ATTACK_MODE_BULLRUSH_VEHICLE;
+ SetSecondaryMode( BULLRUSH_MODE_WAIT_FOR_ENEMY );
+ SetLeadingDistance( 0.0f );
+ }
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputStartCarpetBombing( inputdata_t &inputdata )
+{
+ m_bIsCarpetBombing = true;
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputStopCarpetBombing( inputdata_t &inputdata )
+{
+ m_bIsCarpetBombing = false;
+}
+
+//------------------------------------------------------------------------------
+// Become indestructible
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputBecomeIndestructible( inputdata_t &inputdata )
+{
+ m_bIndestructible = true;
+}
+
+
+//------------------------------------------------------------------------------
+// Deadly shooting, tex!
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputEnableDeadlyShooting( inputdata_t &inputdata )
+{
+ m_bDeadlyShooting = true;
+}
+
+void CNPC_AttackHelicopter::InputDisableDeadlyShooting( inputdata_t &inputdata )
+{
+ m_bDeadlyShooting = false;
+}
+
+void CNPC_AttackHelicopter::InputStartNormalShooting( inputdata_t &inputdata )
+{
+ m_nShootingMode = SHOOT_MODE_DEFAULT;
+}
+
+void CNPC_AttackHelicopter::InputStartLongCycleShooting( inputdata_t &inputdata )
+{
+ m_nShootingMode = SHOOT_MODE_LONG_CYCLE;
+}
+
+void CNPC_AttackHelicopter::InputStartContinuousShooting( inputdata_t &inputdata )
+{
+ m_nShootingMode = SHOOT_MODE_CONTINUOUS;
+}
+
+void CNPC_AttackHelicopter::InputStartFastShooting( inputdata_t &inputdata )
+{
+ m_nShootingMode = SHOOT_MODE_FAST;
+}
+
+//------------------------------------------------------------------------------
+// Deadly shooting, tex!
+//------------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::IsDeadlyShooting()
+{
+ if ( m_bDeadlyShooting )
+ return true;
+
+ if (( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) )
+ {
+ return (!GetEnemyVehicle()) && GetEnemy() && GetEnemy()->IsPlayer();
+ }
+
+ return false;
+}
+
+int CNPC_AttackHelicopter::GetShootingMode( )
+{
+ if ( IsDeadlyShooting() )
+ return SHOOT_MODE_LONG_CYCLE;
+
+ if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
+ return SHOOT_MODE_CONTINUOUS;
+
+ return m_nShootingMode;
+}
+
+
+//-----------------------------------------------------------------------------
+// Bombing suppression
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputEnableBombing( inputdata_t &inputdata )
+{
+ m_bBombingSuppressed = false;
+}
+
+void CNPC_AttackHelicopter::InputDisableBombing( inputdata_t &inputdata )
+{
+ m_bBombingSuppressed = true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Visibility tests
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputDisablePathVisibilityTests( inputdata_t &inputdata )
+{
+ m_bIgnorePathVisibilityTests = true;
+ GetEnemies()->SetUnforgettable( GetEnemy(), true );
+}
+
+void CNPC_AttackHelicopter::InputEnablePathVisibilityTests( inputdata_t &inputdata )
+{
+ m_bIgnorePathVisibilityTests = false;
+ GetEnemies()->SetUnforgettable( GetEnemy(), false );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputSelfDestruct( inputdata_t &inputdata )
+{
+ m_lifeState = LIFE_ALIVE; // Force to die properly.
+ CTakeDamageInfo info( this, this, Vector(0, 0, 1), WorldSpaceCenter(), GetMaxHealth(), CLASS_MISSILE );
+ TakeDamage( info );
+}
+
+//-----------------------------------------------------------------------------
+// For scripted times where it *has* to shoot
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputSetHealthFraction( inputdata_t &inputdata )
+{
+ // Sets the health fraction, no damage effects
+ if ( inputdata.value.Float() > 0 )
+ {
+ SetHealth( GetMaxHealth() * inputdata.value.Float() * 0.01f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputStartBombExplodeOnContact( inputdata_t &inputdata )
+{
+ m_bBombsExplodeOnContact = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputStopBombExplodeOnContact( inputdata_t &inputdata )
+{
+ m_bBombsExplodeOnContact = false;
+}
+
+//------------------------------------------------------------------------------
+// For scripted times where it *has* to shoot
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputResetIdleTime( inputdata_t &inputdata )
+{
+ if ( m_nGunState == GUN_STATE_IDLE )
+ {
+ m_flNextAttack = gpGlobals->curtime;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// This trace filter ignores all breakables + physics props
+//-----------------------------------------------------------------------------
+class CTraceFilterChopper : public CTraceFilterSimple
+{
+ DECLARE_CLASS( CTraceFilterChopper, CTraceFilterSimple );
+
+public:
+ CTraceFilterChopper( const IHandleEntity *passentity, int collisionGroup );
+ virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask );
+
+private:
+ const IHandleEntity *m_pPassEnt;
+ int m_collisionGroup;
+};
+
+CTraceFilterChopper::CTraceFilterChopper( const IHandleEntity *passentity, int collisionGroup ) :
+ CTraceFilterSimple( passentity, collisionGroup )
+{
+}
+
+bool CTraceFilterChopper::ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
+{
+ CBaseEntity *pEnt = static_cast<IServerUnknown*>(pServerEntity)->GetBaseEntity();
+ if ( pEnt )
+ {
+ if ( FClassnameIs( pEnt, "func_breakable" ) ||
+ FClassnameIs( pEnt, "func_physbox" ) ||
+ FClassnameIs( pEnt, "prop_physics" ) ||
+ FClassnameIs( pEnt, "physics_prop" ) )
+ {
+ return false;
+ }
+ }
+
+ return BaseClass::ShouldHitEntity( pServerEntity, contentsMask );
+}
+
+
+//-----------------------------------------------------------------------------
+// Enemy visibility check
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_AttackHelicopter::FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos )
+{
+ if ( m_bIgnorePathVisibilityTests )
+ return NULL;
+
+ CTraceFilterChopper chopperFilter( this, COLLISION_GROUP_NONE );
+
+ trace_t tr;
+ AI_TraceHull( vecViewPoint, vecTargetPos, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT, &chopperFilter, &tr );
+
+ if ( tr.fraction != 1.0f )
+ {
+ Assert( tr.m_pEnt );
+ }
+
+ return (tr.fraction != 1.0f) ? tr.m_pEnt : NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// More Enemy visibility check
+//-----------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ if ( pEntity->GetFlags() & FL_NOTARGET )
+ return false;
+
+#if 0
+ // FIXME: only block LOS through opaque water
+ // don't look through water
+ if ((m_nWaterLevel != 3 && pEntity->m_nWaterLevel == 3)
+ || (m_nWaterLevel == 3 && pEntity->m_nWaterLevel == 0))
+ return false;
+#endif
+
+ Vector vecLookerOrigin = EyePosition();//look through the caller's 'eyes'
+ Vector vecTargetOrigin = pEntity->EyePosition();
+
+ CTraceFilterChopper chopperFilter( this, COLLISION_GROUP_NONE );
+
+ trace_t tr;
+ UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, traceMask, &chopperFilter, &tr);
+
+ if (tr.fraction != 1.0)
+ {
+ // Got line of sight!
+ if ( tr.m_pEnt == pEntity )
+ return true;
+
+ // Got line of sight on the vehicle the player is driving!
+ if ( pEntity && pEntity->IsPlayer() )
+ {
+ CBasePlayer *pPlayer = assert_cast<CBasePlayer*>( pEntity );
+ if ( tr.m_pEnt == pPlayer->GetVehicleEntity() )
+ return true;
+ }
+
+ if (ppBlocker)
+ {
+ *ppBlocker = tr.m_pEnt;
+ }
+ return false;// Line of sight is not established
+ }
+
+ return true;// line of sight is valid.
+}
+
+
+//------------------------------------------------------------------------------
+// Shot spread
+//------------------------------------------------------------------------------
+#define PLAYER_TIGHTEN_FACTOR 0.75f
+Vector CNPC_AttackHelicopter::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
+{
+ float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * PLAYER_TIGHTEN_FACTOR * 0.5f * (3.14f / 180.0f) );
+ Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
+ return vecSpread;
+}
+
+
+//------------------------------------------------------------------------------
+// Find interesting nearby things to shoot
+//------------------------------------------------------------------------------
+int CNPC_AttackHelicopter::BuildMissTargetList( int nCount, CBaseEntity **ppMissCandidates )
+{
+ int numMissCandidates = 0;
+
+ CBaseEntity *pEnts[256];
+ Vector radius( 150, 150, 150 );
+ const Vector &vecSource = GetEnemy()->WorldSpaceCenter();
+
+ int numEnts = UTIL_EntitiesInBox( pEnts, 256, vecSource - radius, vecSource+radius, 0 );
+
+ for ( int i = 0; i < numEnts; i++ )
+ {
+ if ( pEnts[i] == NULL )
+ continue;
+
+ if ( numMissCandidates >= nCount )
+ break;
+
+ // Miss candidates cannot include the player or his vehicle
+ if ( pEnts[i] == GetEnemyVehicle() || pEnts[i] == GetEnemy() )
+ continue;
+
+ // See if it's a good target candidate
+ if ( FClassnameIs( pEnts[i], "prop_dynamic" ) ||
+ FClassnameIs( pEnts[i], "prop_physics" ) ||
+ FClassnameIs( pEnts[i], "physics_prop" ) )
+ {
+ ppMissCandidates[numMissCandidates++] = pEnts[i];
+ }
+ }
+
+ return numMissCandidates;
+}
+
+
+//------------------------------------------------------------------------------
+// Gets a vehicle the enemy is in (if any)
+//------------------------------------------------------------------------------
+CBaseEntity *CNPC_AttackHelicopter::GetEnemyVehicle()
+{
+ if ( !GetEnemy() )
+ return NULL;
+
+ if ( !GetEnemy()->IsPlayer() )
+ return NULL;
+
+ return static_cast<CBasePlayer*>(GetEnemy())->GetVehicleEntity();
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::ShootAtPlayer( const Vector &vBasePos, const Vector &vGunDir )
+{
+ // Fire one shots per round right at the player, using usual rules
+ FireBulletsInfo_t info;
+ info.m_vecSrc = vBasePos;
+ info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
+ info.m_flDistance = MAX_COORD_RANGE;
+ info.m_iAmmoType = m_iAmmoType;
+ info.m_iTracerFreq = 1;
+ info.m_vecDirShooting = GetActualShootTrajectory( vBasePos );
+ info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
+
+ DoMuzzleFlash();
+
+ QAngle vGunAng;
+ VectorAngles( vGunDir, vGunAng );
+
+ FireBullets( info );
+
+ // Fire the rest of the bullets at objects around the player
+ CBaseEntity *ppNearbyTargets[16];
+ int nActualTargets = BuildMissTargetList( 16, ppNearbyTargets );
+
+ // Randomly sort it...
+ int i;
+ for ( i = 0; i < nActualTargets; ++i )
+ {
+ int nSwap = random->RandomInt( 0, nActualTargets - 1 );
+ V_swap( ppNearbyTargets[i], ppNearbyTargets[nSwap] );
+ }
+
+ // Just shoot where we're facing
+ float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * 0.5f * (3.14f / 180.0f) );
+ Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
+
+ // How many times should we hit the player this time?
+ int nDesiredHitCount = (int)(((float)( m_nMaxBurstHits - m_nBurstHits ) / (float)m_nRemainingBursts) + 0.5f);
+ int nNearbyTargetCount = 0;
+ int nPlayerShotCount = 0;
+ for ( i = sk_helicopter_roundsperburst.GetInt() - 1; --i >= 0; )
+ {
+ // Find something interesting around the enemy to shoot instead of just missing.
+ if ( nActualTargets > nNearbyTargetCount )
+ {
+ // FIXME: Constrain to the firing cone?
+ ppNearbyTargets[nNearbyTargetCount]->CollisionProp()->RandomPointInBounds( Vector(.25, .25, .25), Vector(.75, .75, .75), &info.m_vecDirShooting );
+ info.m_vecDirShooting -= vBasePos;
+ VectorNormalize( info.m_vecDirShooting );
+ info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
+ info.m_flDistance = MAX_COORD_RANGE;
+ info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
+
+ FireBullets( info );
+
+ ++nNearbyTargetCount;
+ continue;
+ }
+
+ if ( GetEnemy() && ( nPlayerShotCount < nDesiredHitCount ))
+ {
+ GetEnemy()->CollisionProp()->RandomPointInBounds( Vector(0, 0, 0), Vector(1, 1, 1), &info.m_vecDirShooting );
+ info.m_vecDirShooting -= vBasePos;
+ VectorNormalize( info.m_vecDirShooting );
+ info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
+ info.m_flDistance = MAX_COORD_RANGE;
+ info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
+ FireBullets( info );
+ ++nPlayerShotCount;
+ continue;
+ }
+
+ // Nothing nearby; just fire randomly...
+ info.m_vecDirShooting = vGunDir;
+ info.m_vecSpread = vecSpread;
+ info.m_flDistance = 8192;
+ info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
+
+ FireBullets( info );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Chooses a point within the circle of death to fire in
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::PickDirectionToCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition, Vector *pResult )
+{
+ *pResult = vecFireAtPosition;
+ float x, y;
+ do
+ {
+ x = random->RandomFloat( -1.0f, 1.0f );
+ y = random->RandomFloat( -1.0f, 1.0f );
+ } while ( (x * x + y * y) > 1.0f );
+
+ pResult->x += x * m_flCircleOfDeathRadius;
+ pResult->y += y * m_flCircleOfDeathRadius;
+
+ *pResult -= vBasePos;
+ VectorNormalize( *pResult );
+}
+
+
+//-----------------------------------------------------------------------------
+// Deliberately aims as close as possible w/o hitting
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::AimCloseToTargetButMiss( CBaseEntity *pTarget, float flMinDist, float flMaxDist, const Vector &shootOrigin, Vector *pResult )
+{
+ Vector vecDirection;
+ VectorSubtract( pTarget->WorldSpaceCenter(), shootOrigin, vecDirection );
+ float flDist = VectorNormalize( vecDirection );
+ float flRadius = pTarget->BoundingRadius() + random->RandomFloat( flMinDist, flMaxDist );
+
+ float flMinRadius = flRadius;
+ if ( flDist > flRadius )
+ {
+ flMinRadius = flDist * flRadius / sqrt( flDist * flDist - flRadius * flRadius );
+ }
+
+ // Choose random points in a plane perpendicular to the shoot origin.
+ Vector vecRandomDir;
+ vecRandomDir.Random( -1.0f, 1.0f );
+ VectorMA( vecRandomDir, -DotProduct( vecDirection, vecRandomDir ), vecDirection, vecRandomDir );
+ VectorNormalize( vecRandomDir );
+ vecRandomDir *= flMinRadius;
+ vecRandomDir += pTarget->WorldSpaceCenter();
+
+ VectorSubtract( vecRandomDir, shootOrigin, *pResult );
+ VectorNormalize( *pResult );
+}
+
+
+//-----------------------------------------------------------------------------
+// Make sure we don't hit too many times
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::FireBullets( const FireBulletsInfo_t &info )
+{
+ // Use this to count the number of hits in a burst
+ bool bIsPlayer = GetEnemy() && GetEnemy()->IsPlayer();
+ if ( !bIsPlayer )
+ {
+ BaseClass::FireBullets( info );
+ return;
+ }
+
+ if ( !GetEnemyVehicle() && !IsDeadlyShooting() )
+ {
+ if ( m_nBurstHits >= m_nMaxBurstHits )
+ {
+ FireBulletsInfo_t actualInfo = info;
+ actualInfo.m_pAdditionalIgnoreEnt = GetEnemy();
+ BaseClass::FireBullets( actualInfo );
+ return;
+ }
+ }
+
+ CBasePlayer *pPlayer = assert_cast<CBasePlayer*>(GetEnemy());
+
+ int nPrevHealth = pPlayer->GetHealth();
+ int nPrevArmor = pPlayer->ArmorValue();
+
+ BaseClass::FireBullets( info );
+
+ if (( pPlayer->GetHealth() < nPrevHealth ) || ( pPlayer->ArmorValue() < nPrevArmor ))
+ {
+ ++m_nBurstHits;
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::ShootInsideCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition )
+{
+ Vector vecFireDirection;
+ if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
+ {
+ PickDirectionToCircleOfDeath( vBasePos, vecFireAtPosition, &vecFireDirection );
+ }
+ else if ( ( m_nNearShots < m_nMaxNearShots ) || !GetEnemyVehicle() )
+ {
+ if ( ( m_nBurstHits < m_nMaxBurstHits ) || !GetEnemy() )
+ {
+ ++m_nNearShots;
+ PickDirectionToCircleOfDeath( vBasePos, vecFireAtPosition, &vecFireDirection );
+ }
+ else
+ {
+ m_nNearShots += 6;
+ AimCloseToTargetButMiss( GetEnemy(), 20.0f, 50.0f, vBasePos, &vecFireDirection );
+ }
+ }
+ else
+ {
+ AimCloseToTargetButMiss( GetEnemyVehicle(), 10.0f, 80.0f, vBasePos, &vecFireDirection );
+ }
+
+ FireBulletsInfo_t info( 1, vBasePos, vecFireDirection, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
+ info.m_iTracerFreq = 1;
+ info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
+
+ FireBullets( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::DoMuzzleFlash( void )
+{
+ BaseClass::DoMuzzleFlash();
+
+ CEffectData data;
+
+ data.m_nAttachmentIndex = LookupAttachment( "muzzle" );
+ data.m_nEntIndex = entindex();
+ DispatchEffect( "ChopperMuzzleFlash", data );
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+#define HIT_VEHICLE_SPEED_MIN 200.0f
+#define HIT_VEHICLE_SPEED_MAX 500.0f
+
+void CNPC_AttackHelicopter::ShootAtVehicle( const Vector &vBasePos, const Vector &vecFireAtPosition )
+{
+ int nShotsRemaining = sk_helicopter_roundsperburst.GetInt();
+
+ DoMuzzleFlash();
+
+ // Do special code against episodic drivers
+ if ( hl2_episodic.GetBool() )
+ {
+ Vector vecVelocity;
+ GetEnemyVehicle()->GetVelocity( &vecVelocity, NULL );
+
+ float flSpeed = clamp( vecVelocity.Length(), 0.0f, 400.0f );
+ float flRange = RemapVal( flSpeed, 0.0f, 400.0f, 0.05f, 1.0f );
+
+ // Alter each shot's trajectory based on our speed
+ for ( int i = 0; i < nShotsRemaining; i++ )
+ {
+ Vector vecShotDir;
+
+ // If they're at a dead stand-still, just hit them
+ if ( flRange <= 0.1f )
+ {
+ VectorSubtract( GetEnemy()->EyePosition(), vBasePos, vecShotDir );
+
+ Vector vecOffset;
+ vecOffset.Random( -40.0f, 40.0f );
+ vecShotDir += vecOffset;
+ VectorNormalize( vecShotDir );
+ }
+ else
+ {
+ // Aim in a cone around them
+ AimCloseToTargetButMiss( GetEnemy(), (3*12) * flRange, (10*12) * flRange, vBasePos, &vecShotDir );
+ }
+
+ FireBulletsInfo_t info( 1, vBasePos, vecShotDir, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
+ info.m_iTracerFreq = 1;
+ FireBullets( info );
+ }
+
+ // We opt out of the rest of the function
+ // FIXME: Should we emulate the below functionality and have half the bullets attempt to miss admirably? -- jdw
+ return;
+ }
+
+ // Pop one at the player based on how fast he's going
+ if ( m_nBurstHits < m_nMaxBurstHits )
+ {
+ Vector vecDir;
+ VectorSubtract( GetEnemy()->EyePosition(), vBasePos, vecDir );
+
+ Vector vecOffset;
+ vecOffset.Random( -5.0f, 5.0f );
+ vecDir += vecOffset;
+ VectorNormalize( vecDir );
+
+ FireBulletsInfo_t info( 1, vBasePos, vecDir, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
+ info.m_iTracerFreq = 1;
+ FireBullets( info );
+ --nShotsRemaining;
+ }
+
+ // Fire half of the bullets within the circle of death, the other half at interesting things
+ int i;
+ int nFireInCircle = nShotsRemaining >> 1;
+ nShotsRemaining -= nFireInCircle;
+ for ( i = 0; i < nFireInCircle; ++i )
+ {
+ ShootInsideCircleOfDeath( vBasePos, vecFireAtPosition );
+ }
+
+ // Fire the rest of the bullets at objects around the enemy
+ CBaseEntity *ppNearbyTargets[16];
+ int nActualTargets = BuildMissTargetList( 16, ppNearbyTargets );
+
+ // Randomly sort it...
+ for ( i = 0; i < nActualTargets; ++i )
+ {
+ int nSwap = random->RandomInt( 0, nActualTargets - 1 );
+ V_swap( ppNearbyTargets[i], ppNearbyTargets[nSwap] );
+ }
+
+ // Just shoot where we're facing
+ float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * 0.5f * (3.14f / 180.0f) );
+ Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
+
+ for ( i = nShotsRemaining; --i >= 0; )
+ {
+ // Find something interesting around the enemy to shoot instead of just missing.
+ if ( nActualTargets > i )
+ {
+ Vector vecFireDirection;
+ ppNearbyTargets[i]->CollisionProp()->RandomPointInBounds( Vector(.25, .25, .25), Vector(.75, .75, .75), &vecFireDirection );
+ vecFireDirection -= vBasePos;
+ VectorNormalize( vecFireDirection );
+
+ // FIXME: Constrain to the firing cone?
+
+ // I put in all the default arguments simply so I could guarantee the first shot of one of the bursts always hits
+ FireBulletsInfo_t info( 1, vBasePos, vecFireDirection, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
+ info.m_iTracerFreq = 1;
+ FireBullets( info );
+ }
+ else
+ {
+ ShootInsideCircleOfDeath( vBasePos, vecFireAtPosition );
+ }
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Various states of the helicopter firing...
+//------------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::PoseGunTowardTargetDirection( const Vector &vTargetDir )
+{
+ Vector vecOut;
+ VectorIRotate( vTargetDir, EntityToWorldTransform(), vecOut );
+
+ QAngle angles;
+ VectorAngles(vecOut, angles);
+
+ if (angles.y > 180)
+ {
+ angles.y = angles.y - 360;
+ }
+ else if (angles.y < -180)
+ {
+ angles.y = angles.y + 360;
+ }
+ if (angles.x > 180)
+ {
+ angles.x = angles.x - 360;
+ }
+ else if (angles.x < -180)
+ {
+ angles.x = angles.x + 360;
+ }
+
+ if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && !IsInSecondaryMode(BULLRUSH_MODE_SHOOT_IDLE_PLAYER) && GetEnemy())
+ {
+ if ( GetEnemyVehicle() )
+ {
+ angles.x = clamp( angles.x, -12.0f, 0.0f );
+ angles.y = clamp( angles.y, -10.0f, 10.0f );
+ }
+ else
+ {
+ angles.x = clamp( angles.x, -10.0f, 10.0f );
+ angles.y = clamp( angles.y, -10.0f, 10.0f );
+ }
+ }
+
+ if (angles.x > m_angGun.x)
+ {
+ m_angGun.x = MIN( angles.x, m_angGun.x + 12 );
+ }
+ if (angles.x < m_angGun.x)
+ {
+ m_angGun.x = MAX( angles.x, m_angGun.x - 12 );
+ }
+ if (angles.y > m_angGun.y)
+ {
+ m_angGun.y = MIN( angles.y, m_angGun.y + 12 );
+ }
+ if (angles.y < m_angGun.y)
+ {
+ m_angGun.y = MAX( angles.y, m_angGun.y - 12 );
+ }
+
+ SetPoseParameter( m_poseWeapon_Pitch, -m_angGun.x );
+ SetPoseParameter( m_poseWeapon_Yaw, m_angGun.y );
+
+ return true;
+}
+
+
+//------------------------------------------------------------------------------
+// Compute the enemy position (non-vehicle case)
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::ComputeFireAtPosition( Vector *pVecActualTargetPosition )
+{
+ // Deal with various leading behaviors...
+ *pVecActualTargetPosition = m_vecTargetPosition;
+}
+
+
+//------------------------------------------------------------------------------
+// Compute the enemy position (non-vehicle case)
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::ComputeVehicleFireAtPosition( Vector *pVecActualTargetPosition )
+{
+ CBaseEntity *pVehicle = GetEnemyVehicle();
+
+ // Make sure the circle of death doesn't move more than N units
+ // This will cause the target to have to maintain a large enough speed
+ *pVecActualTargetPosition = pVehicle->BodyTarget( GetAbsOrigin(), false );
+
+// NDebugOverlay::Box( *pVecActualTargetPosition,
+// Vector(-m_flCircleOfDeathRadius, -m_flCircleOfDeathRadius, 0),
+// Vector(m_flCircleOfDeathRadius, m_flCircleOfDeathRadius, 0),
+// 0, 0, 255, false, 0.1f );
+}
+
+
+//------------------------------------------------------------------------------
+// Here's what we do when we're looking for a target
+//------------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::DoGunIdle( const Vector &vGunDir, const Vector &vTargetDir )
+{
+ // When bullrushing, skip the idle
+ if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) &&
+ ( IsInSecondaryMode( BULLRUSH_MODE_SHOOT_GUN ) || IsInSecondaryMode(BULLRUSH_MODE_SHOOT_IDLE_PLAYER) ) )
+ {
+ EmitSound( "NPC_AttackHelicopter.ChargeGun" );
+ m_flChargeTime = gpGlobals->curtime + CHOPPER_GUN_CHARGE_TIME;
+ m_nGunState = GUN_STATE_CHARGING;
+ m_flCircleOfDeathRadius = CHOPPER_MAX_CIRCLE_OF_DEATH_RADIUS;
+ return true;
+ }
+
+ // Can't continually fire....
+ if (m_flNextAttack > gpGlobals->curtime)
+ return false;
+
+ // Don't fire if we're too far away, or if the enemy isn't in front of us
+ if (!GetEnemy())
+ return false;
+
+ float flMaxDistSqr = GetMaxFiringDistance();
+ flMaxDistSqr *= flMaxDistSqr;
+
+ float flDistSqr = WorldSpaceCenter().DistToSqr( GetEnemy()->WorldSpaceCenter() );
+ if (flDistSqr > flMaxDistSqr)
+ return false;
+
+ // If he's mostly within the cone, shoot away!
+ float flChargeCone = sk_helicopter_firingcone.GetFloat() * 0.5f;
+ if ( flChargeCone < 15.0f )
+ {
+ flChargeCone = 15.0f;
+ }
+
+ float flCosConeDegrees = cos( flChargeCone * (3.14f / 180.0f) );
+ float fDotPr = DotProduct( vGunDir, vTargetDir );
+ if (fDotPr < flCosConeDegrees)
+ return false;
+
+ // Fast shooting doesn't charge up
+ if( m_nShootingMode == SHOOT_MODE_FAST )
+ {
+ m_flChargeTime = gpGlobals->curtime;
+ m_nGunState = GUN_STATE_CHARGING;
+ m_flAvoidMetric = 0.0f;
+ m_vecLastAngVelocity.Init( 0, 0, 0 );
+ }
+ else
+ {
+ EmitSound( "NPC_AttackHelicopter.ChargeGun" );
+ float flChargeTime = CHOPPER_GUN_CHARGE_TIME;
+ float flVariance = flChargeTime * 0.1f;
+ m_flChargeTime = gpGlobals->curtime + random->RandomFloat(flChargeTime - flVariance, flChargeTime + flVariance);
+ m_nGunState = GUN_STATE_CHARGING;
+ m_flAvoidMetric = 0.0f;
+ m_vecLastAngVelocity.Init( 0, 0, 0 );
+ }
+
+ return true;
+}
+
+
+//------------------------------------------------------------------------------
+// How easy is the target to hit?
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::UpdateTargetHittability()
+{
+ // This simply is a measure of how much juking is going on.
+ // Along with how much steering is happening.
+ if ( GetEnemyVehicle() )
+ {
+ Vector vecVelocity;
+ AngularImpulse vecAngVelocity;
+ GetEnemyVehicle()->GetVelocity( &vecVelocity, &vecAngVelocity );
+
+ float flDist = fabs( vecAngVelocity.z - m_vecLastAngVelocity.z );
+ m_flAvoidMetric += flDist;
+ m_vecLastAngVelocity = vecAngVelocity;
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Here's what we do when we're getting ready to fire
+//------------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::DoGunCharging( )
+{
+ // Update the target hittability, which will indicate how many hits we'll accept.
+ UpdateTargetHittability();
+
+ if ( m_flChargeTime > gpGlobals->curtime )
+ return false;
+
+ m_nGunState = GUN_STATE_FIRING;
+
+ if ( HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) )
+ {
+ SetPauseState( PAUSE_AT_NEXT_LOS_POSITION );
+ }
+
+ int nHitFactor = 1;
+ switch( GetShootingMode() )
+ {
+ case SHOOT_MODE_DEFAULT:
+ case SHOOT_MODE_FAST:
+ {
+ int nBurstCount = sk_helicopter_burstcount.GetInt();
+ m_nRemainingBursts = random->RandomInt( nBurstCount, 2.0 * nBurstCount );
+ m_flIdleTimeDelay = 0.1f * ( m_nRemainingBursts - nBurstCount );
+ }
+ break;
+
+ case SHOOT_MODE_LONG_CYCLE:
+ {
+ m_nRemainingBursts = 60;
+ m_flIdleTimeDelay = 0.0f;
+ nHitFactor = 2;
+ }
+ break;
+
+ case SHOOT_MODE_CONTINUOUS:
+ if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
+ {
+ // We're relying on the special aiming behavior for bullrushing to just randomly deal damage
+ m_nRemainingBursts = 1;
+ m_flIdleTimeDelay = 0.0f;
+ }
+ else
+ {
+ m_nRemainingBursts = 0;
+ m_flIdleTimeDelay = 0.0f;
+ nHitFactor = 1000;
+ }
+ break;
+ }
+
+ if ( !GetEnemyVehicle() )
+ {
+ m_nMaxBurstHits = !IsDeadlyShooting() ? random->RandomInt( 6, 9 ) : 200;
+ m_nMaxNearShots = 10000;
+ }
+ else
+ {
+ Vector vecVelocity;
+ GetEnemyVehicle()->GetVelocity( &vecVelocity, NULL );
+ float flSpeed = vecVelocity.Length();
+ flSpeed = clamp( flSpeed, 150.0f, 600.0f );
+ flSpeed = RemapVal( flSpeed, 150.0f, 600.0f, 0.0f, 1.0f );
+ float flAvoid = clamp( m_flAvoidMetric, 100.0f, 400.0f );
+ flAvoid = RemapVal( flAvoid, 100.0f, 400.0f, 0.0f, 1.0f );
+
+ float flTotal = 0.5f * ( flSpeed + flAvoid );
+ int nHitCount = (int)(RemapVal( flTotal, 0.0f, 1.0f, 7, -0.5 ) + 0.5f);
+
+ int nMin = nHitCount >= 1 ? nHitCount - 1 : 0;
+ m_nMaxBurstHits = random->RandomInt( nMin, nHitCount + 1 );
+
+ int nNearShots = (int)(RemapVal( flTotal, 0.0f, 1.0f, 70, 5 ) + 0.5f);
+ int nMinNearShots = nNearShots >= 5 ? nNearShots - 5 : 0;
+ m_nMaxNearShots = random->RandomInt( nMinNearShots, nNearShots + 5 );
+
+ // Set up the circle of death parameters at this point
+ m_flCircleOfDeathRadius = SimpleSplineRemapVal( flTotal, 0.0f, 1.0f,
+ CHOPPER_MIN_CIRCLE_OF_DEATH_RADIUS, CHOPPER_MAX_CIRCLE_OF_DEATH_RADIUS );
+ }
+
+ m_nMaxBurstHits *= nHitFactor;
+ m_nMaxNearShots *= nHitFactor;
+
+ m_nBurstHits = 0;
+ m_nNearShots = 0;
+ return true;
+}
+
+
+//------------------------------------------------------------------------------
+// Shoot where we're facing
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::ShootAtFacingDirection( const Vector &vBasePos, const Vector &vGunDir, bool bFirstShotAccurate )
+{
+ // Just shoot where we're facing
+ float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * 0.5f * (3.14f / 180.0f) );
+ Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
+
+ int nShotCount = sk_helicopter_roundsperburst.GetInt();
+ if ( bFirstShotAccurate && GetEnemy() )
+ {
+ // Check to see if the enemy is within his firing cone
+ if ( GetEnemy() )
+ {
+ // Find the closest point to the gunDir
+ const Vector &vecCenter = GetEnemy()->WorldSpaceCenter();
+
+ float t;
+ Vector vNearPoint;
+ Vector vEndPoint;
+ VectorMA( vBasePos, 1024.0f, vGunDir, vEndPoint );
+ CalcClosestPointOnLine( vecCenter, vBasePos, vEndPoint, vNearPoint, &t );
+ if ( t > 0.0f )
+ {
+ Vector vecDelta;
+ VectorSubtract( vecCenter, vBasePos, vecDelta );
+ float flDist = VectorNormalize( vecDelta );
+ float flPerpDist = vecCenter.DistTo( vNearPoint );
+ float flSinAngle = flPerpDist / flDist;
+ if ( flSinAngle <= flSinConeDegrees )
+ {
+ FireBulletsInfo_t info( 1, vBasePos, vecDelta, VECTOR_CONE_PRECALCULATED, 8192, m_iAmmoType );
+ info.m_iTracerFreq = 1;
+ FireBullets( info );
+ --nShotCount;
+ }
+ }
+ }
+ }
+
+#ifdef HL2_EPISODIC
+ if( GetEnemy() != NULL )
+ {
+ CSoundEnt::InsertSound( SOUND_DANGER, GetEnemy()->WorldSpaceCenter(), 180.0f, 0.5f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
+ }
+#endif//HL2_EPISODIC
+
+ DoMuzzleFlash();
+
+ FireBulletsInfo_t info( nShotCount, vBasePos, vGunDir, vecSpread, 8192, m_iAmmoType );
+ info.m_iTracerFreq = 1;
+ FireBullets( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// Can we zap it?
+//-----------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::IsValidZapTarget( CBaseEntity *pTarget )
+{
+ // Don't use the player or vehicle as a zap target, we'll do that ourselves.
+ if ( pTarget->IsPlayer() || pTarget->GetServerVehicle() )
+ return false;
+
+ if ( pTarget == this )
+ return false;
+
+ if ( !pTarget->IsSolid() )
+ return false;
+
+ Assert( pTarget );
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = pTarget->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ for ( int i = 0; i < count; i++ )
+ {
+ int material = pList[i]->GetMaterialIndex();
+ const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material );
+
+ // Is flesh or metal? Go for it!
+ if ( pSurfaceData->game.material == CHAR_TEX_METAL ||
+ pSurfaceData->game.material == CHAR_TEX_FLESH ||
+ pSurfaceData->game.material == CHAR_TEX_VENT ||
+ pSurfaceData->game.material == CHAR_TEX_GRATE ||
+ pSurfaceData->game.material == CHAR_TEX_COMPUTER ||
+ pSurfaceData->game.material == CHAR_TEX_BLOODYFLESH ||
+ pSurfaceData->game.material == CHAR_TEX_ALIENFLESH )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//------------------------------------------------------------------------------
+// Effects
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::CreateZapBeam( const Vector &vecTargetPos )
+{
+ CEffectData data;
+ data.m_nEntIndex = entindex();
+ data.m_nAttachmentIndex = 0; // m_nGunTipAttachment;
+ data.m_vOrigin = vecTargetPos;
+ data.m_flScale = 5;
+ DispatchEffect( "TeslaZap", data );
+}
+
+void CNPC_AttackHelicopter::CreateEntityZapEffect( CBaseEntity *pEnt )
+{
+ CEffectData data;
+ data.m_nEntIndex = pEnt->entindex();
+ data.m_flMagnitude = 10;
+ data.m_flScale = 1.0f;
+ DispatchEffect( "TeslaHitboxes", data );
+}
+
+
+//------------------------------------------------------------------------------
+// Here's what we do when we *are* firing
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::FireElectricityGun( )
+{
+ if ( m_flNextAttack > gpGlobals->curtime )
+ return;
+
+ EmitSound( "ReallyLoudSpark" );
+
+ CBaseEntity *ppEnts[256];
+ Vector vecCenter = WorldSpaceCenter();
+ float flRadius = 500.0f;
+ vecCenter.z -= flRadius * 0.8f;
+ int nEntCount = UTIL_EntitiesInSphere( ppEnts, 256, vecCenter, flRadius, 0 );
+ CBaseEntity *ppCandidates[256];
+ int nCandidateCount = 0;
+ int i;
+ for ( i = 0; i < nEntCount; i++ )
+ {
+ if ( ppEnts[i] == NULL )
+ continue;
+
+ // Zap metal or flesh things.
+ if ( !IsValidZapTarget( ppEnts[i] ) )
+ continue;
+
+ ppCandidates[ nCandidateCount++ ] = ppEnts[i];
+ }
+
+ // First, put a bolt in front of the player, at random
+ float flDist = 1024;
+ if ( GetEnemy() )
+ {
+ Vector vecDelta;
+ Vector2DSubtract( GetEnemy()->WorldSpaceCenter().AsVector2D(), WorldSpaceCenter().AsVector2D(), vecDelta.AsVector2D() );
+ vecDelta.z = 0.0f;
+
+ flDist = VectorNormalize( vecDelta );
+ Vector vecPerp( -vecDelta.y, vecDelta.x, 0.0f );
+ int nBoltCount = (int)(ClampSplineRemapVal( flDist, 256.0f, 1024.0f, 8, 0 ) + 0.5f);
+
+ for ( i = 0; i < nBoltCount; ++i )
+ {
+ Vector vecTargetPt = GetEnemy()->WorldSpaceCenter();
+ VectorMA( vecTargetPt, random->RandomFloat( flDist + 100, flDist + 500 ), vecDelta, vecTargetPt );
+ VectorMA( vecTargetPt, random->RandomFloat( -500, 500 ), vecPerp, vecTargetPt );
+ vecTargetPt.z += random->RandomFloat( -500, 500 );
+ CreateZapBeam( vecTargetPt );
+ }
+ }
+
+ // Next, choose the number of bolts...
+ int nBoltCount = random->RandomInt( 8, 16 );
+ for ( i = 0; i < nBoltCount; ++i )
+ {
+ if ( (nCandidateCount > 0) && random->RandomFloat( 0.0f, 1.0f ) < 0.6f )
+ {
+ --nCandidateCount;
+
+ Vector vecTarget;
+ ppCandidates[nCandidateCount]->CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecTarget );
+ CreateZapBeam( vecTarget );
+ CreateEntityZapEffect( ppCandidates[nCandidateCount] );
+ }
+ else
+ {
+ // Select random point *on* sphere
+ Vector vecTargetPt;
+ float flEffectRadius = random->RandomFloat( flRadius * 1.2, flRadius * 1.5f );
+ float flTheta = random->RandomFloat( 0.0f, 2.0f * M_PI );
+ float flPhi = random->RandomFloat( -0.5f * M_PI, 0.5f * M_PI );
+ vecTargetPt.x = cos(flTheta) * cos(flPhi);
+ vecTargetPt.y = sin(flTheta) * cos(flPhi);
+ vecTargetPt.z = sin(flPhi);
+ vecTargetPt *= flEffectRadius;
+ vecTargetPt += vecCenter;
+
+ CreateZapBeam( vecTargetPt );
+ }
+ }
+
+ // Finally, put a bolt right at the player, at random
+ float flHitRatio = ClampSplineRemapVal( flDist, 128.0f, 512.0f, 0.75f, 0.0f );
+ if ( random->RandomFloat( 0.0f, 1.0f ) < flHitRatio )
+ {
+ if ( GetEnemyVehicle() )
+ {
+ Vector vecTarget;
+ GetEnemyVehicle()->CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecTarget );
+ CreateZapBeam( vecTarget );
+ CreateEntityZapEffect( GetEnemyVehicle() );
+
+ CTakeDamageInfo info( this, this, 5, DMG_SHOCK );
+ GetEnemy()->TakeDamage( info );
+ }
+ else if ( GetEnemy() )
+ {
+ Vector vecTarget;
+ GetEnemy()->CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecTarget );
+ CreateZapBeam( vecTarget );
+
+ CTakeDamageInfo info( this, this, 5, DMG_SHOCK );
+ GetEnemy()->TakeDamage( info );
+ }
+ }
+
+ m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 0.3f, 1.0f );
+}
+
+
+//------------------------------------------------------------------------------
+// Here's what we do when we *are* firing
+//------------------------------------------------------------------------------
+#define INTERVAL_BETWEEN_HITS 4
+
+bool CNPC_AttackHelicopter::DoGunFiring( const Vector &vBasePos, const Vector &vGunDir, const Vector &vecFireAtPosition )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ float flVolume = controller.SoundGetVolume( m_pGunFiringSound );
+ if ( flVolume != 1.0f )
+ {
+ controller.SoundChangeVolume( m_pGunFiringSound, 1.0, 0.01f );
+ }
+
+ if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && ( IsInSecondaryMode( BULLRUSH_MODE_SHOOT_GUN ) ) )
+ {
+ ShootAtFacingDirection( vBasePos, vGunDir, m_nRemainingBursts == 0 );
+ }
+ else if ( GetEnemyVehicle() )
+ {
+ ShootAtVehicle( vBasePos, vecFireAtPosition );
+ }
+ else if ( GetEnemy() && GetEnemy()->IsPlayer() )
+ {
+ if ( !IsDeadlyShooting() )
+ {
+ ShootAtPlayer( vBasePos, vGunDir );
+ }
+ else
+ {
+ ShootAtFacingDirection( vBasePos, vGunDir, true );
+ }
+ }
+ else
+ {
+ ShootAtFacingDirection( vBasePos, vGunDir, false );
+ }
+
+ if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
+ {
+ if ( --m_nRemainingBursts < 0 )
+ {
+ m_nRemainingBursts = INTERVAL_BETWEEN_HITS;
+ }
+ return true;
+ }
+
+ --m_nRemainingBursts;
+ if ( m_nRemainingBursts > 0 )
+ return true;
+
+ controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.01f );
+ float flIdleTime = CHOPPER_GUN_IDLE_TIME;
+ float flVariance = flIdleTime * 0.1f;
+ m_flNextAttack = gpGlobals->curtime + m_flIdleTimeDelay + random->RandomFloat(flIdleTime - flVariance, flIdleTime + flVariance);
+ m_nGunState = GUN_STATE_IDLE;
+ SetPauseState( PAUSE_NO_PAUSE );
+ return true;
+}
+
+
+//------------------------------------------------------------------------------
+// Is it "fair" to drop this bomb?
+//------------------------------------------------------------------------------
+#define MIN_BOMB_DISTANCE_SQR ( 600.0f * 600.0f )
+
+bool CNPC_AttackHelicopter::IsBombDropFair( const Vector &vecBombStartPos, const Vector &vecBombVelocity )
+{
+ if ( (m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE) && IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) )
+ return true;
+
+ // Can happen if you're noclipping around
+ if ( !GetEnemy() )
+ return false;
+
+ // If the player is moving slowly, it's fair
+ if ( GetEnemy()->GetSmoothedVelocity().LengthSqr() < ( CHOPPER_SLOW_BOMB_SPEED * CHOPPER_SLOW_BOMB_SPEED ) )
+ return true;
+
+ // Skip out if we're right above or behind the player.. that's unfair
+ if ( GetEnemy() && GetEnemy()->IsPlayer() )
+ {
+ // How much time will it take to fall?
+ // dx = 0.5 * a * t^2
+ Vector vecTarget = GetEnemy()->BodyTarget( GetAbsOrigin(), false );
+ float dz = vecBombStartPos.z - vecTarget.z;
+ float dt = (dz > 0.0f) ? sqrt( 2 * dz / GetCurrentGravity() ) : 0.0f;
+
+ // Where will the enemy be in that time?
+ Vector vecEnemyVel = GetEnemy()->GetSmoothedVelocity();
+ VectorMA( vecTarget, dt, vecEnemyVel, vecTarget );
+
+ // Where will the bomb be in that time?
+ Vector vecBomb;
+ VectorMA( vecBombStartPos, dt, vecBombVelocity, vecBomb );
+
+ float flEnemySpeed = vecEnemyVel.LengthSqr();
+ flEnemySpeed = clamp( flEnemySpeed, 200.0f, 500.0f );
+ float flDistFactorSq = RemapVal( flEnemySpeed, 200.0f, 500.0f, 0.3f, 1.0f );
+ flDistFactorSq *= flDistFactorSq;
+
+ // If it's too close, then we're not doing it.
+ if ( vecBomb.AsVector2D().DistToSqr( vecTarget.AsVector2D() ) < (flDistFactorSq * MIN_BOMB_DISTANCE_SQR) )
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create the bomb entity and set it up
+// Input : &vecPos - Position to spawn at
+// &vecVelocity - velocity to spawn with
+//-----------------------------------------------------------------------------
+CGrenadeHelicopter *CNPC_AttackHelicopter::SpawnBombEntity( const Vector &vecPos, const Vector &vecVelocity )
+{
+ // Create the grenade and set it up
+ CGrenadeHelicopter *pGrenade = static_cast<CGrenadeHelicopter*>(CreateEntityByName( "grenade_helicopter" ));
+ pGrenade->SetAbsOrigin( vecPos );
+ pGrenade->SetOwnerEntity( this );
+ pGrenade->SetThrower( this );
+ pGrenade->SetAbsVelocity( vecVelocity );
+ DispatchSpawn( pGrenade );
+ pGrenade->SetExplodeOnContact( m_bBombsExplodeOnContact );
+
+#ifdef HL2_EPISODIC
+ // Disable collisions with the owner's bone followers while we drop
+ physfollower_t *pFollower = m_BoneFollowerManager.GetBoneFollower( 0 );
+ if ( pFollower )
+ {
+ CBaseEntity *pBoneFollower = pFollower->hFollower;
+ PhysDisableEntityCollisions( pBoneFollower, pGrenade );
+ pGrenade->SetCollisionObject( pBoneFollower );
+ }
+#endif // HL2_EPISODIC
+
+ return pGrenade;
+}
+
+//------------------------------------------------------------------------------
+// Actually drops the bomb
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::CreateBomb( bool bCheckForFairness, Vector *pVecVelocity, bool bMegaBomb )
+{
+ if ( m_bBombingSuppressed )
+ return;
+
+ Vector vTipPos;
+ GetAttachment( m_nBombAttachment, vTipPos );
+
+ if ( !CBombSuppressor::CanBomb( vTipPos ) )
+ return;
+
+ // Compute velocity
+ Vector vecActualVelocity;
+ if ( !pVecVelocity )
+ {
+ Vector vecAcross;
+ vecActualVelocity = GetAbsVelocity();
+ CrossProduct( vecActualVelocity, Vector( 0, 0, 1 ), vecAcross );
+ VectorNormalize( vecAcross );
+ vecAcross *= random->RandomFloat( 10.0f, 30.0f );
+ vecAcross *= random->RandomFloat( 0.0f, 1.0f ) < 0.5f ? 1.0f : -1.0f;
+
+ // Blat out z component of velocity if it's moving upward....
+ if ( vecActualVelocity.z > 0 )
+ {
+ vecActualVelocity.z = 0.0f;
+ }
+
+ vecActualVelocity += vecAcross;
+ }
+ else
+ {
+ vecActualVelocity = *pVecVelocity;
+ }
+
+ if ( bCheckForFairness )
+ {
+ if ( !IsBombDropFair( vTipPos, vecActualVelocity ) )
+ return;
+ }
+
+ AddGesture( (Activity)ACT_HELICOPTER_DROP_BOMB );
+ EmitSound( "NPC_AttackHelicopter.DropMine" );
+
+ // Make the bomb and send it off
+ CGrenadeHelicopter *pGrenade = SpawnBombEntity( vTipPos, vecActualVelocity );
+ if ( pGrenade && bMegaBomb )
+ {
+ pGrenade->AddSpawnFlags( SF_GRENADE_HELICOPTER_MEGABOMB );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Drop a bomb at a particular location
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputDropBomb( inputdata_t &inputdata )
+{
+ if ( m_flInputDropBombTime > gpGlobals->curtime )
+ return;
+
+ // Prevent two triggers from being hit the same frame
+ m_flInputDropBombTime = gpGlobals->curtime + 0.01f;
+
+ CreateBomb( );
+
+ // If we're in the middle of a bomb dropping schedule, wait to drop another bomb.
+ if ( ShouldDropBombs() )
+ {
+ m_flNextAttack = gpGlobals->curtime + 0.5f + random->RandomFloat( 0.3f, 0.6f );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Drops a bomb straight downwards
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputDropBombStraightDown( inputdata_t &inputdata )
+{
+ if ( m_flInputDropBombTime > gpGlobals->curtime )
+ return;
+
+ // Prevent two triggers from being hit the same frame
+ m_flInputDropBombTime = gpGlobals->curtime + 0.01f;
+
+ Vector vTipPos;
+ GetAttachment( m_nBombAttachment, vTipPos );
+
+ // Make the bomb drop straight down
+ SpawnBombEntity( vTipPos, vec3_origin );
+
+ // If we're in the middle of a bomb dropping schedule, wait to drop another bomb.
+ if ( ShouldDropBombs() )
+ {
+ m_flNextAttack = gpGlobals->curtime + 0.5f + random->RandomFloat( 0.3f, 0.6f );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Drop a bomb at a particular location
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputDropBombAtTargetInternal( inputdata_t &inputdata, bool bCheckFairness )
+{
+ if ( m_flInputDropBombTime > gpGlobals->curtime )
+ return;
+
+ // Prevent two triggers from being hit the same frame
+ m_flInputDropBombTime = gpGlobals->curtime + 0.01f;
+
+ // Find our specified target
+ string_t strBombTarget = MAKE_STRING( inputdata.value.String() );
+ CBaseEntity *pBombEnt = gEntList.FindEntityByName( NULL, strBombTarget );
+ if ( pBombEnt == NULL )
+ {
+ Warning( "%s: Could not find bomb drop target '%s'!\n", GetClassname(), STRING( strBombTarget ) );
+ return;
+ }
+
+ Vector vTipPos;
+ GetAttachment( m_nBombAttachment, vTipPos );
+
+ // Compute the time it would take to fall to the target
+ Vector vecTarget = pBombEnt->BodyTarget( GetAbsOrigin(), false );
+ float dz = vTipPos.z - vecTarget.z;
+ if ( dz <= 0.0f )
+ {
+ Warning("Bomb target %s is above the chopper!\n", STRING( strBombTarget ) );
+ return;
+ }
+ float dt = sqrt( 2 * dz / GetCurrentGravity() );
+
+ // Compute the velocity that would make it happen
+ Vector vecVelocity;
+ VectorSubtract( vecTarget, vTipPos, vecVelocity );
+ vecVelocity /= dt;
+ vecVelocity.z = 0.0f;
+
+ if ( bCheckFairness )
+ {
+ if ( !IsBombDropFair( vTipPos, vecVelocity ) )
+ return;
+ }
+
+ // Make the bomb and send it off
+ SpawnBombEntity( vTipPos, vecVelocity );
+
+ // If we're in the middle of a bomb dropping schedule, wait to drop another bomb.
+ if ( ShouldDropBombs() )
+ {
+ m_flNextAttack = gpGlobals->curtime + 1.5f + random->RandomFloat( 0.1f, 0.2f );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Drop a bomb at a particular location
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputDropBombAtTargetAlways( inputdata_t &inputdata )
+{
+ InputDropBombAtTargetInternal( inputdata, false );
+}
+
+
+//------------------------------------------------------------------------------
+// Drop a bomb at a particular location
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputDropBombAtTarget( inputdata_t &inputdata )
+{
+ InputDropBombAtTargetInternal( inputdata, true );
+}
+
+
+//------------------------------------------------------------------------------
+// Drop a bomb at a particular location
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputDropBombDelay( inputdata_t &inputdata )
+{
+ m_flInputDropBombTime = gpGlobals->curtime + inputdata.value.Float();
+
+ if ( ShouldDropBombs() )
+ {
+ m_flNextAttack = m_flInputDropBombTime;
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Drop those bombs!
+//------------------------------------------------------------------------------
+#define MAX_BULLRUSH_BOMB_DISTANCE_SQR ( 3072.0f * 3072.0f )
+
+void CNPC_AttackHelicopter::DropBombs( )
+{
+ // Can't continually fire....
+ if (m_flNextAttack > gpGlobals->curtime)
+ return;
+
+ // Otherwise, behave as normal.
+ if ( m_nAttackMode != ATTACK_MODE_BULLRUSH_VEHICLE )
+ {
+ if ( GetEnemy() && GetEnemy()->IsPlayer() )
+ {
+ if ( GetEnemy()->GetSmoothedVelocity().LengthSqr() > ( CHOPPER_SLOW_BOMB_SPEED * CHOPPER_SLOW_BOMB_SPEED ) )
+ {
+ // Don't drop bombs if you are behind the player, unless the player is moving slowly
+ float flLeadingDistSq = GetLeadingDistance() * 0.75f;
+ flLeadingDistSq *= flLeadingDistSq;
+
+ Vector vecPoint;
+ ClosestPointToCurrentPath( &vecPoint );
+ if ( vecPoint.AsVector2D().DistToSqr( GetDesiredPosition().AsVector2D() ) > flLeadingDistSq )
+ return;
+ }
+ }
+ }
+ else
+ {
+ // Skip out if we're bullrushing but too far from the player
+ if ( GetEnemy() )
+ {
+ if ( GetEnemy()->GetAbsOrigin().AsVector2D().DistToSqr( GetAbsOrigin().AsVector2D() ) > MAX_BULLRUSH_BOMB_DISTANCE_SQR )
+ return;
+ }
+ }
+
+ CreateBomb( );
+
+ m_flNextAttack = gpGlobals->curtime + 0.5f + random->RandomFloat( 0.3f, 0.6f );
+
+ if ( (m_nAttackMode != ATTACK_MODE_BULLRUSH_VEHICLE) )
+ {
+ if ( --m_nGrenadeCount <= 0 )
+ {
+ m_nGrenadeCount = CHOPPER_BOMB_DROP_COUNT;
+ m_flNextAttack += random->RandomFloat( 1.5f, 3.0f );
+ }
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Should we drop those bombs?
+//------------------------------------------------------------------------------
+#define BOMB_GRACE_PERIOD 1.5f
+#define BOMB_MIN_SPEED 150.0
+
+bool CNPC_AttackHelicopter::ShouldDropBombs( void )
+{
+ if ( IsCarpetBombing() )
+ return true;
+
+ if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
+ {
+ // Distance determines whether or not we should do this
+ if ((m_nSecondaryMode == BULLRUSH_MODE_SHOOT_IDLE_PLAYER) && (SecondaryModeTime() >= BULLRUSH_IDLE_PLAYER_FIRE_TIME))
+ return ShouldBombIdlePlayer();
+
+ return (( m_nSecondaryMode == BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED ) || ( m_nSecondaryMode == BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER ));
+ }
+
+ if (!IsLeading() || !GetEnemyVehicle())
+ return false;
+
+ if (( m_nAttackMode != ATTACK_MODE_BOMB_VEHICLE ) && ( m_nAttackMode != ATTACK_MODE_ALWAYS_LEAD_VEHICLE ))
+ return false;
+
+ if ( m_nGunState != GUN_STATE_IDLE )
+ return false;
+
+ // This is for bombing. If you get hit, give a grace period to get back to speed
+ float flSpeedSqr = GetEnemy()->GetSmoothedVelocity().LengthSqr();
+ if ( flSpeedSqr >= BOMB_MIN_SPEED * BOMB_MIN_SPEED )
+ {
+ m_flLastFastTime = gpGlobals->curtime;
+ }
+ else
+ {
+ if ( ( gpGlobals->curtime - m_flLastFastTime ) < BOMB_GRACE_PERIOD )
+ return false;
+ }
+
+ float flSpeedAlongPath = TargetSpeedAlongPath();
+ if ( m_nAttackMode == ATTACK_MODE_BOMB_VEHICLE )
+ return ( flSpeedAlongPath > -BOMB_MIN_SPEED );
+
+ // This is for ALWAYS_LEAD
+ if ( fabs(flSpeedAlongPath) < 50.0f )
+ return false;
+
+ float flLeadingDist = ComputeDistanceToLeadingPosition( );
+ flLeadingDist = GetLeadingDistance() - flLeadingDist;
+ if ( flSpeedAlongPath < 0.0f )
+ {
+ return flLeadingDist < 300.0f;
+ }
+ else
+ {
+ return flLeadingDist > -300.0f;
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Different bomb-dropping behavior
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::BullrushBombs( )
+{
+ if ( gpGlobals->curtime < m_flNextBullrushBombTime )
+ return;
+
+ if ( m_nBullrushBombMode & 0x1 )
+ {
+ CreateBomb( false, NULL, true );
+ }
+ else
+ {
+ Vector vecAcross;
+ Vector vecVelocity = GetAbsVelocity();
+ CrossProduct( vecVelocity, Vector( 0, 0, 1 ), vecAcross );
+ VectorNormalize( vecAcross );
+ vecAcross *= random->RandomFloat( 300.0f, 500.0f );
+
+ // Blat out z component of velocity if it's moving upward....
+ if ( vecVelocity.z > 0 )
+ {
+ vecVelocity.z = 0.0f;
+ }
+ vecVelocity += vecAcross;
+ CreateBomb( false, &vecVelocity, true );
+
+ VectorMA( vecVelocity, -2.0f, vecAcross, vecVelocity );
+ CreateBomb( false, &vecVelocity, true );
+ }
+
+ m_nBullrushBombMode = !m_nBullrushBombMode;
+ m_flNextBullrushBombTime = gpGlobals->curtime + 0.2f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Turn the gun off
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InputGunOff( inputdata_t &inputdata )
+{
+ BaseClass::InputGunOff( inputdata );
+
+ if ( m_pGunFiringSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.01f );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Fire that gun baby!
+//------------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::FireGun( void )
+{
+ // Do the test electricity gun
+ if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
+ {
+ FireElectricityGun( );
+ return true;
+ }
+
+ // HACK: CBaseHelicopter ignores this, and fire forever at the last place it saw the player. Why?
+ if (( m_nGunState == GUN_STATE_IDLE ) && ( m_nAttackMode != ATTACK_MODE_BULLRUSH_VEHICLE ) && !IsCarpetBombing() )
+ {
+ if ( (m_flLastSeen + 1 <= gpGlobals->curtime) || (m_flPrevSeen + m_flGracePeriod > gpGlobals->curtime) )
+ return false;
+ }
+
+ if ( IsCarpetBombing() )
+ {
+ BullrushBombs();
+ return false;
+ }
+
+ if ( ShouldDropBombs() )
+ {
+ DropBombs( );
+ return false;
+ }
+
+ // Drop those bullrush bombs when shooting...
+ if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
+ {
+ if ( IsInSecondaryMode( BULLRUSH_MODE_MEGA_BOMB ) )
+ {
+ BullrushBombs( );
+ return false;
+ }
+
+ // Don't fire if we're bullrushing and we're getting distance
+ if ( !IsInSecondaryMode( BULLRUSH_MODE_SHOOT_GUN ) && !IsInSecondaryMode(BULLRUSH_MODE_SHOOT_IDLE_PLAYER) )
+ return false;
+
+ // If we're in the grace period on this mode, then don't fire
+ if ( IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) && (SecondaryModeTime() < BULLRUSH_IDLE_PLAYER_FIRE_TIME) )
+ {
+ // Stop our gun sound
+ if ( m_nGunState != GUN_STATE_IDLE )
+ {
+ ShutdownGunDuringBullrush();
+ }
+
+ return false;
+ }
+ }
+
+ // Get gun attachment points
+ Vector vBasePos;
+ GetAttachment( m_nGunBaseAttachment, vBasePos );
+
+ // Aim perfectly while idle, but after charging, the gun don't move so fast.
+ Vector vecFireAtPosition;
+ if ( !GetEnemyVehicle() || (m_nGunState == GUN_STATE_IDLE) )
+ {
+ ComputeFireAtPosition( &vecFireAtPosition );
+ }
+ else
+ {
+ ComputeVehicleFireAtPosition( &vecFireAtPosition );
+ }
+
+ Vector vTargetDir = vecFireAtPosition - vBasePos;
+ VectorNormalize( vTargetDir );
+
+ // Makes the model of the gun point to where we're aiming.
+ if ( !PoseGunTowardTargetDirection( vTargetDir ) )
+ return false;
+
+ // Are we charging?
+ if ( m_nGunState == GUN_STATE_CHARGING )
+ {
+ if ( !DoGunCharging( ) )
+ return false;
+ }
+
+ Vector vTipPos;
+ GetAttachment( m_nGunTipAttachment, vTipPos );
+
+ Vector vGunDir = vTipPos - vBasePos;
+ VectorNormalize( vGunDir );
+
+ // Are we firing?
+ if ( m_nGunState == GUN_STATE_FIRING )
+ {
+ return DoGunFiring( vTipPos, vGunDir, vecFireAtPosition );
+ }
+
+ return DoGunIdle( vGunDir, vTargetDir );
+}
+
+
+//-----------------------------------------------------------------------------
+// Should we trigger a damage effect?
+//-----------------------------------------------------------------------------
+inline bool CNPC_AttackHelicopter::ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const
+{
+ int nPrevRange = (int)( ((float)nPrevHealth / (float)GetMaxHealth()) * nEffectCount );
+ int nRange = (int)( ((float)GetHealth() / (float)GetMaxHealth()) * nEffectCount );
+ return ( nRange != nPrevRange );
+}
+
+
+//-----------------------------------------------------------------------------
+// Add a smoke trail since we've taken more damage
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::AddSmokeTrail( const Vector &vecPos )
+{
+ if ( m_nSmokeTrailCount == MAX_SMOKE_TRAILS )
+ return;
+
+ // See if there's an attachment for this smoke trail
+ int nAttachment = LookupAttachment( UTIL_VarArgs( "damage%d", m_nSmokeTrailCount ) );
+
+ if ( nAttachment == 0 )
+ return;
+
+ // The final smoke trail is a flaming engine
+ if ( m_nSmokeTrailCount == 0 || m_nSmokeTrailCount % 2 )
+ {
+ CFireTrail *pFireTrail = CFireTrail::CreateFireTrail();
+
+ if ( pFireTrail == NULL )
+ return;
+
+ m_hSmokeTrail[m_nSmokeTrailCount] = pFireTrail;
+
+ pFireTrail->FollowEntity( this, UTIL_VarArgs( "damage%d", m_nSmokeTrailCount ) );
+ pFireTrail->SetParent( this, nAttachment );
+ pFireTrail->SetLocalOrigin( vec3_origin );
+ pFireTrail->SetMoveType( MOVETYPE_NONE );
+ pFireTrail->SetLifetime( -1 );
+ }
+ else
+ {
+ SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
+ if( !pSmokeTrail )
+ return;
+
+ m_hSmokeTrail[m_nSmokeTrailCount] = pSmokeTrail;
+
+ pSmokeTrail->m_SpawnRate = 48;
+ pSmokeTrail->m_ParticleLifetime = 0.5f;
+ pSmokeTrail->m_StartColor.Init(0.15, 0.15, 0.15);
+ pSmokeTrail->m_EndColor.Init(0.0, 0.0, 0.0);
+ pSmokeTrail->m_StartSize = 24;
+ pSmokeTrail->m_EndSize = 80;
+ pSmokeTrail->m_SpawnRadius = 8;
+ pSmokeTrail->m_Opacity = 0.2;
+ pSmokeTrail->m_MinSpeed = 16;
+ pSmokeTrail->m_MaxSpeed = 64;
+ pSmokeTrail->SetLifetime(-1);
+ pSmokeTrail->SetParent( this, nAttachment );
+ pSmokeTrail->SetLocalOrigin( vec3_origin );
+ pSmokeTrail->SetMoveType( MOVETYPE_NONE );
+ }
+
+ m_nSmokeTrailCount++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Destroy all smoke trails
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::DestroySmokeTrails()
+{
+ for ( int i = m_nSmokeTrailCount; --i >= 0; )
+ {
+ UTIL_Remove( m_hSmokeTrail[i] );
+ m_hSmokeTrail[i] = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &vecChunkPos -
+//-----------------------------------------------------------------------------
+void Chopper_CreateChunk( CBaseEntity *pChopper, const Vector &vecChunkPos, const QAngle &vecChunkAngles, const char *pszChunkName, bool bSmall )
+{
+ // Drop a flaming, smoking chunk.
+ CGib *pChunk = CREATE_ENTITY( CGib, "gib" );
+ pChunk->Spawn( pszChunkName );
+ pChunk->SetBloodColor( DONT_BLEED );
+
+ pChunk->SetAbsOrigin( vecChunkPos );
+ pChunk->SetAbsAngles( vecChunkAngles );
+
+ pChunk->SetOwnerEntity( pChopper );
+
+ if ( bSmall )
+ {
+ pChunk->m_lifeTime = random->RandomFloat( 0.5f, 1.0f );
+ pChunk->SetSolidFlags( FSOLID_NOT_SOLID );
+ pChunk->SetSolid( SOLID_BBOX );
+ pChunk->AddEffects( EF_NODRAW );
+ pChunk->SetGravity( UTIL_ScaleForGravity( 400 ) );
+ }
+ else
+ {
+ pChunk->m_lifeTime = 5.0f;
+ }
+
+ pChunk->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+
+ // Set the velocity
+ Vector vecVelocity;
+ AngularImpulse angImpulse;
+
+ QAngle angles;
+ angles.x = random->RandomFloat( -70, 20 );
+ angles.y = random->RandomFloat( 0, 360 );
+ angles.z = 0.0f;
+ AngleVectors( angles, &vecVelocity );
+
+ vecVelocity *= random->RandomFloat( 550, 800 );
+ vecVelocity += pChopper->GetAbsVelocity();
+
+ angImpulse = RandomAngularImpulse( -180, 180 );
+
+ pChunk->SetAbsVelocity( vecVelocity );
+
+ if ( bSmall == false )
+ {
+ IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
+
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->EnableMotion( true );
+ pPhysicsObject->SetVelocity(&vecVelocity, &angImpulse );
+ }
+ }
+
+ CFireTrail *pFireTrail = CFireTrail::CreateFireTrail();
+
+ if ( pFireTrail == NULL )
+ return;
+
+ pFireTrail->FollowEntity( pChunk, "" );
+ pFireTrail->SetParent( pChunk, 0 );
+ pFireTrail->SetLocalOrigin( vec3_origin );
+ pFireTrail->SetMoveType( MOVETYPE_NONE );
+ pFireTrail->SetLifetime( pChunk->m_lifeTime );
+}
+
+//------------------------------------------------------------------------------
+// Pow!
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::ExplodeAndThrowChunk( const Vector &vecExplosionPos )
+{
+ CEffectData data;
+ data.m_vOrigin = vecExplosionPos;
+ DispatchEffect( "HelicopterMegaBomb", data );
+
+ EmitSound( "BaseExplosionEffect.Sound" );
+
+ UTIL_ScreenShake( vecExplosionPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
+
+ if(GetCrashPoint() != NULL)
+ {
+ // Make it clear that I'm done for.
+ ExplosionCreate( vecExplosionPos, QAngle(0,0,1), this, 100, 128, false );
+ }
+
+ if ( random->RandomInt( 0, 4 ) )
+ {
+ for ( int i = 0; i < 2; i++ )
+ {
+ Chopper_CreateChunk( this, vecExplosionPos, RandomAngle(0, 360), g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ), true );
+ }
+ }
+ else
+ {
+ Chopper_CreateChunk( this, vecExplosionPos, RandomAngle(0, 360), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_SMALL_CHUNKS - 1 )], false );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Drop a corpse!
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::DropCorpse( int nDamage )
+{
+ // Don't drop another corpse if the next guy's not out on the gun yet
+ if ( m_flLastCorpseFall > gpGlobals->curtime )
+ return;
+
+ // Clamp damage to prevent ridiculous ragdoll velocity
+ if( nDamage > 250.0f )
+ nDamage = 250.0f;
+
+ m_flLastCorpseFall = gpGlobals->curtime + 3.0;
+
+ // Spawn a ragdoll combine guard
+ float forceScale = nDamage * 75 * 4;
+ Vector vecForceVector = RandomVector(-1,1);
+ vecForceVector.z = 0.5;
+ vecForceVector *= forceScale;
+
+ CBaseEntity *pGib = CreateRagGib( "models/combine_soldier.mdl", GetAbsOrigin(), GetAbsAngles(), vecForceVector );
+ if ( pGib )
+ {
+ pGib->SetOwnerEntity( this );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ // Take no damage from trace attacks unless it's blast damage. RadiusDamage() sometimes calls
+ // TraceAttack() as a means for delivering blast damage. Usually when the explosive penetrates
+ // the target. (RPG missiles do this sometimes).
+ if ( ( info.GetDamageType() & DMG_AIRBOAT ) ||
+ ( info.GetInflictor()->Classify() == CLASS_MISSILE ) ||
+ ( info.GetAttacker()->Classify() == CLASS_MISSILE ) )
+ {
+ BaseClass::BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_AttackHelicopter::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ // We don't take blast damage from anything but the airboat or missiles (or myself!)
+ if( info.GetInflictor() != this )
+ {
+ if ( ( ( info.GetDamageType() & DMG_AIRBOAT ) == 0 ) &&
+ ( info.GetInflictor()->Classify() != CLASS_MISSILE ) &&
+ ( info.GetAttacker()->Classify() != CLASS_MISSILE ) )
+ return 0;
+ }
+
+ if ( m_bIndestructible )
+ {
+ if ( GetHealth() < info.GetDamage() )
+ return 0;
+ }
+
+ // helicopter takes extra damage from its own grenades
+ CGrenadeHelicopter *pGren = dynamic_cast<CGrenadeHelicopter *>(info.GetInflictor());
+ if ( pGren && info.GetAttacker() && info.GetAttacker()->IsPlayer() )
+ {
+ CTakeDamageInfo fudgedInfo = info;
+
+ float damage;
+ if( g_pGameRules->IsSkillLevel(SKILL_EASY) )
+ {
+ damage = GetMaxHealth() / sk_helicopter_num_bombs1.GetFloat();
+ }
+ else if( g_pGameRules->IsSkillLevel(SKILL_HARD) )
+ {
+ damage = GetMaxHealth() / sk_helicopter_num_bombs3.GetFloat();
+ }
+ else // Medium, or unspecified
+ {
+ damage = GetMaxHealth() / sk_helicopter_num_bombs2.GetFloat();
+ }
+ damage = ceilf( damage );
+ fudgedInfo.SetDamage( damage );
+ fudgedInfo.SetMaxDamage( damage );
+
+ return BaseClass::OnTakeDamage( fudgedInfo );
+ }
+
+ return BaseClass::OnTakeDamage( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Take damage from trace attacks if they hit the gunner
+//-----------------------------------------------------------------------------
+int CNPC_AttackHelicopter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ int nPrevHealth = GetHealth();
+
+ if ( ( info.GetInflictor() != NULL ) && ( info.GetInflictor()->GetOwnerEntity() != NULL ) && ( info.GetInflictor()->GetOwnerEntity() == this ) )
+ {
+ // Don't take damage from my own bombs. (Unless the player grabbed them and threw them back)
+ return 0;
+ }
+
+ // Chain
+ int nRetVal = BaseClass::OnTakeDamage_Alive( info );
+
+ if( info.GetDamageType() & DMG_BLAST )
+ {
+ // Apply a force push that makes us look like we're reacting to the damage
+ Vector damageDir = info.GetDamageForce();
+ VectorNormalize( damageDir );
+ ApplyAbsVelocityImpulse( damageDir * 500.0f );
+
+ // Knock the helicopter off of the level, too.
+ Vector vecRight, vecForce;
+ float flDot;
+ GetVectors( NULL, &vecRight, NULL );
+ vecForce = info.GetDamageForce();
+ VectorNormalize( vecForce );
+
+ flDot = DotProduct( vecForce, vecRight );
+
+ m_flGoalRollDmg = random->RandomFloat( 10, 30 );
+
+ if( flDot <= 0.0f )
+ {
+ // Missile hit the right side.
+ m_flGoalRollDmg *= -1;
+ }
+ }
+
+ // Spawn damage effects
+ if ( nPrevHealth != GetHealth() )
+ {
+ // Give the badly damaged call to say we're going to mega bomb soon
+ if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
+ {
+ if (( nPrevHealth > m_flNextMegaBombHealth ) && (GetHealth() <= m_flNextMegaBombHealth) )
+ {
+ EmitSound( "NPC_AttackHelicopter.BadlyDamagedAlert" );
+ }
+ }
+
+ if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_SMOKE_TRAILS ) )
+ {
+ AddSmokeTrail( info.GetDamagePosition() );
+ }
+
+ if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_CORPSES ) )
+ {
+ if ( nPrevHealth != GetMaxHealth() )
+ {
+ DropCorpse( info.GetDamage() );
+ }
+ }
+
+ if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) )
+ {
+ ExplodeAndThrowChunk( info.GetDamagePosition() );
+ }
+
+ int nPrevPercent = (int)(100.0f * nPrevHealth / GetMaxHealth());
+ int nCurrPercent = (int)(100.0f * GetHealth() / GetMaxHealth());
+ if (( (nPrevPercent + 9) / 10 ) != ( (nCurrPercent + 9) / 10 ))
+ {
+ m_OnHealthChanged.Set( nCurrPercent, this, this );
+ }
+ }
+
+ return nRetVal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Chopper_BecomeChunks( CBaseEntity *pChopper )
+{
+ QAngle vecChunkAngles = pChopper->GetAbsAngles();
+ Vector vecForward, vecUp;
+ pChopper->GetVectors( &vecForward, NULL, &vecUp );
+
+#ifdef HL2_EPISODIC
+ CNPC_AttackHelicopter *pAttackHelicopter;
+ pAttackHelicopter = dynamic_cast<CNPC_AttackHelicopter*>(pChopper);
+ if( pAttackHelicopter != NULL )
+ {
+ // New for EP2, we may be tailspinning, (crashing) and playing an animation that is spinning
+ // our root bone, which means our model is not facing the way our entity is facing. So we have
+ // to do some attachment point math to get the proper angles to use for computing the relative
+ // positions of the gibs. The attachment points called DAMAGE0 is properly oriented and attached
+ // to the chopper body so we can use its angles.
+ int iAttach = pAttackHelicopter->LookupAttachment( "damage0" );
+ Vector vecAttachPos;
+
+ if( iAttach > -1 )
+ {
+ pAttackHelicopter->GetAttachment(iAttach, vecAttachPos, vecChunkAngles );
+ AngleVectors( vecChunkAngles, &vecForward, NULL, &vecUp );
+ }
+ }
+#endif//HL2_EPISODIC
+
+
+ Vector vecChunkPos = pChopper->GetAbsOrigin();
+
+ Vector vecRight(0,0,0);
+
+ if( hl2_episodic.GetBool() )
+ {
+ // We need to get a right hand vector to toss the cockpit and tail pieces
+ // so their motion looks like a continuation of the tailspin animation
+ // that the chopper plays before crashing.
+ pChopper->GetVectors( NULL, &vecRight, NULL );
+ }
+
+ // Body
+ CHelicopterChunk *pBodyChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, pChopper->GetAbsVelocity(), HELICOPTER_CHUNK_BODY, CHUNK_BODY );
+ Chopper_CreateChunk( pChopper, vecChunkPos, RandomAngle( 0, 360 ), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_CHUNKS - 1 )], false );
+
+ vecChunkPos = pChopper->GetAbsOrigin() + ( vecForward * 100.0f ) + ( vecUp * -38.0f );
+
+ // Cockpit
+ CHelicopterChunk *pCockpitChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, pChopper->GetAbsVelocity() + vecRight * -800.0f, HELICOPTER_CHUNK_COCKPIT, CHUNK_COCKPIT );
+ Chopper_CreateChunk( pChopper, vecChunkPos, RandomAngle( 0, 360 ), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_CHUNKS - 1 )], false );
+
+ pCockpitChunk->m_hMaster = pBodyChunk;
+
+ vecChunkPos = pChopper->GetAbsOrigin() + ( vecForward * -175.0f );
+
+ // Tail
+ CHelicopterChunk *pTailChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, pChopper->GetAbsVelocity() + vecRight * 800.0f, HELICOPTER_CHUNK_TAIL, CHUNK_TAIL );
+ Chopper_CreateChunk( pChopper, vecChunkPos, RandomAngle( 0, 360 ), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_CHUNKS - 1 )], false );
+
+ pTailChunk->m_hMaster = pBodyChunk;
+
+ // Constrain all the pieces together loosely
+ IPhysicsObject *pBodyObject = pBodyChunk->VPhysicsGetObject();
+ Assert( pBodyObject );
+
+ IPhysicsObject *pCockpitObject = pCockpitChunk->VPhysicsGetObject();
+ Assert( pCockpitObject );
+
+ IPhysicsObject *pTailObject = pTailChunk->VPhysicsGetObject();
+ Assert( pTailObject );
+
+ IPhysicsConstraintGroup *pGroup = NULL;
+
+ // Create the constraint
+ constraint_fixedparams_t fixed;
+ fixed.Defaults();
+ fixed.InitWithCurrentObjectState( pBodyObject, pTailObject );
+ fixed.constraint.Defaults();
+
+ pBodyChunk->m_pTailConstraint = physenv->CreateFixedConstraint( pBodyObject, pTailObject, pGroup, fixed );
+
+ fixed.Defaults();
+ fixed.InitWithCurrentObjectState( pBodyObject, pCockpitObject );
+ fixed.constraint.Defaults();
+
+ pBodyChunk->m_pCockpitConstraint = physenv->CreateFixedConstraint( pBodyObject, pCockpitObject, pGroup, fixed );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start us crashing
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::Event_Killed( const CTakeDamageInfo &info )
+{
+ if( m_lifeState == LIFE_ALIVE )
+ {
+ m_OnShotDown.FireOutput( this, this );
+ }
+
+ m_lifeState = LIFE_DYING;
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.1f );
+
+ if( GetCrashPoint() == NULL )
+ {
+ CBaseEntity *pCrashPoint = gEntList.FindEntityByClassname( NULL, "info_target_helicopter_crash" );
+ if( pCrashPoint != NULL )
+ {
+ m_hCrashPoint.Set( pCrashPoint );
+ SetDesiredPosition( pCrashPoint->GetAbsOrigin() );
+
+ // Start the failing engine sound
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundDestroy( m_pRotorSound );
+
+ CPASAttenuationFilter filter( this );
+ m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.EngineFailure" );
+ controller.Play( m_pRotorSound, 1.0, 100 );
+
+ // Tailspin!!
+ SetActivity( ACT_HELICOPTER_CRASHING );
+
+ // Intentionally returning with m_lifeState set to LIFE_DYING
+ return;
+ }
+ }
+
+ Chopper_BecomeChunks( this );
+ StopLoopingSounds();
+
+ m_lifeState = LIFE_DEAD;
+
+ EmitSound( "NPC_CombineGunship.Explode" );
+
+ SetThink( &CNPC_AttackHelicopter::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ AddEffects( EF_NODRAW );
+
+ // Makes the slower rotors fade back in
+ SetStartupTime( gpGlobals->curtime + 99.0f );
+
+ m_iHealth = 0;
+ m_takedamage = DAMAGE_NO;
+
+ m_OnDeath.FireOutput( info.GetAttacker(), this );
+}
+
+//------------------------------------------------------------------------------
+// Creates the breakable husk of an attack chopper
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::CreateChopperHusk()
+{
+ // We're embedded into the ground
+ CBaseEntity *pCorpse = CreateEntityByName( "prop_physics" );
+ pCorpse->SetAbsOrigin( GetAbsOrigin() );
+ pCorpse->SetAbsAngles( GetAbsAngles() );
+ pCorpse->SetModel( CHOPPER_MODEL_CORPSE_NAME );
+ pCorpse->AddSpawnFlags( SF_PHYSPROP_MOTIONDISABLED );
+ pCorpse->Spawn();
+ pCorpse->SetMoveType( MOVETYPE_NONE );
+}
+
+//-----------------------------------------------------------------------------
+// Think!
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::PrescheduleThink( void )
+{
+ if ( m_flGoalRollDmg != 0.0f )
+ {
+ m_flGoalRollDmg = UTIL_Approach( 0, m_flGoalRollDmg, 2.0f );
+ }
+
+ switch( m_lifeState )
+ {
+ case LIFE_DYING:
+ {
+ if( GetCrashPoint() != NULL )
+ {
+ // Stay on this, no matter what.
+ SetDesiredPosition( GetCrashPoint()->WorldSpaceCenter() );
+ }
+
+ if ( random->RandomInt( 0, 4 ) == 0 )
+ {
+ Vector explodePoint;
+ CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );
+
+ ExplodeAndThrowChunk( explodePoint );
+ }
+ }
+ break;
+ }
+
+ BaseClass::PrescheduleThink();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CNPC_AttackHelicopter::UpdatePerpPathDistance( float flMaxPathOffset )
+{
+ if ( !IsLeading() || !GetEnemy() )
+ {
+ m_flCurrPathOffset = 0.0f;
+ return 0.0f;
+ }
+
+ float flNewPathOffset = TargetDistanceToPath();
+
+ // Make bomb dropping more interesting
+ if ( ShouldDropBombs() )
+ {
+ float flSpeedAlongPath = TargetSpeedAlongPath();
+
+ if ( flSpeedAlongPath > 10.0f )
+ {
+ float flLeadTime = GetLeadingDistance() / flSpeedAlongPath;
+ flLeadTime = clamp( flLeadTime, 0.0f, 2.0f );
+ flNewPathOffset += 0.25 * flLeadTime * TargetSpeedAcrossPath();
+ }
+
+ flSpeedAlongPath = clamp( flSpeedAlongPath, 100.0f, 500.0f );
+ float flSinHeight = SimpleSplineRemapVal( flSpeedAlongPath, 100.0f, 500.0f, 0.0f, 200.0f );
+ flNewPathOffset += flSinHeight * sin( 2.0f * M_PI * (gpGlobals->curtime / 6.0f) );
+ }
+
+ if ( (flMaxPathOffset != 0.0f) && (flNewPathOffset > flMaxPathOffset) )
+ {
+ flNewPathOffset = flMaxPathOffset;
+ }
+
+ float flMaxChange = 1000.0f * (gpGlobals->curtime - GetLastThink());
+ if ( fabs( flNewPathOffset - m_flCurrPathOffset ) < flMaxChange )
+ {
+ m_flCurrPathOffset = flNewPathOffset;
+ }
+ else
+ {
+ float flSign = (m_flCurrPathOffset < flNewPathOffset) ? 1.0f : -1.0f;
+ m_flCurrPathOffset += flSign * flMaxChange;
+ }
+
+ return m_flCurrPathOffset;
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the max speed + acceleration:
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::GetMaxSpeedAndAccel( float *pMaxSpeed, float *pAccelRate )
+{
+ *pAccelRate = CHOPPER_ACCEL_RATE;
+ *pMaxSpeed = GetMaxSpeed();
+ if ( GetEnemyVehicle() )
+ {
+ *pAccelRate *= 9.0f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the acceleration:
+//-----------------------------------------------------------------------------
+#define HELICOPTER_GRAVITY 384
+#define HELICOPTER_DT 0.1f
+#define HELICOPTER_MIN_DZ_DAMP -500.0f
+#define HELICOPTER_MAX_DZ_DAMP -1000.0f
+#define HELICOPTER_FORCE_BLEND 0.8f
+#define HELICOPTER_FORCE_BLEND_VEHICLE 0.2f
+
+void CNPC_AttackHelicopter::ComputeVelocity( const Vector &vecTargetPosition,
+ float flAdditionalHeight, float flMinDistFromSegment, float flMaxDistFromSegment, Vector *pVecAccel )
+{
+ Vector deltaPos;
+ VectorSubtract( vecTargetPosition, GetAbsOrigin(), deltaPos );
+
+ // calc goal linear accel to hit deltaPos in dt time.
+ // This is solving the equation xf = 0.5 * a * dt^2 + vo * dt + xo
+ float dt = 1.0f;
+ pVecAccel->x = 2.0f * (deltaPos.x - GetAbsVelocity().x * dt) / (dt * dt);
+ pVecAccel->y = 2.0f * (deltaPos.y - GetAbsVelocity().y * dt) / (dt * dt);
+ pVecAccel->z = 2.0f * (deltaPos.z - GetAbsVelocity().z * dt) / (dt * dt) + HELICOPTER_GRAVITY;
+
+ float flDistFromPath = 0.0f;
+ Vector vecPoint, vecDelta;
+ if ( flMaxDistFromSegment != 0.0f )
+ {
+ // Also, add in a little force to get us closer to our current line segment if we can
+ ClosestPointToCurrentPath( &vecPoint );
+
+ if ( flAdditionalHeight != 0.0f )
+ {
+ Vector vecEndPoint, vecClosest;
+ vecEndPoint = vecPoint;
+ vecEndPoint.z += flAdditionalHeight;
+ CalcClosestPointOnLineSegment( GetAbsOrigin(), vecPoint, vecEndPoint, vecClosest );
+ vecPoint = vecClosest;
+ }
+
+ VectorSubtract( vecPoint, GetAbsOrigin(), vecDelta );
+ flDistFromPath = VectorNormalize( vecDelta );
+ if ( flDistFromPath > flMaxDistFromSegment )
+ {
+ // Strongly constrain to an n unit pipe around the current path
+ // by damping out all impulse forces that would push us further from the pipe
+ float flAmount = (flDistFromPath - flMaxDistFromSegment) / 200.0f;
+ flAmount = clamp( flAmount, 0, 1 );
+ VectorMA( *pVecAccel, flAmount * 200.0f, vecDelta, *pVecAccel );
+ }
+ }
+
+ // Apply avoidance forces
+ if ( !HasSpawnFlags( SF_HELICOPTER_IGNORE_AVOID_FORCES ) )
+ {
+ Vector vecAvoidForce;
+ CAvoidSphere::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
+ *pVecAccel += vecAvoidForce;
+ CAvoidBox::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
+ *pVecAccel += vecAvoidForce;
+ }
+
+ // don't fall faster than 0.2G or climb faster than 2G
+ pVecAccel->z = clamp( pVecAccel->z, HELICOPTER_GRAVITY * 0.2f, HELICOPTER_GRAVITY * 2.0f );
+
+ // The lift factor owing to horizontal movement
+ float flHorizLiftFactor = fabs( pVecAccel->x ) * 0.10f + fabs( pVecAccel->y ) * 0.10f;
+
+ // If we're way above the path, dampen horizontal lift factor
+ float flNewHorizLiftFactor = clamp( deltaPos.z, HELICOPTER_MAX_DZ_DAMP, HELICOPTER_MIN_DZ_DAMP );
+ flNewHorizLiftFactor = SimpleSplineRemapVal( flNewHorizLiftFactor, HELICOPTER_MIN_DZ_DAMP, HELICOPTER_MAX_DZ_DAMP, flHorizLiftFactor, 2.5f * (HELICOPTER_GRAVITY * 0.2) );
+ float flDampening = (flNewHorizLiftFactor != 0.0f) ? (flNewHorizLiftFactor / flHorizLiftFactor) : 1.0f;
+ if ( flDampening < 1.0f )
+ {
+ pVecAccel->x *= flDampening;
+ pVecAccel->y *= flDampening;
+ flHorizLiftFactor = flNewHorizLiftFactor;
+ }
+
+ Vector forward, right, up;
+ GetVectors( &forward, &right, &up );
+
+ // First, attenuate the current force
+ float flForceBlend = GetEnemyVehicle() ? HELICOPTER_FORCE_BLEND_VEHICLE : HELICOPTER_FORCE_BLEND;
+ m_flForce *= flForceBlend;
+
+ // Now add force based on our acceleration factors
+ m_flForce += ( pVecAccel->z + flHorizLiftFactor ) * HELICOPTER_DT * (1.0f - flForceBlend);
+
+ // The force is always *locally* upward based; we pitch + roll the chopper to get movement
+ Vector vecImpulse;
+ VectorMultiply( up, m_flForce, vecImpulse );
+
+ // NOTE: These have to be done *before* the additional path distance drag forces are applied below
+ ApplySidewaysDrag( right );
+ ApplyGeneralDrag();
+
+ // If LIFE_DYING, maintain control as long as we're flying to a crash point.
+ if ( m_lifeState != LIFE_DYING || (m_lifeState == LIFE_DYING && GetCrashPoint() != NULL) )
+ {
+ vecImpulse.z += -HELICOPTER_GRAVITY * HELICOPTER_DT;
+
+ if ( flMinDistFromSegment != 0.0f && ( flDistFromPath > flMinDistFromSegment ) )
+ {
+ Vector vecVelDir = GetAbsVelocity();
+
+ // Strongly constrain to an n unit pipe around the current path
+ // by damping out all impulse forces that would push us further from the pipe
+ float flDot = DotProduct( vecImpulse, vecDelta );
+ if ( flDot < 0.0f )
+ {
+ VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
+ }
+
+ // Also apply an extra impulse to compensate for the current velocity
+ flDot = DotProduct( vecVelDir, vecDelta );
+ if ( flDot < 0.0f )
+ {
+ VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
+ }
+ }
+ }
+ else
+ {
+ // No more upward lift...
+ vecImpulse.z = -HELICOPTER_GRAVITY * HELICOPTER_DT;
+
+ // Damp the horizontal impulses; we should pretty much be falling ballistically
+ vecImpulse.x *= 0.1f;
+ vecImpulse.y *= 0.1f;
+ }
+
+ // Add in our velocity pulse for this frame
+ ApplyAbsVelocityImpulse( vecImpulse );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Computes the max speed + acceleration:
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::ComputeAngularVelocity( const Vector &vecGoalUp, const Vector &vecFacingDirection )
+{
+ QAngle goalAngAccel;
+ if ( m_lifeState != LIFE_DYING || (m_lifeState == LIFE_DYING && GetCrashPoint() != NULL) )
+ {
+ Vector forward, right, up;
+ GetVectors( &forward, &right, &up );
+
+ Vector goalUp = vecGoalUp;
+ VectorNormalize( goalUp );
+
+ // calc goal orientation to hit linear accel forces
+ float goalPitch = RAD2DEG( asin( DotProduct( forward, goalUp ) ) );
+ float goalYaw = UTIL_VecToYaw( vecFacingDirection );
+ float goalRoll = RAD2DEG( asin( DotProduct( right, goalUp ) ) + m_flGoalRollDmg );
+ goalPitch *= 0.75f;
+
+ // clamp goal orientations
+ goalPitch = clamp( goalPitch, -30, 45 );
+ goalRoll = clamp( goalRoll, -45, 45 );
+
+ // calc angular accel needed to hit goal pitch in dt time.
+ float dt = 0.6;
+ goalAngAccel.x = 2.0 * (AngleDiff( goalPitch, AngleNormalize( GetAbsAngles().x ) ) - GetLocalAngularVelocity().x * dt) / (dt * dt);
+ goalAngAccel.y = 2.0 * (AngleDiff( goalYaw, AngleNormalize( GetAbsAngles().y ) ) - GetLocalAngularVelocity().y * dt) / (dt * dt);
+ goalAngAccel.z = 2.0 * (AngleDiff( goalRoll, AngleNormalize( GetAbsAngles().z ) ) - GetLocalAngularVelocity().z * dt) / (dt * dt);
+
+ goalAngAccel.x = clamp( goalAngAccel.x, -300, 300 );
+ //goalAngAccel.y = clamp( goalAngAccel.y, -60, 60 );
+ goalAngAccel.y = clamp( goalAngAccel.y, -120, 120 );
+ goalAngAccel.z = clamp( goalAngAccel.z, -300, 300 );
+ }
+ else
+ {
+ goalAngAccel.x = 0;
+ goalAngAccel.y = random->RandomFloat( 50, 120 );
+ goalAngAccel.z = 0;
+ }
+
+ // limit angular accel changes to similate mechanical response times
+ QAngle angAccelAccel;
+ float dt = 0.1;
+ angAccelAccel.x = (goalAngAccel.x - m_vecAngAcceleration.x) / dt;
+ angAccelAccel.y = (goalAngAccel.y - m_vecAngAcceleration.y) / dt;
+ angAccelAccel.z = (goalAngAccel.z - m_vecAngAcceleration.z) / dt;
+
+ angAccelAccel.x = clamp( angAccelAccel.x, -1000, 1000 );
+ angAccelAccel.y = clamp( angAccelAccel.y, -1000, 1000 );
+ angAccelAccel.z = clamp( angAccelAccel.z, -1000, 1000 );
+
+ // DevMsg( "pitch %6.1f (%6.1f:%6.1f) ", goalPitch, GetLocalAngles().x, m_vecAngVelocity.x );
+ // DevMsg( "roll %6.1f (%6.1f:%6.1f) : ", goalRoll, GetLocalAngles().z, m_vecAngVelocity.z );
+ // DevMsg( "%6.1f %6.1f %6.1f : ", goalAngAccel.x, goalAngAccel.y, goalAngAccel.z );
+ // DevMsg( "%6.0f %6.0f %6.0f\n", angAccelAccel.x, angAccelAccel.y, angAccelAccel.z );
+
+ m_vecAngAcceleration += angAccelAccel * 0.1;
+
+ QAngle angVel = GetLocalAngularVelocity();
+ angVel += m_vecAngAcceleration * 0.1;
+ angVel.y = clamp( angVel.y, -120, 120 );
+
+ // Fix up pitch and yaw to tend toward small values
+ if ( m_lifeState == LIFE_DYING && GetCrashPoint() == NULL )
+ {
+ float flPitchDiff = random->RandomFloat( -5, 5 ) - GetAbsAngles().x;
+ angVel.x = flPitchDiff * 0.1f;
+ float flRollDiff = random->RandomFloat( -5, 5 ) - GetAbsAngles().z;
+ angVel.z = flRollDiff * 0.1f;
+ }
+
+ SetLocalAngularVelocity( angVel );
+
+ float flAmt = clamp( angVel.y, -30, 30 );
+ float flRudderPose = RemapVal( flAmt, -30, 30, 45, -45 );
+ SetPoseParameter( "rudder", flRudderPose );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::FlightDirectlyOverhead( void )
+{
+ Vector vecTargetPosition = m_vecTargetPosition;
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( HasEnemy() && FVisible( pEnemy ) )
+ {
+ if ( GetEnemy()->IsPlayer() )
+ {
+ CBaseEntity *pEnemyVehicle = assert_cast<CBasePlayer*>(GetEnemy())->GetVehicleEntity();
+ if ( pEnemyVehicle )
+ {
+ Vector vecEnemyVel = pEnemyVehicle->GetSmoothedVelocity();
+ Vector vecRelativePosition;
+ VectorSubtract( GetAbsOrigin(), pEnemyVehicle->GetAbsOrigin(), vecRelativePosition );
+ float flDist = VectorNormalize( vecRelativePosition );
+ float flEnemySpeed = VectorNormalize( vecEnemyVel );
+ float flDot = DotProduct( vecRelativePosition, vecEnemyVel );
+ float flSpeed = GetMaxSpeed() * 0.3f; //GetAbsVelocity().Length();
+
+ float a = flSpeed * flSpeed - flEnemySpeed * flEnemySpeed;
+ float b = 2.0f * flEnemySpeed * flDist * flDot;
+ float c = - flDist * flDist;
+
+ float flDiscrim = b * b - 4 * a * c;
+ if ( flDiscrim >= 0 )
+ {
+ float t = ( -b + sqrt( flDiscrim ) ) / (2 * a);
+ t = clamp( t, 0.0f, 4.0f );
+ VectorMA( pEnemyVehicle->GetAbsOrigin(), t * flEnemySpeed, vecEnemyVel, vecTargetPosition );
+ }
+ }
+ }
+ }
+
+// if ( GetCurrentPathTargetPosition() )
+// {
+// vecTargetPosition.z = GetCurrentPathTargetPosition()->z;
+// }
+
+ NDebugOverlay::Cross3D( vecTargetPosition, -Vector(32,32,32), Vector(32,32,32), 0, 0, 255, true, 0.1f );
+
+ UpdateFacingDirection( vecTargetPosition );
+
+ Vector accel;
+ ComputeVelocity( vecTargetPosition, 0.0f, 0.0f, 0.0f, &accel );
+ ComputeAngularVelocity( accel, m_vecDesiredFaceDir );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::Flight( void )
+{
+ if( GetFlags() & FL_ONGROUND )
+ {
+ // This would be really bad.
+ SetGroundEntity( NULL );
+ }
+
+ // Determine the distances we must lie from the path
+ float flMaxPathOffset = MaxDistanceFromCurrentPath();
+ float flPerpDist = UpdatePerpPathDistance( flMaxPathOffset );
+
+ float flMinDistFromSegment, flMaxDistFromSegment;
+ if ( !IsLeading() )
+ {
+ flMinDistFromSegment = 0.0f;
+ flMaxDistFromSegment = 0.0f;
+ }
+ else
+ {
+ flMinDistFromSegment = fabs(flPerpDist) + 100.0f;
+ flMaxDistFromSegment = fabs(flPerpDist) + 200.0f;
+ if ( flMaxPathOffset != 0.0 )
+ {
+ if ( flMaxDistFromSegment > flMaxPathOffset - 100.0f )
+ {
+ flMaxDistFromSegment = flMaxPathOffset - 100.0f;
+ }
+
+ if ( flMinDistFromSegment > flMaxPathOffset - 200.0f )
+ {
+ flMinDistFromSegment = flMaxPathOffset - 200.0f;
+ }
+ }
+ }
+
+ float maxSpeed, accelRate;
+ GetMaxSpeedAndAccel( &maxSpeed, &accelRate );
+
+ Vector vecTargetPosition;
+ float flCurrentSpeed = GetAbsVelocity().Length();
+ float flDist = MIN( flCurrentSpeed + accelRate, maxSpeed );
+ float dt = 1.0f;
+ ComputeActualTargetPosition( flDist, dt, flPerpDist, &vecTargetPosition );
+
+ // Raise high in the air when doing the shooting attack
+ float flAdditionalHeight = 0.0f;
+ if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
+ {
+ flAdditionalHeight = clamp( m_flBullrushAdditionalHeight, 0.0f, flMaxPathOffset );
+ vecTargetPosition.z += flAdditionalHeight;
+ }
+
+ Vector accel;
+ UpdateFacingDirection( vecTargetPosition );
+ ComputeVelocity( vecTargetPosition, flAdditionalHeight, flMinDistFromSegment, flMaxDistFromSegment, &accel );
+ ComputeAngularVelocity( accel, m_vecDesiredFaceDir );
+}
+
+
+//------------------------------------------------------------------------------
+// Updates the facing direction
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::UpdateFacingDirection( const Vector &vecActualDesiredPosition )
+{
+ bool bIsBullrushing = ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE );
+
+ bool bSeenTargetRecently = HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) || ( m_flLastSeen + 5 > gpGlobals->curtime );
+ if ( GetEnemy() && !bIsBullrushing )
+ {
+ if ( !IsLeading() )
+ {
+ if( IsCarpetBombing() && hl2_episodic.GetBool() )
+ {
+ m_vecDesiredFaceDir = vecActualDesiredPosition - GetAbsOrigin();
+ }
+ else if ( !IsCrashing() && bSeenTargetRecently )
+ {
+ // If we've seen the target recently, face the target.
+ m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
+ }
+ else
+ {
+ // Remain facing the way you were facing...
+ }
+ }
+ else
+ {
+ if ( ShouldDropBombs() || IsCarpetBombing() )
+ {
+ m_vecDesiredFaceDir = vecActualDesiredPosition - GetAbsOrigin();
+ }
+ else
+ {
+ m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
+ }
+ }
+ }
+ else
+ {
+ // Face our desired position
+ float flDistSqr = vecActualDesiredPosition.AsVector2D().DistToSqr( GetAbsOrigin().AsVector2D() );
+ if ( flDistSqr <= 50 * 50 )
+ {
+ if (( flDistSqr > 1 * 1 ) && bSeenTargetRecently && IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) )
+ {
+ m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
+ m_vecDesiredFaceDir.z = 0.0f;
+ }
+ else
+ {
+ GetVectors( &m_vecDesiredFaceDir, NULL, NULL );
+ }
+ }
+ else
+ {
+ m_vecDesiredFaceDir = vecActualDesiredPosition - GetAbsOrigin();
+ }
+ }
+ VectorNormalize( m_vecDesiredFaceDir );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+#define ENEMY_CREEP_RATE 400
+float CNPC_AttackHelicopter::CreepTowardEnemy( float flSpeed, float flMinSpeed, float flMaxSpeed, float flMinDist, float flMaxDist )
+{
+ float dt = gpGlobals->curtime - GetLastThink();
+ float flEnemyCreepDist = ENEMY_CREEP_RATE * dt;
+
+ // When the player is slow, creep toward him within a second or two
+ float flLeadingDist = ClampSplineRemapVal( flSpeed, flMinSpeed, flMaxSpeed, flMinDist, flMaxDist );
+ float flCurrentDist = GetLeadingDistance( );
+ if ( fabs(flLeadingDist - flCurrentDist) > flEnemyCreepDist )
+ {
+ float flSign = ( flLeadingDist < flCurrentDist ) ? -1.0f : 1.0f;
+ flLeadingDist = flCurrentDist + flSign * flEnemyCreepDist;
+ }
+
+ return flLeadingDist;
+}
+
+
+#define MIN_ENEMY_SPEED 300
+
+
+//------------------------------------------------------------------------------
+// Computes how far to lead the player when bombing
+//------------------------------------------------------------------------------
+float CNPC_AttackHelicopter::ComputeBombingLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle )
+{
+ if ( ( flSpeed <= MIN_ENEMY_SPEED ) && bEnemyInVehicle )
+ {
+ return CreepTowardEnemy( flSpeed, 0.0f, MIN_ENEMY_SPEED, 0.0f, 1000.0f );
+ }
+
+ return ClampSplineRemapVal( flSpeedAlongPath, 200.0f, 600.0f, 1000.0f, 2000.0f );
+}
+
+
+//------------------------------------------------------------------------------
+// Computes how far to lead the player when bullrushing
+//------------------------------------------------------------------------------
+float CNPC_AttackHelicopter::ComputeBullrushLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle )
+{
+ switch ( m_nSecondaryMode )
+ {
+ case BULLRUSH_MODE_WAIT_FOR_ENEMY:
+ return 0.0f;
+
+ case BULLRUSH_MODE_GET_DISTANCE:
+ return m_bRushForward ? -CHOPPER_BULLRUSH_MODE_DISTANCE : CHOPPER_BULLRUSH_MODE_DISTANCE;
+
+ case BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER:
+// return m_bRushForward ? 1500.0f : -1500.0f;
+ return ComputeBombingLeadingDistance( flSpeed, flSpeedAlongPath, bEnemyInVehicle );
+
+ case BULLRUSH_MODE_SHOOT_IDLE_PLAYER:
+ return 0.0f;
+
+ case BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED:
+ return m_bRushForward ? 7000 : -7000;
+
+ case BULLRUSH_MODE_MEGA_BOMB:
+ return m_bRushForward ? CHOPPER_BULLRUSH_MODE_DISTANCE : -CHOPPER_BULLRUSH_MODE_DISTANCE;
+
+ case BULLRUSH_MODE_SHOOT_GUN:
+ {
+ float flLeadDistance = 1000.f - CHOPPER_BULLRUSH_ENEMY_BOMB_DISTANCE;
+ return m_bRushForward ? flLeadDistance : -flLeadDistance;
+ }
+ }
+
+ Assert(0);
+ return 0.0f;
+}
+
+
+//------------------------------------------------------------------------------
+// Secondary mode
+//------------------------------------------------------------------------------
+inline void CNPC_AttackHelicopter::SetSecondaryMode( int nMode, bool bRetainTime )
+{
+ m_nSecondaryMode = nMode;
+ if (!bRetainTime)
+ {
+ m_flSecondaryModeStartTime = gpGlobals->curtime;
+ }
+}
+
+inline bool CNPC_AttackHelicopter::IsInSecondaryMode( int nMode )
+{
+ return m_nSecondaryMode == nMode;
+}
+
+inline float CNPC_AttackHelicopter::SecondaryModeTime( ) const
+{
+ return gpGlobals->curtime - m_flSecondaryModeStartTime;
+}
+
+
+//------------------------------------------------------------------------------
+// Switch to idle
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::SwitchToBullrushIdle( void )
+{
+ // Put us directly into idle gun state (we're in firing state)
+ m_flNextAttack = gpGlobals->curtime;
+ m_nGunState = GUN_STATE_IDLE;
+ m_nRemainingBursts = 0;
+ m_flBullrushAdditionalHeight = 0.0f;
+ SetPauseState( PAUSE_NO_PAUSE );
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.1f );
+}
+
+
+//------------------------------------------------------------------------------
+// Should the chopper shoot the idle player?
+//------------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::ShouldShootIdlePlayerInBullrush()
+{
+ // Once he starts shooting, then don't stop until the player is moving pretty fast
+ float flSpeedSqr = IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) ? CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2_SQ : CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_SQ;
+ return ( GetEnemy() && GetEnemy()->GetSmoothedVelocity().LengthSqr() <= flSpeedSqr );
+}
+
+
+//------------------------------------------------------------------------------
+// Shutdown shooting during bullrush
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::ShutdownGunDuringBullrush( )
+{
+ // Put us directly into idle gun state (we're in firing state)
+ m_flNextAttack = gpGlobals->curtime;
+ m_nGunState = GUN_STATE_IDLE;
+ m_nRemainingBursts = 0;
+ SetPauseState( PAUSE_NO_PAUSE );
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.1f );
+}
+
+#define HELICOPTER_MIN_IDLE_BOMBING_DIST 350.0f
+#define HELICOPTER_MIN_IDLE_BOMBING_SPEED 350.0f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_AttackHelicopter::ShouldBombIdlePlayer( void )
+{
+ // Must be settled over a position and not moving too quickly to do this
+ if ( GetAbsVelocity().LengthSqr() > Square(HELICOPTER_MIN_IDLE_BOMBING_SPEED) )
+ return false;
+
+ // Must be within a certain range of the target
+ float flDistToTargetSqr = (GetEnemy()->WorldSpaceCenter() - GetAbsOrigin()).Length2DSqr();
+
+ if ( flDistToTargetSqr < Square(HELICOPTER_MIN_IDLE_BOMBING_DIST) )
+ return true;
+
+ // Can't bomb this
+ return false;
+}
+
+//------------------------------------------------------------------------------
+// Update the bullrush state
+//------------------------------------------------------------------------------
+#define BULLRUSH_GOAL_TOLERANCE 200
+#define BULLRUSH_BOMB_MAX_DISTANCE 3500
+
+void CNPC_AttackHelicopter::UpdateBullrushState( void )
+{
+ if ( !GetEnemy() || IsInForcedMove() )
+ {
+ if ( !IsInSecondaryMode( BULLRUSH_MODE_WAIT_FOR_ENEMY ) )
+ {
+ SwitchToBullrushIdle();
+ SetSecondaryMode( BULLRUSH_MODE_WAIT_FOR_ENEMY );
+ }
+ }
+
+ switch( m_nSecondaryMode )
+ {
+ case BULLRUSH_MODE_WAIT_FOR_ENEMY:
+ {
+ m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
+ if ( GetEnemy() && !IsInForcedMove() )
+ {
+ // This forces us to not start trying checking positions
+ // until we have been on the path for a little while
+ if ( SecondaryModeTime() > 0.3f )
+ {
+ float flDistanceToGoal = ComputeDistanceToTargetPosition();
+ Vector vecPathDir;
+ CurrentPathDirection( &vecPathDir );
+ bool bMovingForward = DotProduct2D( GetAbsVelocity().AsVector2D(), vecPathDir.AsVector2D() ) >= 0.0f;
+ if ( flDistanceToGoal * (bMovingForward ? 1.0f : -1.0f) > 1000 )
+ {
+ m_bRushForward = bMovingForward;
+ SetSecondaryMode( BULLRUSH_MODE_SHOOT_GUN );
+ SpotlightStartup();
+ }
+ else
+ {
+ m_bRushForward = !bMovingForward;
+ SetSecondaryMode( BULLRUSH_MODE_GET_DISTANCE );
+ }
+ }
+ }
+ else
+ {
+ m_flSecondaryModeStartTime = gpGlobals->curtime;
+ }
+ }
+ break;
+
+ case BULLRUSH_MODE_GET_DISTANCE:
+ {
+ m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
+
+ float flDistanceToGoal = ComputeDistanceToTargetPosition();
+ if ( m_bRushForward )
+ {
+ if ( flDistanceToGoal < (CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
+ break;
+ }
+ else
+ {
+ if ( flDistanceToGoal > -(CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
+ break;
+ }
+
+ if ( GetHealth() <= m_flNextMegaBombHealth )
+ {
+ m_flNextMegaBombHealth -= GetMaxHealth() * g_helicopter_bullrush_mega_bomb_health.GetFloat();
+ m_flNextBullrushBombTime = gpGlobals->curtime;
+ SetSecondaryMode( BULLRUSH_MODE_MEGA_BOMB );
+ EmitSound( "NPC_AttackHelicopter.MegabombAlert" );
+ }
+ else
+ {
+ SetSecondaryMode( BULLRUSH_MODE_SHOOT_GUN );
+ SpotlightStartup();
+ }
+ }
+ break;
+
+ case BULLRUSH_MODE_MEGA_BOMB:
+ {
+ m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
+
+ float flDistanceToGoal = ComputeDistanceToTargetPosition();
+ if ( m_bRushForward )
+ {
+ if ( flDistanceToGoal > -(CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
+ break;
+ }
+ else
+ {
+ if ( flDistanceToGoal < (CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
+ break;
+ }
+
+ m_bRushForward = !m_bRushForward;
+ SetSecondaryMode( BULLRUSH_MODE_GET_DISTANCE );
+ }
+ break;
+
+ case BULLRUSH_MODE_SHOOT_GUN:
+ {
+ // When shooting, stop when we cross the player's position
+ // Then start bombing. Use the fixed speed version if we're too far
+ // from the enemy or if he's travelling in the opposite direction.
+ // Otherwise, do the standard bombing behavior for a while.
+ float flDistanceToGoal = ComputeDistanceToTargetPosition();
+
+ float flShootingHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
+ float flSwitchToBombDist = CHOPPER_BULLRUSH_ENEMY_BOMB_DISTANCE;
+ float flDropDownDist = 2000.0f;
+ if ( m_bRushForward )
+ {
+ m_flBullrushAdditionalHeight = ClampSplineRemapVal( flDistanceToGoal,
+ flSwitchToBombDist, flSwitchToBombDist + flDropDownDist, 0.0f, flShootingHeight );
+ if ( flDistanceToGoal > flSwitchToBombDist )
+ break;
+ }
+ else
+ {
+ m_flBullrushAdditionalHeight = ClampSplineRemapVal( flDistanceToGoal,
+ -flSwitchToBombDist - flDropDownDist, -flSwitchToBombDist, flShootingHeight, 0.0f );
+ if ( flDistanceToGoal < -flSwitchToBombDist )
+ break;
+ }
+
+ if ( ShouldShootIdlePlayerInBullrush() )
+ {
+ SetSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER );
+ }
+ else
+ {
+ ShutdownGunDuringBullrush( );
+ SetSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED );
+ }
+ }
+ break;
+
+ case BULLRUSH_MODE_SHOOT_IDLE_PLAYER:
+ {
+ // Shut down our gun if we're switching to bombing
+ if ( ShouldBombIdlePlayer() )
+ {
+ // Must not already be shutdown
+ if (( m_nGunState != GUN_STATE_IDLE ) && (SecondaryModeTime() >= BULLRUSH_IDLE_PLAYER_FIRE_TIME))
+ {
+ ShutdownGunDuringBullrush( );
+ }
+ }
+
+// m_nBurstHits = 0;
+ m_flCircleOfDeathRadius = ClampSplineRemapVal( SecondaryModeTime(), BULLRUSH_IDLE_PLAYER_FIRE_TIME, BULLRUSH_IDLE_PLAYER_FIRE_TIME + 5.0f, 256.0f, 64.0f );
+ m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
+ if ( !ShouldShootIdlePlayerInBullrush() )
+ {
+ ShutdownGunDuringBullrush( );
+ SetSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED );
+ }
+ }
+ break;
+
+ case BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER:
+ {
+ m_flBullrushAdditionalHeight = 0.0f;
+ float flDistanceToGoal = ComputeDistanceToTargetPosition();
+ if ( fabs( flDistanceToGoal ) > 2000.0f )
+ {
+ SetSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED, true );
+ break;
+ }
+ }
+ // FALL THROUGH!!
+
+ case BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED:
+ {
+ float flDistanceToGoal = ComputeDistanceToTargetPosition();
+
+ m_flBullrushAdditionalHeight = 0.0f;
+ if (( SecondaryModeTime() >= CHOPPER_BULLRUSH_ENEMY_BOMB_TIME ) || ( flDistanceToGoal > BULLRUSH_BOMB_MAX_DISTANCE ))
+ {
+ m_bRushForward = !m_bRushForward;
+ SetSecondaryMode( BULLRUSH_MODE_GET_DISTANCE );
+ }
+ }
+ break;
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::UpdateEnemyLeading( void )
+{
+ bool bEnemyInVehicle = true;
+ CBaseEntity *pTarget = GetEnemyVehicle();
+ if ( !pTarget )
+ {
+ bEnemyInVehicle = false;
+ if ( (m_nAttackMode == ATTACK_MODE_DEFAULT) || !GetEnemy() )
+ {
+ EnableLeading( false );
+ return;
+ }
+
+ pTarget = GetEnemy();
+ }
+
+ EnableLeading( true );
+
+ float flLeadingDist = 0.0f;
+ float flSpeedAlongPath = TargetSpeedAlongPath();
+ float flSpeed = pTarget->GetSmoothedVelocity().Length();
+
+ // Do the test electricity gun
+ if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
+ {
+ if ( flSpeedAlongPath < 200.0f )
+ {
+ flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, 0.0f, 200.0f, 100.0f, -200.0f );
+ }
+ else
+ {
+ flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, 200.0f, 600.0f, -200.0f, -500.0f );
+ }
+ SetLeadingDistance( flLeadingDist );
+ return;
+ }
+
+ switch( m_nAttackMode )
+ {
+ case ATTACK_MODE_BULLRUSH_VEHICLE:
+ flLeadingDist = ComputeBullrushLeadingDistance( flSpeed, flSpeedAlongPath, bEnemyInVehicle );
+ break;
+
+ case ATTACK_MODE_ALWAYS_LEAD_VEHICLE:
+ if (( flSpeed <= MIN_ENEMY_SPEED ) && (bEnemyInVehicle) )
+ {
+ flLeadingDist = CreepTowardEnemy( flSpeed, 0.0f, MIN_ENEMY_SPEED, 0.0f, 1000.0f );
+ }
+ else
+ {
+ if ( flSpeedAlongPath > 0.0f )
+ {
+ flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, 200.0f, 600.0f, 1000.0f, 2000.0f );
+ }
+ else
+ {
+ flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, -600.0f, -200.0f, -2000.0f, -1000.0f );
+ }
+ }
+ break;
+
+ case ATTACK_MODE_BOMB_VEHICLE:
+ flLeadingDist = ComputeBombingLeadingDistance( flSpeed, flSpeedAlongPath, bEnemyInVehicle );
+ break;
+
+ case ATTACK_MODE_DEFAULT:
+ case ATTACK_MODE_TRAIL_VEHICLE:
+ if (( flSpeed <= MIN_ENEMY_SPEED ) && (bEnemyInVehicle))
+ {
+ flLeadingDist = CreepTowardEnemy( flSpeed, 150.0f, MIN_ENEMY_SPEED, 500.0f, -1000.0f );
+ }
+ else
+ {
+ flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, -600.0f, -200.0f, -2500.0f, -1000.0f );
+ }
+ break;
+ }
+
+ SetLeadingDistance( flLeadingDist );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pInfo -
+// bAlways -
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
+{
+ // Are we already marked for transmission?
+ if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
+ return;
+
+ BaseClass::SetTransmit( pInfo, bAlways );
+
+ // Make our smoke trails always come with us
+ for ( int i = 0; i < m_nSmokeTrailCount; i++ )
+ {
+ m_hSmokeTrail[i]->SetTransmit( pInfo, bAlways );
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+void CNPC_AttackHelicopter::Hunt( void )
+{
+ if ( m_lifeState == LIFE_DEAD )
+ {
+ return;
+ }
+
+ if ( m_lifeState == LIFE_DYING )
+ {
+ Flight();
+ UpdatePlayerDopplerShift( );
+ return;
+ }
+
+ // FIXME: Hack to allow us to change the firing distance
+ SetFarthestPathDist( GetMaxFiringDistance() );
+
+ UpdateEnemy();
+
+ // Give free knowledge of the enemy position if the chopper is "aggressive"
+ if ( HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) && GetEnemy() )
+ {
+ m_vecTargetPosition = GetEnemy()->WorldSpaceCenter();
+ }
+
+ // Test for state transitions when in bullrush mode
+ if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
+ {
+ UpdateBullrushState();
+ }
+
+ UpdateEnemyLeading();
+
+ UpdateTrackNavigation( );
+
+ Flight();
+
+ UpdatePlayerDopplerShift( );
+
+ FireWeapons();
+
+ if ( !(m_fHelicopterFlags & BITS_HELICOPTER_GUN_ON) )
+ {
+ // !!!HACKHACK This is a fairly unsavoury hack that allows the attack
+ // chopper to continue to carpet bomb even with the gun turned off
+ // (Normally the chopper will carpet bomb inside FireGun(), but FireGun()
+ // doesn't get called by the above call to FireWeapons() if the gun is turned off)
+ // Adding this little exception here lets me avoid going into the CBaseHelicopter and
+ // making some functions virtual that don't want to be virtual.
+ if ( IsCarpetBombing() )
+ {
+ BullrushBombs();
+ }
+ }
+
+#ifdef HL2_EPISODIC
+ // Update our bone followers
+ m_BoneFollowerManager.UpdateBoneFollowers(this);
+#endif // HL2_EPISODIC
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Cache whatever pose parameters we intend to use
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::PopulatePoseParameters( void )
+{
+ m_poseWeapon_Pitch = LookupPoseParameter("weapon_pitch");
+ m_poseWeapon_Yaw = LookupPoseParameter("weapon_yaw");
+ m_poseRudder = LookupPoseParameter("rudder");
+
+ BaseClass::PopulatePoseParameters();
+}
+
+#ifdef HL2_EPISODIC
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_AttackHelicopter::InitBoneFollowers( void )
+{
+ // Don't do this if we're already loaded
+ if ( m_BoneFollowerManager.GetNumBoneFollowers() != 0 )
+ return;
+
+ // Init our followers
+ m_BoneFollowerManager.InitBoneFollowers( this, ARRAYSIZE(pFollowerBoneNames), pFollowerBoneNames );
+}
+#endif // HL2_EPISODIC
+
+//-----------------------------------------------------------------------------
+// Where are how should we avoid?
+//-----------------------------------------------------------------------------
+AI_BEGIN_CUSTOM_NPC( npc_helicopter, CNPC_AttackHelicopter )
+
+// DECLARE_TASK( )
+
+ DECLARE_ACTIVITY( ACT_HELICOPTER_DROP_BOMB );
+ DECLARE_ACTIVITY( ACT_HELICOPTER_CRASHING );
+
+// DECLARE_CONDITION( COND_ )
+
+ //=========================================================
+// DEFINE_SCHEDULE
+// (
+// SCHED_DUMMY,
+//
+// " Tasks"
+// " TASK_FACE_ENEMY 0"
+// " "
+// " Interrupts"
+// )
+
+
+AI_END_CUSTOM_NPC()
+
+
+
+//------------------------------------------------------------------------------
+//
+// A sensor used to drop bombs only in the correct points
+//
+//------------------------------------------------------------------------------
+LINK_ENTITY_TO_CLASS( npc_helicoptersensor, CBombDropSensor );
+
+BEGIN_DATADESC( CBombDropSensor )
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "DropBomb", InputDropBomb ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DropBombStraightDown", InputDropBombStraightDown ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTargetAlways", InputDropBombAtTargetAlways ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTarget", InputDropBombAtTarget ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "DropBombDelay", InputDropBombDelay ),
+
+END_DATADESC()
+
+
+void CBombDropSensor::Spawn()
+{
+ BaseClass::Spawn();
+ UTIL_SetSize(this, Vector(-30,-30,-30), Vector(30,30,30) );
+ SetSolid(SOLID_BBOX);
+
+ // Shots pass through
+ SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
+}
+
+// Drop a bomb at a particular location
+void CBombDropSensor::InputDropBomb( inputdata_t &inputdata )
+{
+ inputdata_t myVersion = inputdata;
+ myVersion.pActivator = this;
+ assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBomb( myVersion );
+}
+
+void CBombDropSensor::InputDropBombStraightDown( inputdata_t &inputdata )
+{
+ inputdata_t myVersion = inputdata;
+ myVersion.pActivator = this;
+ assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombStraightDown( myVersion );
+}
+
+void CBombDropSensor::InputDropBombAtTarget( inputdata_t &inputdata )
+{
+ inputdata_t myVersion = inputdata;
+ myVersion.pActivator = this;
+ assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombAtTarget( myVersion );
+}
+
+void CBombDropSensor::InputDropBombAtTargetAlways( inputdata_t &inputdata )
+{
+ inputdata_t myVersion = inputdata;
+ myVersion.pActivator = this;
+ assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombAtTargetAlways( myVersion );
+}
+
+void CBombDropSensor::InputDropBombDelay( inputdata_t &inputdata )
+{
+ inputdata_t myVersion = inputdata;
+ myVersion.pActivator = this;
+ assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombDelay( myVersion );
+}
+
+//------------------------------------------------------------------------------
+//
+// The bombs the helicopter drops on the player
+//
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// Save/load
+//------------------------------------------------------------------------------
+
+LINK_ENTITY_TO_CLASS( grenade_helicopter, CGrenadeHelicopter );
+
+BEGIN_DATADESC( CGrenadeHelicopter )
+
+ DEFINE_FIELD( m_bActivated, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bExplodeOnContact, FIELD_BOOLEAN ),
+ DEFINE_SOUNDPATCH( m_pWarnSound ),
+
+ DEFINE_FIELD( m_hWarningSprite, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bBlinkerAtTop, FIELD_BOOLEAN ),
+
+#ifdef HL2_EPISODIC
+ DEFINE_FIELD( m_flLifetime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_hCollisionObject, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bPickedUp, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flBlinkFastTime, FIELD_TIME ),
+
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "ExplodeIn", InputExplodeIn ),
+
+ DEFINE_OUTPUT( m_OnPhysGunOnlyPickup, "OnPhysGunOnlyPickup" ),
+#endif // HL2_EPISODIC
+
+ DEFINE_THINKFUNC( ExplodeThink ),
+ DEFINE_THINKFUNC( AnimateThink ),
+ DEFINE_THINKFUNC( RampSoundThink ),
+ DEFINE_THINKFUNC( WarningBlinkerThink ),
+ DEFINE_ENTITYFUNC( ExplodeConcussion ),
+
+END_DATADESC()
+
+#define SF_HELICOPTER_GRENADE_DUD (1<<16) // Will not become active on impact, only when launched via physcannon
+
+//------------------------------------------------------------------------------
+// Precache
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::Precache( void )
+{
+ BaseClass::Precache( );
+ PrecacheModel( GRENADE_HELICOPTER_MODEL );
+
+ PrecacheScriptSound( "ReallyLoudSpark" );
+ PrecacheScriptSound( "NPC_AttackHelicopterGrenade.Ping" );
+ PrecacheScriptSound( "NPC_AttackHelicopterGrenade.PingCaptured" );
+ PrecacheScriptSound( "NPC_AttackHelicopterGrenade.HardImpact" );
+}
+
+
+//------------------------------------------------------------------------------
+// Spawn
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::Spawn( void )
+{
+ Precache();
+
+ // point sized, solid, bouncing
+ SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
+ SetModel( GRENADE_HELICOPTER_MODEL );
+
+ if ( HasSpawnFlags( SF_HELICOPTER_GRENADE_DUD ) )
+ {
+ m_nSkin = (int)SKIN_DUD;
+ }
+
+ if ( !HasSpawnFlags( SF_GRENADE_HELICOPTER_MEGABOMB ) )
+ {
+ IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, GetSolidFlags(), false );
+ SetMoveType( MOVETYPE_VPHYSICS );
+
+ Vector vecAbsVelocity = GetAbsVelocity();
+ pPhysicsObject->AddVelocity( &vecAbsVelocity, NULL );
+ }
+ else
+ {
+ SetSolid( SOLID_BBOX );
+ SetCollisionBounds( Vector( -12.5, -12.5, -12.5 ), Vector( 12.5, 12.5, 12.5 ) );
+ VPhysicsInitShadow( false, false );
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
+ SetElasticity( 0.5f );
+ AddEffects( EF_NOSHADOW );
+ }
+
+ // We're always being dropped beneath the helicopter; need to not
+ // be affected by the rotor wash
+ AddEFlags( EFL_NO_ROTORWASH_PUSH );
+
+ // contact grenades arc lower
+ QAngle angles;
+ VectorAngles(GetAbsVelocity(), angles );
+ SetLocalAngles( angles );
+
+ SetThink( NULL );
+
+ // Tumble in air
+ QAngle vecAngVel( random->RandomFloat ( -100, -500 ), 0, 0 );
+ SetLocalAngularVelocity( vecAngVel );
+
+ // Explode on contact
+ SetTouch( &CGrenadeHelicopter::ExplodeConcussion );
+
+ // use a lower gravity for grenades to make them easier to see
+ SetGravity( UTIL_ScaleForGravity( 400 ) );
+
+#ifdef HL2_EPISODIC
+ m_bPickedUp = false;
+ m_flLifetime = BOMB_LIFETIME * 2.0;
+#endif // HL2_EPISODIC
+
+ if ( hl2_episodic.GetBool() )
+ {
+ // Disallow this, we'd rather deal with them as physobjects
+ m_takedamage = DAMAGE_NO;
+ }
+ else
+ {
+ // Allow player to blow this puppy up in the air
+ m_takedamage = DAMAGE_YES;
+ }
+
+ m_bActivated = false;
+ m_pWarnSound = NULL;
+ m_bExplodeOnContact = false;
+
+ m_flDamage = sk_helicopter_grenadedamage.GetFloat();
+
+ g_pNotify->AddEntity( this, this );
+
+ if( hl2_episodic.GetBool() )
+ {
+ SetContextThink( &CGrenadeHelicopter::AnimateThink, gpGlobals->curtime, s_pAnimateThinkContext );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// On Remve
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::UpdateOnRemove()
+{
+ if( m_pWarnSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundDestroy( m_pWarnSound );
+ }
+ g_pNotify->ClearEntity( this );
+ BaseClass::UpdateOnRemove();
+}
+
+
+#ifdef HL2_EPISODIC
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::InputExplodeIn( inputdata_t &inputdata )
+{
+ m_flLifetime = inputdata.value.Float();
+
+ if ( HasSpawnFlags( SF_HELICOPTER_GRENADE_DUD ) )
+ {
+ // We are a dud no more!
+ RemoveSpawnFlags( SF_HELICOPTER_GRENADE_DUD );
+ m_nSkin = (int)SKIN_REGULAR;
+ }
+
+ m_bActivated = false;
+ BecomeActive();
+}
+#endif
+
+
+//------------------------------------------------------------------------------
+// Activate!
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::BecomeActive()
+{
+ if ( m_bActivated )
+ return;
+
+ if ( IsMarkedForDeletion() )
+ return;
+
+ m_bActivated = true;
+
+ bool bMegaBomb = HasSpawnFlags(SF_GRENADE_HELICOPTER_MEGABOMB);
+
+ SetThink( &CGrenadeHelicopter::ExplodeThink );
+
+ if ( hl2_episodic.GetBool() )
+ {
+ if ( HasSpawnFlags( SF_HELICOPTER_GRENADE_DUD ) == false )
+ {
+ SetNextThink( gpGlobals->curtime + GetBombLifetime() );
+ }
+ else
+ {
+ // NOTE: A dud will not explode after a set time, only when launched!
+ SetThink( NULL );
+ return;
+ }
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + GetBombLifetime() );
+ }
+
+ if ( !bMegaBomb )
+ {
+ SetContextThink( &CGrenadeHelicopter::RampSoundThink, gpGlobals->curtime + GetBombLifetime() - BOMB_RAMP_SOUND_TIME, s_pRampSoundContext );
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ CReliableBroadcastRecipientFilter filter;
+ m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopterGrenade.Ping" );
+ controller.Play( m_pWarnSound, 1.0, PITCH_NORM );
+ }
+
+ SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + (GetBombLifetime() - 2.0f), s_pWarningBlinkerContext );
+
+#ifdef HL2_EPISODIC
+ m_flBlinkFastTime = gpGlobals->curtime + GetBombLifetime() - 1.0f;
+#endif//HL2_EPISODIC
+}
+
+
+//------------------------------------------------------------------------------
+// Pow!
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::RampSoundThink( )
+{
+ if ( m_pWarnSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangePitch( m_pWarnSound, 140, BOMB_RAMP_SOUND_TIME );
+ }
+
+ SetContextThink( NULL, gpGlobals->curtime, s_pRampSoundContext );
+}
+
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::WarningBlinkerThink()
+{
+#ifndef HL2_EPISODIC
+ return;
+#endif
+
+/*
+ if( !m_hWarningSprite.Get() )
+ {
+ Vector up;
+ GetVectors( NULL, NULL, &up );
+
+ // Light isn't on, so create the sprite.
+ m_hWarningSprite = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetAbsOrigin() + up * 10.0f, false );
+ CSprite *pSprite = (CSprite *)m_hWarningSprite.Get();
+
+ if( pSprite != NULL )
+ {
+ pSprite->SetParent( this, LookupAttachment("top") );
+ pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
+ pSprite->SetScale( 0.35, 0.0 );
+ }
+
+ m_bBlinkerAtTop = true;
+
+ ResetSequence( LookupActivity( "ACT_ARM" ) );
+ }
+ else
+*/
+ {
+ // Just flip it to the other attachment.
+ if( m_bBlinkerAtTop )
+ {
+ //m_hWarningSprite->SetParentAttachment( "SetParentAttachment", "bottom", false );
+ m_nSkin = (int)SKIN_REGULAR;
+ m_bBlinkerAtTop = false;
+ }
+ else
+ {
+ //m_hWarningSprite->SetParentAttachment( "SetParentAttachment", "top", false );
+ m_nSkin = (int)SKIN_DUD;
+ m_bBlinkerAtTop = true;
+ }
+ }
+
+ // Frighten people
+ CSoundEnt::InsertSound ( SOUND_DANGER, WorldSpaceCenter(), g_helicopter_bomb_danger_radius.GetFloat(), 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
+
+#ifdef HL2_EPISODIC
+ if( gpGlobals->curtime >= m_flBlinkFastTime )
+ {
+ SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + 0.1f, s_pWarningBlinkerContext );
+ }
+ else
+ {
+ SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + 0.2f, s_pWarningBlinkerContext );
+ }
+#endif//HL2_EPISODIC
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::StopWarningBlinker()
+{
+ if( m_hWarningSprite.Get() )
+ {
+ UTIL_Remove( m_hWarningSprite.Get() );
+ m_hWarningSprite.Set( NULL );
+ }
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::AnimateThink()
+{
+ StudioFrameAdvance();
+ SetContextThink( &CGrenadeHelicopter::AnimateThink, gpGlobals->curtime + 0.1f, s_pAnimateThinkContext );
+}
+
+//------------------------------------------------------------------------------
+// Entity events... these are events targetted to a particular entity
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::OnEntityEvent( EntityEvent_t event, void *pEventData )
+{
+ BaseClass::OnEntityEvent( event, pEventData );
+
+ if ( event == ENTITY_EVENT_WATER_TOUCH )
+ {
+ BecomeActive();
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// If we hit water, then stop
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::PhysicsSimulate( void )
+{
+ Vector vecPrevPosition = GetAbsOrigin();
+
+ BaseClass::PhysicsSimulate();
+
+ if (!m_bActivated && (GetMoveType() != MOVETYPE_VPHYSICS))
+ {
+ if ( GetWaterLevel() > 1 )
+ {
+ SetAbsVelocity( vec3_origin );
+ SetMoveType( MOVETYPE_NONE );
+ BecomeActive();
+ }
+
+ // Stuck condition, can happen pretty often
+ if ( vecPrevPosition == GetAbsOrigin() )
+ {
+ SetAbsVelocity( vec3_origin );
+ SetMoveType( MOVETYPE_NONE );
+ BecomeActive();
+ }
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// If we hit something, start the timer
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
+{
+ BaseClass::VPhysicsCollision( index, pEvent );
+ BecomeActive();
+
+#ifndef HL2_EPISODIC // in ep2, don't do this here, do it in Touch()
+ if ( m_bExplodeOnContact )
+ {
+ Vector vecVelocity;
+ GetVelocity( &vecVelocity, NULL );
+ DoExplosion( GetAbsOrigin(), vecVelocity );
+ }
+#endif
+
+
+ if( hl2_episodic.GetBool() )
+ {
+ float flImpactSpeed = pEvent->preVelocity->Length();
+ if( flImpactSpeed > 400.0f && pEvent->pEntities[ 1 ]->IsWorld() )
+ {
+ EmitSound( "NPC_AttackHelicopterGrenade.HardImpact" );
+ }
+ }
+}
+
+
+#if HL2_EPISODIC
+//------------------------------------------------------------------------------
+// double launch velocity for ep2_outland_08
+//------------------------------------------------------------------------------
+Vector CGrenadeHelicopter::PhysGunLaunchVelocity( const Vector &forward, float flMass )
+{
+ // return ( striderbuster_shot_velocity.GetFloat() * forward );
+
+ return BaseClass::PhysGunLaunchVelocity(forward,flMass) * sk_helicopter_grenaderadius.GetFloat();
+}
+#endif
+
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+float CGrenadeHelicopter::GetBombLifetime()
+{
+#if HL2_EPISODIC
+ return m_flLifetime;
+#else
+ return BOMB_LIFETIME;
+#endif
+}
+
+
+//------------------------------------------------------------------------------
+// Pow!
+//------------------------------------------------------------------------------
+int CGrenadeHelicopter::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ // We don't take blast damage
+ if ( info.GetDamageType() & DMG_BLAST )
+ return 0;
+
+ return BaseClass::OnTakeDamage( info );
+}
+
+
+//------------------------------------------------------------------------------
+// Pow!
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::DoExplosion( const Vector &vecOrigin, const Vector &vecVelocity )
+{
+ ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity() ? GetOwnerEntity() : this, sk_helicopter_grenadedamage.GetFloat(),
+ sk_helicopter_grenaderadius.GetFloat(), (SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODECAL|SF_ENVEXPLOSION_NOFIREBALL|SF_ENVEXPLOSION_NOPARTICLES),
+ sk_helicopter_grenadeforce.GetFloat(), this );
+
+ if ( GetShakeAmplitude() )
+ {
+ UTIL_ScreenShake( GetAbsOrigin(), GetShakeAmplitude(), 150.0, 1.0, GetShakeRadius(), SHAKE_START );
+ }
+
+ CEffectData data;
+
+ // If we're under water do a water explosion
+ if ( GetWaterLevel() != 0 && (GetWaterType() & CONTENTS_WATER) )
+ {
+ data.m_vOrigin = WorldSpaceCenter();
+ data.m_flMagnitude = 128;
+ data.m_flScale = 128;
+ data.m_fFlags = 0;
+ DispatchEffect( "WaterSurfaceExplosion", data );
+ }
+ else
+ {
+ // Otherwise do a normal explosion
+ data.m_vOrigin = GetAbsOrigin();
+ DispatchEffect( "HelicopterMegaBomb", data );
+ }
+
+ UTIL_Remove( this );
+}
+
+
+//------------------------------------------------------------------------------
+// I think I Pow!
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::ExplodeThink(void)
+{
+#ifdef HL2_EPISODIC
+ // remember if we were thrown by player, we can only determine this prior to explosion
+ bool bIsThrownByPlayer = IsThrownByPlayer();
+ int iHealthBefore = 0;
+ // get the health of the helicopter we came from prior to our explosion
+ CNPC_AttackHelicopter *pOwner = dynamic_cast<CNPC_AttackHelicopter *>( GetOriginalThrower() );
+ if ( pOwner )
+ {
+ iHealthBefore = pOwner->GetHealth();
+ }
+#endif // HL2_EPISODIC
+
+ Vector vecVelocity;
+ GetVelocity( &vecVelocity, NULL );
+ DoExplosion( GetAbsOrigin(), vecVelocity );
+
+#ifdef HL2_EPISODIC
+ // if we were thrown by player, look at health of helicopter after explosion and determine if we damaged it
+ if ( bIsThrownByPlayer && pOwner && ( iHealthBefore > 0 ) )
+ {
+ int iHealthAfter = pOwner->GetHealth();
+ if ( iHealthAfter == iHealthBefore )
+ {
+ // The player threw us, we exploded due to timer, and we did not damage the helicopter that fired us. Send a miss event
+ SendMissEvent();
+ }
+ }
+#endif // HL2_EPISODIC
+
+}
+
+
+//------------------------------------------------------------------------------
+// I think I Pow!
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity )
+{
+ ResolveFlyCollisionBounce( trace, vecVelocity, 0.1f );
+}
+
+
+//------------------------------------------------------------------------------
+// Contact grenade, explode when it touches something
+//------------------------------------------------------------------------------
+void CGrenadeHelicopter::ExplodeConcussion( CBaseEntity *pOther )
+{
+ if ( !pOther->IsSolid() )
+ return;
+
+ if ( !m_bExplodeOnContact )
+ {
+ if ( pOther->IsWorld() )
+ return;
+
+ if ( hl2_episodic.GetBool() )
+ {
+ // Don't hit anything other than vehicles
+ if ( pOther->GetCollisionGroup() != COLLISION_GROUP_VEHICLE )
+ return;
+ }
+ }
+
+#ifdef HL2_EPISODIC
+ CBaseEntity *pEntityHit = pOther;
+ if ( pEntityHit->ClassMatches( "phys_bone_follower" ) && pEntityHit->GetOwnerEntity() )
+ {
+ pEntityHit = pEntityHit->GetOwnerEntity();
+ }
+ if ( ( CLASS_COMBINE_GUNSHIP != pEntityHit->Classify() ) || !pEntityHit->ClassMatches( "npc_helicopter" ) )
+ {
+ // We hit something other than a helicopter. If the player threw us, send a miss event
+ if ( IsThrownByPlayer() )
+ {
+ SendMissEvent();
+ }
+ }
+#endif // HL2_EPISODIC
+
+ Vector vecVelocity;
+ GetVelocity( &vecVelocity, NULL );
+ DoExplosion( GetAbsOrigin(), vecVelocity );
+}
+
+
+#ifdef HL2_EPISODIC
+//-----------------------------------------------------------------------------
+// Purpose: The bomb will act differently when picked up by the player
+//-----------------------------------------------------------------------------
+void CGrenadeHelicopter::OnPhysGunPickup(CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
+{
+ if ( reason == PICKED_UP_BY_CANNON )
+ {
+ if ( !m_bPickedUp )
+ {
+ if( m_hWarningSprite.Get() != NULL )
+ {
+ UTIL_Remove( m_hWarningSprite );
+ m_hWarningSprite.Set(NULL);
+ }
+
+ // Turn on
+ BecomeActive();
+
+ // Change the warning sound to a captured sound.
+ SetContextThink( &CGrenadeHelicopter::RampSoundThink, gpGlobals->curtime + GetBombLifetime() - BOMB_RAMP_SOUND_TIME, s_pRampSoundContext );
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundDestroy( m_pWarnSound );
+
+ CReliableBroadcastRecipientFilter filter;
+ m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopterGrenade.PingCaptured" );
+ controller.Play( m_pWarnSound, 1.0, PITCH_NORM );
+
+ // Reset our counter so the player has more time
+ SetThink( &CGrenadeHelicopter::ExplodeThink );
+ SetNextThink( gpGlobals->curtime + GetBombLifetime() );
+
+ SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + GetBombLifetime() - 2.0f, s_pWarningBlinkerContext );
+
+#ifdef HL2_EPISODIC
+ m_nSkin = (int)SKIN_REGULAR;
+ m_flBlinkFastTime = gpGlobals->curtime + GetBombLifetime() - 1.0f;
+#endif//HL2_EPISODIC
+
+ // Stop us from sparing damage to the helicopter that dropped us
+ SetOwnerEntity( pPhysGunUser );
+ PhysEnableEntityCollisions( this, m_hCollisionObject );
+
+ // Don't do this again!
+ m_bPickedUp = true;
+
+ m_OnPhysGunOnlyPickup.FireOutput( pPhysGunUser, this );
+ }
+ }
+
+ BaseClass::OnPhysGunPickup( pPhysGunUser, reason );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGrenadeHelicopter::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason )
+{
+ if ( reason == LAUNCHED_BY_CANNON )
+ {
+ // Enable world touches.
+ unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
+ VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
+
+ // Explode on contact
+ SetTouch( &CGrenadeHelicopter::ExplodeConcussion );
+ m_bExplodeOnContact = true;
+
+ }
+
+ BaseClass::OnPhysGunDrop( pPhysGunUser, reason );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns if the player threw this grenade w/phys gun
+//-----------------------------------------------------------------------------
+bool CGrenadeHelicopter::IsThrownByPlayer()
+{
+ // if player is the owner and we're set to explode on contact, then the player threw this grenade.
+ return ( ( GetOwnerEntity() == UTIL_GetLocalPlayer() ) && m_bExplodeOnContact );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If player threw this grenade, sends a miss event
+//-----------------------------------------------------------------------------
+void CGrenadeHelicopter::SendMissEvent()
+{
+ // send a miss event
+ IGameEvent *event = gameeventmanager->CreateEvent( "helicopter_grenade_punt_miss" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+}
+
+#endif // HL2_EPISODIC
+
+//-----------------------------------------------------------------------------
+//
+// This entity is used to create little force spheres that the helicopters should avoid.
+//
+//-----------------------------------------------------------------------------
+CUtlVector< CAvoidSphere::AvoidSphereHandle_t > CAvoidSphere::s_AvoidSpheres;
+
+#define SF_AVOIDSPHERE_AVOID_BELOW 0x00010000
+
+LINK_ENTITY_TO_CLASS( npc_heli_avoidsphere, CAvoidSphere );
+
+BEGIN_DATADESC( CAvoidSphere )
+
+ DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
+
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+// Creates an avoidance sphere
+//-----------------------------------------------------------------------------
+CBaseEntity *CreateHelicopterAvoidanceSphere( CBaseEntity *pParent, int nAttachment, float flRadius, bool bAvoidBelow )
+{
+ CAvoidSphere *pSphere = static_cast<CAvoidSphere*>(CreateEntityByName( "npc_heli_avoidsphere" ));
+ pSphere->Init( flRadius );
+ if ( bAvoidBelow )
+ {
+ pSphere->AddSpawnFlags( SF_AVOIDSPHERE_AVOID_BELOW );
+ }
+ pSphere->Spawn();
+ pSphere->SetParent( pParent, nAttachment );
+ pSphere->SetLocalOrigin( vec3_origin );
+ pSphere->SetLocalAngles( vec3_angle );
+ pSphere->SetOwnerEntity( pParent );
+ return pSphere;
+}
+
+
+//-----------------------------------------------------------------------------
+// Init
+//-----------------------------------------------------------------------------
+void CAvoidSphere::Init( float flRadius )
+{
+ m_flRadius = flRadius;
+}
+
+
+//-----------------------------------------------------------------------------
+// Spawn, remove
+//-----------------------------------------------------------------------------
+void CAvoidSphere::Activate( )
+{
+ BaseClass::Activate();
+ s_AvoidSpheres.AddToTail( this );
+}
+
+void CAvoidSphere::UpdateOnRemove( )
+{
+ s_AvoidSpheres.FindAndRemove( this );
+ BaseClass::UpdateOnRemove();
+}
+
+
+//-----------------------------------------------------------------------------
+// Where are how should we avoid?
+//-----------------------------------------------------------------------------
+void CAvoidSphere::ComputeAvoidanceForces( CBaseEntity *pEntity, float flEntityRadius,
+ float flAvoidTime, Vector *pVecAvoidForce )
+{
+ pVecAvoidForce->Init( );
+
+ Vector vecEntityDelta;
+ VectorMultiply( pEntity->GetAbsVelocity(), flAvoidTime, vecEntityDelta );
+ Vector vecEntityCenter = pEntity->WorldSpaceCenter();
+
+ for ( int i = s_AvoidSpheres.Count(); --i >= 0; )
+ {
+ CAvoidSphere *pSphere = s_AvoidSpheres[i].Get();
+ const Vector &vecAvoidCenter = pSphere->WorldSpaceCenter();
+
+ // NOTE: This test can be thought of sweeping a sphere through space
+ // and seeing if it intersects the avoidance sphere
+ float flTotalRadius = flEntityRadius + pSphere->m_flRadius;
+ float t1, t2;
+ if ( !IntersectRayWithSphere( vecEntityCenter, vecEntityDelta,
+ vecAvoidCenter, flTotalRadius, &t1, &t2 ) )
+ {
+ continue;
+ }
+
+ // NOTE: The point of closest approach is at the average t value
+ Vector vecClosestApproach;
+ float flAverageT = (t1 + t2) * 0.5f;
+ VectorMA( vecEntityCenter, flAverageT, vecEntityDelta, vecClosestApproach );
+
+ // Add velocity to make it be pushed out away from the sphere center
+ // without totally counteracting its velocity.
+ Vector vecDir;
+ VectorSubtract( vecClosestApproach, vecAvoidCenter, vecDir );
+ float flZDist = vecDir.z;
+ float flDist = VectorNormalize( vecDir );
+ float flDistToTravel;
+ if ( flDist < 0.01f )
+ {
+ flDist = 0.01f;
+ vecDir.Init( 0, 0, 1 );
+ flDistToTravel = flTotalRadius;
+ }
+ else
+ {
+ // make the chopper always avoid *above*
+ // That means if a force would be applied to push the chopper down,
+ // figure out a new distance to travel that would push the chopper up.
+ if ( flZDist < 0.0f && !pSphere->HasSpawnFlags(SF_AVOIDSPHERE_AVOID_BELOW) )
+ {
+ Vector vecExitPoint;
+ vecDir.z = -vecDir.z;
+ VectorMA( vecAvoidCenter, flTotalRadius, vecDir, vecExitPoint );
+ VectorSubtract( vecExitPoint, vecClosestApproach, vecDir );
+ flDistToTravel = VectorNormalize( vecDir );
+ }
+ else
+ {
+ Assert( flDist <= flTotalRadius );
+ flDistToTravel = flTotalRadius - flDist;
+ }
+ }
+
+ // The actual force amount is easy to think about:
+ // We need to change the position by dx over a time dt, so dv = dx/dt
+ // But so it doesn't explode, lets clamp t1 to a not-unreasonable time
+ if ( t1 < 0.25f )
+ {
+ t1 = 0.25f;
+ }
+
+ float flForce = 1.25f * flDistToTravel / t1;
+ vecDir *= flForce;
+
+ *pVecAvoidForce += vecDir;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// This entity is used to create little force boxes that the helicopters should avoid.
+//
+//-----------------------------------------------------------------------------
+CUtlVector< CAvoidBox::AvoidBoxHandle_t > CAvoidBox::s_AvoidBoxes;
+
+#define SF_AVOIDBOX_AVOID_BELOW 0x00010000
+
+LINK_ENTITY_TO_CLASS( npc_heli_avoidbox, CAvoidBox );
+
+BEGIN_DATADESC( CAvoidBox )
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+// Spawn, remove
+//-----------------------------------------------------------------------------
+void CAvoidBox::Spawn( )
+{
+ SetModel( STRING( GetModelName() ) );
+ SetSolid( SOLID_BSP );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ AddEffects( EF_NODRAW );
+}
+
+void CAvoidBox::Activate( )
+{
+ BaseClass::Activate();
+ s_AvoidBoxes.AddToTail( this );
+}
+
+void CAvoidBox::UpdateOnRemove( )
+{
+ s_AvoidBoxes.FindAndRemove( this );
+ BaseClass::UpdateOnRemove();
+}
+
+
+//-----------------------------------------------------------------------------
+// Where are how should we avoid?
+//-----------------------------------------------------------------------------
+void CAvoidBox::ComputeAvoidanceForces( CBaseEntity *pEntity, float flEntityRadius, float flAvoidTime, Vector *pVecAvoidForce )
+{
+ pVecAvoidForce->Init( );
+
+ Vector vecEntityDelta, vecEntityEnd;
+ VectorMultiply( pEntity->GetAbsVelocity(), flAvoidTime, vecEntityDelta );
+ Vector vecEntityCenter = pEntity->WorldSpaceCenter();
+ VectorAdd( vecEntityCenter, vecEntityDelta, vecEntityEnd );
+
+ Vector vecVelDir = pEntity->GetAbsVelocity();
+ VectorNormalize( vecVelDir );
+
+ for ( int i = s_AvoidBoxes.Count(); --i >= 0; )
+ {
+ CAvoidBox *pBox = s_AvoidBoxes[i].Get();
+
+ const Vector &vecAvoidCenter = pBox->WorldSpaceCenter();
+
+ // NOTE: This test can be thought of sweeping a sphere through space
+ // and seeing if it intersects the avoidance box
+ float flTotalRadius = flEntityRadius + pBox->BoundingRadius();
+ float t1, t2;
+ if ( !IntersectInfiniteRayWithSphere( vecEntityCenter, vecEntityDelta,
+ vecAvoidCenter, flTotalRadius, &t1, &t2 ) )
+ {
+ continue;
+ }
+
+ if (( t2 < 0.0f ) || ( t1 > 1.0f ))
+ continue;
+
+ // Unlike the avoid spheres, we also need to make sure the ray intersects the box
+ Vector vecLocalCenter, vecLocalDelta;
+ pBox->CollisionProp()->WorldToCollisionSpace( vecEntityCenter, &vecLocalCenter );
+ pBox->CollisionProp()->WorldDirectionToCollisionSpace( vecEntityDelta, &vecLocalDelta );
+
+ Vector vecBoxMin( -flEntityRadius, -flEntityRadius, -flEntityRadius );
+ Vector vecBoxMax( flEntityRadius, flEntityRadius, flEntityRadius );
+ vecBoxMin += pBox->CollisionProp()->OBBMins();
+ vecBoxMax += pBox->CollisionProp()->OBBMaxs();
+
+ trace_t tr;
+ if ( !IntersectRayWithBox( vecLocalCenter, vecLocalDelta, vecBoxMin, vecBoxMax, 0.0f, &tr ) )
+ continue;
+
+ // NOTE: The point of closest approach is at the average t value
+ Vector vecClosestApproach;
+ float flAverageT = (t1 + t2) * 0.5f;
+ VectorMA( vecEntityCenter, flAverageT, vecEntityDelta, vecClosestApproach );
+
+ // Add velocity to make it be pushed out away from the sphere center
+ // without totally counteracting its velocity.
+ Vector vecDir;
+ VectorSubtract( vecClosestApproach, vecAvoidCenter, vecDir );
+
+ // Limit unnecessary sideways motion
+ if ( ( tr.plane.type != 3 ) || ( tr.plane.normal[2] > 0.0f ) )
+ {
+ vecDir.x *= 0.1f;
+ vecDir.y *= 0.1f;
+ }
+
+ float flZDist = vecDir.z;
+ float flDist = VectorNormalize( vecDir );
+ float flDistToTravel;
+ if ( flDist < 10.0f )
+ {
+ flDist = 10.0f;
+ vecDir.Init( 0, 0, 1 );
+ flDistToTravel = flTotalRadius;
+ }
+ else
+ {
+ // make the chopper always avoid *above*
+ // That means if a force would be applied to push the chopper down,
+ // figure out a new distance to travel that would push the chopper up.
+ if ( flZDist < 0.0f && !pBox->HasSpawnFlags(SF_AVOIDSPHERE_AVOID_BELOW) )
+ {
+ Vector vecExitPoint;
+ vecDir.z = -vecDir.z;
+ VectorMA( vecAvoidCenter, flTotalRadius, vecDir, vecExitPoint );
+ VectorSubtract( vecExitPoint, vecClosestApproach, vecDir );
+ flDistToTravel = VectorNormalize( vecDir );
+ }
+ else
+ {
+ Assert( flDist <= flTotalRadius );
+ flDistToTravel = flTotalRadius - flDist;
+ }
+ }
+
+ // The actual force amount is easy to think about:
+ // We need to change the position by dx over a time dt, so dv = dx/dt
+ // But so it doesn't explode, lets clamp t1 to a not-unreasonable time
+ if ( t1 < 0.25f )
+ {
+ t1 = 0.25f;
+ }
+
+ float flForce = 1.5f * flDistToTravel / t1;
+ vecDir *= flForce;
+
+ *pVecAvoidForce += vecDir;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// This entity is used to create little force boxes that the helicopters should avoid.
+//
+//-----------------------------------------------------------------------------
+CUtlVector< CBombSuppressor::BombSuppressorHandle_t > CBombSuppressor::s_BombSuppressors;
+
+LINK_ENTITY_TO_CLASS( npc_heli_nobomb, CBombSuppressor );
+
+BEGIN_DATADESC( CBombSuppressor )
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+// Spawn, remove
+//-----------------------------------------------------------------------------
+void CBombSuppressor::Spawn( )
+{
+ SetModel( STRING( GetModelName() ) );
+ SetSolid( SOLID_BSP );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ AddEffects( EF_NODRAW );
+}
+
+void CBombSuppressor::Activate( )
+{
+ BaseClass::Activate();
+ s_BombSuppressors.AddToTail( this );
+}
+
+void CBombSuppressor::UpdateOnRemove( )
+{
+ s_BombSuppressors.FindAndRemove( this );
+ BaseClass::UpdateOnRemove();
+}
+
+
+//-----------------------------------------------------------------------------
+// Where are how should we avoid?
+//-----------------------------------------------------------------------------
+bool CBombSuppressor::CanBomb( const Vector &vecPosition )
+{
+ for ( int i = s_BombSuppressors.Count(); --i >= 0; )
+ {
+ CBombSuppressor *pBox = s_BombSuppressors[i].Get();
+ if ( pBox->CollisionProp()->IsPointInBounds( vecPosition ) )
+ return false;
+ }
+
+ return true;
+}
+
+LINK_ENTITY_TO_CLASS( helicopter_chunk, CHelicopterChunk );
+
+BEGIN_DATADESC( CHelicopterChunk )
+
+ DEFINE_THINKFUNC( FallThink ),
+
+ DEFINE_FIELD( m_bLanded, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hMaster, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_nChunkID, FIELD_INTEGER ),
+ DEFINE_PHYSPTR( m_pTailConstraint ),
+ DEFINE_PHYSPTR( m_pCockpitConstraint ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHelicopterChunk::Spawn( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHelicopterChunk::FallThink( void )
+{
+ if ( m_bLanded )
+ {
+ SetThink( NULL );
+ return;
+ }
+
+ if ( random->RandomInt( 0, 8 ) == 0 )
+ {
+ CEffectData data;
+ data.m_vOrigin = GetAbsOrigin() + RandomVector( -64, 64 );
+ DispatchEffect( "HelicopterMegaBomb", data );
+
+ EmitSound( "BaseExplosionEffect.Sound" );
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// *pEvent -
+//-----------------------------------------------------------------------------
+void CHelicopterChunk::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
+{
+ BaseClass::VPhysicsCollision( index, pEvent );
+
+ if ( m_bLanded == false )
+ {
+ int otherIndex = !index;
+ CBaseEntity *pOther = pEvent->pEntities[otherIndex];
+ if ( !pOther )
+ return;
+
+ if ( pOther->IsWorld() )
+ {
+ CollisionCallback( this );
+
+ m_bLanded = true;
+ SetThink( NULL );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pCaller -
+//-----------------------------------------------------------------------------
+void CHelicopterChunk::CollisionCallback( CHelicopterChunk *pCaller )
+{
+ if ( m_bLanded )
+ return;
+
+ if ( m_hMaster != NULL )
+ {
+ m_hMaster->CollisionCallback( this );
+ }
+ else
+ {
+ // Break our other constraints
+ if ( m_pTailConstraint )
+ {
+ physenv->DestroyConstraint( m_pTailConstraint );
+ m_pTailConstraint = NULL;
+ }
+
+ if ( m_pCockpitConstraint )
+ {
+ physenv->DestroyConstraint( m_pCockpitConstraint );
+ m_pCockpitConstraint = NULL;
+ }
+
+ // Add a dust cloud
+ AR2Explosion *pExplosion = AR2Explosion::CreateAR2Explosion( GetAbsOrigin() );
+
+ if ( pExplosion != NULL )
+ {
+ pExplosion->SetLifetime( 10 );
+ }
+
+ // Make a loud noise
+ EmitSound( "NPC_AttackHelicopter.Crash" );
+
+ m_bLanded = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &vecPos -
+// &vecAngles -
+// &vecVelocity -
+// *pszModelName -
+// Output : CHelicopterChunk
+//-----------------------------------------------------------------------------
+CHelicopterChunk *CHelicopterChunk::CreateHelicopterChunk( const Vector &vecPos, const QAngle &vecAngles, const Vector &vecVelocity, const char *pszModelName, int chunkID )
+{
+ // Drop a flaming, smoking chunk.
+ CHelicopterChunk *pChunk = CREATE_ENTITY( CHelicopterChunk, "helicopter_chunk" );
+
+ if ( pChunk == NULL )
+ return NULL;
+
+ pChunk->Spawn();
+
+ pChunk->SetAbsOrigin( vecPos );
+ pChunk->SetAbsAngles( vecAngles );
+
+ pChunk->SetModel( pszModelName );
+
+ pChunk->m_nChunkID = chunkID;
+ pChunk->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE );
+
+ IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
+
+ // Set the velocity
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->EnableMotion( true );
+ Vector vecChunkVelocity;
+ AngularImpulse angImpulse;
+
+ vecChunkVelocity = vecVelocity;
+ angImpulse = vec3_origin;
+
+ pPhysicsObject->SetVelocity(&vecChunkVelocity, &angImpulse );
+ }
+
+ pChunk->SetThink( &CHelicopterChunk::FallThink );
+ pChunk->SetNextThink( gpGlobals->curtime + 0.1f );
+
+ pChunk->m_bLanded = false;
+
+ SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
+ pSmokeTrail->FollowEntity( pChunk, "damage" );
+
+ pSmokeTrail->m_SpawnRate = 4;
+ pSmokeTrail->m_ParticleLifetime = 2.0f;
+
+ pSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f );
+ pSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 );
+
+ pSmokeTrail->m_StartSize = 32;
+ pSmokeTrail->m_EndSize = 64;
+ pSmokeTrail->m_SpawnRadius= 8;
+ pSmokeTrail->m_MinSpeed = 0;
+ pSmokeTrail->m_MaxSpeed = 8;
+ pSmokeTrail->m_Opacity = 0.35f;
+
+ CFireTrail *pFireTrail = CFireTrail::CreateFireTrail();
+
+ if ( pFireTrail == NULL )
+ return pChunk;
+
+ pFireTrail->FollowEntity( pChunk, "damage" );
+ pFireTrail->SetParent( pChunk, 1 );
+ pFireTrail->SetLocalOrigin( vec3_origin );
+ pFireTrail->SetMoveType( MOVETYPE_NONE );
+ pFireTrail->SetLifetime( 10.0f );
+
+ return pChunk;
+}