6135 lines
194 KiB
C++
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;
|
|
}
|