sqwarmed/sdk_src/game/server/hl2/npc_attackchopper.cpp

6135 lines
194 KiB
C++

//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "cbase.h"
#include "ai_network.h"
#include "ai_default.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_node.h"
#include "ai_task.h"
#include "entitylist.h"
#include "basecombatweapon.h"
#include "soundenvelope.h"
#include "gib.h"
#include "gamerules.h"
#include "ammodef.h"
#include "grenade_homer.h"
#include "cbasehelicopter.h"
#include "engine/IEngineSound.h"
#include "IEffects.h"
#include "globals.h"
#include "explode.h"
#include "movevars_shared.h"
#include "smoke_trail.h"
#include "ar2_explosion.h"
#include "collisionutils.h"
#include "props.h"
#include "EntityFlame.h"
#include "decals.h"
#include "effect_dispatch_data.h"
#include "te_effect_dispatch.h"
#include "ai_spotlight.h"
#include "vphysics/constraints.h"
#include "physics_saverestore.h"
#include "ai_memory.h"
#include "npc_attackchopper.h"
#ifdef HL2_EPISODIC
#include "physics_bone_follower.h"
#endif // HL2_EPISODIC
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// -------------------------------------
// Bone controllers
// -------------------------------------
#define CHOPPER_DRONE_NAME "models/combine_helicopter/helicopter_bomb01.mdl"
#define CHOPPER_MODEL_NAME "models/combine_helicopter.mdl"
#define CHOPPER_MODEL_CORPSE_NAME "models/combine_helicopter_broken.mdl"
#define CHOPPER_RED_LIGHT_SPRITE "sprites/redglow1.vmt"
#define CHOPPER_MAX_SMALL_CHUNKS 1
#define CHOPPER_MAX_CHUNKS 3
static const char *s_pChunkModelName[CHOPPER_MAX_CHUNKS] =
{
"models/gibs/helicopter_brokenpiece_01.mdl",
"models/gibs/helicopter_brokenpiece_02.mdl",
"models/gibs/helicopter_brokenpiece_03.mdl",
};
#define BOMB_SKIN_LIGHT_ON 1
#define BOMB_SKIN_LIGHT_OFF 0
#define HELICOPTER_CHUNK_COCKPIT "models/gibs/helicopter_brokenpiece_04_cockpit.mdl"
#define HELICOPTER_CHUNK_TAIL "models/gibs/helicopter_brokenpiece_05_tailfan.mdl"
#define HELICOPTER_CHUNK_BODY "models/gibs/helicopter_brokenpiece_06_body.mdl"
#define CHOPPER_MAX_SPEED (60 * 17.6f)
#define CHOPPER_MAX_FIRING_SPEED 250.0f
#define CHOPPER_MAX_GUN_DIST 2000.0f
#define CHOPPER_ACCEL_RATE 500
#define CHOPPER_ACCEL_RATE_BOOST 1500
#define DEFAULT_FREE_KNOWLEDGE_DURATION 5.0f
// -------------------------------------
// Pathing data
#define CHOPPER_LEAD_DISTANCE 800.0f
#define CHOPPER_MIN_CHASE_DIST_DIFF 128.0f // Distance threshold used to determine when a target has moved enough to update our navigation to it
#define CHOPPER_MIN_AGGRESSIVE_CHASE_DIST_DIFF 64.0f
#define CHOPPER_AVOID_DIST 512.0f
#define CHOPPER_ARRIVE_DIST 128.0f
#define CHOPPER_MAX_CIRCLE_OF_DEATH_FOLLOW_SPEED 450.0f
#define CHOPPER_MIN_CIRCLE_OF_DEATH_RADIUS 150.0f
#define CHOPPER_MAX_CIRCLE_OF_DEATH_RADIUS 350.0f
#define CHOPPER_BOMB_DROP_COUNT 6
// Bullrush
#define CHOPPER_BULLRUSH_MODE_DISTANCE g_helicopter_bullrush_distance.GetFloat()
#define CHOPPER_BULLRUSH_ENEMY_BOMB_DISTANCE g_helicopter_bullrush_bomb_enemy_distance.GetFloat()
#define CHOPPER_BULLRUSH_ENEMY_BOMB_TIME g_helicopter_bullrush_bomb_time.GetFloat()
#define CHOPPER_BULLRUSH_ENEMY_BOMB_SPEED g_helicopter_bullrush_bomb_speed.GetFloat()
#define CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET g_helicopter_bullrush_shoot_height.GetFloat()
#define CHOPPER_GUN_CHARGE_TIME g_helicopter_chargetime.GetFloat()
#define CHOPPER_GUN_IDLE_TIME g_helicopter_idletime.GetFloat()
#define CHOPPER_GUN_MAX_FIRING_DIST g_helicopter_maxfiringdist.GetFloat()
#define BULLRUSH_IDLE_PLAYER_FIRE_TIME 6.0f
#define DRONE_SPEED sk_helicopter_drone_speed.GetFloat()
#define SF_HELICOPTER_LOUD_ROTOR_SOUND 0x00010000
#define SF_HELICOPTER_ELECTRICAL_DRONE 0x00020000
#define SF_HELICOPTER_LIGHTS 0x00040000
#define SF_HELICOPTER_IGNORE_AVOID_FORCES 0x00080000
#define SF_HELICOPTER_AGGRESSIVE 0x00100000
#define SF_HELICOPTER_LONG_SHADOW 0x00200000
#define CHOPPER_SLOW_BOMB_SPEED 250
#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED 250
#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_SQ (CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED * CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED)
#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2 450
#define CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2_SQ (CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2 * CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2)
// CVars
ConVar sk_helicopter_health( "sk_helicopter_health","5600");
ConVar sk_helicopter_firingcone( "sk_helicopter_firingcone","20.0", 0, "The angle in degrees of the cone in which the shots will be fired" );
ConVar sk_helicopter_burstcount( "sk_helicopter_burstcount","12", 0, "How many shot bursts to fire after charging up. The bigger the number, the longer the firing is" );
ConVar sk_helicopter_roundsperburst( "sk_helicopter_roundsperburst","5", 0, "How many shots to fire in a single burst" );
ConVar sk_helicopter_grenadedamage( "sk_helicopter_grenadedamage","25.0", 0, "The amount of damage the helicopter grenade deals." );
ConVar sk_helicopter_grenaderadius( "sk_helicopter_grenaderadius","275.0", 0, "The damage radius of the helicopter grenade." );
ConVar sk_helicopter_grenadeforce( "sk_helicopter_grenadeforce","55000.0", 0, "The physics force that the helicopter grenade exerts." );
ConVar sk_helicopter_grenade_puntscale( "sk_helicopter_grenade_puntscale","1.5", 0, "When physpunting a chopper's grenade, scale its velocity by this much." );
// Number of bomb hits it takes to kill a chopper on each skill level.
ConVar sk_helicopter_num_bombs1("sk_helicopter_num_bombs1", "3");
ConVar sk_helicopter_num_bombs2("sk_helicopter_num_bombs2", "5");
ConVar sk_helicopter_num_bombs3("sk_helicopter_num_bombs3", "5");
ConVar sk_npc_dmg_helicopter_to_plr( "sk_npc_dmg_helicopter_to_plr","3", 0, "Damage helicopter shots deal to the player" );
ConVar sk_npc_dmg_helicopter( "sk_npc_dmg_helicopter","6", 0, "Damage helicopter shots deal to everything but the player" );
ConVar sk_helicopter_drone_speed( "sk_helicopter_drone_speed","450.0", 0, "How fast does the zapper drone move?" );
ConVar g_helicopter_chargetime( "g_helicopter_chargetime","2.0", 0, "How much time we have to wait (on average) between the time we start hearing the charging sound + the chopper fires" );
ConVar g_helicopter_bullrush_distance("g_helicopter_bullrush_distance", "5000");
ConVar g_helicopter_bullrush_bomb_enemy_distance("g_helicopter_bullrush_bomb_enemy_distance", "0");
ConVar g_helicopter_bullrush_bomb_time("g_helicopter_bullrush_bomb_time", "10");
ConVar g_helicopter_idletime( "g_helicopter_idletime","3.0", 0, "How much time we have to wait (on average) after we fire before we can charge up again" );
ConVar g_helicopter_maxfiringdist( "g_helicopter_maxfiringdist","2500.0", 0, "The maximum distance the player can be from the chopper before it stops firing" );
ConVar g_helicopter_bullrush_bomb_speed( "g_helicopter_bullrush_bomb_speed","850.0", 0, "The maximum distance the player can be from the chopper before it stops firing" );
ConVar g_helicopter_bullrush_shoot_height( "g_helicopter_bullrush_shoot_height","650.0", 0, "The maximum distance the player can be from the chopper before it stops firing" );
ConVar g_helicopter_bullrush_mega_bomb_health( "g_helicopter_bullrush_mega_bomb_health","0.25", 0, "Fraction of the health of the chopper before it mega-bombs" );
ConVar g_helicopter_bomb_danger_radius( "g_helicopter_bomb_danger_radius", "120" );
Activity ACT_HELICOPTER_DROP_BOMB;
Activity ACT_HELICOPTER_CRASHING;
static const char *s_pBlinkLightThinkContext = "BlinkLights";
static const char *s_pSpotlightThinkContext = "SpotlightThink";
static const char *s_pRampSoundContext = "RampSound";
static const char *s_pWarningBlinkerContext = "WarningBlinker";
static const char *s_pAnimateThinkContext = "Animate";
#define CHOPPER_LIGHT_BLINK_TIME 1.0f
#define CHOPPER_LIGHT_BLINK_TIME_SHORT 0.1f
#define BOMB_LIFETIME 2.5f // Don't access this directly. Call GetBombLifetime();
#define BOMB_RAMP_SOUND_TIME 1.0f
enum
{
MAX_HELICOPTER_LIGHTS = 3,
};
enum
{
SF_GRENADE_HELICOPTER_MEGABOMB = 0x1,
};
#define GRENADE_HELICOPTER_MODEL "models/combine_helicopter/helicopter_bomb01.mdl"
LINK_ENTITY_TO_CLASS( info_target_helicopter_crash, CPointEntity );
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
static inline float ClampSplineRemapVal( float flValue, float flMinValue, float flMaxValue, float flOutMin, float flOutMax )
{
Assert( flMinValue <= flMaxValue );
float flClampedVal = clamp( flValue, flMinValue, flMaxValue );
return SimpleSplineRemapVal( flClampedVal, flMinValue, flMaxValue, flOutMin, flOutMax );
}
//-----------------------------------------------------------------------------
// The bombs the attack helicopter drops
//-----------------------------------------------------------------------------
enum
{
SKIN_REGULAR,
SKIN_DUD,
};
class CGrenadeHelicopter : public CBaseGrenade
{
DECLARE_DATADESC();
public:
DECLARE_CLASS( CGrenadeHelicopter, CBaseGrenade );
virtual void Precache( );
virtual void Spawn( );
virtual void UpdateOnRemove();
virtual void OnEntityEvent( EntityEvent_t event, void *pEventData );
virtual void PhysicsSimulate( void );
virtual float GetShakeAmplitude( void ) { return 25.0; }
virtual float GetShakeRadius( void ) { return sk_helicopter_grenaderadius.GetFloat() * 2; }
virtual int OnTakeDamage( const CTakeDamageInfo &info );
virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
void SetExplodeOnContact( bool bExplode ) { m_bExplodeOnContact = bExplode; }
virtual QAngle PreferredCarryAngles( void ) { return QAngle( -12, 98, 55 ); }
virtual bool HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer ) { return true; }
float GetBombLifetime();
#ifdef HL2_EPISODIC
virtual void OnPhysGunPickup(CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
virtual void OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason );
virtual Class_T Classify( void ) { return CLASS_MISSILE; }
void SetCollisionObject( CBaseEntity *pEntity ) { m_hCollisionObject = pEntity; }
void SendMissEvent();
bool IsThrownByPlayer();
virtual bool ShouldPuntUseLaunchForces( PhysGunForce_t reason ) { return ( reason == PHYSGUN_FORCE_LAUNCHED ); }
virtual Vector PhysGunLaunchVelocity( const Vector &forward, float flMass );
void InputExplodeIn( inputdata_t &inputdata );
#endif // HL2_EPISODIC
private:
// Pow!
void DoExplosion( const Vector &vecOrigin, const Vector &vecVelocity );
void ExplodeThink();
void RampSoundThink();
void WarningBlinkerThink();
void StopWarningBlinker();
void AnimateThink();
void ExplodeConcussion( CBaseEntity *pOther );
void BecomeActive();
void ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity );
bool m_bActivated;
bool m_bExplodeOnContact;
CSoundPatch *m_pWarnSound;
EHANDLE m_hWarningSprite;
bool m_bBlinkerAtTop;
#ifdef HL2_EPISODIC
float m_flLifetime;
EHANDLE m_hCollisionObject; // Pointer to object we re-enable collisions with when picked up
bool m_bPickedUp;
float m_flBlinkFastTime;
COutputEvent m_OnPhysGunOnlyPickup;
#endif // HL2_EPISODIC
};
//-----------------------------------------------------------------------------
// The bombs the attack helicopter drops
//-----------------------------------------------------------------------------
class CBombDropSensor : public CBaseEntity
{
DECLARE_DATADESC();
public:
DECLARE_CLASS( CBombDropSensor, CBaseEntity );
void Spawn();
// Drop a bomb at a particular location
void InputDropBomb( inputdata_t &inputdata );
void InputDropBombStraightDown( inputdata_t &inputdata );
void InputDropBombAtTarget( inputdata_t &inputdata );
void InputDropBombAtTargetAlways( inputdata_t &inputdata );
void InputDropBombDelay( inputdata_t &inputdata );
};
//-----------------------------------------------------------------------------
// This entity is used to create boxes that the helicopter can't bomb in
//-----------------------------------------------------------------------------
class CBombSuppressor : public CBaseEntity
{
DECLARE_DATADESC();
public:
DECLARE_CLASS( CBombSuppressor, CBaseEntity );
virtual void Spawn( );
virtual void Activate();
virtual void UpdateOnRemove();
static bool CanBomb( const Vector &vecPosition );
private:
typedef CHandle<CBombSuppressor> BombSuppressorHandle_t;
static CUtlVector< BombSuppressorHandle_t > s_BombSuppressors;
};
enum
{
CHUNK_COCKPIT,
CHUNK_BODY,
CHUNK_TAIL
};
//-----------------------------------------------------------------------------
// This entity is used for helicopter gibs with specific properties
//-----------------------------------------------------------------------------
class CHelicopterChunk : public CBaseAnimating
{
DECLARE_DATADESC();
public:
DECLARE_CLASS( CHelicopterChunk, CBaseAnimating );
virtual void Spawn( void );
virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
static CHelicopterChunk *CreateHelicopterChunk( const Vector &vecPos, const QAngle &vecAngles, const Vector &vecVelocity, const char *pszModelName, int chunkID );
int m_nChunkID;
CHandle<CHelicopterChunk> m_hMaster;
IPhysicsConstraint *m_pTailConstraint;
IPhysicsConstraint *m_pCockpitConstraint;
protected:
void CollisionCallback( CHelicopterChunk *pCaller );
void FallThink( void );
bool m_bLanded;
};
//-----------------------------------------------------------------------------
// The attack helicopter
//-----------------------------------------------------------------------------
class CNPC_AttackHelicopter : public CBaseHelicopter
{
public:
DECLARE_CLASS( CNPC_AttackHelicopter, CBaseHelicopter );
DECLARE_DATADESC();
DEFINE_CUSTOM_AI;
CNPC_AttackHelicopter();
~CNPC_AttackHelicopter();
virtual void Precache( void );
virtual void Spawn( void );
virtual void Activate( void );
virtual bool CreateComponents();
virtual int ObjectCaps();
#ifdef HL2_EPISODIC
virtual bool CreateVPhysics( void );
#endif // HL2_EPISODIC
virtual void UpdateOnRemove();
virtual void StopLoopingSounds();
int BloodColor( void ) { return DONT_BLEED; }
Class_T Classify ( void ) { return CLASS_COMBINE_GUNSHIP; }
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr );
virtual int OnTakeDamage( const CTakeDamageInfo &info );
// Shot spread
virtual Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget );
// More Enemy visibility check
virtual bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
// Think!
virtual void PrescheduleThink( void );
// Purpose: Set the gunship's paddles flailing!
virtual void Event_Killed( const CTakeDamageInfo &info );
// Drop a bomb at a particular location
void InputDropBomb( inputdata_t &inputdata );
void InputDropBombStraightDown( inputdata_t &inputdata );
void InputDropBombAtTarget( inputdata_t &inputdata );
void InputDropBombAtTargetAlways( inputdata_t &inputdata );
void InputDropBombAtTargetInternal( inputdata_t &inputdata, bool bCheckFairness );
void InputDropBombDelay( inputdata_t &inputdata );
void InputStartCarpetBombing( inputdata_t &inputdata );
void InputStopCarpetBombing( inputdata_t &inputdata );
virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
virtual const char *GetTracerType( void );
virtual void DoImpactEffect( trace_t &tr, int nDamageType );
virtual void DoMuzzleFlash( void );
// FIXME: Work this back into the base class
virtual bool ShouldUseFixedPatrolLogic() { return true; }
protected:
int m_poseWeapon_Pitch, m_poseWeapon_Yaw, m_poseRudder;
virtual void PopulatePoseParameters( void );
private:
enum GunState_t
{
GUN_STATE_IDLE = 0,
GUN_STATE_CHARGING,
GUN_STATE_FIRING,
};
// Gets the max speed of the helicopter
virtual float GetMaxSpeed();
virtual float GetMaxSpeedFiring();
// Startup the chopper
virtual void Startup();
void InitializeRotorSound( void );
// Weaponry
bool FireGun( void );
// Movement:
virtual void Flight( void );
// Changes the main thinking method of helicopters
virtual void Hunt( void );
// For scripted times where it *has* to shoot
void InputResetIdleTime( inputdata_t &inputdata );
void InputSetHealthFraction( inputdata_t &inputdata );
void InputStartBombExplodeOnContact( inputdata_t &inputdata );
void InputStopBombExplodeOnContact( inputdata_t &inputdata );
void InputEnableAlwaysTransition( inputdata_t &inputdata );
void InputDisableAlwaysTransition( inputdata_t &inputdata );
void InputOutsideTransition( inputdata_t &inputdata );
void InputSetOutsideTransitionTarget( inputdata_t &inputdata );
// Turns off the gun
void InputGunOff( inputdata_t &inputdata );
// Vehicle attack modes
void InputStartBombingVehicle( inputdata_t &inputdata );
void InputStartTrailingVehicle( inputdata_t &inputdata );
void InputStartDefaultBehavior( inputdata_t &inputdata );
void InputStartAlwaysLeadingVehicle( inputdata_t &inputdata );
// Deadly shooting, tex!
void InputEnableDeadlyShooting( inputdata_t &inputdata );
void InputDisableDeadlyShooting( inputdata_t &inputdata );
void InputStartNormalShooting( inputdata_t &inputdata );
void InputStartLongCycleShooting( inputdata_t &inputdata );
void InputStartContinuousShooting( inputdata_t &inputdata );
void InputStartFastShooting( inputdata_t &inputdata );
int GetShootingMode( );
bool IsDeadlyShooting();
// Bombing suppression
void InputEnableBombing( inputdata_t &inputdata );
void InputDisableBombing( inputdata_t &inputdata );
// Visibility tests
void InputDisablePathVisibilityTests( inputdata_t &inputdata );
void InputEnablePathVisibilityTests( inputdata_t &inputdata );
// Death, etc.
void InputSelfDestruct( inputdata_t &inputdata );
// Enemy visibility check
CBaseEntity *FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos );
// Special path navigation when we explicitly want to follow a path
void UpdateFollowPathNavigation();
// Find interesting nearby things to shoot
int BuildMissTargetList( int nCount, CBaseEntity **ppMissCandidates );
// Shoot when the player's your enemy :
void ShootAtPlayer( const Vector &vBasePos, const Vector &vGunDir );
// Shoot when the player's your enemy + he's driving a vehicle
void ShootAtVehicle( const Vector &vBasePos, const Vector &vGunDir );
// Shoot where we're facing
void ShootAtFacingDirection( const Vector &vBasePos, const Vector &vGunDir, bool bFirstShotAccurate );
// Updates the facing direction
void UpdateFacingDirection( const Vector &vecActualDesiredPosition );
// Various states of the helicopter firing...
bool PoseGunTowardTargetDirection( const Vector &vTargetDir );
// Compute the position to fire at (vehicle + non-vehicle case)
void ComputeFireAtPosition( Vector *pVecActualTargetPosition );
void ComputeVehicleFireAtPosition( Vector *pVecActualTargetPosition );
// Various states of the helicopter firing...
bool DoGunIdle( const Vector &vecGunDir, const Vector &vTargetDir );
bool DoGunCharging( );
bool DoGunFiring( const Vector &vBasePos, const Vector &vGunDir, const Vector &vecFireAtPosition );
void FireElectricityGun( );
// Chooses a point within the circle of death to fire in
void PickDirectionToCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition, Vector *pResult );
// Gets a vehicle the enemy is in (if any)
CBaseEntity *GetEnemyVehicle();
// Updates the perpendicular path distance for the chopper
float UpdatePerpPathDistance( float flMaxPathOffset );
// Purpose :
void UpdateEnemyLeading( void );
// Drop those bombs!
void DropBombs( );
// Should we drop those bombs?
bool ShouldDropBombs( void );
// Returns the max firing distance
float GetMaxFiringDistance();
// Make sure we don't hit too many times
void FireBullets( const FireBulletsInfo_t &info );
// Is it "fair" to drop this bomb?
bool IsBombDropFair( const Vector &vecBombStartPos, const Vector &vecVelocity );
// Actually drops the bomb
void CreateBomb( bool bCheckForFairness = true, Vector *pVecVelocity = NULL, bool bMegaBomb = false );
CGrenadeHelicopter *SpawnBombEntity( const Vector &vecPos, const Vector &vecVelocity ); // Spawns the bomb entity and sets it up
// Deliberately aims as close as possible w/o hitting
void AimCloseToTargetButMiss( CBaseEntity *pTarget, float flMinDist, float flMaxDist, const Vector &shootOrigin, Vector *pResult );
// Pops a shot inside the circle of death using the burst rules
void ShootInsideCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition );
// How easy is the target to hit?
void UpdateTargetHittability();
// Add a smoke trail since we've taken more damage
void AddSmokeTrail( const Vector &vecPos );
// Destroy all smoke trails
void DestroySmokeTrails();
// Creates the breakable husk of an attack chopper
void CreateChopperHusk();
// Pow!
void ExplodeAndThrowChunk( const Vector &vecExplosionPos );
// Drop a corpse!
void DropCorpse( int nDamage );
// Should we trigger a damage effect?
bool ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const;
// Become indestructible
void InputBecomeIndestructible( inputdata_t &inputdata );
// Purpose :
float CreepTowardEnemy( float flSpeed, float flMinSpeed, float flMaxSpeed, float flMinDist, float flMaxDist );
// Start bullrush
void InputStartBullrushBehavior( inputdata_t &inputdata );
void GetMaxSpeedAndAccel( float *pMaxSpeed, float *pAccelRate );
void ComputeAngularVelocity( const Vector &vecGoalUp, const Vector &vecFacingDirection );
void ComputeVelocity( const Vector &deltaPos, float flAdditionalHeight, float flMinDistFromSegment, float flMaxDistFromSegment, Vector *pVecAccel );
void FlightDirectlyOverhead( void );
// Methods related to computing leading distance
float ComputeBombingLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle );
float ComputeBullrushLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle );
bool IsCarpetBombing() { return m_bIsCarpetBombing == true; }
// Update the bullrush state
void UpdateBullrushState( void );
// Whether to shoot at or bomb an idle player
bool ShouldBombIdlePlayer( void );
// Different bomb-dropping behavior
void BullrushBombs( );
// Switch to idle
void SwitchToBullrushIdle( void );
// Secondary mode
void SetSecondaryMode( int nMode, bool bRetainTime = false );
bool IsInSecondaryMode( int nMode );
float SecondaryModeTime( ) const;
// Should the chopper shoot the idle player?
bool ShouldShootIdlePlayerInBullrush();
// Shutdown shooting during bullrush
void ShutdownGunDuringBullrush( );
// Updates the enemy
virtual float EnemySearchDistance( );
// Prototype zapper
bool IsValidZapTarget( CBaseEntity *pTarget );
void CreateZapBeam( const Vector &vecTargetPos );
void CreateEntityZapEffect( CBaseEntity *pEnt );
// Blink lights
void BlinkLightsThink();
// Spotlights
void SpotlightThink();
void SpotlightStartup();
void SpotlightShutdown();
CBaseEntity *GetCrashPoint() { return m_hCrashPoint.Get(); }
private:
enum
{
ATTACK_MODE_DEFAULT = 0,
ATTACK_MODE_BOMB_VEHICLE,
ATTACK_MODE_TRAIL_VEHICLE,
ATTACK_MODE_ALWAYS_LEAD_VEHICLE,
ATTACK_MODE_BULLRUSH_VEHICLE,
};
enum
{
MAX_SMOKE_TRAILS = 5,
MAX_EXPLOSIONS = 13,
MAX_CORPSES = 2,
};
enum
{
BULLRUSH_MODE_WAIT_FOR_ENEMY = 0,
BULLRUSH_MODE_GET_DISTANCE,
BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED,
BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER,
BULLRUSH_MODE_SHOOT_GUN,
BULLRUSH_MODE_MEGA_BOMB,
BULLRUSH_MODE_SHOOT_IDLE_PLAYER,
};
enum
{
SHOOT_MODE_DEFAULT = 0,
SHOOT_MODE_LONG_CYCLE,
SHOOT_MODE_CONTINUOUS,
SHOOT_MODE_FAST,
};
#ifdef HL2_EPISODIC
void InitBoneFollowers( void );
CBoneFollowerManager m_BoneFollowerManager;
#endif // HL2_EPISODIC
CAI_Spotlight m_Spotlight;
Vector m_angGun;
QAngle m_vecAngAcceleration;
int m_iAmmoType;
float m_flLastCorpseFall;
GunState_t m_nGunState;
float m_flChargeTime;
float m_flIdleTimeDelay;
int m_nRemainingBursts;
int m_nGrenadeCount;
float m_flPathOffset;
float m_flAcrossTime;
float m_flCurrPathOffset;
int m_nBurstHits;
int m_nMaxBurstHits;
float m_flCircleOfDeathRadius;
int m_nAttackMode;
float m_flInputDropBombTime;
CHandle<CBombDropSensor> m_hSensor;
float m_flAvoidMetric;
AngularImpulse m_vecLastAngVelocity;
CHandle<CBaseEntity> m_hSmokeTrail[MAX_SMOKE_TRAILS];
int m_nSmokeTrailCount;
bool m_bIndestructible;
float m_flGracePeriod;
bool m_bBombsExplodeOnContact;
bool m_bNonCombat;
int m_nNearShots;
int m_nMaxNearShots;
// Bomb dropping attachments
int m_nGunTipAttachment;
int m_nGunBaseAttachment;
int m_nBombAttachment;
int m_nSpotlightAttachment;
float m_flLastFastTime;
// Secondary modes
int m_nSecondaryMode;
float m_flSecondaryModeStartTime;
// Bullrush behavior
bool m_bRushForward;
float m_flBullrushAdditionalHeight;
int m_nBullrushBombMode;
float m_flNextBullrushBombTime;
float m_flNextMegaBombHealth;
// Shooting method
int m_nShootingMode;
bool m_bDeadlyShooting;
// Bombing suppression
bool m_bBombingSuppressed;
// Blinking lights
CHandle<CSprite> m_hLights[MAX_HELICOPTER_LIGHTS];
bool m_bShortBlink;
// Path behavior
bool m_bIgnorePathVisibilityTests;
// Teleport
bool m_bAlwaysTransition;
string_t m_iszTransitionTarget;
// Special attacks
bool m_bIsCarpetBombing;
// Fun damage effects
float m_flGoalRollDmg;
float m_flGoalYawDmg;
// Sounds
CSoundPatch *m_pGunFiringSound;
// Outputs
COutputInt m_OnHealthChanged;
COutputEvent m_OnShotDown;
// Crashing
EHANDLE m_hCrashPoint;
};
#ifdef HL2_EPISODIC
static const char *pFollowerBoneNames[] =
{
"Chopper.Body"
};
#endif // HL2_EPISODIC
LINK_ENTITY_TO_CLASS( npc_helicopter, CNPC_AttackHelicopter );
BEGIN_DATADESC( CNPC_AttackHelicopter )
DEFINE_ENTITYFUNC( FlyTouch ),
DEFINE_EMBEDDED( m_Spotlight ),
#ifdef HL2_EPISODIC
DEFINE_EMBEDDED( m_BoneFollowerManager ),
#endif
DEFINE_FIELD( m_angGun, FIELD_VECTOR ),
DEFINE_FIELD( m_vecAngAcceleration, FIELD_VECTOR ),
DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
DEFINE_FIELD( m_flLastCorpseFall, FIELD_TIME ),
DEFINE_FIELD( m_nGunState, FIELD_INTEGER ),
DEFINE_FIELD( m_flChargeTime, FIELD_TIME ),
DEFINE_FIELD( m_flIdleTimeDelay, FIELD_FLOAT ),
DEFINE_FIELD( m_nRemainingBursts, FIELD_INTEGER ),
DEFINE_FIELD( m_nGrenadeCount, FIELD_INTEGER ),
DEFINE_FIELD( m_flPathOffset, FIELD_FLOAT ),
DEFINE_FIELD( m_flAcrossTime, FIELD_TIME ),
DEFINE_FIELD( m_flCurrPathOffset, FIELD_FLOAT ),
DEFINE_FIELD( m_nBurstHits, FIELD_INTEGER ),
DEFINE_FIELD( m_nMaxBurstHits, FIELD_INTEGER ),
DEFINE_FIELD( m_flCircleOfDeathRadius, FIELD_FLOAT ),
DEFINE_FIELD( m_nAttackMode, FIELD_INTEGER ),
DEFINE_FIELD( m_flInputDropBombTime, FIELD_TIME ),
DEFINE_FIELD( m_hSensor, FIELD_EHANDLE ),
DEFINE_FIELD( m_flAvoidMetric, FIELD_FLOAT ),
DEFINE_FIELD( m_vecLastAngVelocity, FIELD_VECTOR ),
DEFINE_AUTO_ARRAY( m_hSmokeTrail, FIELD_EHANDLE ),
DEFINE_FIELD( m_nSmokeTrailCount, FIELD_INTEGER ),
DEFINE_FIELD( m_nNearShots, FIELD_INTEGER ),
DEFINE_FIELD( m_nMaxNearShots, FIELD_INTEGER ),
// DEFINE_FIELD( m_nGunTipAttachment, FIELD_INTEGER ),
// DEFINE_FIELD( m_nGunBaseAttachment, FIELD_INTEGER ),
// DEFINE_FIELD( m_nBombAttachment, FIELD_INTEGER ),
// DEFINE_FIELD( m_nSpotlightAttachment, FIELD_INTEGER ),
DEFINE_FIELD( m_flLastFastTime, FIELD_TIME ),
DEFINE_FIELD( m_nSecondaryMode, FIELD_INTEGER ),
DEFINE_FIELD( m_flSecondaryModeStartTime, FIELD_TIME ),
DEFINE_FIELD( m_bRushForward, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flBullrushAdditionalHeight, FIELD_FLOAT ),
DEFINE_FIELD( m_nBullrushBombMode, FIELD_INTEGER ),
DEFINE_FIELD( m_flNextBullrushBombTime, FIELD_TIME ),
DEFINE_FIELD( m_flNextMegaBombHealth, FIELD_FLOAT ),
DEFINE_FIELD( m_nShootingMode, FIELD_INTEGER ),
DEFINE_FIELD( m_bDeadlyShooting, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bBombingSuppressed, FIELD_BOOLEAN ),
DEFINE_SOUNDPATCH( m_pGunFiringSound ),
DEFINE_AUTO_ARRAY( m_hLights, FIELD_EHANDLE ),
DEFINE_FIELD( m_bIgnorePathVisibilityTests, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bShortBlink, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bIndestructible, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bBombsExplodeOnContact, FIELD_BOOLEAN ),
DEFINE_KEYFIELD( m_bAlwaysTransition, FIELD_BOOLEAN, "AlwaysTransition" ),
DEFINE_KEYFIELD( m_iszTransitionTarget, FIELD_STRING, "TransitionTarget" ),
DEFINE_FIELD( m_bIsCarpetBombing, FIELD_BOOLEAN ),
DEFINE_INPUTFUNC( FIELD_VOID, "EnableAlwaysTransition", InputEnableAlwaysTransition ),
DEFINE_INPUTFUNC( FIELD_VOID, "DisableAlwaysTransition", InputDisableAlwaysTransition ),
DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
DEFINE_INPUTFUNC( FIELD_STRING, "SetTransitionTarget", InputSetOutsideTransitionTarget ),
DEFINE_KEYFIELD( m_flGracePeriod, FIELD_FLOAT, "GracePeriod" ),
DEFINE_KEYFIELD( m_flMaxSpeed, FIELD_FLOAT, "PatrolSpeed" ),
DEFINE_KEYFIELD( m_bNonCombat, FIELD_BOOLEAN, "NonCombat" ),
DEFINE_FIELD( m_hCrashPoint, FIELD_EHANDLE ),
DEFINE_INPUTFUNC( FIELD_VOID, "ResetIdleTime", InputResetIdleTime ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartAlwaysLeadingVehicle", InputStartAlwaysLeadingVehicle ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartBombingVehicle", InputStartBombingVehicle ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartTrailingVehicle", InputStartTrailingVehicle ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartDefaultBehavior", InputStartDefaultBehavior ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartBullrushBehavior", InputStartBullrushBehavior ),
DEFINE_INPUTFUNC( FIELD_VOID, "DropBomb", InputDropBomb ),
DEFINE_INPUTFUNC( FIELD_VOID, "DropBombStraightDown", InputDropBombStraightDown ),
DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTargetAlways", InputDropBombAtTargetAlways ),
DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTarget", InputDropBombAtTarget ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "DropBombDelay", InputDropBombDelay ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartCarpetBombing", InputStartCarpetBombing ),
DEFINE_INPUTFUNC( FIELD_VOID, "StopCarpetBombing", InputStopCarpetBombing ),
DEFINE_INPUTFUNC( FIELD_VOID, "BecomeIndestructible", InputBecomeIndestructible ),
DEFINE_INPUTFUNC( FIELD_VOID, "EnableDeadlyShooting", InputEnableDeadlyShooting ),
DEFINE_INPUTFUNC( FIELD_VOID, "DisableDeadlyShooting", InputDisableDeadlyShooting ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartNormalShooting", InputStartNormalShooting ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartLongCycleShooting", InputStartLongCycleShooting ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartContinuousShooting", InputStartContinuousShooting ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartFastShooting", InputStartFastShooting ),
DEFINE_INPUTFUNC( FIELD_VOID, "GunOff", InputGunOff ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthFraction", InputSetHealthFraction ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartBombExplodeOnContact", InputStartBombExplodeOnContact ),
DEFINE_INPUTFUNC( FIELD_VOID, "StopBombExplodeOnContact", InputStopBombExplodeOnContact ),
DEFINE_INPUTFUNC( FIELD_VOID, "DisablePathVisibilityTests", InputDisablePathVisibilityTests ),
DEFINE_INPUTFUNC( FIELD_VOID, "EnablePathVisibilityTests", InputEnablePathVisibilityTests ),
DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ),
DEFINE_THINKFUNC( BlinkLightsThink ),
DEFINE_THINKFUNC( SpotlightThink ),
DEFINE_OUTPUT( m_OnHealthChanged, "OnHealthChanged" ),
DEFINE_OUTPUT( m_OnShotDown, "OnShotDown" ),
END_DATADESC()
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
CNPC_AttackHelicopter::CNPC_AttackHelicopter() :
m_bNonCombat( false ),
m_flGracePeriod( 2.0f ),
m_bBombsExplodeOnContact( false )
{
m_flMaxSpeed = 0;
}
CNPC_AttackHelicopter::~CNPC_AttackHelicopter(void)
{
}
//-----------------------------------------------------------------------------
// Purpose: Shuts down looping sounds when we are killed in combat or deleted.
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::StopLoopingSounds()
{
BaseClass::StopLoopingSounds();
if ( m_pGunFiringSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pGunFiringSound );
m_pGunFiringSound = NULL;
}
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void Chopper_PrecacheChunks( CBaseEntity *pChopper )
{
for ( int i = 0; i < CHOPPER_MAX_CHUNKS; ++i )
{
pChopper->PrecacheModel( s_pChunkModelName[i] );
}
pChopper->PrecacheModel( HELICOPTER_CHUNK_COCKPIT );
pChopper->PrecacheModel( HELICOPTER_CHUNK_TAIL );
pChopper->PrecacheModel( HELICOPTER_CHUNK_BODY );
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::Precache( void )
{
BaseClass::Precache();
if ( !HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
{
PrecacheModel( CHOPPER_MODEL_NAME );
}
else
{
PrecacheModel( CHOPPER_DRONE_NAME );
}
PrecacheModel( CHOPPER_RED_LIGHT_SPRITE );
//PrecacheModel( CHOPPER_MODEL_CORPSE_NAME );
// If we're never going to engage in combat, we don't need to load these assets!
if ( m_bNonCombat == false )
{
UTIL_PrecacheOther( "grenade_helicopter" );
UTIL_PrecacheOther( "env_fire_trail" );
Chopper_PrecacheChunks( this );
PrecacheModel("models/combine_soldier.mdl");
UTIL_PrecacheOther( "env_explosion" );
}
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" );
PrecacheEffect( "ChopperMuzzleFlash" );
PrecacheEffect( "TeslaZap" );
PrecacheEffect( "TeslaHitboxes" );
PrecacheEffect( "HelicopterMegaBomb" );
const char *pszTracerName = GetTracerType();
if ( pszTracerName )
{
PrecacheEffect( pszTracerName );
}
}
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_flFrozenMax = 0.0f;
m_takedamage = DAMAGE_YES;
m_nGunState = GUN_STATE_IDLE;
SetHullType( HULL_LARGE_CENTERED );
SetHullSizeNormal();
#ifdef HL2_EPISODIC
CreateVPhysics();
#endif // HL2_EPISODIC
SetPauseState( PAUSE_NO_PAUSE );
m_iMaxHealth = m_iHealth = sk_helicopter_health.GetInt();
m_flMaxSpeed = flLoadedSpeed;
if ( m_flMaxSpeed <= 0 )
{
m_flMaxSpeed = CHOPPER_MAX_SPEED;
}
m_flNextMegaBombHealth = m_iMaxHealth - m_iMaxHealth * g_helicopter_bullrush_mega_bomb_health.GetFloat();
m_nGrenadeCount = CHOPPER_BOMB_DROP_COUNT;
m_flFieldOfView = -1.0; // 360 degrees
m_flIdleTimeDelay = 0.0f;
m_iAmmoType = GetAmmoDef()->Index("HelicopterGun");
InitBoneControllers();
m_fHelicopterFlags = BITS_HELICOPTER_GUN_ON;
m_bSuppressSound = false;
m_flAcrossTime = -1.0f;
m_flPathOffset = 0.0f;
m_flCurrPathOffset = 0.0f;
m_nAttackMode = ATTACK_MODE_DEFAULT;
m_flInputDropBombTime = gpGlobals->curtime;
SetActivity( ACT_IDLE );
int nBombAttachment = LookupAttachment("bomb");
m_hSensor = static_cast<CBombDropSensor*>(CreateEntityByName( "npc_helicoptersensor" ));
m_hSensor->Spawn();
m_hSensor->SetParent( this, nBombAttachment );
m_hSensor->SetLocalOrigin( vec3_origin );
m_hSensor->SetLocalAngles( vec3_angle );
m_hSensor->SetOwnerEntity( this );
AddFlag( FL_AIMTARGET );
m_hCrashPoint.Set( NULL );
}
#ifdef HL2_EPISODIC
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_AttackHelicopter::CreateVPhysics( void )
{
InitBoneFollowers();
return BaseClass::CreateVPhysics();
}
#endif // HL2_EPISODIC
//------------------------------------------------------------------------------
// Startup the chopper
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::Startup()
{
BaseClass::Startup();
if ( HasSpawnFlags( SF_HELICOPTER_LIGHTS ) )
{
for ( int i = 0; i < MAX_HELICOPTER_LIGHTS; ++i )
{
// See if there's an attachment for this smoke trail
char buf[32];
Q_snprintf( buf, 32, "Light_Red%d", i );
int nAttachment = LookupAttachment( buf );
if ( nAttachment == 0 )
{
m_hLights[i] = NULL;
continue;
}
m_hLights[i] = CSprite::SpriteCreate( CHOPPER_RED_LIGHT_SPRITE, vec3_origin, false );
if ( !m_hLights[i] )
continue;
m_hLights[i]->SetParent( this, nAttachment );
m_hLights[i]->SetLocalOrigin( vec3_origin );
m_hLights[i]->SetLocalVelocity( vec3_origin );
m_hLights[i]->SetMoveType( MOVETYPE_NONE );
m_hLights[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 200, kRenderFxNone );
m_hLights[i]->SetScale( 1.0f );
m_hLights[i]->TurnOn();
}
SetContextThink( &CNPC_AttackHelicopter::BlinkLightsThink, gpGlobals->curtime + CHOPPER_LIGHT_BLINK_TIME_SHORT, s_pBlinkLightThinkContext );
}
}
//------------------------------------------------------------------------------
// Startup the chopper
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::BlinkLightsThink()
{
bool bIsOn = false;
for ( int i = 0; i < MAX_HELICOPTER_LIGHTS; ++i )
{
if ( !m_hLights[i] )
continue;
if ( m_hLights[i]->GetScale() > 0.1f )
{
m_hLights[i]->SetScale( 0.1f, CHOPPER_LIGHT_BLINK_TIME_SHORT );
}
else
{
m_hLights[i]->SetScale( 0.5f, 0.0f );
bIsOn = true;
}
}
float flTime;
if ( bIsOn )
{
flTime = CHOPPER_LIGHT_BLINK_TIME_SHORT;
}
else
{
flTime = m_bShortBlink ? CHOPPER_LIGHT_BLINK_TIME_SHORT : CHOPPER_LIGHT_BLINK_TIME;
m_bShortBlink = !m_bShortBlink;
}
SetContextThink( &CNPC_AttackHelicopter::BlinkLightsThink, gpGlobals->curtime + flTime, s_pBlinkLightThinkContext );
}
//------------------------------------------------------------------------------
// Start up spotlights
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::SpotlightStartup()
{
if ( !HasSpawnFlags( SF_HELICOPTER_LIGHTS ) )
return;
Vector vecForward;
Vector vecOrigin;
GetAttachment( m_nSpotlightAttachment, vecOrigin, &vecForward );
m_Spotlight.SpotlightCreate( m_nSpotlightAttachment, vecForward );
SpotlightThink();
}
//------------------------------------------------------------------------------
// Shutdown spotlights
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::SpotlightShutdown()
{
m_Spotlight.SpotlightDestroy();
SetContextThink( NULL, gpGlobals->curtime, s_pSpotlightThinkContext );
}
//------------------------------------------------------------------------------
// Spotlights
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::SpotlightThink()
{
// NOTE: This function should deal with all deactivation cases
if ( m_lifeState != LIFE_ALIVE )
{
SpotlightShutdown();
return;
}
switch( m_nAttackMode )
{
case ATTACK_MODE_BULLRUSH_VEHICLE:
{
switch ( m_nSecondaryMode )
{
case BULLRUSH_MODE_SHOOT_GUN:
{
Vector vecForward;
Vector vecOrigin;
GetAttachment( m_nSpotlightAttachment, vecOrigin, &vecForward );
m_Spotlight.SetSpotlightTargetDirection( vecForward );
}
break;
case BULLRUSH_MODE_SHOOT_IDLE_PLAYER:
if ( GetEnemy() )
{
m_Spotlight.SetSpotlightTargetPos( GetEnemy()->WorldSpaceCenter() );
}
break;
default:
SpotlightShutdown();
return;
}
}
break;
default:
SpotlightShutdown();
return;
}
m_Spotlight.Update();
SetContextThink( &CNPC_AttackHelicopter::SpotlightThink, gpGlobals->curtime + TICK_INTERVAL, s_pSpotlightThinkContext );
}
//-----------------------------------------------------------------------------
// Purpose: Always transition along with the player
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputEnableAlwaysTransition( inputdata_t &inputdata )
{
m_bAlwaysTransition = true;
}
//-----------------------------------------------------------------------------
// Purpose: Stop always transitioning along with the player
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputDisableAlwaysTransition( inputdata_t &inputdata )
{
m_bAlwaysTransition = false;
}
//------------------------------------------------------------------------------
// On Remove
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::UpdateOnRemove()
{
BaseClass::UpdateOnRemove();
StopLoopingSounds();
UTIL_Remove(m_hSensor);
DestroySmokeTrails();
for ( int i = 0; i < MAX_HELICOPTER_LIGHTS; ++i )
{
if ( m_hLights[i] )
{
UTIL_Remove( m_hLights[i] );
m_hLights[i] = NULL;
}
}
#ifdef HL2_EPISODIC
m_BoneFollowerManager.DestroyBoneFollowers();
#endif // HL2_EPISODIC
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::Activate( void )
{
BaseClass::Activate();
m_nGunBaseAttachment = LookupAttachment("gun");
m_nGunTipAttachment = LookupAttachment("muzzle");
m_nBombAttachment = LookupAttachment("bomb");
m_nSpotlightAttachment = LookupAttachment("spotlight");
if ( HasSpawnFlags( SF_HELICOPTER_LONG_SHADOW ) )
{
SetShadowCastDistance( 2048 );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *CNPC_AttackHelicopter::GetTracerType( void )
{
return "HelicopterTracer";
}
//-----------------------------------------------------------------------------
// Allows the shooter to change the impact effect of his bullets
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::DoImpactEffect( trace_t &tr, int nDamageType )
{
UTIL_ImpactTrace( &tr, nDamageType, "HelicopterImpact" );
}
//------------------------------------------------------------------------------
// Purpose : Create our rotor sound
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::InitializeRotorSound( void )
{
if ( !m_pRotorSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
CPASAttenuationFilter filter( this );
if ( HasSpawnFlags( SF_HELICOPTER_LOUD_ROTOR_SOUND ) )
{
m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.RotorsLoud" );
}
else
{
m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.Rotors" );
}
m_pRotorBlast = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.RotorBlast" );
m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.FireGun" );
controller.Play( m_pGunFiringSound, 0.0, 100 );
}
else
{
Assert(m_pRotorSound);
Assert(m_pRotorBlast);
Assert(m_pGunFiringSound);
}
BaseClass::InitializeRotorSound();
}
//------------------------------------------------------------------------------
// Gets the max speed of the helicopter
//------------------------------------------------------------------------------
float CNPC_AttackHelicopter::GetMaxSpeed()
{
if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
return DRONE_SPEED;
if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && IsInSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED ) )
return CHOPPER_BULLRUSH_ENEMY_BOMB_SPEED;
if ( !GetEnemyVehicle() )
return BaseClass::GetMaxSpeed();
return 3000.0f;
}
float CNPC_AttackHelicopter::GetMaxSpeedFiring()
{
if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
return DRONE_SPEED;
if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && IsInSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED ) )
return CHOPPER_BULLRUSH_ENEMY_BOMB_SPEED;
if ( !GetEnemyVehicle() )
return BaseClass::GetMaxSpeedFiring();
return 3000.0f;
}
//------------------------------------------------------------------------------
// Returns the max firing distance
//------------------------------------------------------------------------------
float CNPC_AttackHelicopter::GetMaxFiringDistance()
{
if ( !GetEnemyVehicle() )
return CHOPPER_GUN_MAX_FIRING_DIST;
return 8000.0f;
}
//------------------------------------------------------------------------------
// Updates the enemy
//------------------------------------------------------------------------------
float CNPC_AttackHelicopter::EnemySearchDistance( )
{
return 6000.0f;
}
//------------------------------------------------------------------------------
// Leading behaviors
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputStartBombingVehicle( inputdata_t &inputdata )
{
m_nAttackMode = ATTACK_MODE_BOMB_VEHICLE;
SetLeadingDistance( 1500.0f );
}
void CNPC_AttackHelicopter::InputStartTrailingVehicle( inputdata_t &inputdata )
{
m_nAttackMode = ATTACK_MODE_TRAIL_VEHICLE;
SetLeadingDistance( -1500.0f );
}
void CNPC_AttackHelicopter::InputStartDefaultBehavior( inputdata_t &inputdata )
{
m_nAttackMode = ATTACK_MODE_DEFAULT;
}
void CNPC_AttackHelicopter::InputStartAlwaysLeadingVehicle( inputdata_t &inputdata )
{
m_nAttackMode = ATTACK_MODE_ALWAYS_LEAD_VEHICLE;
SetLeadingDistance( 0.0f );
}
void CNPC_AttackHelicopter::InputStartBullrushBehavior( inputdata_t &inputdata )
{
if ( m_nAttackMode != ATTACK_MODE_BULLRUSH_VEHICLE )
{
m_nAttackMode = ATTACK_MODE_BULLRUSH_VEHICLE;
SetSecondaryMode( BULLRUSH_MODE_WAIT_FOR_ENEMY );
SetLeadingDistance( 0.0f );
}
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputStartCarpetBombing( inputdata_t &inputdata )
{
m_bIsCarpetBombing = true;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputStopCarpetBombing( inputdata_t &inputdata )
{
m_bIsCarpetBombing = false;
}
//------------------------------------------------------------------------------
// Become indestructible
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputBecomeIndestructible( inputdata_t &inputdata )
{
m_bIndestructible = true;
}
//------------------------------------------------------------------------------
// Deadly shooting, tex!
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputEnableDeadlyShooting( inputdata_t &inputdata )
{
m_bDeadlyShooting = true;
}
void CNPC_AttackHelicopter::InputDisableDeadlyShooting( inputdata_t &inputdata )
{
m_bDeadlyShooting = false;
}
void CNPC_AttackHelicopter::InputStartNormalShooting( inputdata_t &inputdata )
{
m_nShootingMode = SHOOT_MODE_DEFAULT;
}
void CNPC_AttackHelicopter::InputStartLongCycleShooting( inputdata_t &inputdata )
{
m_nShootingMode = SHOOT_MODE_LONG_CYCLE;
}
void CNPC_AttackHelicopter::InputStartContinuousShooting( inputdata_t &inputdata )
{
m_nShootingMode = SHOOT_MODE_CONTINUOUS;
}
void CNPC_AttackHelicopter::InputStartFastShooting( inputdata_t &inputdata )
{
m_nShootingMode = SHOOT_MODE_FAST;
}
//------------------------------------------------------------------------------
// Deadly shooting, tex!
//------------------------------------------------------------------------------
bool CNPC_AttackHelicopter::IsDeadlyShooting()
{
if ( m_bDeadlyShooting )
return true;
if (( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) )
{
return (!GetEnemyVehicle()) && GetEnemy() && GetEnemy()->IsPlayer();
}
return false;
}
int CNPC_AttackHelicopter::GetShootingMode( )
{
if ( IsDeadlyShooting() )
return SHOOT_MODE_LONG_CYCLE;
if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
return SHOOT_MODE_CONTINUOUS;
return m_nShootingMode;
}
//-----------------------------------------------------------------------------
// Bombing suppression
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputEnableBombing( inputdata_t &inputdata )
{
m_bBombingSuppressed = false;
}
void CNPC_AttackHelicopter::InputDisableBombing( inputdata_t &inputdata )
{
m_bBombingSuppressed = true;
}
//-----------------------------------------------------------------------------
// Visibility tests
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputDisablePathVisibilityTests( inputdata_t &inputdata )
{
m_bIgnorePathVisibilityTests = true;
GetEnemies()->SetUnforgettable( GetEnemy(), true );
}
void CNPC_AttackHelicopter::InputEnablePathVisibilityTests( inputdata_t &inputdata )
{
m_bIgnorePathVisibilityTests = false;
GetEnemies()->SetUnforgettable( GetEnemy(), false );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputSelfDestruct( inputdata_t &inputdata )
{
m_lifeState = LIFE_ALIVE; // Force to die properly.
CTakeDamageInfo info( this, this, Vector(0, 0, 1), WorldSpaceCenter(), GetMaxHealth(), CLASS_MISSILE );
TakeDamage( info );
}
//-----------------------------------------------------------------------------
// For scripted times where it *has* to shoot
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputSetHealthFraction( inputdata_t &inputdata )
{
// Sets the health fraction, no damage effects
if ( inputdata.value.Float() > 0 )
{
SetHealth( GetMaxHealth() * inputdata.value.Float() * 0.01f );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputStartBombExplodeOnContact( inputdata_t &inputdata )
{
m_bBombsExplodeOnContact = true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputStopBombExplodeOnContact( inputdata_t &inputdata )
{
m_bBombsExplodeOnContact = false;
}
//------------------------------------------------------------------------------
// For scripted times where it *has* to shoot
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputResetIdleTime( inputdata_t &inputdata )
{
if ( m_nGunState == GUN_STATE_IDLE )
{
m_flNextAttack = gpGlobals->curtime;
}
}
//-----------------------------------------------------------------------------
// This trace filter ignores all breakables + physics props
//-----------------------------------------------------------------------------
class CTraceFilterChopper : public CTraceFilterSimple
{
DECLARE_CLASS( CTraceFilterChopper, CTraceFilterSimple );
public:
CTraceFilterChopper( const IHandleEntity *passentity, int collisionGroup );
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask );
private:
const IHandleEntity *m_pPassEnt;
int m_collisionGroup;
};
CTraceFilterChopper::CTraceFilterChopper( const IHandleEntity *passentity, int collisionGroup ) :
CTraceFilterSimple( passentity, collisionGroup )
{
}
bool CTraceFilterChopper::ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *pEnt = static_cast<IServerUnknown*>(pServerEntity)->GetBaseEntity();
if ( pEnt )
{
if ( FClassnameIs( pEnt, "func_breakable" ) ||
FClassnameIs( pEnt, "func_physbox" ) ||
FClassnameIs( pEnt, "prop_physics" ) ||
FClassnameIs( pEnt, "physics_prop" ) )
{
return false;
}
}
return BaseClass::ShouldHitEntity( pServerEntity, contentsMask );
}
//-----------------------------------------------------------------------------
// Enemy visibility check
//-----------------------------------------------------------------------------
CBaseEntity *CNPC_AttackHelicopter::FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos )
{
if ( m_bIgnorePathVisibilityTests )
return NULL;
CTraceFilterChopper chopperFilter( this, COLLISION_GROUP_NONE );
trace_t tr;
AI_TraceHull( vecViewPoint, vecTargetPos, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT, &chopperFilter, &tr );
if ( tr.fraction != 1.0f )
{
Assert( tr.m_pEnt );
}
return (tr.fraction != 1.0f) ? tr.m_pEnt : NULL;
}
//-----------------------------------------------------------------------------
// More Enemy visibility check
//-----------------------------------------------------------------------------
bool CNPC_AttackHelicopter::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
{
if ( pEntity->GetFlags() & FL_NOTARGET )
return false;
#if 0
// FIXME: only block LOS through opaque water
// don't look through water
if ((m_nWaterLevel != 3 && pEntity->m_nWaterLevel == 3)
|| (m_nWaterLevel == 3 && pEntity->m_nWaterLevel == 0))
return false;
#endif
Vector vecLookerOrigin = EyePosition();//look through the caller's 'eyes'
Vector vecTargetOrigin = pEntity->EyePosition();
CTraceFilterChopper chopperFilter( this, COLLISION_GROUP_NONE );
trace_t tr;
UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, traceMask, &chopperFilter, &tr);
if (tr.fraction != 1.0)
{
// Got line of sight!
if ( tr.m_pEnt == pEntity )
return true;
// Got line of sight on the vehicle the player is driving!
if ( pEntity && pEntity->IsPlayer() )
{
CBasePlayer *pPlayer = assert_cast<CBasePlayer*>( pEntity );
if ( tr.m_pEnt == pPlayer->GetVehicleEntity() )
return true;
}
if (ppBlocker)
{
*ppBlocker = tr.m_pEnt;
}
return false;// Line of sight is not established
}
return true;// line of sight is valid.
}
//------------------------------------------------------------------------------
// Shot spread
//------------------------------------------------------------------------------
#define PLAYER_TIGHTEN_FACTOR 0.75f
Vector CNPC_AttackHelicopter::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
{
float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * PLAYER_TIGHTEN_FACTOR * 0.5f * (3.14f / 180.0f) );
Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
return vecSpread;
}
//------------------------------------------------------------------------------
// Find interesting nearby things to shoot
//------------------------------------------------------------------------------
int CNPC_AttackHelicopter::BuildMissTargetList( int nCount, CBaseEntity **ppMissCandidates )
{
int numMissCandidates = 0;
CBaseEntity *pEnts[256];
Vector radius( 150, 150, 150 );
const Vector &vecSource = GetEnemy()->WorldSpaceCenter();
int numEnts = UTIL_EntitiesInBox( pEnts, 256, vecSource - radius, vecSource+radius, 0 );
for ( int i = 0; i < numEnts; i++ )
{
if ( pEnts[i] == NULL )
continue;
if ( numMissCandidates >= nCount )
break;
// Miss candidates cannot include the player or his vehicle
if ( pEnts[i] == GetEnemyVehicle() || pEnts[i] == GetEnemy() )
continue;
// See if it's a good target candidate
if ( FClassnameIs( pEnts[i], "prop_dynamic" ) ||
FClassnameIs( pEnts[i], "prop_physics" ) ||
FClassnameIs( pEnts[i], "physics_prop" ) )
{
ppMissCandidates[numMissCandidates++] = pEnts[i];
}
}
return numMissCandidates;
}
//------------------------------------------------------------------------------
// Gets a vehicle the enemy is in (if any)
//------------------------------------------------------------------------------
CBaseEntity *CNPC_AttackHelicopter::GetEnemyVehicle()
{
if ( !GetEnemy() )
return NULL;
if ( !GetEnemy()->IsPlayer() )
return NULL;
return static_cast<CBasePlayer*>(GetEnemy())->GetVehicleEntity();
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::ShootAtPlayer( const Vector &vBasePos, const Vector &vGunDir )
{
// Fire one shots per round right at the player, using usual rules
FireBulletsInfo_t info;
info.m_vecSrc = vBasePos;
info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
info.m_flDistance = MAX_COORD_RANGE;
info.m_iAmmoType = m_iAmmoType;
info.m_iTracerFreq = 1;
info.m_vecDirShooting = GetActualShootTrajectory( vBasePos );
info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
DoMuzzleFlash();
QAngle vGunAng;
VectorAngles( vGunDir, vGunAng );
FireBullets( info );
// Fire the rest of the bullets at objects around the player
CBaseEntity *ppNearbyTargets[16];
int nActualTargets = BuildMissTargetList( 16, ppNearbyTargets );
// Randomly sort it...
int i;
for ( i = 0; i < nActualTargets; ++i )
{
int nSwap = random->RandomInt( 0, nActualTargets - 1 );
V_swap( ppNearbyTargets[i], ppNearbyTargets[nSwap] );
}
// Just shoot where we're facing
float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * 0.5f * (3.14f / 180.0f) );
Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
// How many times should we hit the player this time?
int nDesiredHitCount = (int)(((float)( m_nMaxBurstHits - m_nBurstHits ) / (float)m_nRemainingBursts) + 0.5f);
int nNearbyTargetCount = 0;
int nPlayerShotCount = 0;
for ( i = sk_helicopter_roundsperburst.GetInt() - 1; --i >= 0; )
{
// Find something interesting around the enemy to shoot instead of just missing.
if ( nActualTargets > nNearbyTargetCount )
{
// FIXME: Constrain to the firing cone?
ppNearbyTargets[nNearbyTargetCount]->CollisionProp()->RandomPointInBounds( Vector(.25, .25, .25), Vector(.75, .75, .75), &info.m_vecDirShooting );
info.m_vecDirShooting -= vBasePos;
VectorNormalize( info.m_vecDirShooting );
info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
info.m_flDistance = MAX_COORD_RANGE;
info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
FireBullets( info );
++nNearbyTargetCount;
continue;
}
if ( GetEnemy() && ( nPlayerShotCount < nDesiredHitCount ))
{
GetEnemy()->CollisionProp()->RandomPointInBounds( Vector(0, 0, 0), Vector(1, 1, 1), &info.m_vecDirShooting );
info.m_vecDirShooting -= vBasePos;
VectorNormalize( info.m_vecDirShooting );
info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
info.m_flDistance = MAX_COORD_RANGE;
info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
FireBullets( info );
++nPlayerShotCount;
continue;
}
// Nothing nearby; just fire randomly...
info.m_vecDirShooting = vGunDir;
info.m_vecSpread = vecSpread;
info.m_flDistance = 8192;
info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
FireBullets( info );
}
}
//-----------------------------------------------------------------------------
// Chooses a point within the circle of death to fire in
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::PickDirectionToCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition, Vector *pResult )
{
*pResult = vecFireAtPosition;
float x, y;
do
{
x = random->RandomFloat( -1.0f, 1.0f );
y = random->RandomFloat( -1.0f, 1.0f );
} while ( (x * x + y * y) > 1.0f );
pResult->x += x * m_flCircleOfDeathRadius;
pResult->y += y * m_flCircleOfDeathRadius;
*pResult -= vBasePos;
VectorNormalize( *pResult );
}
//-----------------------------------------------------------------------------
// Deliberately aims as close as possible w/o hitting
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::AimCloseToTargetButMiss( CBaseEntity *pTarget, float flMinDist, float flMaxDist, const Vector &shootOrigin, Vector *pResult )
{
Vector vecDirection;
VectorSubtract( pTarget->WorldSpaceCenter(), shootOrigin, vecDirection );
float flDist = VectorNormalize( vecDirection );
float flRadius = pTarget->BoundingRadius() + random->RandomFloat( flMinDist, flMaxDist );
float flMinRadius = flRadius;
if ( flDist > flRadius )
{
flMinRadius = flDist * flRadius / sqrt( flDist * flDist - flRadius * flRadius );
}
// Choose random points in a plane perpendicular to the shoot origin.
Vector vecRandomDir;
vecRandomDir.Random( -1.0f, 1.0f );
VectorMA( vecRandomDir, -DotProduct( vecDirection, vecRandomDir ), vecDirection, vecRandomDir );
VectorNormalize( vecRandomDir );
vecRandomDir *= flMinRadius;
vecRandomDir += pTarget->WorldSpaceCenter();
VectorSubtract( vecRandomDir, shootOrigin, *pResult );
VectorNormalize( *pResult );
}
//-----------------------------------------------------------------------------
// Make sure we don't hit too many times
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::FireBullets( const FireBulletsInfo_t &info )
{
// Use this to count the number of hits in a burst
bool bIsPlayer = GetEnemy() && GetEnemy()->IsPlayer();
if ( !bIsPlayer )
{
BaseClass::FireBullets( info );
return;
}
if ( !GetEnemyVehicle() && !IsDeadlyShooting() )
{
if ( m_nBurstHits >= m_nMaxBurstHits )
{
FireBulletsInfo_t actualInfo = info;
actualInfo.m_pAdditionalIgnoreEnt = GetEnemy();
BaseClass::FireBullets( actualInfo );
return;
}
}
CBasePlayer *pPlayer = assert_cast<CBasePlayer*>(GetEnemy());
int nPrevHealth = pPlayer->GetHealth();
int nPrevArmor = pPlayer->ArmorValue();
BaseClass::FireBullets( info );
if (( pPlayer->GetHealth() < nPrevHealth ) || ( pPlayer->ArmorValue() < nPrevArmor ))
{
++m_nBurstHits;
}
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::ShootInsideCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition )
{
Vector vecFireDirection;
if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
{
PickDirectionToCircleOfDeath( vBasePos, vecFireAtPosition, &vecFireDirection );
}
else if ( ( m_nNearShots < m_nMaxNearShots ) || !GetEnemyVehicle() )
{
if ( ( m_nBurstHits < m_nMaxBurstHits ) || !GetEnemy() )
{
++m_nNearShots;
PickDirectionToCircleOfDeath( vBasePos, vecFireAtPosition, &vecFireDirection );
}
else
{
m_nNearShots += 6;
AimCloseToTargetButMiss( GetEnemy(), 20.0f, 50.0f, vBasePos, &vecFireDirection );
}
}
else
{
AimCloseToTargetButMiss( GetEnemyVehicle(), 10.0f, 80.0f, vBasePos, &vecFireDirection );
}
FireBulletsInfo_t info( 1, vBasePos, vecFireDirection, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
info.m_iTracerFreq = 1;
info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND;
FireBullets( info );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::DoMuzzleFlash( void )
{
BaseClass::DoMuzzleFlash();
CEffectData data;
data.m_nAttachmentIndex = LookupAttachment( "muzzle" );
data.m_nEntIndex = entindex();
DispatchEffect( "ChopperMuzzleFlash", data );
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
#define HIT_VEHICLE_SPEED_MIN 200.0f
#define HIT_VEHICLE_SPEED_MAX 500.0f
void CNPC_AttackHelicopter::ShootAtVehicle( const Vector &vBasePos, const Vector &vecFireAtPosition )
{
int nShotsRemaining = sk_helicopter_roundsperburst.GetInt();
DoMuzzleFlash();
// Do special code against episodic drivers
if ( hl2_episodic.GetBool() )
{
Vector vecVelocity;
GetEnemyVehicle()->GetVelocity( &vecVelocity, NULL );
float flSpeed = clamp( vecVelocity.Length(), 0.0f, 400.0f );
float flRange = RemapVal( flSpeed, 0.0f, 400.0f, 0.05f, 1.0f );
// Alter each shot's trajectory based on our speed
for ( int i = 0; i < nShotsRemaining; i++ )
{
Vector vecShotDir;
// If they're at a dead stand-still, just hit them
if ( flRange <= 0.1f )
{
VectorSubtract( GetEnemy()->EyePosition(), vBasePos, vecShotDir );
Vector vecOffset;
vecOffset.Random( -40.0f, 40.0f );
vecShotDir += vecOffset;
VectorNormalize( vecShotDir );
}
else
{
// Aim in a cone around them
AimCloseToTargetButMiss( GetEnemy(), (3*12) * flRange, (10*12) * flRange, vBasePos, &vecShotDir );
}
FireBulletsInfo_t info( 1, vBasePos, vecShotDir, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
info.m_iTracerFreq = 1;
FireBullets( info );
}
// We opt out of the rest of the function
// FIXME: Should we emulate the below functionality and have half the bullets attempt to miss admirably? -- jdw
return;
}
// Pop one at the player based on how fast he's going
if ( m_nBurstHits < m_nMaxBurstHits )
{
Vector vecDir;
VectorSubtract( GetEnemy()->EyePosition(), vBasePos, vecDir );
Vector vecOffset;
vecOffset.Random( -5.0f, 5.0f );
vecDir += vecOffset;
VectorNormalize( vecDir );
FireBulletsInfo_t info( 1, vBasePos, vecDir, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
info.m_iTracerFreq = 1;
FireBullets( info );
--nShotsRemaining;
}
// Fire half of the bullets within the circle of death, the other half at interesting things
int i;
int nFireInCircle = nShotsRemaining >> 1;
nShotsRemaining -= nFireInCircle;
for ( i = 0; i < nFireInCircle; ++i )
{
ShootInsideCircleOfDeath( vBasePos, vecFireAtPosition );
}
// Fire the rest of the bullets at objects around the enemy
CBaseEntity *ppNearbyTargets[16];
int nActualTargets = BuildMissTargetList( 16, ppNearbyTargets );
// Randomly sort it...
for ( i = 0; i < nActualTargets; ++i )
{
int nSwap = random->RandomInt( 0, nActualTargets - 1 );
V_swap( ppNearbyTargets[i], ppNearbyTargets[nSwap] );
}
// Just shoot where we're facing
float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * 0.5f * (3.14f / 180.0f) );
Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
for ( i = nShotsRemaining; --i >= 0; )
{
// Find something interesting around the enemy to shoot instead of just missing.
if ( nActualTargets > i )
{
Vector vecFireDirection;
ppNearbyTargets[i]->CollisionProp()->RandomPointInBounds( Vector(.25, .25, .25), Vector(.75, .75, .75), &vecFireDirection );
vecFireDirection -= vBasePos;
VectorNormalize( vecFireDirection );
// FIXME: Constrain to the firing cone?
// I put in all the default arguments simply so I could guarantee the first shot of one of the bursts always hits
FireBulletsInfo_t info( 1, vBasePos, vecFireDirection, VECTOR_CONE_PRECALCULATED, MAX_COORD_RANGE, m_iAmmoType );
info.m_iTracerFreq = 1;
FireBullets( info );
}
else
{
ShootInsideCircleOfDeath( vBasePos, vecFireAtPosition );
}
}
}
//------------------------------------------------------------------------------
// Various states of the helicopter firing...
//------------------------------------------------------------------------------
bool CNPC_AttackHelicopter::PoseGunTowardTargetDirection( const Vector &vTargetDir )
{
Vector vecOut;
VectorIRotate( vTargetDir, EntityToWorldTransform(), vecOut );
QAngle angles;
VectorAngles(vecOut, angles);
if (angles.y > 180)
{
angles.y = angles.y - 360;
}
else if (angles.y < -180)
{
angles.y = angles.y + 360;
}
if (angles.x > 180)
{
angles.x = angles.x - 360;
}
else if (angles.x < -180)
{
angles.x = angles.x + 360;
}
if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && !IsInSecondaryMode(BULLRUSH_MODE_SHOOT_IDLE_PLAYER) && GetEnemy())
{
if ( GetEnemyVehicle() )
{
angles.x = clamp( angles.x, -12.0f, 0.0f );
angles.y = clamp( angles.y, -10.0f, 10.0f );
}
else
{
angles.x = clamp( angles.x, -10.0f, 10.0f );
angles.y = clamp( angles.y, -10.0f, 10.0f );
}
}
if (angles.x > m_angGun.x)
{
m_angGun.x = MIN( angles.x, m_angGun.x + 12 );
}
if (angles.x < m_angGun.x)
{
m_angGun.x = MAX( angles.x, m_angGun.x - 12 );
}
if (angles.y > m_angGun.y)
{
m_angGun.y = MIN( angles.y, m_angGun.y + 12 );
}
if (angles.y < m_angGun.y)
{
m_angGun.y = MAX( angles.y, m_angGun.y - 12 );
}
SetPoseParameter( m_poseWeapon_Pitch, -m_angGun.x );
SetPoseParameter( m_poseWeapon_Yaw, m_angGun.y );
return true;
}
//------------------------------------------------------------------------------
// Compute the enemy position (non-vehicle case)
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::ComputeFireAtPosition( Vector *pVecActualTargetPosition )
{
// Deal with various leading behaviors...
*pVecActualTargetPosition = m_vecTargetPosition;
}
//------------------------------------------------------------------------------
// Compute the enemy position (non-vehicle case)
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::ComputeVehicleFireAtPosition( Vector *pVecActualTargetPosition )
{
CBaseEntity *pVehicle = GetEnemyVehicle();
// Make sure the circle of death doesn't move more than N units
// This will cause the target to have to maintain a large enough speed
*pVecActualTargetPosition = pVehicle->BodyTarget( GetAbsOrigin(), false );
// NDebugOverlay::Box( *pVecActualTargetPosition,
// Vector(-m_flCircleOfDeathRadius, -m_flCircleOfDeathRadius, 0),
// Vector(m_flCircleOfDeathRadius, m_flCircleOfDeathRadius, 0),
// 0, 0, 255, false, 0.1f );
}
//------------------------------------------------------------------------------
// Here's what we do when we're looking for a target
//------------------------------------------------------------------------------
bool CNPC_AttackHelicopter::DoGunIdle( const Vector &vGunDir, const Vector &vTargetDir )
{
// When bullrushing, skip the idle
if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) &&
( IsInSecondaryMode( BULLRUSH_MODE_SHOOT_GUN ) || IsInSecondaryMode(BULLRUSH_MODE_SHOOT_IDLE_PLAYER) ) )
{
EmitSound( "NPC_AttackHelicopter.ChargeGun" );
m_flChargeTime = gpGlobals->curtime + CHOPPER_GUN_CHARGE_TIME;
m_nGunState = GUN_STATE_CHARGING;
m_flCircleOfDeathRadius = CHOPPER_MAX_CIRCLE_OF_DEATH_RADIUS;
return true;
}
// Can't continually fire....
if (m_flNextAttack > gpGlobals->curtime)
return false;
// Don't fire if we're too far away, or if the enemy isn't in front of us
if (!GetEnemy())
return false;
float flMaxDistSqr = GetMaxFiringDistance();
flMaxDistSqr *= flMaxDistSqr;
float flDistSqr = WorldSpaceCenter().DistToSqr( GetEnemy()->WorldSpaceCenter() );
if (flDistSqr > flMaxDistSqr)
return false;
// If he's mostly within the cone, shoot away!
float flChargeCone = sk_helicopter_firingcone.GetFloat() * 0.5f;
if ( flChargeCone < 15.0f )
{
flChargeCone = 15.0f;
}
float flCosConeDegrees = cos( flChargeCone * (3.14f / 180.0f) );
float fDotPr = DotProduct( vGunDir, vTargetDir );
if (fDotPr < flCosConeDegrees)
return false;
// Fast shooting doesn't charge up
if( m_nShootingMode == SHOOT_MODE_FAST )
{
m_flChargeTime = gpGlobals->curtime;
m_nGunState = GUN_STATE_CHARGING;
m_flAvoidMetric = 0.0f;
m_vecLastAngVelocity.Init( 0, 0, 0 );
}
else
{
EmitSound( "NPC_AttackHelicopter.ChargeGun" );
float flChargeTime = CHOPPER_GUN_CHARGE_TIME;
float flVariance = flChargeTime * 0.1f;
m_flChargeTime = gpGlobals->curtime + random->RandomFloat(flChargeTime - flVariance, flChargeTime + flVariance);
m_nGunState = GUN_STATE_CHARGING;
m_flAvoidMetric = 0.0f;
m_vecLastAngVelocity.Init( 0, 0, 0 );
}
return true;
}
//------------------------------------------------------------------------------
// How easy is the target to hit?
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::UpdateTargetHittability()
{
// This simply is a measure of how much juking is going on.
// Along with how much steering is happening.
if ( GetEnemyVehicle() )
{
Vector vecVelocity;
AngularImpulse vecAngVelocity;
GetEnemyVehicle()->GetVelocity( &vecVelocity, &vecAngVelocity );
float flDist = fabs( vecAngVelocity.z - m_vecLastAngVelocity.z );
m_flAvoidMetric += flDist;
m_vecLastAngVelocity = vecAngVelocity;
}
}
//------------------------------------------------------------------------------
// Here's what we do when we're getting ready to fire
//------------------------------------------------------------------------------
bool CNPC_AttackHelicopter::DoGunCharging( )
{
// Update the target hittability, which will indicate how many hits we'll accept.
UpdateTargetHittability();
if ( m_flChargeTime > gpGlobals->curtime )
return false;
m_nGunState = GUN_STATE_FIRING;
if ( HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) )
{
SetPauseState( PAUSE_AT_NEXT_LOS_POSITION );
}
int nHitFactor = 1;
switch( GetShootingMode() )
{
case SHOOT_MODE_DEFAULT:
case SHOOT_MODE_FAST:
{
int nBurstCount = sk_helicopter_burstcount.GetInt();
m_nRemainingBursts = random->RandomInt( nBurstCount, 2.0 * nBurstCount );
m_flIdleTimeDelay = 0.1f * ( m_nRemainingBursts - nBurstCount );
}
break;
case SHOOT_MODE_LONG_CYCLE:
{
m_nRemainingBursts = 60;
m_flIdleTimeDelay = 0.0f;
nHitFactor = 2;
}
break;
case SHOOT_MODE_CONTINUOUS:
if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
{
// We're relying on the special aiming behavior for bullrushing to just randomly deal damage
m_nRemainingBursts = 1;
m_flIdleTimeDelay = 0.0f;
}
else
{
m_nRemainingBursts = 0;
m_flIdleTimeDelay = 0.0f;
nHitFactor = 1000;
}
break;
}
if ( !GetEnemyVehicle() )
{
m_nMaxBurstHits = !IsDeadlyShooting() ? random->RandomInt( 6, 9 ) : 200;
m_nMaxNearShots = 10000;
}
else
{
Vector vecVelocity;
GetEnemyVehicle()->GetVelocity( &vecVelocity, NULL );
float flSpeed = vecVelocity.Length();
flSpeed = clamp( flSpeed, 150.0f, 600.0f );
flSpeed = RemapVal( flSpeed, 150.0f, 600.0f, 0.0f, 1.0f );
float flAvoid = clamp( m_flAvoidMetric, 100.0f, 400.0f );
flAvoid = RemapVal( flAvoid, 100.0f, 400.0f, 0.0f, 1.0f );
float flTotal = 0.5f * ( flSpeed + flAvoid );
int nHitCount = (int)(RemapVal( flTotal, 0.0f, 1.0f, 7, -0.5 ) + 0.5f);
int nMin = nHitCount >= 1 ? nHitCount - 1 : 0;
m_nMaxBurstHits = random->RandomInt( nMin, nHitCount + 1 );
int nNearShots = (int)(RemapVal( flTotal, 0.0f, 1.0f, 70, 5 ) + 0.5f);
int nMinNearShots = nNearShots >= 5 ? nNearShots - 5 : 0;
m_nMaxNearShots = random->RandomInt( nMinNearShots, nNearShots + 5 );
// Set up the circle of death parameters at this point
m_flCircleOfDeathRadius = SimpleSplineRemapVal( flTotal, 0.0f, 1.0f,
CHOPPER_MIN_CIRCLE_OF_DEATH_RADIUS, CHOPPER_MAX_CIRCLE_OF_DEATH_RADIUS );
}
m_nMaxBurstHits *= nHitFactor;
m_nMaxNearShots *= nHitFactor;
m_nBurstHits = 0;
m_nNearShots = 0;
return true;
}
//------------------------------------------------------------------------------
// Shoot where we're facing
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::ShootAtFacingDirection( const Vector &vBasePos, const Vector &vGunDir, bool bFirstShotAccurate )
{
// Just shoot where we're facing
float flSinConeDegrees = sin( sk_helicopter_firingcone.GetFloat() * 0.5f * (3.14f / 180.0f) );
Vector vecSpread( flSinConeDegrees, flSinConeDegrees, flSinConeDegrees );
int nShotCount = sk_helicopter_roundsperburst.GetInt();
if ( bFirstShotAccurate && GetEnemy() )
{
// Check to see if the enemy is within his firing cone
if ( GetEnemy() )
{
// Find the closest point to the gunDir
const Vector &vecCenter = GetEnemy()->WorldSpaceCenter();
float t;
Vector vNearPoint;
Vector vEndPoint;
VectorMA( vBasePos, 1024.0f, vGunDir, vEndPoint );
CalcClosestPointOnLine( vecCenter, vBasePos, vEndPoint, vNearPoint, &t );
if ( t > 0.0f )
{
Vector vecDelta;
VectorSubtract( vecCenter, vBasePos, vecDelta );
float flDist = VectorNormalize( vecDelta );
float flPerpDist = vecCenter.DistTo( vNearPoint );
float flSinAngle = flPerpDist / flDist;
if ( flSinAngle <= flSinConeDegrees )
{
FireBulletsInfo_t info( 1, vBasePos, vecDelta, VECTOR_CONE_PRECALCULATED, 8192, m_iAmmoType );
info.m_iTracerFreq = 1;
FireBullets( info );
--nShotCount;
}
}
}
}
#ifdef HL2_EPISODIC
if( GetEnemy() != NULL )
{
CSoundEnt::InsertSound( SOUND_DANGER, GetEnemy()->WorldSpaceCenter(), 180.0f, 0.5f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
}
#endif//HL2_EPISODIC
DoMuzzleFlash();
FireBulletsInfo_t info( nShotCount, vBasePos, vGunDir, vecSpread, 8192, m_iAmmoType );
info.m_iTracerFreq = 1;
FireBullets( info );
}
//-----------------------------------------------------------------------------
// Can we zap it?
//-----------------------------------------------------------------------------
bool CNPC_AttackHelicopter::IsValidZapTarget( CBaseEntity *pTarget )
{
// Don't use the player or vehicle as a zap target, we'll do that ourselves.
if ( pTarget->IsPlayer() || pTarget->GetServerVehicle() )
return false;
if ( pTarget == this )
return false;
if ( !pTarget->IsSolid() )
return false;
Assert( pTarget );
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
int count = pTarget->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
for ( int i = 0; i < count; i++ )
{
int material = pList[i]->GetMaterialIndex();
const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material );
// Is flesh or metal? Go for it!
if ( pSurfaceData->game.material == CHAR_TEX_METAL ||
pSurfaceData->game.material == CHAR_TEX_FLESH ||
pSurfaceData->game.material == CHAR_TEX_VENT ||
pSurfaceData->game.material == CHAR_TEX_GRATE ||
pSurfaceData->game.material == CHAR_TEX_COMPUTER ||
pSurfaceData->game.material == CHAR_TEX_BLOODYFLESH ||
pSurfaceData->game.material == CHAR_TEX_ALIENFLESH )
{
return true;
}
}
return false;
}
//------------------------------------------------------------------------------
// Effects
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::CreateZapBeam( const Vector &vecTargetPos )
{
CEffectData data;
data.m_nEntIndex = entindex();
data.m_nAttachmentIndex = 0; // m_nGunTipAttachment;
data.m_vOrigin = vecTargetPos;
data.m_flScale = 5;
DispatchEffect( "TeslaZap", data );
}
void CNPC_AttackHelicopter::CreateEntityZapEffect( CBaseEntity *pEnt )
{
CEffectData data;
data.m_nEntIndex = pEnt->entindex();
data.m_flMagnitude = 10;
data.m_flScale = 1.0f;
DispatchEffect( "TeslaHitboxes", data );
}
//------------------------------------------------------------------------------
// Here's what we do when we *are* firing
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::FireElectricityGun( )
{
if ( m_flNextAttack > gpGlobals->curtime )
return;
EmitSound( "ReallyLoudSpark" );
CBaseEntity *ppEnts[256];
Vector vecCenter = WorldSpaceCenter();
float flRadius = 500.0f;
vecCenter.z -= flRadius * 0.8f;
int nEntCount = UTIL_EntitiesInSphere( ppEnts, 256, vecCenter, flRadius, 0 );
CBaseEntity *ppCandidates[256];
int nCandidateCount = 0;
int i;
for ( i = 0; i < nEntCount; i++ )
{
if ( ppEnts[i] == NULL )
continue;
// Zap metal or flesh things.
if ( !IsValidZapTarget( ppEnts[i] ) )
continue;
ppCandidates[ nCandidateCount++ ] = ppEnts[i];
}
// First, put a bolt in front of the player, at random
float flDist = 1024;
if ( GetEnemy() )
{
Vector vecDelta;
Vector2DSubtract( GetEnemy()->WorldSpaceCenter().AsVector2D(), WorldSpaceCenter().AsVector2D(), vecDelta.AsVector2D() );
vecDelta.z = 0.0f;
flDist = VectorNormalize( vecDelta );
Vector vecPerp( -vecDelta.y, vecDelta.x, 0.0f );
int nBoltCount = (int)(ClampSplineRemapVal( flDist, 256.0f, 1024.0f, 8, 0 ) + 0.5f);
for ( i = 0; i < nBoltCount; ++i )
{
Vector vecTargetPt = GetEnemy()->WorldSpaceCenter();
VectorMA( vecTargetPt, random->RandomFloat( flDist + 100, flDist + 500 ), vecDelta, vecTargetPt );
VectorMA( vecTargetPt, random->RandomFloat( -500, 500 ), vecPerp, vecTargetPt );
vecTargetPt.z += random->RandomFloat( -500, 500 );
CreateZapBeam( vecTargetPt );
}
}
// Next, choose the number of bolts...
int nBoltCount = random->RandomInt( 8, 16 );
for ( i = 0; i < nBoltCount; ++i )
{
if ( (nCandidateCount > 0) && random->RandomFloat( 0.0f, 1.0f ) < 0.6f )
{
--nCandidateCount;
Vector vecTarget;
ppCandidates[nCandidateCount]->CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecTarget );
CreateZapBeam( vecTarget );
CreateEntityZapEffect( ppCandidates[nCandidateCount] );
}
else
{
// Select random point *on* sphere
Vector vecTargetPt;
float flEffectRadius = random->RandomFloat( flRadius * 1.2, flRadius * 1.5f );
float flTheta = random->RandomFloat( 0.0f, 2.0f * M_PI );
float flPhi = random->RandomFloat( -0.5f * M_PI, 0.5f * M_PI );
vecTargetPt.x = cos(flTheta) * cos(flPhi);
vecTargetPt.y = sin(flTheta) * cos(flPhi);
vecTargetPt.z = sin(flPhi);
vecTargetPt *= flEffectRadius;
vecTargetPt += vecCenter;
CreateZapBeam( vecTargetPt );
}
}
// Finally, put a bolt right at the player, at random
float flHitRatio = ClampSplineRemapVal( flDist, 128.0f, 512.0f, 0.75f, 0.0f );
if ( random->RandomFloat( 0.0f, 1.0f ) < flHitRatio )
{
if ( GetEnemyVehicle() )
{
Vector vecTarget;
GetEnemyVehicle()->CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecTarget );
CreateZapBeam( vecTarget );
CreateEntityZapEffect( GetEnemyVehicle() );
CTakeDamageInfo info( this, this, 5, DMG_SHOCK );
GetEnemy()->TakeDamage( info );
}
else if ( GetEnemy() )
{
Vector vecTarget;
GetEnemy()->CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecTarget );
CreateZapBeam( vecTarget );
CTakeDamageInfo info( this, this, 5, DMG_SHOCK );
GetEnemy()->TakeDamage( info );
}
}
m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 0.3f, 1.0f );
}
//------------------------------------------------------------------------------
// Here's what we do when we *are* firing
//------------------------------------------------------------------------------
#define INTERVAL_BETWEEN_HITS 4
bool CNPC_AttackHelicopter::DoGunFiring( const Vector &vBasePos, const Vector &vGunDir, const Vector &vecFireAtPosition )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
float flVolume = controller.SoundGetVolume( m_pGunFiringSound );
if ( flVolume != 1.0f )
{
controller.SoundChangeVolume( m_pGunFiringSound, 1.0, 0.01f );
}
if ( ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE ) && ( IsInSecondaryMode( BULLRUSH_MODE_SHOOT_GUN ) ) )
{
ShootAtFacingDirection( vBasePos, vGunDir, m_nRemainingBursts == 0 );
}
else if ( GetEnemyVehicle() )
{
ShootAtVehicle( vBasePos, vecFireAtPosition );
}
else if ( GetEnemy() && GetEnemy()->IsPlayer() )
{
if ( !IsDeadlyShooting() )
{
ShootAtPlayer( vBasePos, vGunDir );
}
else
{
ShootAtFacingDirection( vBasePos, vGunDir, true );
}
}
else
{
ShootAtFacingDirection( vBasePos, vGunDir, false );
}
if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
{
if ( --m_nRemainingBursts < 0 )
{
m_nRemainingBursts = INTERVAL_BETWEEN_HITS;
}
return true;
}
--m_nRemainingBursts;
if ( m_nRemainingBursts > 0 )
return true;
controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.01f );
float flIdleTime = CHOPPER_GUN_IDLE_TIME;
float flVariance = flIdleTime * 0.1f;
m_flNextAttack = gpGlobals->curtime + m_flIdleTimeDelay + random->RandomFloat(flIdleTime - flVariance, flIdleTime + flVariance);
m_nGunState = GUN_STATE_IDLE;
SetPauseState( PAUSE_NO_PAUSE );
return true;
}
//------------------------------------------------------------------------------
// Is it "fair" to drop this bomb?
//------------------------------------------------------------------------------
#define MIN_BOMB_DISTANCE_SQR ( 600.0f * 600.0f )
bool CNPC_AttackHelicopter::IsBombDropFair( const Vector &vecBombStartPos, const Vector &vecBombVelocity )
{
if ( (m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE) && IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) )
return true;
// Can happen if you're noclipping around
if ( !GetEnemy() )
return false;
// If the player is moving slowly, it's fair
if ( GetEnemy()->GetSmoothedVelocity().LengthSqr() < ( CHOPPER_SLOW_BOMB_SPEED * CHOPPER_SLOW_BOMB_SPEED ) )
return true;
// Skip out if we're right above or behind the player.. that's unfair
if ( GetEnemy() && GetEnemy()->IsPlayer() )
{
// How much time will it take to fall?
// dx = 0.5 * a * t^2
Vector vecTarget = GetEnemy()->BodyTarget( GetAbsOrigin(), false );
float dz = vecBombStartPos.z - vecTarget.z;
float dt = (dz > 0.0f) ? sqrt( 2 * dz / sv_gravity.GetFloat() ) : 0.0f;
// Where will the enemy be in that time?
Vector vecEnemyVel = GetEnemy()->GetSmoothedVelocity();
VectorMA( vecTarget, dt, vecEnemyVel, vecTarget );
// Where will the bomb be in that time?
Vector vecBomb;
VectorMA( vecBombStartPos, dt, vecBombVelocity, vecBomb );
float flEnemySpeed = vecEnemyVel.LengthSqr();
flEnemySpeed = clamp( flEnemySpeed, 200.0f, 500.0f );
float flDistFactorSq = RemapVal( flEnemySpeed, 200.0f, 500.0f, 0.3f, 1.0f );
flDistFactorSq *= flDistFactorSq;
// If it's too close, then we're not doing it.
if ( vecBomb.AsVector2D().DistToSqr( vecTarget.AsVector2D() ) < (flDistFactorSq * MIN_BOMB_DISTANCE_SQR) )
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Create the bomb entity and set it up
// Input : &vecPos - Position to spawn at
// &vecVelocity - velocity to spawn with
//-----------------------------------------------------------------------------
CGrenadeHelicopter *CNPC_AttackHelicopter::SpawnBombEntity( const Vector &vecPos, const Vector &vecVelocity )
{
// Create the grenade and set it up
CGrenadeHelicopter *pGrenade = static_cast<CGrenadeHelicopter*>(CreateEntityByName( "grenade_helicopter" ));
pGrenade->SetAbsOrigin( vecPos );
pGrenade->SetOwnerEntity( this );
pGrenade->SetThrower( this );
pGrenade->SetAbsVelocity( vecVelocity );
DispatchSpawn( pGrenade );
pGrenade->SetExplodeOnContact( m_bBombsExplodeOnContact );
#ifdef HL2_EPISODIC
// Disable collisions with the owner's bone followers while we drop
physfollower_t *pFollower = m_BoneFollowerManager.GetBoneFollower( 0 );
if ( pFollower )
{
CBaseEntity *pBoneFollower = pFollower->hFollower;
PhysDisableEntityCollisions( pBoneFollower, pGrenade );
pGrenade->SetCollisionObject( pBoneFollower );
}
#endif // HL2_EPISODIC
return pGrenade;
}
//------------------------------------------------------------------------------
// Actually drops the bomb
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::CreateBomb( bool bCheckForFairness, Vector *pVecVelocity, bool bMegaBomb )
{
if ( m_bBombingSuppressed )
return;
Vector vTipPos;
GetAttachment( m_nBombAttachment, vTipPos );
if ( !CBombSuppressor::CanBomb( vTipPos ) )
return;
// Compute velocity
Vector vecActualVelocity;
if ( !pVecVelocity )
{
Vector vecAcross;
vecActualVelocity = GetAbsVelocity();
CrossProduct( vecActualVelocity, Vector( 0, 0, 1 ), vecAcross );
VectorNormalize( vecAcross );
vecAcross *= random->RandomFloat( 10.0f, 30.0f );
vecAcross *= random->RandomFloat( 0.0f, 1.0f ) < 0.5f ? 1.0f : -1.0f;
// Blat out z component of velocity if it's moving upward....
if ( vecActualVelocity.z > 0 )
{
vecActualVelocity.z = 0.0f;
}
vecActualVelocity += vecAcross;
}
else
{
vecActualVelocity = *pVecVelocity;
}
if ( bCheckForFairness )
{
if ( !IsBombDropFair( vTipPos, vecActualVelocity ) )
return;
}
AddGesture( (Activity)ACT_HELICOPTER_DROP_BOMB );
EmitSound( "NPC_AttackHelicopter.DropMine" );
// Make the bomb and send it off
CGrenadeHelicopter *pGrenade = SpawnBombEntity( vTipPos, vecActualVelocity );
if ( pGrenade && bMegaBomb )
{
pGrenade->AddSpawnFlags( SF_GRENADE_HELICOPTER_MEGABOMB );
}
}
//------------------------------------------------------------------------------
// Drop a bomb at a particular location
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputDropBomb( inputdata_t &inputdata )
{
if ( m_flInputDropBombTime > gpGlobals->curtime )
return;
// Prevent two triggers from being hit the same frame
m_flInputDropBombTime = gpGlobals->curtime + 0.01f;
CreateBomb( );
// If we're in the middle of a bomb dropping schedule, wait to drop another bomb.
if ( ShouldDropBombs() )
{
m_flNextAttack = gpGlobals->curtime + 0.5f + random->RandomFloat( 0.3f, 0.6f );
}
}
//------------------------------------------------------------------------------
// Drops a bomb straight downwards
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputDropBombStraightDown( inputdata_t &inputdata )
{
if ( m_flInputDropBombTime > gpGlobals->curtime )
return;
// Prevent two triggers from being hit the same frame
m_flInputDropBombTime = gpGlobals->curtime + 0.01f;
Vector vTipPos;
GetAttachment( m_nBombAttachment, vTipPos );
// Make the bomb drop straight down
SpawnBombEntity( vTipPos, vec3_origin );
// If we're in the middle of a bomb dropping schedule, wait to drop another bomb.
if ( ShouldDropBombs() )
{
m_flNextAttack = gpGlobals->curtime + 0.5f + random->RandomFloat( 0.3f, 0.6f );
}
}
//------------------------------------------------------------------------------
// Drop a bomb at a particular location
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::InputDropBombAtTargetInternal( inputdata_t &inputdata, bool bCheckFairness )
{
if ( m_flInputDropBombTime > gpGlobals->curtime )
return;
// Prevent two triggers from being hit the same frame
m_flInputDropBombTime = gpGlobals->curtime + 0.01f;
// Find our specified target
string_t strBombTarget = MAKE_STRING( inputdata.value.String() );
CBaseEntity *pBombEnt = gEntList.FindEntityByName( NULL, strBombTarget );
if ( pBombEnt == NULL )
{
Warning( "%s: Could not find bomb drop target '%s'!\n", GetClassname(), STRING( strBombTarget ) );
return;
}
Vector vTipPos;
GetAttachment( m_nBombAttachment, vTipPos );
// Compute the time it would take to fall to the target
Vector vecTarget = pBombEnt->BodyTarget( GetAbsOrigin(), false );
float dz = vTipPos.z - vecTarget.z;
if ( dz <= 0.0f )
{
Warning("Bomb target %s is above the chopper!\n", STRING( strBombTarget ) );
return;
}
float dt = sqrt( 2 * dz / sv_gravity.GetFloat() );
// 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 )
{
// 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 );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CNPC_AttackHelicopter::OnTakeDamage( const CTakeDamageInfo &info )
{
// We don't take blast damage from anything but the airboat or missiles (or myself!)
if( info.GetInflictor() != this )
{
if ( ( ( info.GetDamageType() & DMG_AIRBOAT ) == 0 ) &&
( info.GetInflictor()->Classify() != CLASS_MISSILE ) &&
( info.GetAttacker()->Classify() != CLASS_MISSILE ) )
return 0;
}
if ( m_bIndestructible )
{
if ( GetHealth() < info.GetDamage() )
return 0;
}
// helicopter takes extra damage from its own grenades
CGrenadeHelicopter *pGren = dynamic_cast<CGrenadeHelicopter *>(info.GetInflictor());
if ( pGren && info.GetAttacker() && info.GetAttacker()->IsPlayer() )
{
CTakeDamageInfo fudgedInfo = info;
float damage;
if( g_pGameRules->IsSkillLevel(SKILL_EASY) )
{
damage = GetMaxHealth() / sk_helicopter_num_bombs1.GetFloat();
}
else if( g_pGameRules->IsSkillLevel(SKILL_HARD) )
{
damage = GetMaxHealth() / sk_helicopter_num_bombs3.GetFloat();
}
else // Medium, or unspecified
{
damage = GetMaxHealth() / sk_helicopter_num_bombs2.GetFloat();
}
damage = ceilf( damage );
fudgedInfo.SetDamage( damage );
fudgedInfo.SetMaxDamage( damage );
return BaseClass::OnTakeDamage( fudgedInfo );
}
return BaseClass::OnTakeDamage( info );
}
//-----------------------------------------------------------------------------
// Purpose: Take damage from trace attacks if they hit the gunner
//-----------------------------------------------------------------------------
int CNPC_AttackHelicopter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
int nPrevHealth = GetHealth();
if ( ( info.GetInflictor() != NULL ) && ( info.GetInflictor()->GetOwnerEntity() != NULL ) && ( info.GetInflictor()->GetOwnerEntity() == this ) )
{
// Don't take damage from my own bombs. (Unless the player grabbed them and threw them back)
return 0;
}
// Chain
int nRetVal = BaseClass::OnTakeDamage_Alive( info );
if( info.GetDamageType() & DMG_BLAST )
{
// Apply a force push that makes us look like we're reacting to the damage
Vector damageDir = info.GetDamageForce();
VectorNormalize( damageDir );
ApplyAbsVelocityImpulse( damageDir * 500.0f );
// Knock the helicopter off of the level, too.
Vector vecRight, vecForce;
float flDot;
GetVectors( NULL, &vecRight, NULL );
vecForce = info.GetDamageForce();
VectorNormalize( vecForce );
flDot = DotProduct( vecForce, vecRight );
m_flGoalRollDmg = random->RandomFloat( 10, 30 );
if( flDot <= 0.0f )
{
// Missile hit the right side.
m_flGoalRollDmg *= -1;
}
}
// Spawn damage effects
if ( nPrevHealth != GetHealth() )
{
// Give the badly damaged call to say we're going to mega bomb soon
if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
{
if (( nPrevHealth > m_flNextMegaBombHealth ) && (GetHealth() <= m_flNextMegaBombHealth) )
{
EmitSound( "NPC_AttackHelicopter.BadlyDamagedAlert" );
}
}
if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_SMOKE_TRAILS ) )
{
AddSmokeTrail( info.GetDamagePosition() );
}
if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_CORPSES ) )
{
if ( nPrevHealth != GetMaxHealth() )
{
DropCorpse( info.GetDamage() );
}
}
if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) )
{
ExplodeAndThrowChunk( info.GetDamagePosition() );
}
int nPrevPercent = (int)(100.0f * nPrevHealth / GetMaxHealth());
int nCurrPercent = (int)(100.0f * GetHealth() / GetMaxHealth());
if (( (nPrevPercent + 9) / 10 ) != ( (nCurrPercent + 9) / 10 ))
{
m_OnHealthChanged.Set( nCurrPercent, this, this );
}
}
return nRetVal;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void Chopper_BecomeChunks( CBaseEntity *pChopper )
{
QAngle vecChunkAngles = pChopper->GetAbsAngles();
Vector vecForward, vecUp;
pChopper->GetVectors( &vecForward, NULL, &vecUp );
#ifdef HL2_EPISODIC
CNPC_AttackHelicopter *pAttackHelicopter;
pAttackHelicopter = dynamic_cast<CNPC_AttackHelicopter*>(pChopper);
if( pAttackHelicopter != NULL )
{
// New for EP2, we may be tailspinning, (crashing) and playing an animation that is spinning
// our root bone, which means our model is not facing the way our entity is facing. So we have
// to do some attachment point math to get the proper angles to use for computing the relative
// positions of the gibs. The attachment points called DAMAGE0 is properly oriented and attached
// to the chopper body so we can use its angles.
int iAttach = pAttackHelicopter->LookupAttachment( "damage0" );
Vector vecAttachPos;
if( iAttach > -1 )
{
pAttackHelicopter->GetAttachment(iAttach, vecAttachPos, vecChunkAngles );
AngleVectors( vecChunkAngles, &vecForward, NULL, &vecUp );
}
}
#endif//HL2_EPISODIC
Vector vecChunkPos = pChopper->GetAbsOrigin();
Vector vecRight(0,0,0);
if( hl2_episodic.GetBool() )
{
// We need to get a right hand vector to toss the cockpit and tail pieces
// so their motion looks like a continuation of the tailspin animation
// that the chopper plays before crashing.
pChopper->GetVectors( NULL, &vecRight, NULL );
}
// Body
CHelicopterChunk *pBodyChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, pChopper->GetAbsVelocity(), HELICOPTER_CHUNK_BODY, CHUNK_BODY );
Chopper_CreateChunk( pChopper, vecChunkPos, RandomAngle( 0, 360 ), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_CHUNKS - 1 )], false );
vecChunkPos = pChopper->GetAbsOrigin() + ( vecForward * 100.0f ) + ( vecUp * -38.0f );
// Cockpit
CHelicopterChunk *pCockpitChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, pChopper->GetAbsVelocity() + vecRight * -800.0f, HELICOPTER_CHUNK_COCKPIT, CHUNK_COCKPIT );
Chopper_CreateChunk( pChopper, vecChunkPos, RandomAngle( 0, 360 ), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_CHUNKS - 1 )], false );
pCockpitChunk->m_hMaster = pBodyChunk;
vecChunkPos = pChopper->GetAbsOrigin() + ( vecForward * -175.0f );
// Tail
CHelicopterChunk *pTailChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, pChopper->GetAbsVelocity() + vecRight * 800.0f, HELICOPTER_CHUNK_TAIL, CHUNK_TAIL );
Chopper_CreateChunk( pChopper, vecChunkPos, RandomAngle( 0, 360 ), s_pChunkModelName[random->RandomInt( 0, CHOPPER_MAX_CHUNKS - 1 )], false );
pTailChunk->m_hMaster = pBodyChunk;
// Constrain all the pieces together loosely
IPhysicsObject *pBodyObject = pBodyChunk->VPhysicsGetObject();
Assert( pBodyObject );
IPhysicsObject *pCockpitObject = pCockpitChunk->VPhysicsGetObject();
Assert( pCockpitObject );
IPhysicsObject *pTailObject = pTailChunk->VPhysicsGetObject();
Assert( pTailObject );
IPhysicsConstraintGroup *pGroup = NULL;
// Create the constraint
constraint_fixedparams_t fixed;
fixed.Defaults();
fixed.InitWithCurrentObjectState( pBodyObject, pTailObject );
fixed.constraint.Defaults();
pBodyChunk->m_pTailConstraint = physenv->CreateFixedConstraint( pBodyObject, pTailObject, pGroup, fixed );
fixed.Defaults();
fixed.InitWithCurrentObjectState( pBodyObject, pCockpitObject );
fixed.constraint.Defaults();
pBodyChunk->m_pCockpitConstraint = physenv->CreateFixedConstraint( pBodyObject, pCockpitObject, pGroup, fixed );
}
//-----------------------------------------------------------------------------
// Purpose: Start us crashing
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::Event_Killed( const CTakeDamageInfo &info )
{
if( m_lifeState == LIFE_ALIVE )
{
m_OnShotDown.FireOutput( this, this );
}
m_lifeState = LIFE_DYING;
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.1f );
if( GetCrashPoint() == NULL )
{
CBaseEntity *pCrashPoint = gEntList.FindEntityByClassname( NULL, "info_target_helicopter_crash" );
if( pCrashPoint != NULL )
{
m_hCrashPoint.Set( pCrashPoint );
SetDesiredPosition( pCrashPoint->GetAbsOrigin() );
// Start the failing engine sound
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pRotorSound );
CPASAttenuationFilter filter( this );
m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopter.EngineFailure" );
controller.Play( m_pRotorSound, 1.0, 100 );
// Tailspin!!
SetActivity( ACT_HELICOPTER_CRASHING );
// Intentionally returning with m_lifeState set to LIFE_DYING
return;
}
}
Chopper_BecomeChunks( this );
StopLoopingSounds();
m_lifeState = LIFE_DEAD;
EmitSound( "NPC_CombineGunship.Explode" );
SetThink( &CNPC_AttackHelicopter::SUB_Remove );
SetNextThink( gpGlobals->curtime + 0.1f );
AddEffects( EF_NODRAW );
// Makes the slower rotors fade back in
SetStartupTime( gpGlobals->curtime + 99.0f );
m_iHealth = 0;
m_takedamage = DAMAGE_NO;
m_OnDeath.FireOutput( info.GetAttacker(), this );
}
//------------------------------------------------------------------------------
// Creates the breakable husk of an attack chopper
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::CreateChopperHusk()
{
// We're embedded into the ground
CBaseEntity *pCorpse = CreateEntityByName( "prop_physics" );
pCorpse->SetAbsOrigin( GetAbsOrigin() );
pCorpse->SetAbsAngles( GetAbsAngles() );
pCorpse->SetModel( CHOPPER_MODEL_CORPSE_NAME );
pCorpse->AddSpawnFlags( SF_PHYSPROP_MOTIONDISABLED );
pCorpse->Spawn();
pCorpse->SetMoveType( MOVETYPE_NONE );
}
//-----------------------------------------------------------------------------
// Think!
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::PrescheduleThink( void )
{
if ( m_flGoalRollDmg != 0.0f )
{
m_flGoalRollDmg = UTIL_Approach( 0, m_flGoalRollDmg, 2.0f );
}
switch( m_lifeState )
{
case LIFE_DYING:
{
if( GetCrashPoint() != NULL )
{
// Stay on this, no matter what.
SetDesiredPosition( GetCrashPoint()->WorldSpaceCenter() );
}
if ( random->RandomInt( 0, 4 ) == 0 )
{
Vector explodePoint;
CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );
ExplodeAndThrowChunk( explodePoint );
}
}
break;
}
BaseClass::PrescheduleThink();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CNPC_AttackHelicopter::UpdatePerpPathDistance( float flMaxPathOffset )
{
if ( !IsLeading() || !GetEnemy() )
{
m_flCurrPathOffset = 0.0f;
return 0.0f;
}
float flNewPathOffset = TargetDistanceToPath();
// Make bomb dropping more interesting
if ( ShouldDropBombs() )
{
float flSpeedAlongPath = TargetSpeedAlongPath();
if ( flSpeedAlongPath > 10.0f )
{
float flLeadTime = GetLeadingDistance() / flSpeedAlongPath;
flLeadTime = clamp( flLeadTime, 0.0f, 2.0f );
flNewPathOffset += 0.25 * flLeadTime * TargetSpeedAcrossPath();
}
flSpeedAlongPath = clamp( flSpeedAlongPath, 100.0f, 500.0f );
float flSinHeight = SimpleSplineRemapVal( flSpeedAlongPath, 100.0f, 500.0f, 0.0f, 200.0f );
flNewPathOffset += flSinHeight * sin( 2.0f * M_PI * (gpGlobals->curtime / 6.0f) );
}
if ( (flMaxPathOffset != 0.0f) && (flNewPathOffset > flMaxPathOffset) )
{
flNewPathOffset = flMaxPathOffset;
}
float flMaxChange = 1000.0f * (gpGlobals->curtime - GetLastThink());
if ( fabs( flNewPathOffset - m_flCurrPathOffset ) < flMaxChange )
{
m_flCurrPathOffset = flNewPathOffset;
}
else
{
float flSign = (m_flCurrPathOffset < flNewPathOffset) ? 1.0f : -1.0f;
m_flCurrPathOffset += flSign * flMaxChange;
}
return m_flCurrPathOffset;
}
//-----------------------------------------------------------------------------
// Computes the max speed + acceleration:
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::GetMaxSpeedAndAccel( float *pMaxSpeed, float *pAccelRate )
{
*pAccelRate = CHOPPER_ACCEL_RATE;
*pMaxSpeed = GetMaxSpeed();
if ( GetEnemyVehicle() )
{
*pAccelRate *= 9.0f;
}
}
//-----------------------------------------------------------------------------
// Computes the acceleration:
//-----------------------------------------------------------------------------
#define HELICOPTER_GRAVITY 384
#define HELICOPTER_DT 0.1f
#define HELICOPTER_MIN_DZ_DAMP -500.0f
#define HELICOPTER_MAX_DZ_DAMP -1000.0f
#define HELICOPTER_FORCE_BLEND 0.8f
#define HELICOPTER_FORCE_BLEND_VEHICLE 0.2f
void CNPC_AttackHelicopter::ComputeVelocity( const Vector &vecTargetPosition,
float flAdditionalHeight, float flMinDistFromSegment, float flMaxDistFromSegment, Vector *pVecAccel )
{
Vector deltaPos;
VectorSubtract( vecTargetPosition, GetAbsOrigin(), deltaPos );
// calc goal linear accel to hit deltaPos in dt time.
// This is solving the equation xf = 0.5 * a * dt^2 + vo * dt + xo
float dt = 1.0f;
pVecAccel->x = 2.0f * (deltaPos.x - GetAbsVelocity().x * dt) / (dt * dt);
pVecAccel->y = 2.0f * (deltaPos.y - GetAbsVelocity().y * dt) / (dt * dt);
pVecAccel->z = 2.0f * (deltaPos.z - GetAbsVelocity().z * dt) / (dt * dt) + HELICOPTER_GRAVITY;
float flDistFromPath = 0.0f;
Vector vecPoint, vecDelta;
if ( flMaxDistFromSegment != 0.0f )
{
// Also, add in a little force to get us closer to our current line segment if we can
ClosestPointToCurrentPath( &vecPoint );
if ( flAdditionalHeight != 0.0f )
{
Vector vecEndPoint, vecClosest;
vecEndPoint = vecPoint;
vecEndPoint.z += flAdditionalHeight;
CalcClosestPointOnLineSegment( GetAbsOrigin(), vecPoint, vecEndPoint, vecClosest );
vecPoint = vecClosest;
}
VectorSubtract( vecPoint, GetAbsOrigin(), vecDelta );
flDistFromPath = VectorNormalize( vecDelta );
if ( flDistFromPath > flMaxDistFromSegment )
{
// Strongly constrain to an n unit pipe around the current path
// by damping out all impulse forces that would push us further from the pipe
float flAmount = (flDistFromPath - flMaxDistFromSegment) / 200.0f;
flAmount = clamp( flAmount, 0, 1 );
VectorMA( *pVecAccel, flAmount * 200.0f, vecDelta, *pVecAccel );
}
}
// Apply avoidance forces
if ( !HasSpawnFlags( SF_HELICOPTER_IGNORE_AVOID_FORCES ) )
{
Vector vecAvoidForce;
CAvoidSphere::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
*pVecAccel += vecAvoidForce;
CAvoidBox::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
*pVecAccel += vecAvoidForce;
}
// don't fall faster than 0.2G or climb faster than 2G
pVecAccel->z = clamp( pVecAccel->z, HELICOPTER_GRAVITY * 0.2f, HELICOPTER_GRAVITY * 2.0f );
// The lift factor owing to horizontal movement
float flHorizLiftFactor = fabs( pVecAccel->x ) * 0.10f + fabs( pVecAccel->y ) * 0.10f;
// If we're way above the path, dampen horizontal lift factor
float flNewHorizLiftFactor = clamp( deltaPos.z, HELICOPTER_MAX_DZ_DAMP, HELICOPTER_MIN_DZ_DAMP );
flNewHorizLiftFactor = SimpleSplineRemapVal( flNewHorizLiftFactor, HELICOPTER_MIN_DZ_DAMP, HELICOPTER_MAX_DZ_DAMP, flHorizLiftFactor, 2.5f * (HELICOPTER_GRAVITY * 0.2) );
float flDampening = (flNewHorizLiftFactor != 0.0f) ? (flNewHorizLiftFactor / flHorizLiftFactor) : 1.0f;
if ( flDampening < 1.0f )
{
pVecAccel->x *= flDampening;
pVecAccel->y *= flDampening;
flHorizLiftFactor = flNewHorizLiftFactor;
}
Vector forward, right, up;
GetVectors( &forward, &right, &up );
// First, attenuate the current force
float flForceBlend = GetEnemyVehicle() ? HELICOPTER_FORCE_BLEND_VEHICLE : HELICOPTER_FORCE_BLEND;
m_flForce *= flForceBlend;
// Now add force based on our acceleration factors
m_flForce += ( pVecAccel->z + flHorizLiftFactor ) * HELICOPTER_DT * (1.0f - flForceBlend);
// The force is always *locally* upward based; we pitch + roll the chopper to get movement
Vector vecImpulse;
VectorMultiply( up, m_flForce, vecImpulse );
// NOTE: These have to be done *before* the additional path distance drag forces are applied below
ApplySidewaysDrag( right );
ApplyGeneralDrag();
// If LIFE_DYING, maintain control as long as we're flying to a crash point.
if ( m_lifeState != LIFE_DYING || (m_lifeState == LIFE_DYING && GetCrashPoint() != NULL) )
{
vecImpulse.z += -HELICOPTER_GRAVITY * HELICOPTER_DT;
if ( flMinDistFromSegment != 0.0f && ( flDistFromPath > flMinDistFromSegment ) )
{
Vector vecVelDir = GetAbsVelocity();
// Strongly constrain to an n unit pipe around the current path
// by damping out all impulse forces that would push us further from the pipe
float flDot = DotProduct( vecImpulse, vecDelta );
if ( flDot < 0.0f )
{
VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
}
// Also apply an extra impulse to compensate for the current velocity
flDot = DotProduct( vecVelDir, vecDelta );
if ( flDot < 0.0f )
{
VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
}
}
}
else
{
// No more upward lift...
vecImpulse.z = -HELICOPTER_GRAVITY * HELICOPTER_DT;
// Damp the horizontal impulses; we should pretty much be falling ballistically
vecImpulse.x *= 0.1f;
vecImpulse.y *= 0.1f;
}
// Add in our velocity pulse for this frame
ApplyAbsVelocityImpulse( vecImpulse );
}
//-----------------------------------------------------------------------------
// Computes the max speed + acceleration:
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::ComputeAngularVelocity( const Vector &vecGoalUp, const Vector &vecFacingDirection )
{
QAngle goalAngAccel;
if ( m_lifeState != LIFE_DYING || (m_lifeState == LIFE_DYING && GetCrashPoint() != NULL) )
{
Vector forward, right, up;
GetVectors( &forward, &right, &up );
Vector goalUp = vecGoalUp;
VectorNormalize( goalUp );
// calc goal orientation to hit linear accel forces
float goalPitch = RAD2DEG( asin( DotProduct( forward, goalUp ) ) );
float goalYaw = UTIL_VecToYaw( vecFacingDirection );
float goalRoll = RAD2DEG( asin( DotProduct( right, goalUp ) ) + m_flGoalRollDmg );
goalPitch *= 0.75f;
// clamp goal orientations
goalPitch = clamp( goalPitch, -30, 45 );
goalRoll = clamp( goalRoll, -45, 45 );
// calc angular accel needed to hit goal pitch in dt time.
float dt = 0.6;
goalAngAccel.x = 2.0 * (AngleDiff( goalPitch, AngleNormalize( GetAbsAngles().x ) ) - GetLocalAngularVelocity().x * dt) / (dt * dt);
goalAngAccel.y = 2.0 * (AngleDiff( goalYaw, AngleNormalize( GetAbsAngles().y ) ) - GetLocalAngularVelocity().y * dt) / (dt * dt);
goalAngAccel.z = 2.0 * (AngleDiff( goalRoll, AngleNormalize( GetAbsAngles().z ) ) - GetLocalAngularVelocity().z * dt) / (dt * dt);
goalAngAccel.x = clamp( goalAngAccel.x, -300, 300 );
//goalAngAccel.y = clamp( goalAngAccel.y, -60, 60 );
goalAngAccel.y = clamp( goalAngAccel.y, -120, 120 );
goalAngAccel.z = clamp( goalAngAccel.z, -300, 300 );
}
else
{
goalAngAccel.x = 0;
goalAngAccel.y = random->RandomFloat( 50, 120 );
goalAngAccel.z = 0;
}
// limit angular accel changes to similate mechanical response times
QAngle angAccelAccel;
float dt = 0.1;
angAccelAccel.x = (goalAngAccel.x - m_vecAngAcceleration.x) / dt;
angAccelAccel.y = (goalAngAccel.y - m_vecAngAcceleration.y) / dt;
angAccelAccel.z = (goalAngAccel.z - m_vecAngAcceleration.z) / dt;
angAccelAccel.x = clamp( angAccelAccel.x, -1000, 1000 );
angAccelAccel.y = clamp( angAccelAccel.y, -1000, 1000 );
angAccelAccel.z = clamp( angAccelAccel.z, -1000, 1000 );
// DevMsg( "pitch %6.1f (%6.1f:%6.1f) ", goalPitch, GetLocalAngles().x, m_vecAngVelocity.x );
// DevMsg( "roll %6.1f (%6.1f:%6.1f) : ", goalRoll, GetLocalAngles().z, m_vecAngVelocity.z );
// DevMsg( "%6.1f %6.1f %6.1f : ", goalAngAccel.x, goalAngAccel.y, goalAngAccel.z );
// DevMsg( "%6.0f %6.0f %6.0f\n", angAccelAccel.x, angAccelAccel.y, angAccelAccel.z );
m_vecAngAcceleration += angAccelAccel * 0.1;
QAngle angVel = GetLocalAngularVelocity();
angVel += m_vecAngAcceleration * 0.1;
angVel.y = clamp( angVel.y, -120, 120 );
// Fix up pitch and yaw to tend toward small values
if ( m_lifeState == LIFE_DYING && GetCrashPoint() == NULL )
{
float flPitchDiff = random->RandomFloat( -5, 5 ) - GetAbsAngles().x;
angVel.x = flPitchDiff * 0.1f;
float flRollDiff = random->RandomFloat( -5, 5 ) - GetAbsAngles().z;
angVel.z = flRollDiff * 0.1f;
}
SetLocalAngularVelocity( angVel );
float flAmt = clamp( angVel.y, -30, 30 );
float flRudderPose = RemapVal( flAmt, -30, 30, 45, -45 );
SetPoseParameter( "rudder", flRudderPose );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::FlightDirectlyOverhead( void )
{
Vector vecTargetPosition = m_vecTargetPosition;
CBaseEntity *pEnemy = GetEnemy();
if ( HasEnemy() && FVisible( pEnemy ) )
{
if ( GetEnemy()->IsPlayer() )
{
CBaseEntity *pEnemyVehicle = assert_cast<CBasePlayer*>(GetEnemy())->GetVehicleEntity();
if ( pEnemyVehicle )
{
Vector vecEnemyVel = pEnemyVehicle->GetSmoothedVelocity();
Vector vecRelativePosition;
VectorSubtract( GetAbsOrigin(), pEnemyVehicle->GetAbsOrigin(), vecRelativePosition );
float flDist = VectorNormalize( vecRelativePosition );
float flEnemySpeed = VectorNormalize( vecEnemyVel );
float flDot = DotProduct( vecRelativePosition, vecEnemyVel );
float flSpeed = GetMaxSpeed() * 0.3f; //GetAbsVelocity().Length();
float a = flSpeed * flSpeed - flEnemySpeed * flEnemySpeed;
float b = 2.0f * flEnemySpeed * flDist * flDot;
float c = - flDist * flDist;
float flDiscrim = b * b - 4 * a * c;
if ( flDiscrim >= 0 )
{
float t = ( -b + sqrt( flDiscrim ) ) / (2 * a);
t = clamp( t, 0.0f, 4.0f );
VectorMA( pEnemyVehicle->GetAbsOrigin(), t * flEnemySpeed, vecEnemyVel, vecTargetPosition );
}
}
}
}
// if ( GetCurrentPathTargetPosition() )
// {
// vecTargetPosition.z = GetCurrentPathTargetPosition()->z;
// }
NDebugOverlay::Cross3D( vecTargetPosition, -Vector(32,32,32), Vector(32,32,32), 0, 0, 255, true, 0.1f );
UpdateFacingDirection( vecTargetPosition );
Vector accel;
ComputeVelocity( vecTargetPosition, 0.0f, 0.0f, 0.0f, &accel );
ComputeAngularVelocity( accel, m_vecDesiredFaceDir );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::Flight( void )
{
if( GetFlags() & FL_ONGROUND )
{
// This would be really bad.
SetGroundEntity( NULL );
}
// Determine the distances we must lie from the path
float flMaxPathOffset = MaxDistanceFromCurrentPath();
float flPerpDist = UpdatePerpPathDistance( flMaxPathOffset );
float flMinDistFromSegment, flMaxDistFromSegment;
if ( !IsLeading() )
{
flMinDistFromSegment = 0.0f;
flMaxDistFromSegment = 0.0f;
}
else
{
flMinDistFromSegment = fabs(flPerpDist) + 100.0f;
flMaxDistFromSegment = fabs(flPerpDist) + 200.0f;
if ( flMaxPathOffset != 0.0 )
{
if ( flMaxDistFromSegment > flMaxPathOffset - 100.0f )
{
flMaxDistFromSegment = flMaxPathOffset - 100.0f;
}
if ( flMinDistFromSegment > flMaxPathOffset - 200.0f )
{
flMinDistFromSegment = flMaxPathOffset - 200.0f;
}
}
}
float maxSpeed, accelRate;
GetMaxSpeedAndAccel( &maxSpeed, &accelRate );
Vector vecTargetPosition;
float flCurrentSpeed = GetAbsVelocity().Length();
float flDist = MIN( flCurrentSpeed + accelRate, maxSpeed );
float dt = 1.0f;
ComputeActualTargetPosition( flDist, dt, flPerpDist, &vecTargetPosition );
// Raise high in the air when doing the shooting attack
float flAdditionalHeight = 0.0f;
if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
{
flAdditionalHeight = clamp( m_flBullrushAdditionalHeight, 0.0f, flMaxPathOffset );
vecTargetPosition.z += flAdditionalHeight;
}
Vector accel;
UpdateFacingDirection( vecTargetPosition );
ComputeVelocity( vecTargetPosition, flAdditionalHeight, flMinDistFromSegment, flMaxDistFromSegment, &accel );
ComputeAngularVelocity( accel, m_vecDesiredFaceDir );
}
//------------------------------------------------------------------------------
// Updates the facing direction
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::UpdateFacingDirection( const Vector &vecActualDesiredPosition )
{
bool bIsBullrushing = ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE );
bool bSeenTargetRecently = HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) || ( m_flLastSeen + 5 > gpGlobals->curtime );
if ( GetEnemy() && !bIsBullrushing )
{
if ( !IsLeading() )
{
if( IsCarpetBombing() && hl2_episodic.GetBool() )
{
m_vecDesiredFaceDir = vecActualDesiredPosition - GetAbsOrigin();
}
else if ( !IsCrashing() && bSeenTargetRecently )
{
// If we've seen the target recently, face the target.
m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
}
else
{
// Remain facing the way you were facing...
}
}
else
{
if ( ShouldDropBombs() || IsCarpetBombing() )
{
m_vecDesiredFaceDir = vecActualDesiredPosition - GetAbsOrigin();
}
else
{
m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
}
}
}
else
{
// Face our desired position
float flDistSqr = vecActualDesiredPosition.AsVector2D().DistToSqr( GetAbsOrigin().AsVector2D() );
if ( flDistSqr <= 50 * 50 )
{
if (( flDistSqr > 1 * 1 ) && bSeenTargetRecently && IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) )
{
m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
m_vecDesiredFaceDir.z = 0.0f;
}
else
{
GetVectors( &m_vecDesiredFaceDir, NULL, NULL );
}
}
else
{
m_vecDesiredFaceDir = vecActualDesiredPosition - GetAbsOrigin();
}
}
VectorNormalize( m_vecDesiredFaceDir );
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
#define ENEMY_CREEP_RATE 400
float CNPC_AttackHelicopter::CreepTowardEnemy( float flSpeed, float flMinSpeed, float flMaxSpeed, float flMinDist, float flMaxDist )
{
float dt = gpGlobals->curtime - GetLastThink();
float flEnemyCreepDist = ENEMY_CREEP_RATE * dt;
// When the player is slow, creep toward him within a second or two
float flLeadingDist = ClampSplineRemapVal( flSpeed, flMinSpeed, flMaxSpeed, flMinDist, flMaxDist );
float flCurrentDist = GetLeadingDistance( );
if ( fabs(flLeadingDist - flCurrentDist) > flEnemyCreepDist )
{
float flSign = ( flLeadingDist < flCurrentDist ) ? -1.0f : 1.0f;
flLeadingDist = flCurrentDist + flSign * flEnemyCreepDist;
}
return flLeadingDist;
}
#define MIN_ENEMY_SPEED 300
//------------------------------------------------------------------------------
// Computes how far to lead the player when bombing
//------------------------------------------------------------------------------
float CNPC_AttackHelicopter::ComputeBombingLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle )
{
if ( ( flSpeed <= MIN_ENEMY_SPEED ) && bEnemyInVehicle )
{
return CreepTowardEnemy( flSpeed, 0.0f, MIN_ENEMY_SPEED, 0.0f, 1000.0f );
}
return ClampSplineRemapVal( flSpeedAlongPath, 200.0f, 600.0f, 1000.0f, 2000.0f );
}
//------------------------------------------------------------------------------
// Computes how far to lead the player when bullrushing
//------------------------------------------------------------------------------
float CNPC_AttackHelicopter::ComputeBullrushLeadingDistance( float flSpeed, float flSpeedAlongPath, bool bEnemyInVehicle )
{
switch ( m_nSecondaryMode )
{
case BULLRUSH_MODE_WAIT_FOR_ENEMY:
return 0.0f;
case BULLRUSH_MODE_GET_DISTANCE:
return m_bRushForward ? -CHOPPER_BULLRUSH_MODE_DISTANCE : CHOPPER_BULLRUSH_MODE_DISTANCE;
case BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER:
// return m_bRushForward ? 1500.0f : -1500.0f;
return ComputeBombingLeadingDistance( flSpeed, flSpeedAlongPath, bEnemyInVehicle );
case BULLRUSH_MODE_SHOOT_IDLE_PLAYER:
return 0.0f;
case BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED:
return m_bRushForward ? 7000 : -7000;
case BULLRUSH_MODE_MEGA_BOMB:
return m_bRushForward ? CHOPPER_BULLRUSH_MODE_DISTANCE : -CHOPPER_BULLRUSH_MODE_DISTANCE;
case BULLRUSH_MODE_SHOOT_GUN:
{
float flLeadDistance = 1000.f - CHOPPER_BULLRUSH_ENEMY_BOMB_DISTANCE;
return m_bRushForward ? flLeadDistance : -flLeadDistance;
}
}
Assert(0);
return 0.0f;
}
//------------------------------------------------------------------------------
// Secondary mode
//------------------------------------------------------------------------------
inline void CNPC_AttackHelicopter::SetSecondaryMode( int nMode, bool bRetainTime )
{
m_nSecondaryMode = nMode;
if (!bRetainTime)
{
m_flSecondaryModeStartTime = gpGlobals->curtime;
}
}
inline bool CNPC_AttackHelicopter::IsInSecondaryMode( int nMode )
{
return m_nSecondaryMode == nMode;
}
inline float CNPC_AttackHelicopter::SecondaryModeTime( ) const
{
return gpGlobals->curtime - m_flSecondaryModeStartTime;
}
//------------------------------------------------------------------------------
// Switch to idle
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::SwitchToBullrushIdle( void )
{
// Put us directly into idle gun state (we're in firing state)
m_flNextAttack = gpGlobals->curtime;
m_nGunState = GUN_STATE_IDLE;
m_nRemainingBursts = 0;
m_flBullrushAdditionalHeight = 0.0f;
SetPauseState( PAUSE_NO_PAUSE );
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.1f );
}
//------------------------------------------------------------------------------
// Should the chopper shoot the idle player?
//------------------------------------------------------------------------------
bool CNPC_AttackHelicopter::ShouldShootIdlePlayerInBullrush()
{
// Once he starts shooting, then don't stop until the player is moving pretty fast
float flSpeedSqr = IsInSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER ) ? CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_2_SQ : CHOPPER_BULLRUSH_SLOW_SHOOT_SPEED_SQ;
return ( GetEnemy() && GetEnemy()->GetSmoothedVelocity().LengthSqr() <= flSpeedSqr );
}
//------------------------------------------------------------------------------
// Shutdown shooting during bullrush
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::ShutdownGunDuringBullrush( )
{
// Put us directly into idle gun state (we're in firing state)
m_flNextAttack = gpGlobals->curtime;
m_nGunState = GUN_STATE_IDLE;
m_nRemainingBursts = 0;
SetPauseState( PAUSE_NO_PAUSE );
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.1f );
}
#define HELICOPTER_MIN_IDLE_BOMBING_DIST 350.0f
#define HELICOPTER_MIN_IDLE_BOMBING_SPEED 350.0f
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_AttackHelicopter::ShouldBombIdlePlayer( void )
{
// Must be settled over a position and not moving too quickly to do this
if ( GetAbsVelocity().LengthSqr() > Square(HELICOPTER_MIN_IDLE_BOMBING_SPEED) )
return false;
// Must be within a certain range of the target
float flDistToTargetSqr = (GetEnemy()->WorldSpaceCenter() - GetAbsOrigin()).Length2DSqr();
if ( flDistToTargetSqr < Square(HELICOPTER_MIN_IDLE_BOMBING_DIST) )
return true;
// Can't bomb this
return false;
}
//------------------------------------------------------------------------------
// Update the bullrush state
//------------------------------------------------------------------------------
#define BULLRUSH_GOAL_TOLERANCE 200
#define BULLRUSH_BOMB_MAX_DISTANCE 3500
void CNPC_AttackHelicopter::UpdateBullrushState( void )
{
if ( !GetEnemy() || IsInForcedMove() )
{
if ( !IsInSecondaryMode( BULLRUSH_MODE_WAIT_FOR_ENEMY ) )
{
SwitchToBullrushIdle();
SetSecondaryMode( BULLRUSH_MODE_WAIT_FOR_ENEMY );
}
}
switch( m_nSecondaryMode )
{
case BULLRUSH_MODE_WAIT_FOR_ENEMY:
{
m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
if ( GetEnemy() && !IsInForcedMove() )
{
// This forces us to not start trying checking positions
// until we have been on the path for a little while
if ( SecondaryModeTime() > 0.3f )
{
float flDistanceToGoal = ComputeDistanceToTargetPosition();
Vector vecPathDir;
CurrentPathDirection( &vecPathDir );
bool bMovingForward = DotProduct2D( GetAbsVelocity().AsVector2D(), vecPathDir.AsVector2D() ) >= 0.0f;
if ( flDistanceToGoal * (bMovingForward ? 1.0f : -1.0f) > 1000 )
{
m_bRushForward = bMovingForward;
SetSecondaryMode( BULLRUSH_MODE_SHOOT_GUN );
SpotlightStartup();
}
else
{
m_bRushForward = !bMovingForward;
SetSecondaryMode( BULLRUSH_MODE_GET_DISTANCE );
}
}
}
else
{
m_flSecondaryModeStartTime = gpGlobals->curtime;
}
}
break;
case BULLRUSH_MODE_GET_DISTANCE:
{
m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
float flDistanceToGoal = ComputeDistanceToTargetPosition();
if ( m_bRushForward )
{
if ( flDistanceToGoal < (CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
break;
}
else
{
if ( flDistanceToGoal > -(CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
break;
}
if ( GetHealth() <= m_flNextMegaBombHealth )
{
m_flNextMegaBombHealth -= GetMaxHealth() * g_helicopter_bullrush_mega_bomb_health.GetFloat();
m_flNextBullrushBombTime = gpGlobals->curtime;
SetSecondaryMode( BULLRUSH_MODE_MEGA_BOMB );
EmitSound( "NPC_AttackHelicopter.MegabombAlert" );
}
else
{
SetSecondaryMode( BULLRUSH_MODE_SHOOT_GUN );
SpotlightStartup();
}
}
break;
case BULLRUSH_MODE_MEGA_BOMB:
{
m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
float flDistanceToGoal = ComputeDistanceToTargetPosition();
if ( m_bRushForward )
{
if ( flDistanceToGoal > -(CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
break;
}
else
{
if ( flDistanceToGoal < (CHOPPER_BULLRUSH_MODE_DISTANCE - 1000) )
break;
}
m_bRushForward = !m_bRushForward;
SetSecondaryMode( BULLRUSH_MODE_GET_DISTANCE );
}
break;
case BULLRUSH_MODE_SHOOT_GUN:
{
// When shooting, stop when we cross the player's position
// Then start bombing. Use the fixed speed version if we're too far
// from the enemy or if he's travelling in the opposite direction.
// Otherwise, do the standard bombing behavior for a while.
float flDistanceToGoal = ComputeDistanceToTargetPosition();
float flShootingHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
float flSwitchToBombDist = CHOPPER_BULLRUSH_ENEMY_BOMB_DISTANCE;
float flDropDownDist = 2000.0f;
if ( m_bRushForward )
{
m_flBullrushAdditionalHeight = ClampSplineRemapVal( flDistanceToGoal,
flSwitchToBombDist, flSwitchToBombDist + flDropDownDist, 0.0f, flShootingHeight );
if ( flDistanceToGoal > flSwitchToBombDist )
break;
}
else
{
m_flBullrushAdditionalHeight = ClampSplineRemapVal( flDistanceToGoal,
-flSwitchToBombDist - flDropDownDist, -flSwitchToBombDist, flShootingHeight, 0.0f );
if ( flDistanceToGoal < -flSwitchToBombDist )
break;
}
if ( ShouldShootIdlePlayerInBullrush() )
{
SetSecondaryMode( BULLRUSH_MODE_SHOOT_IDLE_PLAYER );
}
else
{
ShutdownGunDuringBullrush( );
SetSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED );
}
}
break;
case BULLRUSH_MODE_SHOOT_IDLE_PLAYER:
{
// Shut down our gun if we're switching to bombing
if ( ShouldBombIdlePlayer() )
{
// Must not already be shutdown
if (( m_nGunState != GUN_STATE_IDLE ) && (SecondaryModeTime() >= BULLRUSH_IDLE_PLAYER_FIRE_TIME))
{
ShutdownGunDuringBullrush( );
}
}
// m_nBurstHits = 0;
m_flCircleOfDeathRadius = ClampSplineRemapVal( SecondaryModeTime(), BULLRUSH_IDLE_PLAYER_FIRE_TIME, BULLRUSH_IDLE_PLAYER_FIRE_TIME + 5.0f, 256.0f, 64.0f );
m_flBullrushAdditionalHeight = CHOPPER_BULLRUSH_SHOOTING_VERTICAL_OFFSET;
if ( !ShouldShootIdlePlayerInBullrush() )
{
ShutdownGunDuringBullrush( );
SetSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED );
}
}
break;
case BULLRUSH_MODE_DROP_BOMBS_FOLLOW_PLAYER:
{
m_flBullrushAdditionalHeight = 0.0f;
float flDistanceToGoal = ComputeDistanceToTargetPosition();
if ( fabs( flDistanceToGoal ) > 2000.0f )
{
SetSecondaryMode( BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED, true );
break;
}
}
// FALL THROUGH!!
case BULLRUSH_MODE_DROP_BOMBS_FIXED_SPEED:
{
float flDistanceToGoal = ComputeDistanceToTargetPosition();
m_flBullrushAdditionalHeight = 0.0f;
if (( SecondaryModeTime() >= CHOPPER_BULLRUSH_ENEMY_BOMB_TIME ) || ( flDistanceToGoal > BULLRUSH_BOMB_MAX_DISTANCE ))
{
m_bRushForward = !m_bRushForward;
SetSecondaryMode( BULLRUSH_MODE_GET_DISTANCE );
}
}
break;
}
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::UpdateEnemyLeading( void )
{
bool bEnemyInVehicle = true;
CBaseEntity *pTarget = GetEnemyVehicle();
if ( !pTarget )
{
bEnemyInVehicle = false;
if ( (m_nAttackMode == ATTACK_MODE_DEFAULT) || !GetEnemy() )
{
EnableLeading( false );
return;
}
pTarget = GetEnemy();
}
EnableLeading( true );
float flLeadingDist = 0.0f;
float flSpeedAlongPath = TargetSpeedAlongPath();
float flSpeed = pTarget->GetSmoothedVelocity().Length();
// Do the test electricity gun
if ( HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) )
{
if ( flSpeedAlongPath < 200.0f )
{
flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, 0.0f, 200.0f, 100.0f, -200.0f );
}
else
{
flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, 200.0f, 600.0f, -200.0f, -500.0f );
}
SetLeadingDistance( flLeadingDist );
return;
}
switch( m_nAttackMode )
{
case ATTACK_MODE_BULLRUSH_VEHICLE:
flLeadingDist = ComputeBullrushLeadingDistance( flSpeed, flSpeedAlongPath, bEnemyInVehicle );
break;
case ATTACK_MODE_ALWAYS_LEAD_VEHICLE:
if (( flSpeed <= MIN_ENEMY_SPEED ) && (bEnemyInVehicle) )
{
flLeadingDist = CreepTowardEnemy( flSpeed, 0.0f, MIN_ENEMY_SPEED, 0.0f, 1000.0f );
}
else
{
if ( flSpeedAlongPath > 0.0f )
{
flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, 200.0f, 600.0f, 1000.0f, 2000.0f );
}
else
{
flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, -600.0f, -200.0f, -2000.0f, -1000.0f );
}
}
break;
case ATTACK_MODE_BOMB_VEHICLE:
flLeadingDist = ComputeBombingLeadingDistance( flSpeed, flSpeedAlongPath, bEnemyInVehicle );
break;
case ATTACK_MODE_DEFAULT:
case ATTACK_MODE_TRAIL_VEHICLE:
if (( flSpeed <= MIN_ENEMY_SPEED ) && (bEnemyInVehicle))
{
flLeadingDist = CreepTowardEnemy( flSpeed, 150.0f, MIN_ENEMY_SPEED, 500.0f, -1000.0f );
}
else
{
flLeadingDist = ClampSplineRemapVal( flSpeedAlongPath, -600.0f, -200.0f, -2500.0f, -1000.0f );
}
break;
}
SetLeadingDistance( flLeadingDist );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pInfo -
// bAlways -
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
{
// Are we already marked for transmission?
if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
return;
BaseClass::SetTransmit( pInfo, bAlways );
// Make our smoke trails always come with us
for ( int i = 0; i < m_nSmokeTrailCount; i++ )
{
m_hSmokeTrail[i]->SetTransmit( pInfo, bAlways );
}
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CNPC_AttackHelicopter::Hunt( void )
{
if ( m_lifeState == LIFE_DEAD )
{
return;
}
if ( m_lifeState == LIFE_DYING )
{
Flight();
UpdatePlayerDopplerShift( );
return;
}
// FIXME: Hack to allow us to change the firing distance
SetFarthestPathDist( GetMaxFiringDistance() );
UpdateEnemy();
// Give free knowledge of the enemy position if the chopper is "aggressive"
if ( HasSpawnFlags( SF_HELICOPTER_AGGRESSIVE ) && GetEnemy() )
{
m_vecTargetPosition = GetEnemy()->WorldSpaceCenter();
}
// Test for state transitions when in bullrush mode
if ( m_nAttackMode == ATTACK_MODE_BULLRUSH_VEHICLE )
{
UpdateBullrushState();
}
UpdateEnemyLeading();
UpdateTrackNavigation( );
Flight();
UpdatePlayerDopplerShift( );
FireWeapons();
if ( !(m_fHelicopterFlags & BITS_HELICOPTER_GUN_ON) )
{
// !!!HACKHACK This is a fairly unsavoury hack that allows the attack
// chopper to continue to carpet bomb even with the gun turned off
// (Normally the chopper will carpet bomb inside FireGun(), but FireGun()
// doesn't get called by the above call to FireWeapons() if the gun is turned off)
// Adding this little exception here lets me avoid going into the CBaseHelicopter and
// making some functions virtual that don't want to be virtual.
if ( IsCarpetBombing() )
{
BullrushBombs();
}
}
#ifdef HL2_EPISODIC
// Update our bone followers
m_BoneFollowerManager.UpdateBoneFollowers(this);
#endif // HL2_EPISODIC
}
//-----------------------------------------------------------------------------
// Purpose: Cache whatever pose parameters we intend to use
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::PopulatePoseParameters( void )
{
m_poseWeapon_Pitch = LookupPoseParameter("weapon_pitch");
m_poseWeapon_Yaw = LookupPoseParameter("weapon_yaw");
m_poseRudder = LookupPoseParameter("rudder");
BaseClass::PopulatePoseParameters();
}
#ifdef HL2_EPISODIC
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_AttackHelicopter::InitBoneFollowers( void )
{
// Don't do this if we're already loaded
if ( m_BoneFollowerManager.GetNumBoneFollowers() != 0 )
return;
// Init our followers
m_BoneFollowerManager.InitBoneFollowers( this, ARRAYSIZE(pFollowerBoneNames), pFollowerBoneNames );
}
#endif // HL2_EPISODIC
//-----------------------------------------------------------------------------
// Where are how should we avoid?
//-----------------------------------------------------------------------------
AI_BEGIN_CUSTOM_NPC( npc_helicopter, CNPC_AttackHelicopter )
// DECLARE_TASK( )
DECLARE_ACTIVITY( ACT_HELICOPTER_DROP_BOMB );
DECLARE_ACTIVITY( ACT_HELICOPTER_CRASHING );
// DECLARE_CONDITION( COND_ )
//=========================================================
// DEFINE_SCHEDULE
// (
// SCHED_DUMMY,
//
// " Tasks"
// " TASK_FACE_ENEMY 0"
// " "
// " Interrupts"
// )
AI_END_CUSTOM_NPC()
//------------------------------------------------------------------------------
//
// A sensor used to drop bombs only in the correct points
//
//------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( npc_helicoptersensor, CBombDropSensor );
BEGIN_DATADESC( CBombDropSensor )
DEFINE_INPUTFUNC( FIELD_VOID, "DropBomb", InputDropBomb ),
DEFINE_INPUTFUNC( FIELD_VOID, "DropBombStraightDown", InputDropBombStraightDown ),
DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTargetAlways", InputDropBombAtTargetAlways ),
DEFINE_INPUTFUNC( FIELD_STRING, "DropBombAtTarget", InputDropBombAtTarget ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "DropBombDelay", InputDropBombDelay ),
END_DATADESC()
void CBombDropSensor::Spawn()
{
BaseClass::Spawn();
UTIL_SetSize(this, Vector(-30,-30,-30), Vector(30,30,30) );
SetSolid(SOLID_BBOX);
// Shots pass through
SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
}
// Drop a bomb at a particular location
void CBombDropSensor::InputDropBomb( inputdata_t &inputdata )
{
inputdata_t myVersion = inputdata;
myVersion.pActivator = this;
assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBomb( myVersion );
}
void CBombDropSensor::InputDropBombStraightDown( inputdata_t &inputdata )
{
inputdata_t myVersion = inputdata;
myVersion.pActivator = this;
assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombStraightDown( myVersion );
}
void CBombDropSensor::InputDropBombAtTarget( inputdata_t &inputdata )
{
inputdata_t myVersion = inputdata;
myVersion.pActivator = this;
assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombAtTarget( myVersion );
}
void CBombDropSensor::InputDropBombAtTargetAlways( inputdata_t &inputdata )
{
inputdata_t myVersion = inputdata;
myVersion.pActivator = this;
assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombAtTargetAlways( myVersion );
}
void CBombDropSensor::InputDropBombDelay( inputdata_t &inputdata )
{
inputdata_t myVersion = inputdata;
myVersion.pActivator = this;
assert_cast<CNPC_AttackHelicopter*>(GetOwnerEntity())->InputDropBombDelay( myVersion );
}
//------------------------------------------------------------------------------
//
// The bombs the helicopter drops on the player
//
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Save/load
//------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( grenade_helicopter, CGrenadeHelicopter );
BEGIN_DATADESC( CGrenadeHelicopter )
DEFINE_FIELD( m_bActivated, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bExplodeOnContact, FIELD_BOOLEAN ),
DEFINE_SOUNDPATCH( m_pWarnSound ),
DEFINE_FIELD( m_hWarningSprite, FIELD_EHANDLE ),
DEFINE_FIELD( m_bBlinkerAtTop, FIELD_BOOLEAN ),
#ifdef HL2_EPISODIC
DEFINE_FIELD( m_flLifetime, FIELD_FLOAT ),
DEFINE_FIELD( m_hCollisionObject, FIELD_EHANDLE ),
DEFINE_FIELD( m_bPickedUp, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flBlinkFastTime, FIELD_TIME ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "ExplodeIn", InputExplodeIn ),
DEFINE_OUTPUT( m_OnPhysGunOnlyPickup, "OnPhysGunOnlyPickup" ),
#endif // HL2_EPISODIC
DEFINE_THINKFUNC( ExplodeThink ),
DEFINE_THINKFUNC( AnimateThink ),
DEFINE_THINKFUNC( RampSoundThink ),
DEFINE_THINKFUNC( WarningBlinkerThink ),
DEFINE_ENTITYFUNC( ExplodeConcussion ),
END_DATADESC()
#define SF_HELICOPTER_GRENADE_DUD (1<<16) // Will not become active on impact, only when launched via physcannon
//------------------------------------------------------------------------------
// Precache
//------------------------------------------------------------------------------
void CGrenadeHelicopter::Precache( void )
{
BaseClass::Precache( );
PrecacheModel( GRENADE_HELICOPTER_MODEL );
PrecacheScriptSound( "ReallyLoudSpark" );
PrecacheScriptSound( "NPC_AttackHelicopterGrenade.Ping" );
PrecacheScriptSound( "NPC_AttackHelicopterGrenade.PingCaptured" );
PrecacheScriptSound( "NPC_AttackHelicopterGrenade.HardImpact" );
PrecacheEffect( "HelicopterImpact" );
PrecacheEffect( "WaterSurfaceExplosion" );
PrecacheEffect( "HelicopterMegaBomb" );
UTIL_PrecacheOther( "env_explosion" );
}
//------------------------------------------------------------------------------
// Spawn
//------------------------------------------------------------------------------
void CGrenadeHelicopter::Spawn( void )
{
Precache();
// point sized, solid, bouncing
SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
SetModel( GRENADE_HELICOPTER_MODEL );
if ( HasSpawnFlags( SF_HELICOPTER_GRENADE_DUD ) )
{
m_nSkin = (int)SKIN_DUD;
}
if ( !HasSpawnFlags( SF_GRENADE_HELICOPTER_MEGABOMB ) )
{
IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, GetSolidFlags(), false );
SetMoveType( MOVETYPE_VPHYSICS );
Vector vecAbsVelocity = GetAbsVelocity();
pPhysicsObject->AddVelocity( &vecAbsVelocity, NULL );
}
else
{
SetSolid( SOLID_BBOX );
SetCollisionBounds( Vector( -12.5, -12.5, -12.5 ), Vector( 12.5, 12.5, 12.5 ) );
VPhysicsInitShadow( false, false );
SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
SetElasticity( 0.5f );
AddEffects( EF_NOSHADOW );
}
// We're always being dropped beneath the helicopter; need to not
// be affected by the rotor wash
AddEFlags( EFL_NO_ROTORWASH_PUSH );
// contact grenades arc lower
QAngle angles;
VectorAngles(GetAbsVelocity(), angles );
SetLocalAngles( angles );
SetThink( NULL );
// Tumble in air
QAngle vecAngVel( random->RandomFloat ( -100, -500 ), 0, 0 );
SetLocalAngularVelocity( vecAngVel );
// Explode on contact
SetTouch( &CGrenadeHelicopter::ExplodeConcussion );
// use a lower gravity for grenades to make them easier to see
SetGravity( UTIL_ScaleForGravity( 400 ) );
#ifdef HL2_EPISODIC
m_bPickedUp = false;
m_flLifetime = BOMB_LIFETIME * 2.0;
#endif // HL2_EPISODIC
if ( hl2_episodic.GetBool() )
{
// Disallow this, we'd rather deal with them as physobjects
m_takedamage = DAMAGE_NO;
}
else
{
// Allow player to blow this puppy up in the air
m_takedamage = DAMAGE_YES;
}
m_bActivated = false;
m_pWarnSound = NULL;
m_bExplodeOnContact = false;
m_flDamage = sk_helicopter_grenadedamage.GetFloat();
g_pNotify->AddEntity( this, this );
if( hl2_episodic.GetBool() )
{
SetContextThink( &CGrenadeHelicopter::AnimateThink, gpGlobals->curtime, s_pAnimateThinkContext );
}
}
//------------------------------------------------------------------------------
// On Remve
//------------------------------------------------------------------------------
void CGrenadeHelicopter::UpdateOnRemove()
{
if( m_pWarnSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pWarnSound );
}
g_pNotify->ClearEntity( this );
BaseClass::UpdateOnRemove();
}
#ifdef HL2_EPISODIC
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CGrenadeHelicopter::InputExplodeIn( inputdata_t &inputdata )
{
m_flLifetime = inputdata.value.Float();
if ( HasSpawnFlags( SF_HELICOPTER_GRENADE_DUD ) )
{
// We are a dud no more!
RemoveSpawnFlags( SF_HELICOPTER_GRENADE_DUD );
m_nSkin = (int)SKIN_REGULAR;
}
m_bActivated = false;
BecomeActive();
}
#endif
//------------------------------------------------------------------------------
// Activate!
//------------------------------------------------------------------------------
void CGrenadeHelicopter::BecomeActive()
{
if ( m_bActivated )
return;
if ( IsMarkedForDeletion() )
return;
m_bActivated = true;
bool bMegaBomb = HasSpawnFlags(SF_GRENADE_HELICOPTER_MEGABOMB);
SetThink( &CGrenadeHelicopter::ExplodeThink );
if ( hl2_episodic.GetBool() )
{
if ( HasSpawnFlags( SF_HELICOPTER_GRENADE_DUD ) == false )
{
SetNextThink( gpGlobals->curtime + GetBombLifetime() );
}
else
{
// NOTE: A dud will not explode after a set time, only when launched!
SetThink( NULL );
return;
}
}
else
{
SetNextThink( gpGlobals->curtime + GetBombLifetime() );
}
if ( !bMegaBomb )
{
SetContextThink( &CGrenadeHelicopter::RampSoundThink, gpGlobals->curtime + GetBombLifetime() - BOMB_RAMP_SOUND_TIME, s_pRampSoundContext );
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
CReliableBroadcastRecipientFilter filter;
m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopterGrenade.Ping" );
controller.Play( m_pWarnSound, 1.0, PITCH_NORM );
}
SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + (GetBombLifetime() - 2.0f), s_pWarningBlinkerContext );
#ifdef HL2_EPISODIC
m_flBlinkFastTime = gpGlobals->curtime + GetBombLifetime() - 1.0f;
#endif//HL2_EPISODIC
}
//------------------------------------------------------------------------------
// Pow!
//------------------------------------------------------------------------------
void CGrenadeHelicopter::RampSoundThink( )
{
if ( m_pWarnSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangePitch( m_pWarnSound, 140, BOMB_RAMP_SOUND_TIME );
}
SetContextThink( NULL, gpGlobals->curtime, s_pRampSoundContext );
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CGrenadeHelicopter::WarningBlinkerThink()
{
#ifndef HL2_EPISODIC
return;
#endif
/*
if( !m_hWarningSprite.Get() )
{
Vector up;
GetVectors( NULL, NULL, &up );
// Light isn't on, so create the sprite.
m_hWarningSprite = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetAbsOrigin() + up * 10.0f, false );
CSprite *pSprite = (CSprite *)m_hWarningSprite.Get();
if( pSprite != NULL )
{
pSprite->SetParent( this, LookupAttachment("top") );
pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
pSprite->SetScale( 0.35, 0.0 );
}
m_bBlinkerAtTop = true;
ResetSequence( LookupActivity( "ACT_ARM" ) );
}
else
*/
{
// Just flip it to the other attachment.
if( m_bBlinkerAtTop )
{
//m_hWarningSprite->SetParentAttachment( "SetParentAttachment", "bottom", false );
m_nSkin = (int)SKIN_REGULAR;
m_bBlinkerAtTop = false;
}
else
{
//m_hWarningSprite->SetParentAttachment( "SetParentAttachment", "top", false );
m_nSkin = (int)SKIN_DUD;
m_bBlinkerAtTop = true;
}
}
// Frighten people
CSoundEnt::InsertSound ( SOUND_DANGER, WorldSpaceCenter(), g_helicopter_bomb_danger_radius.GetFloat(), 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
#ifdef HL2_EPISODIC
if( gpGlobals->curtime >= m_flBlinkFastTime )
{
SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + 0.1f, s_pWarningBlinkerContext );
}
else
{
SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + 0.2f, s_pWarningBlinkerContext );
}
#endif//HL2_EPISODIC
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CGrenadeHelicopter::StopWarningBlinker()
{
if( m_hWarningSprite.Get() )
{
UTIL_Remove( m_hWarningSprite.Get() );
m_hWarningSprite.Set( NULL );
}
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CGrenadeHelicopter::AnimateThink()
{
StudioFrameAdvance();
SetContextThink( &CGrenadeHelicopter::AnimateThink, gpGlobals->curtime + 0.1f, s_pAnimateThinkContext );
}
//------------------------------------------------------------------------------
// Entity events... these are events targetted to a particular entity
//------------------------------------------------------------------------------
void CGrenadeHelicopter::OnEntityEvent( EntityEvent_t event, void *pEventData )
{
BaseClass::OnEntityEvent( event, pEventData );
if ( event == ENTITY_EVENT_WATER_TOUCH )
{
BecomeActive();
}
}
//------------------------------------------------------------------------------
// If we hit water, then stop
//------------------------------------------------------------------------------
void CGrenadeHelicopter::PhysicsSimulate( void )
{
Vector vecPrevPosition = GetAbsOrigin();
BaseClass::PhysicsSimulate();
if (!m_bActivated && (GetMoveType() != MOVETYPE_VPHYSICS))
{
if ( GetWaterLevel() > 1 )
{
SetAbsVelocity( vec3_origin );
SetMoveType( MOVETYPE_NONE );
BecomeActive();
}
// Stuck condition, can happen pretty often
if ( vecPrevPosition == GetAbsOrigin() )
{
SetAbsVelocity( vec3_origin );
SetMoveType( MOVETYPE_NONE );
BecomeActive();
}
}
}
//------------------------------------------------------------------------------
// If we hit something, start the timer
//------------------------------------------------------------------------------
void CGrenadeHelicopter::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
BaseClass::VPhysicsCollision( index, pEvent );
BecomeActive();
#ifndef HL2_EPISODIC // in ep2, don't do this here, do it in Touch()
if ( m_bExplodeOnContact )
{
Vector vecVelocity;
GetVelocity( &vecVelocity, NULL );
DoExplosion( GetAbsOrigin(), vecVelocity );
}
#endif
if( hl2_episodic.GetBool() )
{
float flImpactSpeed = pEvent->preVelocity->Length();
if( flImpactSpeed > 400.0f && pEvent->pEntities[ 1 ]->IsWorld() )
{
EmitSound( "NPC_AttackHelicopterGrenade.HardImpact" );
}
}
}
#if HL2_EPISODIC
//------------------------------------------------------------------------------
// double launch velocity for ep2_outland_08
//------------------------------------------------------------------------------
Vector CGrenadeHelicopter::PhysGunLaunchVelocity( const Vector &forward, float flMass )
{
// return ( striderbuster_shot_velocity.GetFloat() * forward );
return BaseClass::PhysGunLaunchVelocity(forward,flMass) * sk_helicopter_grenaderadius.GetFloat();
}
#endif
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
float CGrenadeHelicopter::GetBombLifetime()
{
#if HL2_EPISODIC
return m_flLifetime;
#else
return BOMB_LIFETIME;
#endif
}
//------------------------------------------------------------------------------
// Pow!
//------------------------------------------------------------------------------
int CGrenadeHelicopter::OnTakeDamage( const CTakeDamageInfo &info )
{
// We don't take blast damage
if ( info.GetDamageType() & DMG_BLAST )
return 0;
return BaseClass::OnTakeDamage( info );
}
//------------------------------------------------------------------------------
// Pow!
//------------------------------------------------------------------------------
void CGrenadeHelicopter::DoExplosion( const Vector &vecOrigin, const Vector &vecVelocity )
{
ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity() ? GetOwnerEntity() : this, sk_helicopter_grenadedamage.GetFloat(),
sk_helicopter_grenaderadius.GetFloat(), (SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODECAL|SF_ENVEXPLOSION_NOFIREBALL|SF_ENVEXPLOSION_NOPARTICLES),
sk_helicopter_grenadeforce.GetFloat(), this );
if ( GetShakeAmplitude() )
{
UTIL_ScreenShake( GetAbsOrigin(), GetShakeAmplitude(), 150.0, 1.0, GetShakeRadius(), SHAKE_START );
}
CEffectData data;
// If we're under water do a water explosion
if ( GetWaterLevel() != 0 && (GetWaterType() & CONTENTS_WATER) )
{
data.m_vOrigin = WorldSpaceCenter();
data.m_flMagnitude = 128;
data.m_flScale = 128;
data.m_fFlags = 0;
DispatchEffect( "WaterSurfaceExplosion", data );
}
else
{
// Otherwise do a normal explosion
data.m_vOrigin = GetAbsOrigin();
DispatchEffect( "HelicopterMegaBomb", data );
}
UTIL_Remove( this );
}
//------------------------------------------------------------------------------
// I think I Pow!
//------------------------------------------------------------------------------
void CGrenadeHelicopter::ExplodeThink(void)
{
#ifdef HL2_EPISODIC
// remember if we were thrown by player, we can only determine this prior to explosion
bool bIsThrownByPlayer = IsThrownByPlayer();
int iHealthBefore = 0;
// get the health of the helicopter we came from prior to our explosion
CNPC_AttackHelicopter *pOwner = dynamic_cast<CNPC_AttackHelicopter *>( GetOriginalThrower() );
if ( pOwner )
{
iHealthBefore = pOwner->GetHealth();
}
#endif // HL2_EPISODIC
Vector vecVelocity;
GetVelocity( &vecVelocity, NULL );
DoExplosion( GetAbsOrigin(), vecVelocity );
#ifdef HL2_EPISODIC
// if we were thrown by player, look at health of helicopter after explosion and determine if we damaged it
if ( bIsThrownByPlayer && pOwner && ( iHealthBefore > 0 ) )
{
int iHealthAfter = pOwner->GetHealth();
if ( iHealthAfter == iHealthBefore )
{
// The player threw us, we exploded due to timer, and we did not damage the helicopter that fired us. Send a miss event
SendMissEvent();
}
}
#endif // HL2_EPISODIC
}
//------------------------------------------------------------------------------
// I think I Pow!
//------------------------------------------------------------------------------
void CGrenadeHelicopter::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity )
{
ResolveFlyCollisionBounce( trace, vecVelocity, 0.1f );
}
//------------------------------------------------------------------------------
// Contact grenade, explode when it touches something
//------------------------------------------------------------------------------
void CGrenadeHelicopter::ExplodeConcussion( CBaseEntity *pOther )
{
if ( !pOther->IsSolid() )
return;
if ( !m_bExplodeOnContact )
{
if ( pOther->IsWorld() )
return;
if ( hl2_episodic.GetBool() )
{
// Don't hit anything other than vehicles
if ( pOther->GetCollisionGroup() != COLLISION_GROUP_VEHICLE )
return;
}
}
#ifdef HL2_EPISODIC
CBaseEntity *pEntityHit = pOther;
if ( pEntityHit->ClassMatches( "phys_bone_follower" ) && pEntityHit->GetOwnerEntity() )
{
pEntityHit = pEntityHit->GetOwnerEntity();
}
if ( ( CLASS_COMBINE_GUNSHIP != pEntityHit->Classify() ) || !pEntityHit->ClassMatches( "npc_helicopter" ) )
{
// We hit something other than a helicopter. If the player threw us, send a miss event
if ( IsThrownByPlayer() )
{
SendMissEvent();
}
}
#endif // HL2_EPISODIC
Vector vecVelocity;
GetVelocity( &vecVelocity, NULL );
DoExplosion( GetAbsOrigin(), vecVelocity );
}
#ifdef HL2_EPISODIC
//-----------------------------------------------------------------------------
// Purpose: The bomb will act differently when picked up by the player
//-----------------------------------------------------------------------------
void CGrenadeHelicopter::OnPhysGunPickup(CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
{
if ( reason == PICKED_UP_BY_CANNON )
{
if ( !m_bPickedUp )
{
if( m_hWarningSprite.Get() != NULL )
{
UTIL_Remove( m_hWarningSprite );
m_hWarningSprite.Set(NULL);
}
// Turn on
BecomeActive();
// Change the warning sound to a captured sound.
SetContextThink( &CGrenadeHelicopter::RampSoundThink, gpGlobals->curtime + GetBombLifetime() - BOMB_RAMP_SOUND_TIME, s_pRampSoundContext );
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pWarnSound );
CReliableBroadcastRecipientFilter filter;
m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_AttackHelicopterGrenade.PingCaptured" );
controller.Play( m_pWarnSound, 1.0, PITCH_NORM );
// Reset our counter so the player has more time
SetThink( &CGrenadeHelicopter::ExplodeThink );
SetNextThink( gpGlobals->curtime + GetBombLifetime() );
SetContextThink( &CGrenadeHelicopter::WarningBlinkerThink, gpGlobals->curtime + GetBombLifetime() - 2.0f, s_pWarningBlinkerContext );
#ifdef HL2_EPISODIC
m_nSkin = (int)SKIN_REGULAR;
m_flBlinkFastTime = gpGlobals->curtime + GetBombLifetime() - 1.0f;
#endif//HL2_EPISODIC
// Stop us from sparing damage to the helicopter that dropped us
SetOwnerEntity( pPhysGunUser );
PhysEnableEntityCollisions( this, m_hCollisionObject );
// Don't do this again!
m_bPickedUp = true;
m_OnPhysGunOnlyPickup.FireOutput( pPhysGunUser, this );
}
}
BaseClass::OnPhysGunPickup( pPhysGunUser, reason );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGrenadeHelicopter::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason )
{
if ( reason == LAUNCHED_BY_CANNON )
{
// Enable world touches.
unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
// Explode on contact
SetTouch( &CGrenadeHelicopter::ExplodeConcussion );
m_bExplodeOnContact = true;
}
BaseClass::OnPhysGunDrop( pPhysGunUser, reason );
}
//-----------------------------------------------------------------------------
// Purpose: Returns if the player threw this grenade w/phys gun
//-----------------------------------------------------------------------------
bool CGrenadeHelicopter::IsThrownByPlayer()
{
// if player is the owner and we're set to explode on contact, then the player threw this grenade.
return ( ( GetOwnerEntity() == UTIL_GetLocalPlayer() ) && m_bExplodeOnContact );
}
//-----------------------------------------------------------------------------
// Purpose: If player threw this grenade, sends a miss event
//-----------------------------------------------------------------------------
void CGrenadeHelicopter::SendMissEvent()
{
// send a miss event
IGameEvent *event = gameeventmanager->CreateEvent( "helicopter_grenade_punt_miss" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
}
#endif // HL2_EPISODIC
//-----------------------------------------------------------------------------
//
// This entity is used to create little force spheres that the helicopters should avoid.
//
//-----------------------------------------------------------------------------
CUtlVector< CAvoidSphere::AvoidSphereHandle_t > CAvoidSphere::s_AvoidSpheres;
#define SF_AVOIDSPHERE_AVOID_BELOW 0x00010000
LINK_ENTITY_TO_CLASS( npc_heli_avoidsphere, CAvoidSphere );
BEGIN_DATADESC( CAvoidSphere )
DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Creates an avoidance sphere
//-----------------------------------------------------------------------------
CBaseEntity *CreateHelicopterAvoidanceSphere( CBaseEntity *pParent, int nAttachment, float flRadius, bool bAvoidBelow )
{
CAvoidSphere *pSphere = static_cast<CAvoidSphere*>(CreateEntityByName( "npc_heli_avoidsphere" ));
pSphere->Init( flRadius );
if ( bAvoidBelow )
{
pSphere->AddSpawnFlags( SF_AVOIDSPHERE_AVOID_BELOW );
}
pSphere->Spawn();
pSphere->SetParent( pParent, nAttachment );
pSphere->SetLocalOrigin( vec3_origin );
pSphere->SetLocalAngles( vec3_angle );
pSphere->SetOwnerEntity( pParent );
return pSphere;
}
//-----------------------------------------------------------------------------
// Init
//-----------------------------------------------------------------------------
void CAvoidSphere::Init( float flRadius )
{
m_flRadius = flRadius;
}
//-----------------------------------------------------------------------------
// Spawn, remove
//-----------------------------------------------------------------------------
void CAvoidSphere::Activate( )
{
BaseClass::Activate();
s_AvoidSpheres.AddToTail( this );
}
void CAvoidSphere::UpdateOnRemove( )
{
s_AvoidSpheres.FindAndRemove( this );
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------
// Where are how should we avoid?
//-----------------------------------------------------------------------------
void CAvoidSphere::ComputeAvoidanceForces( CBaseEntity *pEntity, float flEntityRadius,
float flAvoidTime, Vector *pVecAvoidForce )
{
pVecAvoidForce->Init( );
Vector vecEntityDelta;
VectorMultiply( pEntity->GetAbsVelocity(), flAvoidTime, vecEntityDelta );
Vector vecEntityCenter = pEntity->WorldSpaceCenter();
for ( int i = s_AvoidSpheres.Count(); --i >= 0; )
{
CAvoidSphere *pSphere = s_AvoidSpheres[i].Get();
const Vector &vecAvoidCenter = pSphere->WorldSpaceCenter();
// NOTE: This test can be thought of sweeping a sphere through space
// and seeing if it intersects the avoidance sphere
float flTotalRadius = flEntityRadius + pSphere->m_flRadius;
float t1, t2;
if ( !IntersectRayWithSphere( vecEntityCenter, vecEntityDelta,
vecAvoidCenter, flTotalRadius, &t1, &t2 ) )
{
continue;
}
// NOTE: The point of closest approach is at the average t value
Vector vecClosestApproach;
float flAverageT = (t1 + t2) * 0.5f;
VectorMA( vecEntityCenter, flAverageT, vecEntityDelta, vecClosestApproach );
// Add velocity to make it be pushed out away from the sphere center
// without totally counteracting its velocity.
Vector vecDir;
VectorSubtract( vecClosestApproach, vecAvoidCenter, vecDir );
float flZDist = vecDir.z;
float flDist = VectorNormalize( vecDir );
float flDistToTravel;
if ( flDist < 0.01f )
{
flDist = 0.01f;
vecDir.Init( 0, 0, 1 );
flDistToTravel = flTotalRadius;
}
else
{
// make the chopper always avoid *above*
// That means if a force would be applied to push the chopper down,
// figure out a new distance to travel that would push the chopper up.
if ( flZDist < 0.0f && !pSphere->HasSpawnFlags(SF_AVOIDSPHERE_AVOID_BELOW) )
{
Vector vecExitPoint;
vecDir.z = -vecDir.z;
VectorMA( vecAvoidCenter, flTotalRadius, vecDir, vecExitPoint );
VectorSubtract( vecExitPoint, vecClosestApproach, vecDir );
flDistToTravel = VectorNormalize( vecDir );
}
else
{
Assert( flDist <= flTotalRadius );
flDistToTravel = flTotalRadius - flDist;
}
}
// The actual force amount is easy to think about:
// We need to change the position by dx over a time dt, so dv = dx/dt
// But so it doesn't explode, lets clamp t1 to a not-unreasonable time
if ( t1 < 0.25f )
{
t1 = 0.25f;
}
float flForce = 1.25f * flDistToTravel / t1;
vecDir *= flForce;
*pVecAvoidForce += vecDir;
}
}
//-----------------------------------------------------------------------------
//
// This entity is used to create little force boxes that the helicopters should avoid.
//
//-----------------------------------------------------------------------------
CUtlVector< CAvoidBox::AvoidBoxHandle_t > CAvoidBox::s_AvoidBoxes;
#define SF_AVOIDBOX_AVOID_BELOW 0x00010000
LINK_ENTITY_TO_CLASS( npc_heli_avoidbox, CAvoidBox );
BEGIN_DATADESC( CAvoidBox )
END_DATADESC()
//-----------------------------------------------------------------------------
// Spawn, remove
//-----------------------------------------------------------------------------
void CAvoidBox::Spawn( )
{
SetModel( STRING( GetModelName() ) );
SetSolid( SOLID_BSP );
AddSolidFlags( FSOLID_NOT_SOLID );
AddEffects( EF_NODRAW );
}
void CAvoidBox::Activate( )
{
BaseClass::Activate();
s_AvoidBoxes.AddToTail( this );
}
void CAvoidBox::UpdateOnRemove( )
{
s_AvoidBoxes.FindAndRemove( this );
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------
// Where are how should we avoid?
//-----------------------------------------------------------------------------
void CAvoidBox::ComputeAvoidanceForces( CBaseEntity *pEntity, float flEntityRadius, float flAvoidTime, Vector *pVecAvoidForce )
{
pVecAvoidForce->Init( );
Vector vecEntityDelta, vecEntityEnd;
VectorMultiply( pEntity->GetAbsVelocity(), flAvoidTime, vecEntityDelta );
Vector vecEntityCenter = pEntity->WorldSpaceCenter();
VectorAdd( vecEntityCenter, vecEntityDelta, vecEntityEnd );
Vector vecVelDir = pEntity->GetAbsVelocity();
VectorNormalize( vecVelDir );
for ( int i = s_AvoidBoxes.Count(); --i >= 0; )
{
CAvoidBox *pBox = s_AvoidBoxes[i].Get();
const Vector &vecAvoidCenter = pBox->WorldSpaceCenter();
// NOTE: This test can be thought of sweeping a sphere through space
// and seeing if it intersects the avoidance box
float flTotalRadius = flEntityRadius + pBox->BoundingRadius();
float t1, t2;
if ( !IntersectInfiniteRayWithSphere( vecEntityCenter, vecEntityDelta,
vecAvoidCenter, flTotalRadius, &t1, &t2 ) )
{
continue;
}
if (( t2 < 0.0f ) || ( t1 > 1.0f ))
continue;
// Unlike the avoid spheres, we also need to make sure the ray intersects the box
Vector vecLocalCenter, vecLocalDelta;
pBox->CollisionProp()->WorldToCollisionSpace( vecEntityCenter, &vecLocalCenter );
pBox->CollisionProp()->WorldDirectionToCollisionSpace( vecEntityDelta, &vecLocalDelta );
Vector vecBoxMin( -flEntityRadius, -flEntityRadius, -flEntityRadius );
Vector vecBoxMax( flEntityRadius, flEntityRadius, flEntityRadius );
vecBoxMin += pBox->CollisionProp()->OBBMins();
vecBoxMax += pBox->CollisionProp()->OBBMaxs();
trace_t tr;
if ( !IntersectRayWithBox( vecLocalCenter, vecLocalDelta, vecBoxMin, vecBoxMax, 0.0f, &tr ) )
continue;
// NOTE: The point of closest approach is at the average t value
Vector vecClosestApproach;
float flAverageT = (t1 + t2) * 0.5f;
VectorMA( vecEntityCenter, flAverageT, vecEntityDelta, vecClosestApproach );
// Add velocity to make it be pushed out away from the sphere center
// without totally counteracting its velocity.
Vector vecDir;
VectorSubtract( vecClosestApproach, vecAvoidCenter, vecDir );
// Limit unnecessary sideways motion
if ( ( tr.plane.type != 3 ) || ( tr.plane.normal[2] > 0.0f ) )
{
vecDir.x *= 0.1f;
vecDir.y *= 0.1f;
}
float flZDist = vecDir.z;
float flDist = VectorNormalize( vecDir );
float flDistToTravel;
if ( flDist < 10.0f )
{
flDist = 10.0f;
vecDir.Init( 0, 0, 1 );
flDistToTravel = flTotalRadius;
}
else
{
// make the chopper always avoid *above*
// That means if a force would be applied to push the chopper down,
// figure out a new distance to travel that would push the chopper up.
if ( flZDist < 0.0f && !pBox->HasSpawnFlags(SF_AVOIDSPHERE_AVOID_BELOW) )
{
Vector vecExitPoint;
vecDir.z = -vecDir.z;
VectorMA( vecAvoidCenter, flTotalRadius, vecDir, vecExitPoint );
VectorSubtract( vecExitPoint, vecClosestApproach, vecDir );
flDistToTravel = VectorNormalize( vecDir );
}
else
{
Assert( flDist <= flTotalRadius );
flDistToTravel = flTotalRadius - flDist;
}
}
// The actual force amount is easy to think about:
// We need to change the position by dx over a time dt, so dv = dx/dt
// But so it doesn't explode, lets clamp t1 to a not-unreasonable time
if ( t1 < 0.25f )
{
t1 = 0.25f;
}
float flForce = 1.5f * flDistToTravel / t1;
vecDir *= flForce;
*pVecAvoidForce += vecDir;
}
}
//-----------------------------------------------------------------------------
//
// This entity is used to create little force boxes that the helicopters should avoid.
//
//-----------------------------------------------------------------------------
CUtlVector< CBombSuppressor::BombSuppressorHandle_t > CBombSuppressor::s_BombSuppressors;
LINK_ENTITY_TO_CLASS( npc_heli_nobomb, CBombSuppressor );
BEGIN_DATADESC( CBombSuppressor )
END_DATADESC()
//-----------------------------------------------------------------------------
// Spawn, remove
//-----------------------------------------------------------------------------
void CBombSuppressor::Spawn( )
{
SetModel( STRING( GetModelName() ) );
SetSolid( SOLID_BSP );
AddSolidFlags( FSOLID_NOT_SOLID );
AddEffects( EF_NODRAW );
}
void CBombSuppressor::Activate( )
{
BaseClass::Activate();
s_BombSuppressors.AddToTail( this );
}
void CBombSuppressor::UpdateOnRemove( )
{
s_BombSuppressors.FindAndRemove( this );
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------
// Where are how should we avoid?
//-----------------------------------------------------------------------------
bool CBombSuppressor::CanBomb( const Vector &vecPosition )
{
for ( int i = s_BombSuppressors.Count(); --i >= 0; )
{
CBombSuppressor *pBox = s_BombSuppressors[i].Get();
if ( pBox->CollisionProp()->IsPointInBounds( vecPosition ) )
return false;
}
return true;
}
LINK_ENTITY_TO_CLASS( helicopter_chunk, CHelicopterChunk );
BEGIN_DATADESC( CHelicopterChunk )
DEFINE_THINKFUNC( FallThink ),
DEFINE_FIELD( m_bLanded, FIELD_BOOLEAN ),
DEFINE_FIELD( m_hMaster, FIELD_EHANDLE ),
DEFINE_FIELD( m_nChunkID, FIELD_INTEGER ),
DEFINE_PHYSPTR( m_pTailConstraint ),
DEFINE_PHYSPTR( m_pCockpitConstraint ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHelicopterChunk::Spawn( void )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHelicopterChunk::FallThink( void )
{
if ( m_bLanded )
{
SetThink( NULL );
return;
}
if ( random->RandomInt( 0, 8 ) == 0 )
{
CEffectData data;
data.m_vOrigin = GetAbsOrigin() + RandomVector( -64, 64 );
DispatchEffect( "HelicopterMegaBomb", data );
EmitSound( "BaseExplosionEffect.Sound" );
}
SetNextThink( gpGlobals->curtime + 0.1f );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
// *pEvent -
//-----------------------------------------------------------------------------
void CHelicopterChunk::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
BaseClass::VPhysicsCollision( index, pEvent );
if ( m_bLanded == false )
{
int otherIndex = !index;
CBaseEntity *pOther = pEvent->pEntities[otherIndex];
if ( !pOther )
return;
if ( pOther->IsWorld() )
{
CollisionCallback( this );
m_bLanded = true;
SetThink( NULL );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pCaller -
//-----------------------------------------------------------------------------
void CHelicopterChunk::CollisionCallback( CHelicopterChunk *pCaller )
{
if ( m_bLanded )
return;
if ( m_hMaster != NULL )
{
m_hMaster->CollisionCallback( this );
}
else
{
// Break our other constraints
if ( m_pTailConstraint )
{
physenv->DestroyConstraint( m_pTailConstraint );
m_pTailConstraint = NULL;
}
if ( m_pCockpitConstraint )
{
physenv->DestroyConstraint( m_pCockpitConstraint );
m_pCockpitConstraint = NULL;
}
// Add a dust cloud
AR2Explosion *pExplosion = AR2Explosion::CreateAR2Explosion( GetAbsOrigin() );
if ( pExplosion != NULL )
{
pExplosion->SetLifetime( 10 );
}
// Make a loud noise
EmitSound( "NPC_AttackHelicopter.Crash" );
m_bLanded = true;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &vecPos -
// &vecAngles -
// &vecVelocity -
// *pszModelName -
// Output : CHelicopterChunk
//-----------------------------------------------------------------------------
CHelicopterChunk *CHelicopterChunk::CreateHelicopterChunk( const Vector &vecPos, const QAngle &vecAngles, const Vector &vecVelocity, const char *pszModelName, int chunkID )
{
// Drop a flaming, smoking chunk.
CHelicopterChunk *pChunk = CREATE_ENTITY( CHelicopterChunk, "helicopter_chunk" );
if ( pChunk == NULL )
return NULL;
pChunk->Spawn();
pChunk->SetAbsOrigin( vecPos );
pChunk->SetAbsAngles( vecAngles );
pChunk->SetModel( pszModelName );
pChunk->m_nChunkID = chunkID;
pChunk->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE );
IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
// Set the velocity
if ( pPhysicsObject )
{
pPhysicsObject->EnableMotion( true );
Vector vecChunkVelocity;
AngularImpulse angImpulse;
vecChunkVelocity = vecVelocity;
angImpulse = vec3_origin;
pPhysicsObject->SetVelocity(&vecChunkVelocity, &angImpulse );
}
pChunk->SetThink( &CHelicopterChunk::FallThink );
pChunk->SetNextThink( gpGlobals->curtime + 0.1f );
pChunk->m_bLanded = false;
SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
pSmokeTrail->FollowEntity( pChunk, "damage" );
pSmokeTrail->m_SpawnRate = 4;
pSmokeTrail->m_ParticleLifetime = 2.0f;
pSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f );
pSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 );
pSmokeTrail->m_StartSize = 32;
pSmokeTrail->m_EndSize = 64;
pSmokeTrail->m_SpawnRadius= 8;
pSmokeTrail->m_MinSpeed = 0;
pSmokeTrail->m_MaxSpeed = 8;
pSmokeTrail->m_Opacity = 0.35f;
CFireTrail *pFireTrail = CFireTrail::CreateFireTrail();
if ( pFireTrail == NULL )
return pChunk;
pFireTrail->FollowEntity( pChunk, "damage" );
pFireTrail->SetParent( pChunk, 1 );
pFireTrail->SetLocalOrigin( vec3_origin );
pFireTrail->SetMoveType( MOVETYPE_NONE );
pFireTrail->SetLifetime( 10.0f );
return pChunk;
}