3246 lines
97 KiB
C++
3246 lines
97 KiB
C++
//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "baseanimating.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 "ai_motor.h"
|
|
#include "entitylist.h"
|
|
#include "basecombatweapon.h"
|
|
#include "soundenvelope.h"
|
|
#include "gib.h"
|
|
#include "gamerules.h"
|
|
#include "ammodef.h"
|
|
#include "cbasehelicopter.h"
|
|
#include "npcevent.h"
|
|
#include "ndebugoverlay.h"
|
|
#include "decals.h"
|
|
#include "explode.h" // temp (sjb)
|
|
#include "smoke_trail.h" // temp (sjb)
|
|
#include "IEffects.h"
|
|
#include "vstdlib/random.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "ar2_explosion.h"
|
|
#include "te_effect_dispatch.h"
|
|
#include "rope.h"
|
|
#include "effect_dispatch_data.h"
|
|
#include "trains.h"
|
|
#include "globals.h"
|
|
#include "physics_prop_ragdoll.h"
|
|
#include "iservervehicle.h"
|
|
#include "soundent.h"
|
|
#include "npc_citizen17.h"
|
|
#include "physics_saverestore.h"
|
|
#include "hl2_shareddefs.h"
|
|
#include "props.h"
|
|
#include "npc_attackchopper.h"
|
|
#include "citadel_effects_shared.h"
|
|
#include "eventqueue.h"
|
|
#include "beam_flags.h"
|
|
#include "ai_eventresponse.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#define GUNSHIP_MSG_BIG_SHOT 1
|
|
#define GUNSHIP_MSG_STREAKS 2
|
|
|
|
#define GUNSHIP_NUM_DAMAGE_OUTPUTS 4
|
|
|
|
extern int g_sModelIndexFireball; // holds the index for the fireball
|
|
|
|
int g_iGunshipEffectIndex = -1;
|
|
|
|
#define GUNSHIP_ACCEL_RATE 500
|
|
|
|
// Spawnflags
|
|
#define SF_GUNSHIP_NO_GROUND_ATTACK ( 1 << 12 )
|
|
#define SF_GUNSHIP_USE_CHOPPER_MODEL ( 1 << 13 )
|
|
|
|
ConVar sk_gunship_burst_size("sk_gunship_burst_size", "15" );
|
|
ConVar sk_gunship_burst_min("sk_gunship_burst_min", "800" );
|
|
ConVar sk_gunship_burst_dist("sk_gunship_burst_dist", "768" );
|
|
|
|
// Number of times the gunship must be struck by explosive damage
|
|
ConVar sk_gunship_health_increments( "sk_gunship_health_increments", "0" );
|
|
|
|
/*
|
|
|
|
Wedge's notes:
|
|
|
|
Gunship should move its head according to flight model when the target is behind the gunship,
|
|
or when the target is too far away to shoot at. Otherwise, the head should aim at the target.
|
|
|
|
Negative angvelocity.y is a RIGHT turn.
|
|
Negative angvelocity.x is UP
|
|
|
|
*/
|
|
|
|
#define GUNSHIP_AP_MUZZLE 5
|
|
|
|
#define GUNSHIP_MAX_SPEED 1056.0f
|
|
|
|
#define GUNSHIP_MAX_FIRING_SPEED 200.0f
|
|
#define GUNSHIP_MIN_ROCKET_DIST 1000.0f
|
|
#define GUNSHIP_MAX_GUN_DIST 2000.0f
|
|
#define GUNSHIP_ARRIVE_DIST 128.0f
|
|
|
|
#define GUNSHIP_HOVER_SPEED 300.0f // play hover animation if moving slower than this.
|
|
|
|
#define GUNSHIP_AE_THRUST 1
|
|
|
|
#define GUNSHIP_HEAD_MAX_UP -65
|
|
#define GUNSHIP_HEAD_MAX_DOWN 60
|
|
#define GUNSHIP_HEAD_MAX_LEFT 60
|
|
#define GUNSHIP_HEAD_MAX_RIGHT -60
|
|
|
|
#define BASE_STITCH_VELOCITY 800 //Units per second
|
|
#define MAX_STITCH_VELOCITY 1000 //Units per second
|
|
|
|
#define GUNSHIP_LEAD_DISTANCE 800.0f
|
|
#define GUNSHIP_AVOID_DIST 512.0f
|
|
#define GUNSHIP_STITCH_MIN 512.0f
|
|
|
|
#define GUNSHIP_MIN_CHASE_DIST_DIFF 128.0f // Distance threshold used to determine when a target has moved enough to update our navigation to it
|
|
|
|
#define MIN_GROUND_ATTACK_DIST 500.0f // Minimum distance a target has to be for the gunship to consider using the ground attack weapon
|
|
#define MIN_GROUND_ATTACK_HEIGHT_DIFF 128.0f // Target's position and hit position must be within this threshold vertically
|
|
|
|
#define GUNSHIP_WASH_ALTITUDE 1024.0f
|
|
|
|
#define GUNSHIP_MIN_DAMAGE_THRESHOLD 50.0f
|
|
|
|
#define GUNSHIP_INNER_NAV_DIST 400.0f
|
|
#define GUNSHIP_OUTER_NAV_DIST 800.0f
|
|
|
|
#define GUNSHIP_BELLYBLAST_TARGET_HEIGHT 512.0 // Height above targets that the gunship wants to be when bellyblasting
|
|
|
|
#define GUNSHIP_MISSILE_MAX_RESPONSE_TIME 0.4
|
|
#define GUNSHIP_MAX_HITS_PER_BURST 5
|
|
|
|
#define GUNSHIP_FLARE_IGNORE_TIME 6.0
|
|
|
|
//=====================================
|
|
// Custom activities
|
|
//=====================================
|
|
Activity ACT_GUNSHIP_PATROL;
|
|
Activity ACT_GUNSHIP_HOVER;
|
|
Activity ACT_GUNSHIP_CRASH;
|
|
|
|
#define GUNSHIP_DEBUG_LEADING 1
|
|
#define GUNSHIP_DEBUG_PATH 2
|
|
#define GUNSHIP_DEBUG_STITCHING 3
|
|
#define GUNSHIP_DEBUG_BELLYBLAST 4
|
|
|
|
ConVar g_debug_gunship( "g_debug_gunship", "0", FCVAR_CHEAT );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Dying gunship ragdoll controller
|
|
//-----------------------------------------------------------------------------
|
|
class CGunshipRagdollMotion : public IMotionEvent
|
|
{
|
|
DECLARE_SIMPLE_DATADESC();
|
|
public:
|
|
virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
|
|
{
|
|
linear = Vector(0,0,400);
|
|
angular = Vector(0,600,100);
|
|
|
|
return SIM_GLOBAL_ACCELERATION;
|
|
}
|
|
};
|
|
|
|
BEGIN_SIMPLE_DATADESC( CGunshipRagdollMotion )
|
|
END_DATADESC()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
class CTargetGunshipCrash : public CPointEntity
|
|
{
|
|
DECLARE_CLASS( CTargetGunshipCrash, CPointEntity );
|
|
public:
|
|
DECLARE_DATADESC();
|
|
|
|
void InputEnable( inputdata_t &inputdata )
|
|
{
|
|
m_bDisabled = false;
|
|
}
|
|
void InputDisable( inputdata_t &inputdata )
|
|
{
|
|
m_bDisabled = true;
|
|
}
|
|
bool IsDisabled( void )
|
|
{
|
|
return m_bDisabled;
|
|
}
|
|
void GunshipCrashedOnTarget( void )
|
|
{
|
|
m_OnCrashed.FireOutput( this, this );
|
|
}
|
|
|
|
private:
|
|
bool m_bDisabled;
|
|
|
|
COutputEvent m_OnCrashed;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS( info_target_gunshipcrash, CTargetGunshipCrash );
|
|
|
|
BEGIN_DATADESC( CTargetGunshipCrash )
|
|
DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ),
|
|
|
|
// Inputs
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
|
|
|
|
// Outputs
|
|
DEFINE_OUTPUT( m_OnCrashed, "OnCrashed" ),
|
|
END_DATADESC()
|
|
|
|
|
|
//===================================================================
|
|
// Gunship - the combine dugongic like attack vehicle.
|
|
//===================================================================
|
|
class CNPC_CombineGunship : public CBaseHelicopter
|
|
{
|
|
public:
|
|
DECLARE_CLASS( CNPC_CombineGunship, CBaseHelicopter );
|
|
|
|
CNPC_CombineGunship( void );
|
|
~CNPC_CombineGunship( void );
|
|
|
|
DECLARE_DATADESC();
|
|
DECLARE_SERVERCLASS();
|
|
DEFINE_CUSTOM_AI;
|
|
|
|
void PlayPatrolLoop( void );
|
|
void PlayAngryLoop( void );
|
|
|
|
void Spawn( void );
|
|
void Precache( void );
|
|
void OnRestore( void );
|
|
void PrescheduleThink( void );
|
|
void HelicopterPostThink( void );
|
|
void StopLoopingSounds( void );
|
|
|
|
bool IsValidEnemy( CBaseEntity *pEnemy );
|
|
void GatherEnemyConditions( CBaseEntity *pEnemy );
|
|
|
|
void Flight( void );
|
|
|
|
bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
|
|
int OnTakeDamage_Alive( const CTakeDamageInfo &info );
|
|
void FireDamageOutputsUpto( int iDamageNumber );
|
|
|
|
virtual float GetAcceleration( void ) { return 15; }
|
|
|
|
virtual void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
|
|
virtual void DoImpactEffect( trace_t &tr, int nDamageType );
|
|
|
|
void MoveHead( void );
|
|
void UpdateDesiredPosition( void );
|
|
void DoCombat( void );
|
|
bool ChooseEnemy( void );
|
|
void DoMuzzleFlash( void );
|
|
void Ping( void );
|
|
|
|
void FireCannonRound( void );
|
|
|
|
// Gunship death process
|
|
void Event_Killed( const CTakeDamageInfo &info );
|
|
void BeginCrash( void ); // I'm going to go to a crash point and die there
|
|
void BeginDestruct( void ); // I want to die now, so create my ragdoll
|
|
void SelfDestruct( void ); // I'm now fully dead, so remove myself.
|
|
void CreateSmokeTrail( void );
|
|
bool FindNearestGunshipCrash( void );
|
|
|
|
int BloodColor( void ) { return DONT_BLEED; }
|
|
void GibMonster( void );
|
|
|
|
void UpdateRotorSoundPitch( int iPitch );
|
|
void InitializeRotorSound( void );
|
|
|
|
void ApplyGeneralDrag( void );
|
|
void ApplySidewaysDrag( const Vector &vecRight );
|
|
|
|
void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr );
|
|
|
|
void UpdateEnemyTarget( void );
|
|
|
|
Vector GetEnemyTarget( void );
|
|
Vector GetMissileTarget( void );
|
|
|
|
float GroundDistToPosition( const Vector &pos );
|
|
|
|
bool FireGun( void );
|
|
bool IsTargettingMissile( void );
|
|
|
|
Class_T Classify( void ) { return CLASS_COMBINE_GUNSHIP; } // for now
|
|
float GetAutoAimRadius() { return 144.0f; }
|
|
|
|
// Input functions
|
|
void InputSetPenetrationDepth( inputdata_t &inputdata );
|
|
void InputOmniscientOn( inputdata_t &inputdata );
|
|
void InputOmniscientOff( inputdata_t &inputdata );
|
|
void InputBlindfireOn( inputdata_t &inputdata );
|
|
void InputBlindfireOff( inputdata_t &inputdata );
|
|
void InputSelfDestruct( inputdata_t &inputdata );
|
|
void InputSetDockingBBox( inputdata_t &inputdata );
|
|
void InputSetNormalBBox( inputdata_t &inputdata );
|
|
void InputEnableGroundAttack( inputdata_t &inputdata );
|
|
void InputDisableGroundAttack( inputdata_t &inputdata );
|
|
void InputDoGroundAttack( inputdata_t &inputdata );
|
|
|
|
//NOTENOTE: I'm rather queasy about adding these, as they can lead to nasty bugs...
|
|
void InputBecomeInvulnerable( inputdata_t &inputdata );
|
|
void InputBecomeVulnerable( inputdata_t &inputdata );
|
|
|
|
bool PoseGunTowardTargetDirection( const Vector &vTargetDir );
|
|
void StartCannonBurst( int iBurstSize );
|
|
void StopCannonBurst( void );
|
|
|
|
bool CheckGroundAttack( void );
|
|
void StartGroundAttack( void );
|
|
void StopGroundAttack( bool bDoAttack );
|
|
Vector GetGroundAttackHitPosition( void );
|
|
void DoGroundAttackExplosion( void );
|
|
void DrawRotorWash( float flAltitude, const Vector &vecRotorOrigin );
|
|
|
|
void ManageWarningBeam( void );
|
|
void DoBellyBlastDamage( trace_t &tr, Vector vMins, Vector vMaxs );
|
|
|
|
// Updates the facing direction
|
|
void UpdateFacingDirection( void );
|
|
void CreateBellyBlastEnergyCore( void );
|
|
|
|
protected:
|
|
// Because the combine gunship is a leaf class, we can use
|
|
// static variables to store this information, and save some memory.
|
|
// Should the gunship end up having inheritors, their activate may
|
|
// stomp these numbers, in which case you should make these ordinary members
|
|
// again.
|
|
static int m_poseFlex_Horz, m_poseFlex_Vert, m_posePitch, m_poseYaw, m_poseFin_Accel, m_poseFin_Sway;
|
|
static int m_poseWeapon_Pitch, m_poseWeapon_Yaw;
|
|
|
|
static bool m_sbStaticPoseParamsLoaded;
|
|
virtual void PopulatePoseParameters( void );
|
|
|
|
private:
|
|
// Outputs
|
|
COutputEvent m_OnFireCannon;
|
|
COutputEvent m_OnCrashed;
|
|
|
|
COutputEvent m_OnFirstDamage; // First damage tick
|
|
COutputEvent m_OnSecondDamage;
|
|
COutputEvent m_OnThirdDamage;
|
|
COutputEvent m_OnFourthDamage;
|
|
// Keep track of which damage outputs we've fired. This is necessary
|
|
// to ensure that the game doesn't break if a mapmaker has outputs that
|
|
// must be fired on gunships, and the player switches skill levels
|
|
// midway through a gunship battle.
|
|
bool m_bDamageOutputsFired[GUNSHIP_NUM_DAMAGE_OUTPUTS];
|
|
|
|
float m_flNextGroundAttack; // Time to wait before the next ground attack
|
|
bool m_bIsGroundAttacking; // Denotes that we are ground attacking
|
|
bool m_bCanGroundAttack; // Denotes whether we can ground attack or not
|
|
float m_flGroundAttackTime; // Delay before blast happens from ground attack
|
|
|
|
CHandle<SmokeTrail> m_pSmokeTrail;
|
|
EHANDLE m_hGroundAttackTarget;
|
|
|
|
CSoundPatch *m_pAirExhaustSound;
|
|
CSoundPatch *m_pAirBlastSound;
|
|
CSoundPatch *m_pCannonSound;
|
|
|
|
CBaseEntity *m_pRotorWashModel;
|
|
QAngle m_vecAngAcceleration;
|
|
float m_fMaxAngAcceleration;
|
|
Vector m_vMaxAngVelocity;
|
|
|
|
float m_flEndDestructTime;
|
|
|
|
int m_iDoSmokePuff;
|
|
int m_iAmmoType;
|
|
int m_iBurstSize;
|
|
|
|
bool m_fBlindfire;
|
|
bool m_fOmniscient;
|
|
bool m_bIsFiring;
|
|
int m_iBurstHits;
|
|
bool m_bPreFire;
|
|
bool m_bInvulnerable;
|
|
|
|
float m_flTimeNextPing;
|
|
float m_flPenetrationDepth;
|
|
float m_flDeltaT;
|
|
float m_flTimeNextAttack;
|
|
float m_flNextSeeEnemySound;
|
|
float m_flNextRocket;
|
|
float m_flBurstDelay;
|
|
|
|
Vector m_vecAttackPosition;
|
|
Vector m_vecAttackVelocity;
|
|
|
|
// Used when the gunships using the chopper model
|
|
Vector m_angGun;
|
|
|
|
// For my death throes
|
|
IPhysicsMotionController *m_pCrashingController;
|
|
CGunshipRagdollMotion m_crashCallback;
|
|
EHANDLE m_hRagdoll;
|
|
CHandle<CTargetGunshipCrash> m_hCrashTarget;
|
|
float m_flNextGunshipCrashFind;
|
|
|
|
CHandle<CCitadelEnergyCore> m_hEnergyCore;
|
|
|
|
CNetworkVector( m_vecHitPos );
|
|
|
|
// If true, playing patrol loop.
|
|
// Else, playing angry.
|
|
bool m_fPatrolLoopPlaying;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS( npc_combinegunship, CNPC_CombineGunship );
|
|
|
|
IMPLEMENT_SERVERCLASS_ST( CNPC_CombineGunship, DT_CombineGunship )
|
|
SendPropVector(SENDINFO(m_vecHitPos), -1, SPROP_COORD),
|
|
END_SEND_TABLE()
|
|
|
|
BEGIN_DATADESC( CNPC_CombineGunship )
|
|
|
|
DEFINE_ENTITYFUNC( FlyTouch ),
|
|
|
|
DEFINE_FIELD( m_flNextGroundAttack,FIELD_TIME ),
|
|
DEFINE_FIELD( m_bIsGroundAttacking,FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bCanGroundAttack, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_flGroundAttackTime,FIELD_TIME ),
|
|
DEFINE_FIELD( m_pRotorWashModel, FIELD_CLASSPTR ),
|
|
DEFINE_FIELD( m_pSmokeTrail, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_hGroundAttackTarget, FIELD_EHANDLE ),
|
|
DEFINE_SOUNDPATCH( m_pAirExhaustSound ),
|
|
DEFINE_SOUNDPATCH( m_pAirBlastSound ),
|
|
DEFINE_SOUNDPATCH( m_pCannonSound ),
|
|
DEFINE_FIELD( m_vecAngAcceleration,FIELD_VECTOR ),
|
|
DEFINE_KEYFIELD( m_fMaxAngAcceleration, FIELD_FLOAT, "MaxAngAccel" ),
|
|
DEFINE_KEYFIELD( m_vMaxAngVelocity, FIELD_VECTOR, "MaxAngVelocity" ),
|
|
DEFINE_FIELD( m_flDeltaT, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flTimeNextAttack, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flNextSeeEnemySound, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flEndDestructTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flNextRocket, FIELD_TIME ),
|
|
DEFINE_FIELD( m_iDoSmokePuff, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iBurstSize, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flBurstDelay, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_fBlindfire, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_fOmniscient, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bIsFiring, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_iBurstHits, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flTimeNextPing, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flPenetrationDepth,FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_vecAttackPosition, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_vecAttackVelocity, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_angGun, FIELD_VECTOR ),
|
|
DEFINE_PHYSPTR( m_pCrashingController ),
|
|
DEFINE_EMBEDDED( m_crashCallback ),
|
|
DEFINE_FIELD( m_hRagdoll, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_hCrashTarget, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_vecHitPos, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_fPatrolLoopPlaying,FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bPreFire, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bInvulnerable, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_flNextGunshipCrashFind, FIELD_TIME ),
|
|
|
|
DEFINE_FIELD( m_hEnergyCore, FIELD_EHANDLE ),
|
|
|
|
DEFINE_ARRAY( m_bDamageOutputsFired, FIELD_BOOLEAN, GUNSHIP_NUM_DAMAGE_OUTPUTS ),
|
|
|
|
// Function pointers
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "OmniscientOn", InputOmniscientOn ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "OmniscientOff", InputOmniscientOff ),
|
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPenetrationDepth", InputSetPenetrationDepth ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "BlindfireOn", InputBlindfireOn ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "BlindfireOff", InputBlindfireOff ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "SetDockingBBox", InputSetDockingBBox ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "SetNormalBBox", InputSetNormalBBox ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableGroundAttack", InputEnableGroundAttack ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableGroundAttack", InputDisableGroundAttack ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "DoGroundAttack", InputDoGroundAttack ),
|
|
|
|
DEFINE_OUTPUT( m_OnFireCannon, "OnFireCannon" ),
|
|
DEFINE_OUTPUT( m_OnFirstDamage, "OnFirstDamage" ),
|
|
DEFINE_OUTPUT( m_OnSecondDamage, "OnSecondDamage" ),
|
|
DEFINE_OUTPUT( m_OnThirdDamage, "OnThirdDamage" ),
|
|
DEFINE_OUTPUT( m_OnFourthDamage, "OnFourthDamage" ),
|
|
DEFINE_OUTPUT( m_OnCrashed, "OnCrashed" ),
|
|
|
|
END_DATADESC()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constructor
|
|
//-----------------------------------------------------------------------------
|
|
CNPC_CombineGunship::CNPC_CombineGunship( void )
|
|
{
|
|
m_hGroundAttackTarget = NULL;
|
|
m_pSmokeTrail = NULL;
|
|
m_iAmmoType = -1;
|
|
m_pCrashingController = NULL;
|
|
m_hRagdoll = NULL;
|
|
m_hCrashTarget = NULL;
|
|
|
|
m_fMaxAngAcceleration = 1000.0f;
|
|
m_vMaxAngVelocity = Vector( 300.0f, 120.0f, 300.0f );
|
|
}
|
|
|
|
|
|
void CNPC_CombineGunship::CreateBellyBlastEnergyCore( void )
|
|
{
|
|
CCitadelEnergyCore *pCore = static_cast<CCitadelEnergyCore*>( CreateEntityByName( "env_citadel_energy_core" ) );
|
|
|
|
if ( pCore == NULL )
|
|
return;
|
|
|
|
m_hEnergyCore = pCore;
|
|
|
|
int iAttachment = LookupAttachment( "BellyGun" );
|
|
|
|
Vector vOrigin;
|
|
QAngle vAngle;
|
|
|
|
GetAttachment( iAttachment, vOrigin, vAngle );
|
|
|
|
pCore->SetAbsOrigin( vOrigin );
|
|
pCore->SetAbsAngles( vAngle );
|
|
|
|
DispatchSpawn( pCore );
|
|
pCore->Activate();
|
|
|
|
pCore->SetParent( this, iAttachment );
|
|
pCore->SetScale( 4.0f );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::Spawn( void )
|
|
{
|
|
Precache( );
|
|
|
|
if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
|
|
{
|
|
SetModel( "models/combine_helicopter.mdl" );
|
|
}
|
|
else
|
|
{
|
|
SetModel( "models/gunship.mdl" );
|
|
}
|
|
|
|
ExtractBbox( SelectHeaviestSequence( ACT_GUNSHIP_PATROL ), m_cullBoxMins, m_cullBoxMaxs );
|
|
BaseClass::Spawn();
|
|
|
|
InitPathingData( GUNSHIP_ARRIVE_DIST, GUNSHIP_MIN_CHASE_DIST_DIFF, sk_gunship_burst_min.GetFloat() );
|
|
AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION );
|
|
|
|
m_takedamage = DAMAGE_YES;
|
|
|
|
SetHullType(HULL_LARGE_CENTERED);
|
|
SetHullSizeNormal();
|
|
|
|
m_iMaxHealth = m_iHealth = 100;
|
|
|
|
m_flFrozenMax = 0.0f;
|
|
|
|
m_flFieldOfView = -0.707; // 270 degrees
|
|
|
|
m_fHelicopterFlags |= BITS_HELICOPTER_GUN_ON;
|
|
|
|
InitBoneControllers();
|
|
|
|
InitCustomSchedules();
|
|
|
|
SetActivity( (Activity)ACT_GUNSHIP_PATROL );
|
|
SetCollisionGroup( HL2COLLISION_GROUP_GUNSHIP );
|
|
|
|
m_flMaxSpeed = GUNSHIP_MAX_SPEED;
|
|
m_flMaxSpeedFiring = GUNSHIP_MAX_SPEED;
|
|
|
|
m_flTimeNextAttack = gpGlobals->curtime;
|
|
m_flNextSeeEnemySound = gpGlobals->curtime;
|
|
|
|
// Init the pose parameters
|
|
SetPoseParameter( "flex_horz", 0 );
|
|
SetPoseParameter( "flex_vert", 0 );
|
|
SetPoseParameter( "fin_accel", 0 );
|
|
SetPoseParameter( "fin_sway", 0 );
|
|
|
|
if( m_iAmmoType == -1 )
|
|
{
|
|
// Since there's no weapon to index the ammo type,
|
|
// do it manually here.
|
|
m_iAmmoType = GetAmmoDef()->Index("CombineCannon");
|
|
}
|
|
|
|
//!!!HACKHACK
|
|
// This tricks the AI code that constantly complains that the gunship has no schedule.
|
|
SetSchedule( SCHED_IDLE_STAND );
|
|
|
|
AddRelationship( "env_flare D_LI 9", NULL );
|
|
AddRelationship( "rpg_missile D_HT 99", NULL );
|
|
|
|
m_flTimeNextPing = gpGlobals->curtime + 2;
|
|
|
|
m_flPenetrationDepth = 24;
|
|
m_flBurstDelay = 2.0f;
|
|
|
|
// Blindfire and Omniscience default to off
|
|
m_fBlindfire = false;
|
|
m_fOmniscient = false;
|
|
m_bIsFiring = false;
|
|
m_bPreFire = false;
|
|
m_bInvulnerable = false;
|
|
|
|
// See if we should start being able to attack
|
|
m_bCanGroundAttack = ( m_spawnflags & SF_GUNSHIP_NO_GROUND_ATTACK ) ? false : true;
|
|
|
|
m_flEndDestructTime = 0;
|
|
|
|
m_iBurstSize = 0;
|
|
m_iBurstHits = 0;
|
|
|
|
// Do not dissolve
|
|
AddEFlags( EFL_NO_DISSOLVE );
|
|
|
|
for ( int i = 0; i < GUNSHIP_NUM_DAMAGE_OUTPUTS; i++ )
|
|
{
|
|
m_bDamageOutputsFired[i] = false;
|
|
}
|
|
|
|
CapabilitiesAdd( bits_CAP_SQUAD);
|
|
|
|
if ( hl2_episodic.GetBool() == true )
|
|
{
|
|
CreateBellyBlastEnergyCore();
|
|
}
|
|
|
|
// Allows autoaim to help attack the gunship.
|
|
if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
|
|
{
|
|
AddFlag( FL_AIMTARGET );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Restore the motion controller
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::OnRestore( void )
|
|
{
|
|
BaseClass::OnRestore();
|
|
|
|
if ( m_pCrashingController )
|
|
{
|
|
m_pCrashingController->SetEventHandler( &m_crashCallback );
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::Precache( void )
|
|
{
|
|
if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
|
|
{
|
|
PrecacheModel( "models/combine_helicopter.mdl" );
|
|
Chopper_PrecacheChunks( this );
|
|
}
|
|
else
|
|
{
|
|
PrecacheModel("models/gunship.mdl");
|
|
}
|
|
|
|
PrecacheModel("sprites/lgtning.vmt");
|
|
|
|
PrecacheMaterial( "effects/ar2ground2" );
|
|
PrecacheMaterial( "effects/blueblackflash" );
|
|
|
|
PrecacheScriptSound( "NPC_CombineGunship.SearchPing" );
|
|
PrecacheScriptSound( "NPC_CombineGunship.PatrolPing" );
|
|
PrecacheScriptSound( "NPC_Strider.Charge" );
|
|
PrecacheScriptSound( "NPC_Strider.Shoot" );
|
|
PrecacheScriptSound( "NPC_CombineGunship.SeeEnemy" );
|
|
PrecacheScriptSound( "NPC_CombineGunship.CannonStartSound" );
|
|
PrecacheScriptSound( "NPC_CombineGunship.Explode");
|
|
PrecacheScriptSound( "NPC_CombineGunship.Pain" );
|
|
PrecacheScriptSound( "NPC_CombineGunship.CannonStopSound" );
|
|
|
|
PrecacheScriptSound( "NPC_CombineGunship.DyingSound" );
|
|
PrecacheScriptSound( "NPC_CombineGunship.CannonSound" );
|
|
PrecacheScriptSound( "NPC_CombineGunship.RotorSound" );
|
|
PrecacheScriptSound( "NPC_CombineGunship.ExhaustSound" );
|
|
PrecacheScriptSound( "NPC_CombineGunship.RotorBlastSound" );
|
|
|
|
PrecacheEffect( "ImpactGunship" );
|
|
PrecacheEffect( "AR2Explosion" );
|
|
PrecacheEffect( "GunshipMuzzleFlash" );
|
|
PrecacheEffect( "GunshipTracer" );
|
|
|
|
if ( hl2_episodic.GetBool() == true )
|
|
{
|
|
UTIL_PrecacheOther( "env_citadel_energy_core" );
|
|
g_iGunshipEffectIndex = PrecacheModel( "sprites/physbeam.vmt" );
|
|
}
|
|
|
|
PropBreakablePrecacheAll( MAKE_STRING("models/gunship.mdl") );
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Cache whatever pose parameters we intend to use
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CombineGunship::m_sbStaticPoseParamsLoaded = false;
|
|
int CNPC_CombineGunship::m_poseFlex_Horz = 0;
|
|
int CNPC_CombineGunship::m_poseFlex_Vert = 0;
|
|
int CNPC_CombineGunship::m_posePitch = 0;
|
|
int CNPC_CombineGunship::m_poseYaw = 0;
|
|
int CNPC_CombineGunship::m_poseFin_Accel = 0;
|
|
int CNPC_CombineGunship::m_poseFin_Sway = 0;
|
|
int CNPC_CombineGunship::m_poseWeapon_Pitch = 0;
|
|
int CNPC_CombineGunship::m_poseWeapon_Yaw = 0;
|
|
void CNPC_CombineGunship::PopulatePoseParameters( void )
|
|
{
|
|
if (!m_sbStaticPoseParamsLoaded)
|
|
{
|
|
m_poseFlex_Horz = LookupPoseParameter( "flex_horz");
|
|
m_poseFlex_Vert = LookupPoseParameter( "flex_vert" );
|
|
m_posePitch = LookupPoseParameter( "pitch" );
|
|
m_poseYaw = LookupPoseParameter( "yaw" );
|
|
m_poseFin_Accel = LookupPoseParameter( "fin_accel" );
|
|
m_poseFin_Sway = LookupPoseParameter( "fin_sway" );
|
|
|
|
m_poseWeapon_Pitch = LookupPoseParameter( "weapon_pitch" );
|
|
m_poseWeapon_Yaw = LookupPoseParameter( "weapon_yaw" );
|
|
|
|
m_sbStaticPoseParamsLoaded = true;
|
|
}
|
|
|
|
BaseClass::PopulatePoseParameters();
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
//------------------------------------------------------------------------------
|
|
CNPC_CombineGunship::~CNPC_CombineGunship(void)
|
|
{
|
|
StopLoopingSounds();
|
|
|
|
if ( m_pCrashingController )
|
|
{
|
|
physenv->DestroyMotionController( m_pCrashingController );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::Ping( void )
|
|
{
|
|
if( IsCrashing() )
|
|
return;
|
|
|
|
if( GetEnemy() != NULL )
|
|
{
|
|
if( !HasCondition(COND_SEE_ENEMY) && gpGlobals->curtime > m_flTimeNextPing )
|
|
{
|
|
EmitSound( "NPC_CombineGunship.SearchPing" );
|
|
m_flTimeNextPing = gpGlobals->curtime + 3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( gpGlobals->curtime > m_flTimeNextPing )
|
|
{
|
|
EmitSound( "NPC_CombineGunship.PatrolPing" );
|
|
m_flTimeNextPing = gpGlobals->curtime + 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &pos -
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CNPC_CombineGunship::GroundDistToPosition( const Vector &pos )
|
|
{
|
|
Vector vecDiff;
|
|
VectorSubtract( GetAbsOrigin(), pos, vecDiff );
|
|
|
|
// Only interested in the 2d dist
|
|
vecDiff.z = 0;
|
|
|
|
return vecDiff.Length();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::PlayPatrolLoop( void )
|
|
{
|
|
m_fPatrolLoopPlaying = true;
|
|
/*
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
|
controller.SoundChangeVolume( m_pPatrolSound, 1.0, 1.0 );
|
|
controller.SoundChangeVolume( m_pAngrySound, 0.0, 1.0 );
|
|
*/
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::PlayAngryLoop( void )
|
|
{
|
|
m_fPatrolLoopPlaying = false;
|
|
/*
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
|
controller.SoundChangeVolume( m_pPatrolSound, 0.0, 1.0 );
|
|
controller.SoundChangeVolume( m_pAngrySound, 1.0, 1.0 );
|
|
*/
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::HelicopterPostThink( void )
|
|
{
|
|
// After HelicopterThink()
|
|
if ( HasCondition( COND_ENEMY_DEAD ) )
|
|
{
|
|
if ( m_bIsFiring )
|
|
{
|
|
// Fire more shots at the dead body for effect
|
|
if ( m_iBurstSize > 8 )
|
|
{
|
|
m_iBurstSize = 8;
|
|
}
|
|
}
|
|
|
|
// Fade out search sound, fade in patrol sound.
|
|
PlayPatrolLoop();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Vector
|
|
//-----------------------------------------------------------------------------
|
|
Vector CNPC_CombineGunship::GetGroundAttackHitPosition( void )
|
|
{
|
|
trace_t tr;
|
|
Vector vecShootPos, vecShootDir;
|
|
|
|
GetAttachment( "BellyGun", vecShootPos, &vecShootDir, NULL, NULL );
|
|
|
|
AI_TraceLine( vecShootPos, vecShootPos + Vector( 0, 0, -MAX_TRACE_LENGTH ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
|
|
|
|
if ( m_hGroundAttackTarget )
|
|
{
|
|
return Vector( tr.endpos.x, tr.endpos.y, m_hGroundAttackTarget->WorldSpaceCenter().z );
|
|
}
|
|
return tr.endpos;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CombineGunship::CheckGroundAttack( void )
|
|
{
|
|
if ( m_bCanGroundAttack == false )
|
|
return false;
|
|
|
|
if ( m_bIsGroundAttacking )
|
|
return false;
|
|
|
|
// Must have an enemy
|
|
if ( GetEnemy() == NULL )
|
|
return false;
|
|
|
|
// Must not have done it too recently
|
|
if ( m_flNextGroundAttack > gpGlobals->curtime )
|
|
return false;
|
|
|
|
Vector predPos, predDest;
|
|
|
|
// Find where the enemy is most likely to be in two seconds
|
|
UTIL_PredictedPosition( GetEnemy(), 1.0f, &predPos );
|
|
UTIL_PredictedPosition( this, 1.0f, &predDest );
|
|
|
|
Vector predGap = ( predDest - predPos );
|
|
predGap.z = 0;
|
|
|
|
float predDistance = predGap.Length();
|
|
|
|
// Must be within distance
|
|
if ( predDistance > MIN_GROUND_ATTACK_DIST )
|
|
return false;
|
|
|
|
// Can't ground attack missiles
|
|
if ( IsTargettingMissile() )
|
|
return false;
|
|
|
|
//FIXME: Check to make sure we're not firing too far above or below the target
|
|
if ( fabs( GetGroundAttackHitPosition().z - GetEnemy()->WorldSpaceCenter().z ) > MIN_GROUND_ATTACK_HEIGHT_DIFF )
|
|
return false;
|
|
|
|
//FIXME: Check for ground movement capabilities?
|
|
|
|
//TODO: Check for friendly-fire
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::StartGroundAttack( void )
|
|
{
|
|
// Mark us as attacking
|
|
m_bIsGroundAttacking = true;
|
|
m_flGroundAttackTime = gpGlobals->curtime + 3.0f;
|
|
|
|
// Setup the attack effects
|
|
Vector vecShootPos;
|
|
|
|
GetAttachment( "BellyGun", vecShootPos );
|
|
|
|
EntityMessageBegin( this, true );
|
|
WRITE_BYTE( GUNSHIP_MSG_STREAKS );
|
|
WRITE_VEC3COORD( vecShootPos );
|
|
MessageEnd();
|
|
|
|
CPASAttenuationFilter filter2( this, "NPC_Strider.Charge" );
|
|
EmitSound( filter2, entindex(), "NPC_Strider.Charge" );
|
|
|
|
Vector endpos = GetGroundAttackHitPosition();
|
|
|
|
CSoundEnt::InsertSound ( SOUND_DANGER, endpos, 1024, 0.5f );
|
|
|
|
if ( hl2_episodic.GetBool() == true )
|
|
{
|
|
if ( m_hEnergyCore )
|
|
{
|
|
variant_t value;
|
|
value.SetFloat( 3.0f );
|
|
|
|
g_EventQueue.AddEvent( m_hEnergyCore, "StartCharge", value, 0, this, this );
|
|
}
|
|
}
|
|
}
|
|
|
|
#define GUNSHIP_BELLY_BLAST_RADIUS 256.0f
|
|
#define BELLY_BLAST_MAX_PUNCH 5
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::ManageWarningBeam( void )
|
|
{
|
|
Vector vecSrc, vecShootDir;
|
|
GetAttachment( "BellyGun", vecSrc, NULL, NULL, NULL );
|
|
|
|
trace_t tr;
|
|
CTraceFilterSkipTwoEntities filter( m_hGroundAttackTarget, this, COLLISION_GROUP_NONE );
|
|
|
|
UTIL_TraceLine( vecSrc, m_vecHitPos, MASK_SOLID, &filter, &tr );
|
|
|
|
int iPunch = 0;
|
|
|
|
while ( tr.endpos != m_vecHitPos )
|
|
{
|
|
iPunch++;
|
|
|
|
if ( iPunch > BELLY_BLAST_MAX_PUNCH )
|
|
break;
|
|
|
|
if ( tr.fraction != 1.0 )
|
|
{
|
|
if ( tr.m_pEnt )
|
|
{
|
|
CTakeDamageInfo info( this, this, 1.0f, DMG_ENERGYBEAM );
|
|
|
|
Vector vTargetDir = tr.m_pEnt->BodyTarget( tr.endpos, false ) - tr.endpos;
|
|
|
|
VectorNormalize( vTargetDir );
|
|
|
|
info.SetDamagePosition( tr.endpos + ( tr.plane.normal * 64.0f ) );
|
|
info.SetDamageForce( vTargetDir * 100 );
|
|
|
|
if ( tr.m_pEnt->m_takedamage != DAMAGE_NO )
|
|
{
|
|
// Deal damage
|
|
tr.m_pEnt->TakeDamage( info );
|
|
}
|
|
}
|
|
|
|
Vector vDir = m_vecHitPos - vecSrc;
|
|
VectorNormalize( vDir );
|
|
|
|
Vector vStartPunch = tr.endpos + vDir * 1;
|
|
|
|
UTIL_TraceLine( vStartPunch, m_vecHitPos, MASK_SOLID, &filter, &tr );
|
|
|
|
if ( tr.startsolid )
|
|
{
|
|
float flLength = (vStartPunch - tr.endpos).Length();
|
|
|
|
Vector vEndPunch = vStartPunch + vDir * ( flLength * tr.fractionleftsolid );
|
|
|
|
UTIL_TraceLine( vEndPunch, m_vecHitPos, MASK_SOLID, &filter, &tr );
|
|
|
|
trace_t tr2;
|
|
UTIL_TraceLine( vEndPunch, vEndPunch - vDir * 2, MASK_SOLID, &filter, &tr2 );
|
|
|
|
if ( (m_flGroundAttackTime - gpGlobals->curtime) <= 2.0f )
|
|
{
|
|
g_pEffects->EnergySplash( tr2.endpos + vDir * 8, tr2.plane.normal, true );
|
|
}
|
|
|
|
g_pEffects->Sparks( tr2.endpos, 3.0f - (m_flGroundAttackTime-gpGlobals->curtime), 3.5f - (m_flGroundAttackTime-gpGlobals->curtime), &tr2.plane.normal );
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::DoBellyBlastDamage( trace_t &tr, Vector vMins, Vector vMaxs )
|
|
{
|
|
CBaseEntity* pList[100];
|
|
|
|
if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_BELLYBLAST )
|
|
{
|
|
NDebugOverlay::Box( tr.endpos, vMins, vMaxs, 255, 255, 0, true, 5.0f );
|
|
}
|
|
|
|
int count = UTIL_EntitiesInBox( pList, 100, tr.endpos + vMins, tr.endpos + vMaxs, 0 );
|
|
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
CBaseEntity *pEntity = pList[i];
|
|
|
|
if ( pEntity == this )
|
|
continue;
|
|
|
|
if ( pEntity->m_takedamage == DAMAGE_NO )
|
|
continue;
|
|
|
|
float damage = 150;
|
|
|
|
if ( pEntity->IsPlayer() )
|
|
{
|
|
float damageDist = ( pEntity->GetAbsOrigin() - tr.endpos ).Length();
|
|
damage = RemapValClamped( damageDist, 0, 300, 200, 0 );
|
|
}
|
|
|
|
CTakeDamageInfo info( this, this, damage, DMG_DISSOLVE );
|
|
|
|
Vector vTargetDir = pEntity->BodyTarget( tr.endpos, false ) - tr.endpos;
|
|
|
|
VectorNormalize( vTargetDir );
|
|
|
|
info.SetDamagePosition( tr.endpos + ( tr.plane.normal * 64.0f ) );
|
|
info.SetDamageForce( vTargetDir * 25000 );
|
|
|
|
// Deal damage
|
|
pEntity->TakeDamage( info );
|
|
|
|
trace_t groundTrace;
|
|
UTIL_TraceLine( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin() - Vector( 0, 0, 256 ), MASK_SOLID, pEntity, COLLISION_GROUP_NONE, &groundTrace );
|
|
|
|
if ( tr.fraction < 1.0f )
|
|
{
|
|
CEffectData data;
|
|
|
|
// Find the floor and add a dissolve explosion at that point
|
|
data.m_flRadius = GUNSHIP_BELLY_BLAST_RADIUS * 0.5f;
|
|
data.m_vNormal = groundTrace.plane.normal;
|
|
data.m_vOrigin = groundTrace.endpos;
|
|
|
|
DispatchEffect( "AR2Explosion", data );
|
|
}
|
|
|
|
// If the creature was killed, then dissolve it
|
|
if ( pEntity->GetHealth() <= 0.0f )
|
|
{
|
|
if ( pEntity->GetBaseAnimating() != NULL && !pEntity->IsEFlagSet( EFL_NO_DISSOLVE ) )
|
|
{
|
|
pEntity->GetBaseAnimating()->Dissolve( NULL, gpGlobals->curtime );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::DoGroundAttackExplosion( void )
|
|
{
|
|
// Fire the bullets
|
|
Vector vecSrc, vecShootDir;
|
|
Vector vecAttachmentOrigin;
|
|
GetAttachment( "BellyGun", vecAttachmentOrigin, &vecShootDir, NULL, NULL );
|
|
|
|
vecSrc = vecAttachmentOrigin;
|
|
|
|
if ( m_hGroundAttackTarget )
|
|
{
|
|
vecSrc = m_hGroundAttackTarget->GetAbsOrigin();
|
|
}
|
|
|
|
Vector impactPoint = vecSrc + ( Vector( 0, 0, -1 ) * MAX_TRACE_LENGTH );
|
|
|
|
trace_t tr;
|
|
UTIL_TraceLine( vecSrc, impactPoint, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
|
|
UTIL_DecalTrace( &tr, "Scorch" );
|
|
|
|
if ( hl2_episodic.GetBool() == true )
|
|
{
|
|
g_pEffects->EnergySplash( tr.endpos, tr.plane.normal );
|
|
|
|
CBroadcastRecipientFilter filter;
|
|
te->BeamRingPoint( filter, 0.0,
|
|
tr.endpos, //origin
|
|
0, //start radius
|
|
GUNSHIP_BELLY_BLAST_RADIUS, //end radius
|
|
g_iGunshipEffectIndex, //texture
|
|
0, //halo index
|
|
0, //start frame
|
|
0, //framerate
|
|
0.2, //life
|
|
10, //width
|
|
0, //spread
|
|
0, //amplitude
|
|
255, //r
|
|
255, //g
|
|
255, //b
|
|
50, //a
|
|
0, //speed
|
|
FBEAM_FADEOUT
|
|
);
|
|
}
|
|
|
|
// Send the effect over
|
|
CEffectData data;
|
|
|
|
// Do an extra effect if we struck the world
|
|
if ( tr.m_pEnt && tr.m_pEnt->IsWorld() )
|
|
{
|
|
data.m_flRadius = GUNSHIP_BELLY_BLAST_RADIUS;
|
|
data.m_vNormal = tr.plane.normal;
|
|
data.m_vOrigin = tr.endpos;
|
|
|
|
DispatchEffect( "AR2Explosion", data );
|
|
}
|
|
|
|
float flZLength = vecAttachmentOrigin.z - tr.endpos.z;
|
|
|
|
Vector vBeamMins = Vector( -16, -16, 0 );
|
|
Vector vBeamMaxs = Vector( 16, 16, flZLength );
|
|
|
|
DoBellyBlastDamage( tr, vBeamMins, vBeamMaxs );
|
|
|
|
Vector vBlastMins = Vector( -GUNSHIP_BELLY_BLAST_RADIUS, -GUNSHIP_BELLY_BLAST_RADIUS, 0 );
|
|
Vector vBlastMaxs = Vector( GUNSHIP_BELLY_BLAST_RADIUS, GUNSHIP_BELLY_BLAST_RADIUS, 96 );
|
|
|
|
DoBellyBlastDamage( tr, vBlastMins, vBlastMaxs );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::StopGroundAttack( bool bDoAttack )
|
|
{
|
|
if ( !m_bIsGroundAttacking )
|
|
return;
|
|
|
|
// Mark us as no longer attacking
|
|
m_bIsGroundAttacking = false;
|
|
m_flNextGroundAttack = gpGlobals->curtime + 4.0f;
|
|
m_flTimeNextAttack = gpGlobals->curtime + 2.0f;
|
|
|
|
Vector hitPos = GetGroundAttackHitPosition();
|
|
|
|
// tell the client side effect to complete
|
|
EntityMessageBegin( this, true );
|
|
WRITE_BYTE( GUNSHIP_MSG_BIG_SHOT );
|
|
WRITE_VEC3COORD( hitPos );
|
|
MessageEnd();
|
|
|
|
if ( hl2_episodic.GetBool() == true )
|
|
{
|
|
if ( m_hEnergyCore )
|
|
{
|
|
variant_t value;
|
|
value.SetFloat( 1.0f );
|
|
|
|
g_EventQueue.AddEvent( m_hEnergyCore, "Stop", value, 0, this, this );
|
|
}
|
|
}
|
|
|
|
// Only attack if told to
|
|
if ( bDoAttack )
|
|
{
|
|
CPASAttenuationFilter filter2( this, "NPC_Strider.Shoot" );
|
|
EmitSound( filter2, entindex(), "NPC_Strider.Shoot");
|
|
|
|
ApplyAbsVelocityImpulse( Vector( 0, 0, 200.0f ) );
|
|
|
|
//ExplosionCreate( hitPos, QAngle( 0, 0, 1 ), this, 500, 500, true );
|
|
DoGroundAttackExplosion();
|
|
}
|
|
|
|
// If we were attacking a target, revert to our previous target
|
|
if ( m_hGroundAttackTarget )
|
|
{
|
|
m_hGroundAttackTarget = NULL;
|
|
if ( GetDestPathTarget() )
|
|
{
|
|
// Return to our old path
|
|
SetupNewCurrentTarget( GetDestPathTarget() );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::DrawRotorWash( float flAltitude, const Vector &vecRotorOrigin )
|
|
{
|
|
// If we have a ragdoll, we want the wash under that, not me
|
|
if ( m_hRagdoll )
|
|
{
|
|
BaseClass::DrawRotorWash( flAltitude, m_hRagdoll->GetAbsOrigin() );
|
|
return;
|
|
}
|
|
|
|
BaseClass::DrawRotorWash( flAltitude, vecRotorOrigin );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Override the desired position if your derived helicopter is doing something special
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::UpdateDesiredPosition( void )
|
|
{
|
|
if ( m_hCrashTarget )
|
|
{
|
|
SetDesiredPosition( m_hCrashTarget->WorldSpaceCenter() + Vector(0,0,128) );
|
|
}
|
|
else if ( m_hGroundAttackTarget )
|
|
{
|
|
SetDesiredPosition( m_hGroundAttackTarget->GetAbsOrigin() + Vector(0,0,GUNSHIP_BELLYBLAST_TARGET_HEIGHT) );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: do all of the stuff related to having an enemy, attacking, etc.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::DoCombat( void )
|
|
{
|
|
// Check for enemy change-overs
|
|
if ( HasEnemy() )
|
|
{
|
|
if ( HasCondition( COND_NEW_ENEMY ) )
|
|
{
|
|
if ( GetEnemy() && GetEnemy()->IsPlayer() && m_flNextSeeEnemySound < gpGlobals->curtime )
|
|
{
|
|
m_flNextSeeEnemySound = gpGlobals->curtime + 5.0;
|
|
|
|
if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
|
|
{
|
|
EmitSound( "NPC_CombineGunship.SeeEnemy" );
|
|
}
|
|
}
|
|
|
|
// If we're shooting at a missile, do it immediately!
|
|
if ( IsTargettingMissile() )
|
|
{
|
|
EmitSound( "NPC_CombineGunship.SeeMissile" );
|
|
|
|
// Allow the gunship to attack again immediately
|
|
if ( ( m_flTimeNextAttack > gpGlobals->curtime ) && ( ( m_flTimeNextAttack - gpGlobals->curtime ) > GUNSHIP_MISSILE_MAX_RESPONSE_TIME ) )
|
|
{
|
|
m_flTimeNextAttack = gpGlobals->curtime + GUNSHIP_MISSILE_MAX_RESPONSE_TIME;
|
|
m_iBurstSize = sk_gunship_burst_size.GetInt();
|
|
}
|
|
}
|
|
|
|
// Fade in angry sound, fade out patrol sound.
|
|
PlayAngryLoop();
|
|
}
|
|
}
|
|
|
|
// Do we have a belly blast target?
|
|
if ( m_hGroundAttackTarget && !m_bIsGroundAttacking )
|
|
{
|
|
// If we're over it, blast. Can't use GetDesiredPosition() because it's not updated yet.
|
|
Vector vecTarget = m_hGroundAttackTarget->GetAbsOrigin() + Vector(0,0,GUNSHIP_BELLYBLAST_TARGET_HEIGHT);
|
|
Vector vecToTarget = (vecTarget - GetAbsOrigin());
|
|
float flDistance = vecToTarget.Length();
|
|
|
|
// Get the difference between our velocity & the target's velocity
|
|
Vector vec2DVelocity = GetAbsVelocity();
|
|
Vector vec2DTargetVelocity = m_hGroundAttackTarget->GetAbsVelocity();
|
|
vec2DVelocity.z = vec2DTargetVelocity.z = 0;
|
|
float flVelocityDiff = (vec2DVelocity - vec2DTargetVelocity).Length();
|
|
if ( flDistance < 100 && flVelocityDiff < 200 )
|
|
{
|
|
StartGroundAttack();
|
|
}
|
|
}
|
|
|
|
// Update our firing
|
|
if ( m_bIsFiring )
|
|
{
|
|
// Fire if we have rounds remaining in this burst
|
|
if ( ( m_iBurstSize > 0 ) && ( gpGlobals->curtime > m_flTimeNextAttack ) )
|
|
{
|
|
UpdateEnemyTarget();
|
|
FireCannonRound();
|
|
}
|
|
else if ( m_iBurstSize < 1 )
|
|
{
|
|
// We're done firing
|
|
StopCannonBurst();
|
|
|
|
if ( IsTargettingMissile() )
|
|
{
|
|
m_flTimeNextAttack = gpGlobals->curtime + 0.5f;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we're not firing, look at the enemy
|
|
if ( GetEnemy() )
|
|
{
|
|
m_vecAttackPosition = GetEnemy()->EyePosition();
|
|
}
|
|
|
|
#ifdef BELLYBLAST
|
|
// Check for a ground attack
|
|
if ( CheckGroundAttack() )
|
|
{
|
|
StartGroundAttack();
|
|
}
|
|
#endif
|
|
|
|
// See if we're attacking
|
|
if ( m_bIsGroundAttacking )
|
|
{
|
|
m_vecHitPos = GetGroundAttackHitPosition();
|
|
|
|
ManageWarningBeam();
|
|
|
|
// If our time is up, fire the blast and be done
|
|
if ( m_flGroundAttackTime < gpGlobals->curtime )
|
|
{
|
|
// Fire!
|
|
StopGroundAttack( true );
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're using the chopper model, align the gun towards the target
|
|
if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
|
|
{
|
|
Vector vGunPosition;
|
|
GetAttachment( "gun", vGunPosition );
|
|
Vector vecToAttackPos = (m_vecAttackPosition - vGunPosition);
|
|
PoseGunTowardTargetDirection( vecToAttackPos );
|
|
}
|
|
|
|
// Forget flares once I've seen them for a while
|
|
float flDeltaSeen = m_flLastSeen - m_flPrevSeen;
|
|
if ( GetEnemy() != NULL && GetEnemy()->Classify() == CLASS_FLARE && flDeltaSeen > GUNSHIP_FLARE_IGNORE_TIME )
|
|
{
|
|
AddEntityRelationship( GetEnemy(), D_NU, 5 );
|
|
|
|
PlayPatrolLoop();
|
|
|
|
// Forget the flare now.
|
|
SetEnemy( NULL );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CombineGunship::ChooseEnemy( void )
|
|
{
|
|
// If we're firing, don't switch enemies. This stops the gunship occasionally
|
|
// stopping a burst before he's really fired at all, which makes him look indecisive.
|
|
if ( m_bIsFiring )
|
|
return true;
|
|
|
|
return BaseClass::ChooseEnemy();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: There's a lot of code in here now. We should consider moving
|
|
// helicopters and such to scheduled AI. (sjb)
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::MoveHead( void )
|
|
{
|
|
float flYaw = GetPoseParameter( m_poseFlex_Horz );
|
|
float flPitch = GetPoseParameter( m_poseFlex_Vert );
|
|
|
|
/*
|
|
This head-turning code will cause the head to POP when switching from looking at the enemy
|
|
to looking according to the flight model. I will fix this later. Right now I'm turning
|
|
the code over to Ken for some aiming fixups. (sjb)
|
|
*/
|
|
|
|
while( 1 )
|
|
{
|
|
if ( GetEnemy() != NULL )
|
|
{
|
|
Vector vecToEnemy, vecAimDir;
|
|
float flDot;
|
|
|
|
Vector vTargetPos, vGunPosition;
|
|
Vector vecTargetOffset;
|
|
QAngle vGunAngles;
|
|
|
|
GetAttachment( "muzzle", vGunPosition, vGunAngles );
|
|
|
|
vTargetPos = GetEnemyTarget();
|
|
|
|
VectorSubtract( vTargetPos, vGunPosition, vecToEnemy );
|
|
VectorNormalize( vecToEnemy );
|
|
|
|
// get angles relative to body position
|
|
AngleVectors( GetAbsAngles(), &vecAimDir );
|
|
flDot = DotProduct( vecAimDir, vecToEnemy );
|
|
|
|
// Look at Enemy!!
|
|
if ( flDot > 0.3f )
|
|
{
|
|
float flDiff;
|
|
|
|
float flDesiredYaw = VecToYaw(vTargetPos - vGunPosition);
|
|
flDiff = UTIL_AngleDiff( flDesiredYaw, vGunAngles.y ) * 0.90;
|
|
flYaw = UTIL_Approach( flYaw + flDiff, flYaw, 5.0 );
|
|
|
|
float flDesiredPitch = UTIL_VecToPitch(vTargetPos - vGunPosition);
|
|
flDiff = UTIL_AngleDiff( flDesiredPitch, vGunAngles.x ) * 0.90;
|
|
flPitch = UTIL_Approach( flPitch + flDiff, flPitch, 5.0 );
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Look where going!
|
|
#if 1 // old way- look according to rotational velocity
|
|
flYaw = UTIL_Approach( GetLocalAngularVelocity().y, flYaw, 2.0 * 10 * m_flDeltaT );
|
|
flPitch = UTIL_Approach( GetLocalAngularVelocity().x, flPitch, 2.0 * 10 * m_flDeltaT );
|
|
#else // new way- look towards the next waypoint?
|
|
// !!!UNDONE
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
// Set the body flexes
|
|
SetPoseParameter( m_poseFlex_Vert, flPitch );
|
|
SetPoseParameter( m_poseFlex_Horz, flYaw );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: There's a lot of code in here now. We should consider moving
|
|
// helicopters and such to scheduled AI. (sjb)
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::PrescheduleThink( void )
|
|
{
|
|
m_flDeltaT = gpGlobals->curtime - GetLastThink();
|
|
|
|
// Are we crashing?
|
|
if ( m_flEndDestructTime && gpGlobals->curtime > m_flEndDestructTime )
|
|
{
|
|
// We're dead, remove ourselves
|
|
SelfDestruct();
|
|
return;
|
|
}
|
|
|
|
if( m_lifeState == LIFE_ALIVE )
|
|
{
|
|
// Chopper doesn't ping
|
|
if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
|
|
{
|
|
Ping();
|
|
}
|
|
|
|
DoCombat();
|
|
MoveHead();
|
|
}
|
|
else if( m_lifeState == LIFE_DYING )
|
|
{
|
|
// Increase the number of explosions as he gets closer to death
|
|
bool bCreateExplosion = false;
|
|
float flTimeLeft = m_flEndDestructTime - gpGlobals->curtime;
|
|
if ( flTimeLeft > 1.5 )
|
|
{
|
|
bCreateExplosion = (random->RandomInt( 0, 3 ) == 0);
|
|
}
|
|
else
|
|
{
|
|
bCreateExplosion = (random->RandomInt( 0, 2 ) == 0);
|
|
}
|
|
|
|
if ( bCreateExplosion )
|
|
{
|
|
Vector explodePoint;
|
|
if ( m_hRagdoll )
|
|
{
|
|
m_hRagdoll->CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );
|
|
}
|
|
else
|
|
{
|
|
CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );
|
|
|
|
// Knock the gunship a little, but not if we're trying to fly to a point
|
|
if ( !m_hCrashTarget )
|
|
{
|
|
Vector vecPush = (GetAbsOrigin() - explodePoint);
|
|
VectorNormalize( vecPush );
|
|
ApplyAbsVelocityImpulse( vecPush * 128 );
|
|
}
|
|
}
|
|
|
|
ExplosionCreate( explodePoint, QAngle(0,0,1), this, 100, 128, false );
|
|
}
|
|
|
|
// Have we reached our crash point?
|
|
if ( m_flNextGunshipCrashFind && !m_hRagdoll )
|
|
{
|
|
// Update nearest crash point. The RPG that killed us may have knocked us
|
|
// closer to a different point than the one we were near when we first died.
|
|
if ( m_flNextGunshipCrashFind < gpGlobals->curtime )
|
|
{
|
|
FindNearestGunshipCrash();
|
|
}
|
|
|
|
if ( m_hCrashTarget )
|
|
{
|
|
MoveHead();
|
|
|
|
UpdateDesiredPosition();
|
|
|
|
// If we're over it, destruct
|
|
Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
|
|
if ( vecToTarget.LengthSqr() < (384 * 384) )
|
|
{
|
|
BeginDestruct();
|
|
m_OnCrashed.FireOutput( this, this );
|
|
m_hCrashTarget->GunshipCrashedOnTarget();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BaseClass::PrescheduleThink();
|
|
|
|
#ifdef JACOBS_GUNSHIP
|
|
SetPoseParameter( m_posePitch, random->RandomFloat( GUNSHIP_HEAD_MAX_LEFT, GUNSHIP_HEAD_MAX_RIGHT ) );
|
|
SetPoseParameter( m_poseYaw, random->RandomFloat( GUNSHIP_HEAD_MAX_UP, GUNSHIP_HEAD_MAX_DOWN ) );
|
|
#endif
|
|
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : If the enemy is in front of the gun, load up a burst.
|
|
// Actual gunfire is handled in PrescheduleThink
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
bool CNPC_CombineGunship::FireGun( void )
|
|
{
|
|
if ( m_lifeState != LIFE_ALIVE )
|
|
return false;
|
|
|
|
if ( m_bIsGroundAttacking )
|
|
return false;
|
|
|
|
if ( GetEnemy() && !m_bIsFiring && gpGlobals->curtime > m_flTimeNextAttack )
|
|
{
|
|
// We want to decelerate to attack
|
|
if (m_flGoalSpeed > GetMaxSpeedFiring() )
|
|
{
|
|
m_flGoalSpeed = GetMaxSpeedFiring();
|
|
}
|
|
|
|
bool bTargetingMissile = IsTargettingMissile();
|
|
if ( !bTargetingMissile && !m_bPreFire )
|
|
{
|
|
m_bPreFire = true;
|
|
m_flTimeNextAttack = gpGlobals->curtime + 0.5f;
|
|
|
|
EmitSound( "NPC_CombineGunship.CannonStartSound" );
|
|
return false;
|
|
}
|
|
|
|
//TODO: Emit the danger noise and wait until it's finished
|
|
|
|
// Don't fire at an occluded enemy unless blindfire is on.
|
|
if ( HasCondition( COND_ENEMY_OCCLUDED ) && ( m_fBlindfire == false ) )
|
|
return false;
|
|
|
|
// Don't shoot if the enemy is too close
|
|
if ( !bTargetingMissile && GroundDistToPosition( GetEnemy()->GetAbsOrigin() ) < GUNSHIP_STITCH_MIN )
|
|
return false;
|
|
|
|
Vector vecAimDir, vecToEnemy;
|
|
Vector vecMuzzle, vecEnemyTarget;
|
|
|
|
GetAttachment( "muzzle", vecMuzzle, &vecAimDir, NULL, NULL );
|
|
vecEnemyTarget = GetEnemyTarget();
|
|
|
|
// Aim with the muzzle's attachment point.
|
|
VectorSubtract( vecEnemyTarget, vecMuzzle, vecToEnemy );
|
|
|
|
VectorNormalize( vecToEnemy );
|
|
VectorNormalize( vecAimDir );
|
|
|
|
if ( DotProduct( vecToEnemy, vecAimDir ) > 0.9 )
|
|
{
|
|
StartCannonBurst( sk_gunship_burst_size.GetInt() );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Fire a round from the cannon
|
|
// Notes: Only call this if you have an enemy.
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::FireCannonRound( void )
|
|
{
|
|
Vector vecPenetrate;
|
|
trace_t tr;
|
|
|
|
Vector vecToEnemy, vecEnemyTarget;
|
|
Vector vecMuzzle;
|
|
Vector vecAimDir;
|
|
|
|
GetAttachment( "muzzle", vecMuzzle, &vecAimDir );
|
|
vecEnemyTarget = GetEnemyTarget();
|
|
|
|
// Aim with the muzzle's attachment point.
|
|
VectorSubtract( vecEnemyTarget, vecMuzzle, vecToEnemy );
|
|
VectorNormalize( vecToEnemy );
|
|
|
|
// If the gun is wildly off target, stop firing!
|
|
// FIXME - this should use a vector pointing
|
|
// to the enemy's location PLUS the stitching
|
|
// error! (sjb) !!!BUGBUG
|
|
|
|
if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
|
|
{
|
|
QAngle vecAimAngle;
|
|
Vector vForward, vRight, vUp;
|
|
GetAttachment( "muzzle", vecMuzzle, &vForward, &vRight, &vUp );
|
|
AngleVectors( vecAimAngle, &vForward, &vRight, &vUp );
|
|
NDebugOverlay::Line( vecMuzzle, vecEnemyTarget, 255, 255, 0, true, 1.0f );
|
|
|
|
NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vForward * 64.0f ), 255, 0, 0, true, 1.0f );
|
|
NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vRight * 32.0f ), 0, 255, 0, true, 1.0f );
|
|
NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vUp * 32.0f ), 0, 0, 255, true, 1.0f );
|
|
}
|
|
|
|
// Robin: Check the dotproduct to the enemy, NOT to the offsetted firing angle
|
|
// Fixes problems firing at close enemies, where the enemy is valid but
|
|
// the offset firing stitch isn't.
|
|
Vector vecDotCheck = vecToEnemy;
|
|
if ( GetEnemy() )
|
|
{
|
|
VectorSubtract( GetEnemy()->GetAbsOrigin(), vecMuzzle, vecDotCheck );
|
|
VectorNormalize( vecDotCheck );
|
|
}
|
|
|
|
if ( DotProduct( vecDotCheck, vecAimDir ) < 0.8f )
|
|
{
|
|
StopCannonBurst();
|
|
return;
|
|
}
|
|
|
|
DoMuzzleFlash();
|
|
|
|
m_OnFireCannon.FireOutput( this, this, 0 );
|
|
|
|
m_flTimeNextAttack = gpGlobals->curtime + 0.05f;
|
|
|
|
float flPrevHealth = 0;
|
|
if ( GetEnemy() )
|
|
{
|
|
flPrevHealth = GetEnemy()->GetHealth();
|
|
}
|
|
|
|
// Make sure we hit missiles
|
|
if ( IsTargettingMissile() )
|
|
{
|
|
// Fire a fake shot
|
|
FireBullets( 1, vecMuzzle, vecToEnemy, VECTOR_CONE_5DEGREES, 8192, m_iAmmoType, 1 );
|
|
|
|
CBaseEntity *pMissile = GetEnemy();
|
|
|
|
Vector missileDir, threatDir;
|
|
|
|
AngleVectors( pMissile->GetAbsAngles(), &missileDir );
|
|
|
|
threatDir = ( WorldSpaceCenter() - pMissile->GetAbsOrigin() );
|
|
float threatDist = VectorNormalize( threatDir );
|
|
|
|
// Check that the target is within some threshold
|
|
if ( ( DotProduct( threatDir, missileDir ) > 0.95f ) && ( threatDist < 1024.0f ) )
|
|
{
|
|
if ( random->RandomInt( 0, 1 ) == 0 )
|
|
{
|
|
CTakeDamageInfo info( this, this, 200, DMG_MISSILEDEFENSE );
|
|
CalculateBulletDamageForce( &info, m_iAmmoType, -threatDir, WorldSpaceCenter() );
|
|
GetEnemy()->TakeDamage( info );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//FIXME: Some other metric
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_iBurstSize--;
|
|
|
|
// Fire directly at the target
|
|
FireBulletsInfo_t info( 1, vecMuzzle, vecToEnemy, vec3_origin, MAX_COORD_RANGE, m_iAmmoType );
|
|
info.m_iTracerFreq = 1;
|
|
CAmmoDef *pAmmoDef = GetAmmoDef();
|
|
info.m_flPlayerDamage = pAmmoDef->PlrDamage( m_iAmmoType );
|
|
|
|
// If we've already hit the player, do 0 damage. This ensures we don't hit the
|
|
// player multiple times during a single burst.
|
|
if ( m_iBurstHits >= GUNSHIP_MAX_HITS_PER_BURST )
|
|
{
|
|
info.m_flPlayerDamage = 1.0f;
|
|
}
|
|
|
|
FireBullets( info );
|
|
|
|
if ( GetEnemy() && flPrevHealth != GetEnemy()->GetHealth() )
|
|
{
|
|
m_iBurstHits++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::DoMuzzleFlash( void )
|
|
{
|
|
BaseClass::DoMuzzleFlash();
|
|
|
|
CEffectData data;
|
|
|
|
data.m_nAttachmentIndex = LookupAttachment( "muzzle" );
|
|
data.m_nEntIndex = entindex();
|
|
DispatchEffect( "GunshipMuzzleFlash", data );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CombineGunship::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
|
|
{
|
|
bool fReturn = BaseClass::FVisible( pEntity, traceMask, ppBlocker );
|
|
|
|
if( m_fOmniscient )
|
|
{
|
|
if( !fReturn )
|
|
{
|
|
// Set this condition so that we can check it later and know that the
|
|
// enemy truly is occluded, but the gunship regards it as visible due
|
|
// to omniscience.
|
|
SetCondition( COND_ENEMY_OCCLUDED );
|
|
}
|
|
else
|
|
{
|
|
ClearCondition( COND_ENEMY_OCCLUDED );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if( fReturn )
|
|
{
|
|
ClearCondition( COND_ENEMY_OCCLUDED );
|
|
}
|
|
else
|
|
{
|
|
SetCondition( COND_ENEMY_OCCLUDED );
|
|
}
|
|
|
|
return fReturn;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Change the depth that gunship bullets can penetrate through solids
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputSetPenetrationDepth( inputdata_t &inputdata )
|
|
{
|
|
m_flPenetrationDepth = inputdata.value.Float();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Allow the gunship to sense its enemy's location even when enemy
|
|
// is hidden from sight.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputOmniscientOn( inputdata_t &inputdata )
|
|
{
|
|
m_fOmniscient = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the gunship to its default requirement that it see the
|
|
// enemy to know its current position
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputOmniscientOff( inputdata_t &inputdata )
|
|
{
|
|
m_fOmniscient = false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Allows the gunship to fire at an unseen enemy. The gunship is relying
|
|
// on hitting the target with bullets that will punch through the
|
|
// cover that the enemy is hiding behind. (Such as the Depot lighthouse)
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputBlindfireOn( inputdata_t &inputdata )
|
|
{
|
|
m_fBlindfire = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the gunship to default rules for attacking the enemy. The
|
|
// enemy must be seen to be fired at.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputBlindfireOff( inputdata_t &inputdata )
|
|
{
|
|
m_fBlindfire = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Set the gunship's paddles flailing!
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
m_takedamage = DAMAGE_NO;
|
|
|
|
StopCannonBurst();
|
|
|
|
// Replace the rotor sound with broken engine sound.
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
|
controller.SoundDestroy( m_pRotorSound );
|
|
|
|
// BUGBUG: Isn't this sound just going to get stomped when the base class calls StopLoopingSounds() ??
|
|
CPASAttenuationFilter filter2( this );
|
|
m_pRotorSound = controller.SoundCreate( filter2, entindex(), "NPC_CombineGunship.DyingSound" );
|
|
controller.Play( m_pRotorSound, 1.0, 100 );
|
|
|
|
m_OnDeath.FireOutput( info.GetAttacker(), this );
|
|
SendOnKilledGameEvent( info );
|
|
|
|
BeginCrash();
|
|
|
|
// we deliberately do not call BaseClass::EventKilled
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::BeginCrash( void )
|
|
{
|
|
m_lifeState = LIFE_DYING;
|
|
StopGroundAttack( false );
|
|
|
|
// Increase our smoke trail
|
|
CreateSmokeTrail();
|
|
if ( m_pSmokeTrail )
|
|
{
|
|
m_pSmokeTrail->SetLifetime( -1 );
|
|
m_pSmokeTrail->m_StartSize = 64;
|
|
m_pSmokeTrail->m_EndSize = 128;
|
|
m_pSmokeTrail->m_Opacity = 0.5f;
|
|
}
|
|
|
|
if ( !FindNearestGunshipCrash() )
|
|
{
|
|
// We couldn't find a crash target, so just die right here.
|
|
BeginDestruct();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CombineGunship::FindNearestGunshipCrash( void )
|
|
{
|
|
// Find the nearest crash point. If we find one, we'll try to fly to it and die.
|
|
// If we can't find one, we'll die right here.
|
|
bool bFoundAnyCrashTargets = false;
|
|
float flNearest = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH;
|
|
CTargetGunshipCrash *pNearest = NULL;
|
|
CBaseEntity *pEnt = NULL;
|
|
while( (pEnt = gEntList.FindEntityByClassname(pEnt, "info_target_gunshipcrash")) != NULL )
|
|
{
|
|
CTargetGunshipCrash *pCrashTarget = assert_cast<CTargetGunshipCrash*>(pEnt);
|
|
if ( pCrashTarget->IsDisabled() )
|
|
continue;
|
|
|
|
bFoundAnyCrashTargets = true;
|
|
|
|
float flDist = ( pEnt->WorldSpaceCenter() - WorldSpaceCenter() ).LengthSqr();
|
|
if( flDist < flNearest )
|
|
{
|
|
trace_t tr;
|
|
UTIL_TraceLine( WorldSpaceCenter(), pEnt->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
|
|
if( tr.fraction == 1.0 )
|
|
{
|
|
pNearest = pCrashTarget;
|
|
flNearest = flDist;
|
|
}
|
|
else if ( g_debug_gunship.GetInt() )
|
|
{
|
|
NDebugOverlay::Line( WorldSpaceCenter(), tr.endpos, 255,0,0, true, 99);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !pNearest )
|
|
{
|
|
// If we found a gunship crash, but none near enough, claim we did find one, so that we
|
|
// don't blow up yet. This will give us 3 seconds to attempt to find one before dying.
|
|
if ( !m_hCrashTarget && bFoundAnyCrashTargets )
|
|
{
|
|
m_flNextGunshipCrashFind = gpGlobals->curtime + 0.5;
|
|
m_flEndDestructTime = gpGlobals->curtime + 3.0;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Fly to the crash point and destruct there
|
|
m_hCrashTarget = pNearest;
|
|
m_flNextGunshipCrashFind = gpGlobals->curtime + 0.5;
|
|
m_flEndDestructTime = 0;
|
|
|
|
if ( g_debug_gunship.GetInt() )
|
|
{
|
|
NDebugOverlay::Line(GetAbsOrigin(), m_hCrashTarget->GetAbsOrigin(), 0,255,0, true, 0.5);
|
|
NDebugOverlay::Box( m_hCrashTarget->GetAbsOrigin(), -Vector(200,200,200), Vector(200,200,200), 0,255,0, 128, 0.5 );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: I'm now ready to die. Create my ragdoll & hide myself.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::BeginDestruct( void )
|
|
{
|
|
m_flEndDestructTime = gpGlobals->curtime + 3.0;
|
|
|
|
// Clamp velocity
|
|
if( hl2_episodic.GetBool() && GetAbsVelocity().Length() > 700.0f )
|
|
{
|
|
Vector vecVelocity = GetAbsVelocity();
|
|
VectorNormalize( vecVelocity );
|
|
SetAbsVelocity( vecVelocity * 700.0f );
|
|
}
|
|
|
|
CTakeDamageInfo info;
|
|
info.SetDamage( 40000 );
|
|
CalculateExplosiveDamageForce( &info, GetAbsVelocity(), GetAbsOrigin() );
|
|
|
|
// Don't create a ragdoll if we're going to explode into gibs
|
|
if ( !m_hCrashTarget )
|
|
return;
|
|
|
|
// Switch to damaged skin
|
|
m_nSkin = 1;
|
|
|
|
if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
|
|
{
|
|
Chopper_BecomeChunks( this );
|
|
SetThink( &CNPC_CombineGunship::SUB_Remove );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
AddEffects( EF_NODRAW );
|
|
return;
|
|
}
|
|
|
|
// Create the ragdoll
|
|
m_hRagdoll = CreateServerRagdoll( this, 0, info, COLLISION_GROUP_NONE );
|
|
if ( !m_hRagdoll )
|
|
{
|
|
// Failed, just explode
|
|
SelfDestruct();
|
|
return;
|
|
}
|
|
|
|
m_hRagdoll->SetName( AllocPooledString( UTIL_VarArgs("%s_ragdoll", STRING(GetEntityName()) ) ) );
|
|
|
|
// Tell the smoke trail to follow the ragdoll
|
|
CreateSmokeTrail();
|
|
if ( m_pSmokeTrail )
|
|
{
|
|
// Force the smoke trail to stay on, and tell it to follow the ragdoll
|
|
m_pSmokeTrail->SetLifetime( -1 );
|
|
m_pSmokeTrail->FollowEntity( m_hRagdoll );
|
|
|
|
m_pSmokeTrail->m_StartSize = 64;
|
|
m_pSmokeTrail->m_EndSize = 128;
|
|
m_pSmokeTrail->m_Opacity = 0.5f;
|
|
}
|
|
|
|
/*
|
|
// ROBIN: Disabled this for now.
|
|
//
|
|
// Create the crashing controller and attach it to the ragdoll physics objects
|
|
m_pCrashingController = physenv->CreateMotionController( &m_crashCallback );
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = m_hRagdoll->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
m_pCrashingController->AttachObject( pList[i], false );
|
|
}
|
|
*/
|
|
|
|
// Hide myself, because the ragdoll's now taken my place
|
|
AddEffects( EF_NODRAW );
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Create a smoke trail
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::CreateSmokeTrail( void )
|
|
{
|
|
if ( m_pSmokeTrail )
|
|
return;
|
|
|
|
m_pSmokeTrail = SmokeTrail::CreateSmokeTrail();
|
|
|
|
if ( m_pSmokeTrail )
|
|
{
|
|
m_pSmokeTrail->m_SpawnRate = 48;
|
|
m_pSmokeTrail->m_ParticleLifetime = 2.5f;
|
|
|
|
m_pSmokeTrail->m_StartColor.Init( 0.25f, 0.25f, 0.25f );
|
|
m_pSmokeTrail->m_EndColor.Init( 0.0, 0.0, 0.0 );
|
|
|
|
m_pSmokeTrail->m_StartSize = 24;
|
|
m_pSmokeTrail->m_EndSize = 128;
|
|
m_pSmokeTrail->m_SpawnRadius = 4;
|
|
m_pSmokeTrail->m_MinSpeed = 8;
|
|
m_pSmokeTrail->m_MaxSpeed = 64;
|
|
m_pSmokeTrail->m_Opacity = 0.2f;
|
|
|
|
m_pSmokeTrail->SetLifetime( -1 );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::ApplyGeneralDrag( void )
|
|
{
|
|
Vector vecNewVelocity = GetAbsVelocity();
|
|
|
|
// See if we need to stop more quickly
|
|
if ( m_bIsGroundAttacking )
|
|
{
|
|
vecNewVelocity *= 0.95f;
|
|
}
|
|
else
|
|
{
|
|
vecNewVelocity *= 0.995;
|
|
}
|
|
|
|
SetAbsVelocity( vecNewVelocity );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CNPC_CombineGunship::Flight( void )
|
|
{
|
|
if( GetFlags() & FL_ONGROUND )
|
|
{
|
|
//This would be really bad.
|
|
SetGroundEntity( NULL );
|
|
}
|
|
|
|
if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_PATH )
|
|
{
|
|
NDebugOverlay::Line(GetLocalOrigin(), GetDesiredPosition(), 0,0,255, true, 0.1);
|
|
}
|
|
|
|
// calc desired acceleration
|
|
float dt = 1.0f;
|
|
|
|
Vector accel;
|
|
float accelRate = GUNSHIP_ACCEL_RATE;
|
|
float maxSpeed = GetMaxSpeed();
|
|
|
|
if ( m_lifeState == LIFE_DYING && m_hCrashTarget != NULL )
|
|
{
|
|
// Gunship can fly faster to the place where it's supposed to crash, but
|
|
// maintain normal speeds if we haven't found a place to crash.
|
|
accelRate *= 2.0;
|
|
maxSpeed *= 4.0;
|
|
}
|
|
|
|
float flCurrentSpeed = GetAbsVelocity().Length();
|
|
float flDist = MIN( flCurrentSpeed + accelRate, maxSpeed );
|
|
|
|
Vector deltaPos;
|
|
if ( m_lifeState == LIFE_DYING || m_hGroundAttackTarget )
|
|
{
|
|
// Move directly to the target point
|
|
deltaPos = GetDesiredPosition();
|
|
}
|
|
else
|
|
{
|
|
ComputeActualTargetPosition( flDist, dt, 0.0f, &deltaPos );
|
|
}
|
|
deltaPos -= GetAbsOrigin();
|
|
|
|
// calc goal linear accel to hit deltaPos in dt time.
|
|
accel.x = 2.0 * (deltaPos.x - GetAbsVelocity().x * dt) / (dt * dt);
|
|
accel.y = 2.0 * (deltaPos.y - GetAbsVelocity().y * dt) / (dt * dt);
|
|
accel.z = 2.0 * (deltaPos.z - GetAbsVelocity().z * dt + 0.5 * 384 * dt * dt) / (dt * dt);
|
|
|
|
float flDistFromPath = 0.0f;
|
|
Vector vecPoint, vecDelta;
|
|
if ( m_lifeState != LIFE_DYING && IsOnPathTrack() )
|
|
{
|
|
// Also, add in a little force to get us closer to our current line segment if we can
|
|
ClosestPointToCurrentPath( &vecPoint );
|
|
VectorSubtract( vecPoint, GetAbsOrigin(), vecDelta );
|
|
flDistFromPath = VectorNormalize( vecDelta );
|
|
if ( flDistFromPath > GUNSHIP_OUTER_NAV_DIST )
|
|
{
|
|
// 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 - GUNSHIP_OUTER_NAV_DIST) / 200.0f;
|
|
flAmount = clamp( flAmount, 0, 1 );
|
|
VectorMA( accel, flAmount * 200.0f, vecDelta, accel );
|
|
}
|
|
}
|
|
|
|
Vector vecAvoidForce;
|
|
CAvoidSphere::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
|
|
accel += vecAvoidForce;
|
|
CAvoidBox::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
|
|
accel += vecAvoidForce;
|
|
|
|
if ( m_lifeState != LIFE_DYING || m_hCrashTarget == NULL )
|
|
{
|
|
// don't fall faster than 0.2G or climb faster than 2G
|
|
accel.z = clamp( accel.z, 384 * 0.2, 384 * 2.0 );
|
|
}
|
|
|
|
Vector forward, right, up;
|
|
GetVectors( &forward, &right, &up );
|
|
|
|
Vector goalUp = accel;
|
|
VectorNormalize( goalUp );
|
|
|
|
// calc goal orientation to hit linear accel forces
|
|
float goalPitch = RAD2DEG( asin( DotProduct( forward, goalUp ) ) );
|
|
float goalYaw = UTIL_VecToYaw( m_vecDesiredFaceDir );
|
|
float goalRoll = RAD2DEG( asin( DotProduct( right, goalUp ) ) );
|
|
|
|
// clamp goal orientations
|
|
goalPitch = clamp( goalPitch, -45, 60 );
|
|
goalRoll = clamp( goalRoll, -45, 45 );
|
|
|
|
// calc angular accel needed to hit goal pitch in dt time.
|
|
dt = 0.6;
|
|
QAngle goalAngAccel;
|
|
goalAngAccel.x = 2.0 * (AngleDiff( goalPitch, AngleNormalize( GetLocalAngles().x ) ) - GetLocalAngularVelocity().x * dt) / (dt * dt);
|
|
goalAngAccel.y = 2.0 * (AngleDiff( goalYaw, AngleNormalize( GetLocalAngles().y ) ) - GetLocalAngularVelocity().y * dt) / (dt * dt);
|
|
goalAngAccel.z = 2.0 * (AngleDiff( goalRoll, AngleNormalize( GetLocalAngles().z ) ) - GetLocalAngularVelocity().z * dt) / (dt * dt);
|
|
|
|
goalAngAccel.x = clamp( goalAngAccel.x, -300, 300 );
|
|
goalAngAccel.y = clamp( goalAngAccel.y, -120, 120 );
|
|
goalAngAccel.z = clamp( goalAngAccel.z, -300, 300 );
|
|
|
|
// limit angular accel changes to similate mechanical response times
|
|
dt = 0.1;
|
|
QAngle angAccelAccel;
|
|
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, -m_fMaxAngAcceleration, m_fMaxAngAcceleration );
|
|
angAccelAccel.y = clamp( angAccelAccel.y, -m_fMaxAngAcceleration, m_fMaxAngAcceleration );
|
|
angAccelAccel.z = clamp( angAccelAccel.z, -m_fMaxAngAcceleration, m_fMaxAngAcceleration );
|
|
|
|
m_vecAngAcceleration += angAccelAccel * 0.1;
|
|
|
|
// 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 );
|
|
|
|
ApplySidewaysDrag( right );
|
|
ApplyGeneralDrag();
|
|
|
|
QAngle angVel = GetLocalAngularVelocity();
|
|
angVel += m_vecAngAcceleration * 0.1;
|
|
|
|
angVel.x = clamp( angVel.x, -m_vMaxAngVelocity.x, m_vMaxAngVelocity.x );
|
|
angVel.y = clamp( angVel.y, -m_vMaxAngVelocity.y, m_vMaxAngVelocity.y );
|
|
angVel.z = clamp( angVel.z, -m_vMaxAngVelocity.z, m_vMaxAngVelocity.z );
|
|
|
|
SetLocalAngularVelocity( angVel );
|
|
|
|
m_flForce = m_flForce * 0.8 + (accel.z + fabs( accel.x ) * 0.1 + fabs( accel.y ) * 0.1) * 0.1 * 0.2;
|
|
|
|
Vector vecImpulse = m_flForce * up;
|
|
|
|
if ( !m_hCrashTarget && m_lifeState == LIFE_DYING && !hl2_episodic.GetBool() )
|
|
{
|
|
// Force gunship to the ground if it doesn't have a specific place to crash.
|
|
// EXCEPT In episodic, where forcing it to the ground means it crashes where the player can't see (attic showdown) (sjb)
|
|
vecImpulse.z = -10;
|
|
}
|
|
else
|
|
{
|
|
vecImpulse.z -= 38.4; // 32ft/sec
|
|
}
|
|
|
|
// Find our current velocity
|
|
Vector vecVelDir = GetAbsVelocity();
|
|
VectorNormalize( vecVelDir );
|
|
|
|
if ( flDistFromPath > GUNSHIP_INNER_NAV_DIST )
|
|
{
|
|
// 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 );
|
|
}
|
|
}
|
|
|
|
// Find our acceleration direction
|
|
Vector vecAccelDir = vecImpulse;
|
|
VectorNormalize( vecAccelDir );
|
|
|
|
// Level out our plane of movement
|
|
vecAccelDir.z = 0.0f;
|
|
vecVelDir.z = 0.0f;
|
|
forward.z = 0.0f;
|
|
right.z = 0.0f;
|
|
|
|
// Find out how "fast" we're moving in relation to facing and acceleration
|
|
float speed = m_flForce * DotProduct( vecVelDir, vecAccelDir );// * DotProduct( forward, vecVelDir );
|
|
|
|
// Apply the acceleration blend to the fins
|
|
float finAccelBlend = SimpleSplineRemapVal( speed, -60, 60, -1, 1 );
|
|
float curFinAccel = GetPoseParameter( m_poseFin_Accel );
|
|
|
|
curFinAccel = UTIL_Approach( finAccelBlend, curFinAccel, 0.5f );
|
|
SetPoseParameter( m_poseFin_Accel, curFinAccel );
|
|
|
|
speed = m_flForce * DotProduct( vecVelDir, right );
|
|
|
|
// Apply the spin sway to the fins
|
|
float finSwayBlend = SimpleSplineRemapVal( speed, -60, 60, -1, 1 );
|
|
float curFinSway = GetPoseParameter( m_poseFin_Sway );
|
|
|
|
curFinSway = UTIL_Approach( finSwayBlend, curFinSway, 0.5f );
|
|
SetPoseParameter( m_poseFin_Sway, curFinSway );
|
|
|
|
if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_PATH )
|
|
{
|
|
NDebugOverlay::Line(GetLocalOrigin(), GetLocalOrigin() + vecImpulse, 255,0,0, true, 0.1);
|
|
}
|
|
|
|
// Add in our velocity pulse for this frame
|
|
ApplyAbsVelocityImpulse( vecImpulse );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Updates the facing direction
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::UpdateFacingDirection( void )
|
|
{
|
|
if ( GetEnemy() )
|
|
{
|
|
if ( !IsCrashing() && m_flLastSeen + 5 > gpGlobals->curtime )
|
|
{
|
|
// If we've seen the target recently, face the target.
|
|
//Msg( "Facing Target \n" );
|
|
m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
|
|
}
|
|
else
|
|
{
|
|
// Remain facing the way you were facing...
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Face our desired position.
|
|
if ( GetDesiredPosition().DistToSqr( GetAbsOrigin() ) > 1 )
|
|
{
|
|
m_vecDesiredFaceDir = GetDesiredPosition() - GetAbsOrigin();
|
|
}
|
|
else
|
|
{
|
|
GetVectors( &m_vecDesiredFaceDir, NULL, NULL );
|
|
}
|
|
}
|
|
VectorNormalize( m_vecDesiredFaceDir );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Fire up the Gunships 'second' rotor sound. The Search sound.
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InitializeRotorSound( void )
|
|
{
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
|
|
|
CPASAttenuationFilter filter( this );
|
|
|
|
m_pCannonSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.CannonSound" );
|
|
m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.RotorSound" );
|
|
m_pAirExhaustSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.ExhaustSound" );
|
|
m_pAirBlastSound = controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.RotorBlastSound" );
|
|
|
|
controller.Play( m_pCannonSound, 0.0, 100 );
|
|
controller.Play( m_pAirExhaustSound, 0.0, 100 );
|
|
controller.Play( m_pAirBlastSound, 0.0, 100 );
|
|
|
|
BaseClass::InitializeRotorSound();
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::UpdateRotorSoundPitch( int iPitch )
|
|
{
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
|
|
|
// Apply the pitch to both sounds.
|
|
controller.SoundChangePitch( m_pAirExhaustSound, iPitch, 0.1 );
|
|
|
|
// FIXME: Doesn't work in multiplayer
|
|
CBaseEntity *pPlayer = UTIL_PlayerByIndex(1);
|
|
if (pPlayer)
|
|
{
|
|
Vector pos;
|
|
Vector up;
|
|
GetAttachment( "rotor", pos, NULL, NULL, &up );
|
|
|
|
float flDistance = (pPlayer->WorldSpaceCenter() - pos).Length2DSqr();
|
|
|
|
// Fade in exhaust when we're far from the player
|
|
float flVolume = clamp( RemapVal( flDistance, (900*900), (1800*1800), 1, 0 ), 0, 1 );
|
|
controller.SoundChangeVolume( m_pAirExhaustSound, flVolume * GetRotorVolume(), 0.1 );
|
|
|
|
// Fade in the blast when it's close to the player (in 2D)
|
|
flVolume = clamp( RemapVal( flDistance, (600*600), (700*700), 1, 0 ), 0, 1 );
|
|
controller.SoundChangeVolume( m_pAirBlastSound, flVolume * GetRotorVolume(), 0.1 );
|
|
}
|
|
|
|
BaseClass::UpdateRotorSoundPitch( iPitch );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::ApplySidewaysDrag( const Vector &vecRight )
|
|
{
|
|
Vector vecVelocity = GetAbsVelocity();
|
|
if( m_lifeState == LIFE_ALIVE )
|
|
{
|
|
vecVelocity.x *= (1.0 - fabs( vecRight.x ) * 0.04);
|
|
vecVelocity.y *= (1.0 - fabs( vecRight.y ) * 0.04);
|
|
vecVelocity.z *= (1.0 - fabs( vecRight.z ) * 0.04);
|
|
}
|
|
else
|
|
{
|
|
vecVelocity.x *= (1.0 - fabs( vecRight.x ) * 0.03);
|
|
vecVelocity.y *= (1.0 - fabs( vecRight.y ) * 0.03);
|
|
vecVelocity.z *= (1.0 - fabs( vecRight.z ) * 0.09);
|
|
}
|
|
SetAbsVelocity( vecVelocity );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Explode the gunship.
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::SelfDestruct( void )
|
|
{
|
|
SetThink( NULL );
|
|
m_lifeState = LIFE_DEAD;
|
|
|
|
StopLoopingSounds();
|
|
StopCannonBurst();
|
|
|
|
Vector vecVelocity = GetAbsVelocity();
|
|
vecVelocity.z = 0.0; // stop falling.
|
|
SetAbsVelocity( vecVelocity );
|
|
|
|
CBaseEntity *pBreakEnt = this;
|
|
|
|
// If we've ragdolled, play the explosions on the ragdoll instead
|
|
Vector vecOrigin;
|
|
if ( m_hRagdoll )
|
|
{
|
|
m_hRagdoll->EmitSound( "NPC_CombineGunship.Explode" );
|
|
vecOrigin = m_hRagdoll->GetAbsOrigin();
|
|
pBreakEnt = m_hRagdoll;
|
|
}
|
|
else
|
|
{
|
|
EmitSound( "NPC_CombineGunship.Explode" );
|
|
vecOrigin = GetAbsOrigin();
|
|
}
|
|
|
|
// Create some explosions on the gunship body
|
|
Vector vecDelta;
|
|
for( int i = 0 ; i < 6 ; i++ )
|
|
{
|
|
vecDelta = RandomVector( -200,200 );
|
|
ExplosionCreate( vecOrigin + vecDelta, QAngle( -90, 0, 0 ), this, 10, 10, false );
|
|
}
|
|
|
|
AR2Explosion *pExplosion = AR2Explosion::CreateAR2Explosion( vecOrigin );
|
|
if ( pExplosion )
|
|
{
|
|
pExplosion->SetLifetime( 10 );
|
|
}
|
|
|
|
// If we don't have a crash target, explode into chunks
|
|
if ( !m_hCrashTarget )
|
|
{
|
|
Vector angVelocity;
|
|
QAngleToAngularImpulse( pBreakEnt->GetLocalAngularVelocity(), angVelocity );
|
|
PropBreakableCreateAll( pBreakEnt->GetModelIndex(), pBreakEnt->VPhysicsGetObject(), pBreakEnt->GetAbsOrigin(), pBreakEnt->GetAbsAngles(), pBreakEnt->GetAbsVelocity(), angVelocity, 1.0, 800, COLLISION_GROUP_NPC, pBreakEnt );
|
|
|
|
// Throw out some small chunks too
|
|
CPVSFilter filter( GetAbsOrigin() );
|
|
for ( int i = 0; i < 20; i++ )
|
|
{
|
|
Vector gibVelocity = RandomVector(-100,100) * 10;
|
|
int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );
|
|
te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, Vector(40,40,40), gibVelocity, iModelIndex, 400, 1, 2.5, BREAK_METAL );
|
|
}
|
|
|
|
if ( m_hRagdoll )
|
|
{
|
|
UTIL_Remove( m_hRagdoll );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_pSmokeTrail )
|
|
{
|
|
// If we have a ragdoll, let it smoke for a few more seconds
|
|
if ( m_hRagdoll )
|
|
{
|
|
m_pSmokeTrail->SetLifetime(3.0f);
|
|
}
|
|
else
|
|
{
|
|
m_pSmokeTrail->SetLifetime(0.1f);
|
|
}
|
|
m_pSmokeTrail = NULL;
|
|
}
|
|
}
|
|
|
|
UTIL_Remove( this );
|
|
|
|
#ifndef INFESTED_DLL
|
|
// Record this so a nearby citizen can respond.
|
|
if ( GetCitizenResponse() )
|
|
{
|
|
GetCitizenResponse()->AddResponseTrigger( CR_PLAYER_KILLED_GUNSHIP );
|
|
}
|
|
#endif
|
|
|
|
#ifdef HL2_EPISODIC
|
|
NPCEventResponse()->TriggerEvent( "TLK_CITIZEN_RESPONSE_KILLED_GUNSHIP", false, false );
|
|
#endif
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Explode the gunship.
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputSelfDestruct( inputdata_t &inputdata )
|
|
{
|
|
BeginCrash();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Shrink the gunship's bbox so that it fits in docking bays
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputSetDockingBBox( inputdata_t &inputdata )
|
|
{
|
|
Vector vecSize( 32, 32, 32 );
|
|
|
|
UTIL_SetSize( this, vecSize * -1, vecSize );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Set the gunship BBox to normal size
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputSetNormalBBox( inputdata_t &inputdata )
|
|
{
|
|
Vector vecBBMin, vecBBMax;
|
|
|
|
ExtractBbox( SelectHeaviestSequence( ACT_GUNSHIP_PATROL ), vecBBMin, vecBBMax );
|
|
|
|
// Trim the bounding box a bit. It's huge.
|
|
#define GUNSHIP_TRIM_BOX 38
|
|
vecBBMin.x += GUNSHIP_TRIM_BOX;
|
|
vecBBMax.x -= GUNSHIP_TRIM_BOX;
|
|
vecBBMin.y += GUNSHIP_TRIM_BOX;
|
|
vecBBMax.y -= GUNSHIP_TRIM_BOX;
|
|
|
|
UTIL_SetSize( this, vecBBMin, vecBBMax );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputEnableGroundAttack( inputdata_t &inputdata )
|
|
{
|
|
m_bCanGroundAttack = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputDisableGroundAttack( inputdata_t &inputdata )
|
|
{
|
|
m_bCanGroundAttack = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputDoGroundAttack( inputdata_t &inputdata )
|
|
{
|
|
// Was a target node specified?
|
|
CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID(), NULL, inputdata.pActivator, inputdata.pCaller );
|
|
if ( pEntity )
|
|
{
|
|
// Mapmaker wants us to ground attack a specific target
|
|
m_hGroundAttackTarget = pEntity;
|
|
}
|
|
else
|
|
{
|
|
StartGroundAttack();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &vGunPosition -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::UpdateEnemyTarget( void )
|
|
{
|
|
Vector vGunPosition;
|
|
|
|
GetAttachment( "muzzle", vGunPosition );
|
|
|
|
// Follow mode
|
|
Vector enemyPos;
|
|
bool bTargettingPlayer;
|
|
|
|
if ( GetEnemy() != NULL )
|
|
{
|
|
CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
|
|
if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
|
|
{
|
|
// Update against a driving target
|
|
enemyPos = GetEnemy()->WorldSpaceCenter();
|
|
}
|
|
else
|
|
{
|
|
enemyPos = GetEnemy()->EyePosition();
|
|
}
|
|
bTargettingPlayer = GetEnemy()->IsPlayer();
|
|
}
|
|
else
|
|
{
|
|
enemyPos = m_vecAttackPosition;
|
|
bTargettingPlayer = false;
|
|
}
|
|
|
|
// Direction towards the enemy
|
|
Vector targetDir = enemyPos - m_vecAttackPosition;
|
|
VectorNormalize( targetDir );
|
|
|
|
// Direction from the gunship to the enemy
|
|
Vector enemyDir = enemyPos - vGunPosition;
|
|
VectorNormalize( enemyDir );
|
|
|
|
float lastSpeed = VectorNormalize( m_vecAttackVelocity );
|
|
QAngle chaseAngles, lastChaseAngles;
|
|
|
|
VectorAngles( targetDir, chaseAngles );
|
|
VectorAngles( m_vecAttackVelocity, lastChaseAngles );
|
|
|
|
// Debug info
|
|
if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
|
|
{
|
|
// Final position
|
|
NDebugOverlay::Cross3D( m_vecAttackPosition, -Vector(2,2,2), Vector(2,2,2), 0, 0, 255, true, 4.0f );
|
|
}
|
|
|
|
float yawDiff = UTIL_AngleDiff( lastChaseAngles[YAW], chaseAngles[YAW] );
|
|
|
|
int maxYaw;
|
|
if ( bTargettingPlayer )
|
|
{
|
|
maxYaw = 6;
|
|
}
|
|
else
|
|
{
|
|
maxYaw = 30;
|
|
}
|
|
|
|
yawDiff = clamp( yawDiff, -maxYaw, maxYaw );
|
|
|
|
chaseAngles[PITCH] = 0.0f;
|
|
chaseAngles[ROLL] = 0.0f;
|
|
|
|
bool bMaxHits = ( m_iBurstHits >= GUNSHIP_MAX_HITS_PER_BURST || (GetEnemy() && !GetEnemy()->IsAlive()) );
|
|
|
|
if ( bMaxHits )
|
|
{
|
|
// We've hit our target. Stop chasing, and return to max speed.
|
|
chaseAngles[YAW] = lastChaseAngles[YAW];
|
|
lastSpeed = BASE_STITCH_VELOCITY;
|
|
}
|
|
else
|
|
{
|
|
// Move towards the target yaw
|
|
chaseAngles[YAW] = UTIL_AngleMod( lastChaseAngles[YAW] - yawDiff );
|
|
}
|
|
|
|
// If we've hit the target already, or we're not close enough to it, then just stitch along
|
|
if ( bMaxHits || ( m_vecAttackPosition - enemyPos ).LengthSqr() > (64 * 64) )
|
|
{
|
|
AngleVectors( chaseAngles, &targetDir );
|
|
|
|
// Update our new velocity
|
|
m_vecAttackVelocity = targetDir * lastSpeed;
|
|
|
|
if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
|
|
{
|
|
NDebugOverlay::Line( m_vecAttackPosition, m_vecAttackPosition + (m_vecAttackVelocity * 0.1), 255, 0, 0, true, 4.0f );
|
|
}
|
|
|
|
// Move along that velocity for this step in time
|
|
m_vecAttackPosition += ( m_vecAttackVelocity * 0.1f );
|
|
m_vecAttackPosition.z = enemyPos.z;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise always continue to hit an NPC when close enough
|
|
m_vecAttackPosition = enemyPos;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Utility function to aim the helicopter gun at the direction
|
|
//------------------------------------------------------------------------------
|
|
bool CNPC_CombineGunship::PoseGunTowardTargetDirection( const Vector &vTargetDir )
|
|
{
|
|
Vector vecOut;
|
|
VectorIRotate( vTargetDir, EntityToWorldTransform(), vecOut );
|
|
|
|
QAngle angles;
|
|
VectorAngles(vecOut, angles);
|
|
angles.y = AngleNormalize( angles.y );
|
|
angles.x = AngleNormalize( angles.x );
|
|
|
|
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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Vector
|
|
//-----------------------------------------------------------------------------
|
|
Vector CNPC_CombineGunship::GetMissileTarget( void )
|
|
{
|
|
return GetEnemy()->GetAbsOrigin();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Get the target position for the enemy- the position we fire upon.
|
|
// this is often modified by m_flAttackOffset to provide the 'stitching'
|
|
// behavior that's so popular with the kids these days (sjb)
|
|
//
|
|
// Input : vGunPosition - location of gunship's muzzle
|
|
// : pTarget = vector to paste enemy target into.
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
Vector CNPC_CombineGunship::GetEnemyTarget( void )
|
|
{
|
|
// Make sure we have an enemy
|
|
if ( GetEnemy() == NULL )
|
|
return m_vecAttackPosition;
|
|
|
|
// If we're locked onto a missile, use special code to try and destroy it
|
|
if ( IsTargettingMissile() )
|
|
return GetMissileTarget();
|
|
|
|
return m_vecAttackPosition;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &tr -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::DoImpactEffect( trace_t &tr, int nDamageType )
|
|
{
|
|
UTIL_ImpactTrace( &tr, nDamageType, "ImpactGunship" );
|
|
|
|
// These glow effects don't sort properly, so they're cut for E3 2003 (sjb)
|
|
#if 0
|
|
CEffectData data;
|
|
|
|
data.m_vOrigin = tr.endpos;
|
|
data.m_vNormal = vec3_origin;
|
|
data.m_vAngles = vec3_angle;
|
|
|
|
DispatchEffect( "GunshipImpact", data );
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Make the gunship's signature blue tracer!
|
|
// Input : &vecTracerSrc -
|
|
// &tr -
|
|
// iTracerType -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
|
|
{
|
|
switch ( iTracerType )
|
|
{
|
|
case TRACER_LINE:
|
|
{
|
|
float flTracerDist;
|
|
Vector vecDir;
|
|
Vector vecEndPos;
|
|
|
|
vecDir = tr.endpos - vecTracerSrc;
|
|
|
|
flTracerDist = VectorNormalize( vecDir );
|
|
|
|
UTIL_Tracer( vecTracerSrc, tr.endpos, 0, TRACER_DONT_USE_ATTACHMENT, 8000, true, "GunshipTracer" );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &info -
|
|
// &vecDir -
|
|
// *ptr -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
|
|
{
|
|
// Reflect bullets
|
|
if ( info.GetDamageType() & DMG_BULLET )
|
|
{
|
|
if ( random->RandomInt( 0, 2 ) == 0 )
|
|
{
|
|
Vector vecRicochetDir = vecDir * -1;
|
|
|
|
vecRicochetDir.x += random->RandomFloat( -0.5, 0.5 );
|
|
vecRicochetDir.y += random->RandomFloat( -0.5, 0.5 );
|
|
vecRicochetDir.z += random->RandomFloat( -0.5, 0.5 );
|
|
|
|
VectorNormalize( vecRicochetDir );
|
|
|
|
Vector end = ptr->endpos + vecRicochetDir * 1024;
|
|
UTIL_Tracer( ptr->endpos, end, entindex(), TRACER_DONT_USE_ATTACHMENT, 3500 );
|
|
}
|
|
|
|
// If this is from a player, record it so a nearby citizen can respond.
|
|
if ( info.GetAttacker()->IsPlayer() )
|
|
{
|
|
#ifndef INFESTED_DLL
|
|
if ( GetCitizenResponse() )
|
|
{
|
|
GetCitizenResponse()->AddResponseTrigger( CR_PLAYER_SHOT_GUNSHIP );
|
|
}
|
|
#endif
|
|
|
|
#ifdef HL2_EPISODIC
|
|
NPCEventResponse()->TriggerEvent( "TLK_CITIZEN_RESPONSE_SHOT_GUNSHIP", false, false );
|
|
#endif
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
BaseClass::TraceAttack( info, vecDir, ptr );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: This is necessary to ensure that the game doesn't break if a mapmaker has outputs that
|
|
// must be fired on gunships, and the player switches skill levels
|
|
// midway through a gunship battle.
|
|
// Input : iDamageNumber -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::FireDamageOutputsUpto( int iDamageNumber )
|
|
{
|
|
for ( int i = 0; i <= iDamageNumber; i++ )
|
|
{
|
|
if ( !m_bDamageOutputsFired[i] )
|
|
{
|
|
m_bDamageOutputsFired[i] = true;
|
|
|
|
switch ( i )
|
|
{
|
|
case 0:
|
|
//Msg("Fired first\n");
|
|
m_OnFirstDamage.FireOutput( this, this );
|
|
break;
|
|
|
|
case 1:
|
|
//Msg("Fired second\n");
|
|
m_OnSecondDamage.FireOutput( this, this );
|
|
break;
|
|
|
|
case 2:
|
|
//Msg("Fired third\n");
|
|
m_OnThirdDamage.FireOutput( this, this );
|
|
break;
|
|
|
|
case 3:
|
|
//Msg("Fired fourth\n");
|
|
m_OnFourthDamage.FireOutput( this, this );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Damage filtering
|
|
//------------------------------------------------------------------------------
|
|
int CNPC_CombineGunship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
|
|
{
|
|
// Allow npc_kill to kill me
|
|
if ( inputInfo.GetDamageType() != DMG_GENERIC )
|
|
{
|
|
// Ignore mundane bullet damage.
|
|
if ( ( inputInfo.GetDamageType() & DMG_BLAST ) == false )
|
|
return 0;
|
|
|
|
// Ignore blasts less than this amount
|
|
if ( inputInfo.GetDamage() < GUNSHIP_MIN_DAMAGE_THRESHOLD )
|
|
return 0;
|
|
}
|
|
|
|
// Only take blast damage
|
|
CTakeDamageInfo info = inputInfo;
|
|
|
|
// Make a pain sound
|
|
if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
|
|
{
|
|
EmitSound( "NPC_CombineGunship.Pain" );
|
|
}
|
|
|
|
Vector damageDir = info.GetDamageForce();
|
|
VectorNormalize( damageDir );
|
|
|
|
// Don't get knocked around if I'm ground attacking
|
|
if ( !m_bIsGroundAttacking )
|
|
{
|
|
ApplyAbsVelocityImpulse( damageDir * 200.0f );
|
|
}
|
|
|
|
if ( m_bInvulnerable == false )
|
|
{
|
|
// Take a percentage of our health away
|
|
// Adjust health for damage
|
|
int iHealthIncrements = sk_gunship_health_increments.GetInt();
|
|
if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
|
|
{
|
|
iHealthIncrements = ceil( iHealthIncrements * 0.5 );
|
|
}
|
|
else if ( g_pGameRules->IsSkillLevel( SKILL_HARD ) )
|
|
{
|
|
iHealthIncrements = floor( iHealthIncrements * 1.5 );
|
|
}
|
|
info.SetDamage( ( GetMaxHealth() / (float)iHealthIncrements ) + 1 );
|
|
|
|
// Find out which "stage" we're at in our health
|
|
int healthIncrement = iHealthIncrements - ( GetHealth() / (float)(( GetMaxHealth() / (float)iHealthIncrements )) );
|
|
switch ( healthIncrement )
|
|
{
|
|
case 1:
|
|
// If we're on Easy, we're half dead now, so fire the rest of our outputs too
|
|
// This is done in case the mapmaker's connected those inputs to something important
|
|
// that has to happen before the gunship dies.
|
|
if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
|
|
{
|
|
FireDamageOutputsUpto( 3 );
|
|
}
|
|
else
|
|
{
|
|
FireDamageOutputsUpto( 1 );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
FireDamageOutputsUpto( healthIncrement );
|
|
break;
|
|
}
|
|
|
|
// Start smoking when we're almost dead
|
|
CreateSmokeTrail();
|
|
|
|
if ( m_pSmokeTrail )
|
|
{
|
|
if ( healthIncrement < 2 )
|
|
{
|
|
m_pSmokeTrail->SetLifetime( 8.0 );
|
|
}
|
|
|
|
m_pSmokeTrail->FollowEntity( this, "exhaustl" );
|
|
}
|
|
|
|
// Move with the target
|
|
Vector gibVelocity = GetAbsVelocity() + (-damageDir * 200.0f);
|
|
|
|
// Dump out metal gibs
|
|
CPVSFilter filter( GetAbsOrigin() );
|
|
for ( int i = 0; i < 10; i++ )
|
|
{
|
|
int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );
|
|
te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, Vector(40,40,40), gibVelocity, iModelIndex, 400, 1, 2.5, BREAK_METAL );
|
|
}
|
|
}
|
|
|
|
return BaseClass::OnTakeDamage_Alive( info );
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : The proper way to begin the gunship cannon firing at the enemy.
|
|
// Input : iBurstSize - the size of the burst, in rounds.
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::StartCannonBurst( int iBurstSize )
|
|
{
|
|
m_iBurstSize = iBurstSize;
|
|
m_iBurstHits = 0;
|
|
|
|
m_flTimeNextAttack = gpGlobals->curtime;
|
|
|
|
// Start up the cannon sound.
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
|
controller.SoundChangeVolume( m_pCannonSound, 1.0, 0 );
|
|
|
|
m_bIsFiring = true;
|
|
|
|
// Setup the initial position of the burst
|
|
if ( GetEnemy() )
|
|
{
|
|
// Follow mode
|
|
Vector enemyPos;
|
|
UTIL_PredictedPosition( GetEnemy(), 2.0f, &enemyPos );
|
|
|
|
QAngle offsetAngles;
|
|
Vector offsetDir = ( WorldSpaceCenter() - enemyPos );
|
|
VectorNormalize( offsetDir );
|
|
VectorAngles( offsetDir, offsetAngles );
|
|
|
|
int angleOffset = random->RandomInt( 15, 30 );
|
|
if ( random->RandomInt( 0, 1 ) )
|
|
{
|
|
angleOffset *= -1;
|
|
}
|
|
offsetAngles[YAW] += angleOffset;
|
|
offsetAngles[PITCH] = 0;
|
|
offsetAngles[ROLL] = 0;
|
|
|
|
AngleVectors( offsetAngles, &offsetDir );
|
|
|
|
float stitchOffset;
|
|
float enemyDist = GroundDistToPosition( GetEnemy()->GetAbsOrigin() );
|
|
if ( enemyDist < ( sk_gunship_burst_dist.GetFloat() + GUNSHIP_STITCH_MIN ) )
|
|
{
|
|
stitchOffset = GUNSHIP_STITCH_MIN;
|
|
}
|
|
else
|
|
{
|
|
stitchOffset = sk_gunship_burst_dist.GetFloat();
|
|
}
|
|
|
|
// Move out to the start of our stitch run
|
|
m_vecAttackPosition = enemyPos + ( offsetDir * stitchOffset );
|
|
m_vecAttackPosition.z = enemyPos.z;
|
|
|
|
// Point at our target
|
|
m_vecAttackVelocity = -offsetDir * BASE_STITCH_VELOCITY;
|
|
|
|
CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_REACT_TO_SOURCE, enemyPos, 512, 0.2f, this );
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : The proper way to cease the gunship cannon firing.
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::StopCannonBurst( void )
|
|
{
|
|
m_iBurstHits = 0;
|
|
m_bIsFiring = false;
|
|
m_bPreFire = false;
|
|
|
|
// Reduce the burst time when we get lower in health
|
|
float flPerc = (float)GetHealth() / (float)GetMaxHealth();
|
|
float flDelay = clamp( flPerc * m_flBurstDelay, 0.5, m_flBurstDelay );
|
|
|
|
// If we didn't finish the burst, don't wait so long
|
|
flPerc = 1.0 - (m_iBurstSize / sk_gunship_burst_size.GetFloat());
|
|
flDelay *= flPerc;
|
|
|
|
m_flTimeNextAttack = gpGlobals->curtime + flDelay;
|
|
m_iBurstSize = 0;
|
|
|
|
// Stop the cannon sound.
|
|
if ( m_pCannonSound != NULL )
|
|
{
|
|
CSoundEnvelopeController::GetController().SoundChangeVolume( m_pCannonSound, 0.0, 0.05 );
|
|
}
|
|
|
|
EmitSound( "NPC_CombineGunship.CannonStopSound" );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::StopLoopingSounds( void )
|
|
{
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
|
|
|
if ( m_pCannonSound )
|
|
{
|
|
controller.SoundDestroy( m_pCannonSound );
|
|
m_pCannonSound = NULL;
|
|
}
|
|
|
|
if ( m_pRotorSound )
|
|
{
|
|
controller.SoundDestroy( m_pRotorSound );
|
|
m_pRotorSound = NULL;
|
|
}
|
|
|
|
if ( m_pAirExhaustSound )
|
|
{
|
|
controller.SoundDestroy( m_pAirExhaustSound );
|
|
m_pAirExhaustSound = NULL;
|
|
}
|
|
|
|
if ( m_pAirBlastSound )
|
|
{
|
|
controller.SoundDestroy( m_pAirBlastSound );
|
|
m_pAirBlastSound = NULL;
|
|
}
|
|
|
|
BaseClass::StopLoopingSounds();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEnemy -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CombineGunship::IsValidEnemy( CBaseEntity *pEnemy )
|
|
{
|
|
// Always track missiles
|
|
if ( pEnemy->IsAlive() && !pEnemy->MyNPCPointer() && FClassnameIs( pEnemy, "rpg_missile" ) )
|
|
return true;
|
|
|
|
// If we're shooting off a burst, don't pick up a new enemy
|
|
if ( ( m_bIsFiring ) && ( ( GetEnemy() == NULL ) || ( GetEnemy() != pEnemy ) ) )
|
|
return false;
|
|
|
|
return BaseClass::IsValidEnemy( pEnemy );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::GatherEnemyConditions( CBaseEntity *pEnemy )
|
|
{
|
|
BaseClass::GatherEnemyConditions(pEnemy);
|
|
|
|
// If we can't see the enemy for a few seconds, consider him unreachable
|
|
if ( !HasCondition(COND_SEE_ENEMY) )
|
|
{
|
|
if ( gpGlobals->curtime - GetEnemyLastTimeSeen() >= 3.0f )
|
|
{
|
|
MarkEnemyAsEluded();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Tells us whether or not we're targetting an incoming missile
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CombineGunship::IsTargettingMissile( void )
|
|
{
|
|
if ( GetEnemy() == NULL )
|
|
return false;
|
|
|
|
if ( FClassnameIs( GetEnemy(), "rpg_missile" ) == false )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputBecomeInvulnerable( inputdata_t &input )
|
|
{
|
|
m_bInvulnerable = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CombineGunship::InputBecomeVulnerable( inputdata_t &input )
|
|
{
|
|
m_bInvulnerable = false;
|
|
}
|
|
|
|
AI_BEGIN_CUSTOM_NPC( npc_combinegunship, CNPC_CombineGunship )
|
|
|
|
// DECLARE_TASK( )
|
|
|
|
DECLARE_ACTIVITY( ACT_GUNSHIP_PATROL );
|
|
DECLARE_ACTIVITY( ACT_GUNSHIP_HOVER );
|
|
DECLARE_ACTIVITY( ACT_GUNSHIP_CRASH );
|
|
|
|
//DECLARE_CONDITION( COND_ )
|
|
|
|
//=========================================================
|
|
// DEFINE_SCHEDULE
|
|
// (
|
|
// SCHED_DUMMY,
|
|
//
|
|
// " Tasks"
|
|
// " TASK_FACE_ENEMY 0"
|
|
// " "
|
|
// " Interrupts"
|
|
// )
|
|
|
|
|
|
AI_END_CUSTOM_NPC()
|
|
|