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