From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/hl2/npc_attackchopper.cpp | 12234 ++++++++++++------------- 1 file changed, 6117 insertions(+), 6117 deletions(-) (limited to 'mp/src/game/server/hl2/npc_attackchopper.cpp') 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 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 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 m_hSensor; - float m_flAvoidMetric; - AngularImpulse m_vecLastAngVelocity; - CHandle 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 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(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(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( 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(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(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(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(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(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(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(GetOwnerEntity())->InputDropBomb( myVersion ); -} - -void CBombDropSensor::InputDropBombStraightDown( inputdata_t &inputdata ) -{ - inputdata_t myVersion = inputdata; - myVersion.pActivator = this; - assert_cast(GetOwnerEntity())->InputDropBombStraightDown( myVersion ); -} - -void CBombDropSensor::InputDropBombAtTarget( inputdata_t &inputdata ) -{ - inputdata_t myVersion = inputdata; - myVersion.pActivator = this; - assert_cast(GetOwnerEntity())->InputDropBombAtTarget( myVersion ); -} - -void CBombDropSensor::InputDropBombAtTargetAlways( inputdata_t &inputdata ) -{ - inputdata_t myVersion = inputdata; - myVersion.pActivator = this; - assert_cast(GetOwnerEntity())->InputDropBombAtTargetAlways( myVersion ); -} - -void CBombDropSensor::InputDropBombDelay( inputdata_t &inputdata ) -{ - inputdata_t myVersion = inputdata; - myVersion.pActivator = this; - assert_cast(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( 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(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 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 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 m_hSensor; + float m_flAvoidMetric; + AngularImpulse m_vecLastAngVelocity; + CHandle 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 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(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(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( 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(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(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(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(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(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(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(GetOwnerEntity())->InputDropBomb( myVersion ); +} + +void CBombDropSensor::InputDropBombStraightDown( inputdata_t &inputdata ) +{ + inputdata_t myVersion = inputdata; + myVersion.pActivator = this; + assert_cast(GetOwnerEntity())->InputDropBombStraightDown( myVersion ); +} + +void CBombDropSensor::InputDropBombAtTarget( inputdata_t &inputdata ) +{ + inputdata_t myVersion = inputdata; + myVersion.pActivator = this; + assert_cast(GetOwnerEntity())->InputDropBombAtTarget( myVersion ); +} + +void CBombDropSensor::InputDropBombAtTargetAlways( inputdata_t &inputdata ) +{ + inputdata_t myVersion = inputdata; + myVersion.pActivator = this; + assert_cast(GetOwnerEntity())->InputDropBombAtTargetAlways( myVersion ); +} + +void CBombDropSensor::InputDropBombDelay( inputdata_t &inputdata ) +{ + inputdata_t myVersion = inputdata; + myVersion.pActivator = this; + assert_cast(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( 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(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; +} -- cgit v1.2.3