aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_combinedropship.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mp/src/game/server/hl2/npc_combinedropship.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_combinedropship.cpp6008
1 files changed, 3004 insertions, 3004 deletions
diff --git a/mp/src/game/server/hl2/npc_combinedropship.cpp b/mp/src/game/server/hl2/npc_combinedropship.cpp
index b36296bb..ffe1befd 100644
--- a/mp/src/game/server/hl2/npc_combinedropship.cpp
+++ b/mp/src/game/server/hl2/npc_combinedropship.cpp
@@ -1,3004 +1,3004 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Large vehicle what delivers combine troops.
-//
-// $NoKeywords: $
-//=============================================================================//
-
-#include "cbase.h"
-#include "ai_default.h"
-#include "ai_basenpc.h"
-#include "soundenvelope.h"
-#include "cbasehelicopter.h"
-#include "ai_schedule.h"
-#include "engine/IEngineSound.h"
-#include "smoke_trail.h"
-#include "IEffects.h"
-#include "props.h"
-#include "TemplateEntities.h"
-#include "baseanimating.h"
-#include "ai_senses.h"
-#include "entitylist.h"
-#include "ammodef.h"
-#include "ndebugoverlay.h"
-#include "npc_combines.h"
-#include "soundent.h"
-#include "mapentities.h"
-#include "npc_rollermine.h"
-#include "scripted.h"
-#include "explode.h"
-#include "gib.h"
-#include "EntityFlame.h"
-#include "entityblocker.h"
-#include "eventqueue.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-// Spawnflags
-#define SF_DROPSHIP_WAIT_FOR_DROPOFF_INPUT ( 1 << 15 )
-
-#define DROPSHIP_ACCEL_RATE 300
-
-// Timers
-#define DROPSHIP_LANDING_HOVER_TIME 5 // Time to spend on the ground if we have no troops to drop
-#define DROPSHIP_TIME_BETWEEN_MINES 0.5f
-
-// Special actions
-#define DROPSHIP_DEFAULT_SOLDIERS 4
-#define DROPSHIP_MAX_SOLDIERS 6
-
-// Movement
-#define DROPSHIP_BUFF_TIME 0.3f
-#define DROPSHIP_MAX_LAND_TILT 2.5f
-#define DROPSHIP_CONTAINER_HEIGHT 130.0f
-#define DROPSHIP_MAX_SPEED (60 * 17.6) // 120 miles per hour.
-
-// Pathing data
-#define DROPSHIP_LEAD_DISTANCE 800.0f
-#define DROPSHIP_MIN_CHASE_DIST_DIFF 128.0f // Distance threshold used to determine when a target has moved enough to update our navigation to it
-#define DROPSHIP_AVOID_DIST 256.0f
-#define DROPSHIP_ARRIVE_DIST 128.0f
-
-#define CRATE_BBOX_MIN (Vector( -100, -80, -60 ))
-#define CRATE_BBOX_MAX (Vector( 100, 80, 80 ))
-
-// Size
-// With crate
-#define DROPSHIP_BBOX_CRATE_MIN (-Vector(40,40,60))
-#define DROPSHIP_BBOX_CRATE_MAX (Vector(40,40,40))
-// Without crate
-#define DROPSHIP_BBOX_MIN (-Vector(40,40,0))
-#define DROPSHIP_BBOX_MAX (Vector(40,40,40))
-
-// Container gun
-#define DROPSHIP_GUN_SPEED 10 // Rotation speed
-
-#define DROPSHIP_CRATE_ROCKET_HITS 4
-
-enum DROP_STATES
-{
- DROP_IDLE = 0,
- DROP_NEXT,
-};
-
-enum CRATE_TYPES
-{
- CRATE_JEEP = -3,
- CRATE_APC = -2,
- CRATE_STRIDER = -1,
- CRATE_ROLLER_HOPPER,
- CRATE_SOLDIER,
- CRATE_NONE,
-};
-
-ConVar g_debug_dropship( "g_debug_dropship", "0" );
-ConVar sk_dropship_container_health( "sk_dropship_container_health", "750" );
-ConVar sk_npc_dmg_dropship( "sk_npc_dmg_dropship","5", FCVAR_NONE, "Dropship container cannon damage." );
-
-//=====================================
-// Animation Events
-//=====================================
-#define AE_DROPSHIP_RAMP_OPEN 1 // the tailgate is open.
-
-//=====================================
-// Custom activities
-//=====================================
-// Without Cargo
-Activity ACT_DROPSHIP_FLY_IDLE; // Flying. Vertical aspect
-Activity ACT_DROPSHIP_FLY_IDLE_EXAGG; // Exaggerated version of the flying idle
-// With Cargo
-Activity ACT_DROPSHIP_FLY_IDLE_CARGO; // Flying. Vertical aspect
-Activity ACT_DROPSHIP_DESCEND_IDLE; // waiting to touchdown
-Activity ACT_DROPSHIP_DEPLOY_IDLE; // idle on the ground with door open. Troops are leaving.
-Activity ACT_DROPSHIP_LIFTOFF; // transition back to FLY IDLE
-
-enum LandingState_t
-{
- LANDING_NO = 0,
-
- // Dropoff
- LANDING_LEVEL_OUT, // Heading to a point above the dropoff point
- LANDING_DESCEND, // Descending from to the dropoff point
- LANDING_TOUCHDOWN,
- LANDING_UNLOADING,
- LANDING_UNLOADED,
- LANDING_LIFTOFF,
-
- // Pickup
- LANDING_SWOOPING, // Swooping down to the target
-
- // Hovering, which we're saying is a type of landing since there's so much landing code to leverage
- LANDING_START_HOVER,
- LANDING_HOVER_LEVEL_OUT,
- LANDING_HOVER_DESCEND,
- LANDING_HOVER_TOUCHDOWN,
- LANDING_END_HOVER,
-};
-
-
-#define DROPSHIP_NEAR_SOUND_MIN_DISTANCE 1000
-#define DROPSHIP_NEAR_SOUND_MAX_DISTANCE 2500
-#define DROPSHIP_GROUND_WASH_MIN_ALTITUDE 100.0f
-#define DROPSHIP_GROUND_WASH_MAX_ALTITUDE 750.0f
-
-
-
-//=============================================================================
-// The combine dropship container
-//=============================================================================
-#define DROPSHIP_CONTAINER_MODEL "models/combine_dropship_container.mdl"
-
-#define DROPSHIP_CONTAINER_MAX_CHUNKS 3
-static const char *s_pChunkModelName[DROPSHIP_CONTAINER_MAX_CHUNKS] =
-{
- "models/gibs/helicopter_brokenpiece_01.mdl",
- "models/gibs/helicopter_brokenpiece_02.mdl",
- "models/gibs/helicopter_brokenpiece_03.mdl",
-};
-
-#define DROPSHIP_CONTAINER_MAX_GIBS 1
-static const char *s_pGibModelName[DROPSHIP_CONTAINER_MAX_GIBS] =
-{
- "models/combine_dropship_container.mdl",
-};
-
-class CCombineDropshipContainer : public CPhysicsProp
-{
- DECLARE_CLASS( CCombineDropshipContainer, CPhysicsProp );
- DECLARE_DATADESC();
-
-public:
- void Precache();
- virtual void Spawn();
- virtual bool OverridePropdata( void );
- virtual int OnTakeDamage( const CTakeDamageInfo &info );
- virtual void Event_Killed( const CTakeDamageInfo &info );
-
-private:
- enum
- {
- MAX_SMOKE_TRAILS = 4,
- MAX_EXPLOSIONS = 4,
- };
-
- // Should we trigger a damage effect?
- bool ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const;
-
- // Add a smoke trail since we've taken more damage
- void AddSmokeTrail( const Vector &vecPos );
-
- // Pow!
- void ThrowFlamingGib();
-
- // Create a corpse
- void CreateCorpse();
-
-private:
- int m_nSmokeTrailCount;
- EHANDLE m_hLastInflictor;
- float m_flLastHitTime;
-};
-
-//=============================================================================
-// The combine dropship
-//=============================================================================
-class CNPC_CombineDropship : public CBaseHelicopter
-{
- DECLARE_CLASS( CNPC_CombineDropship, CBaseHelicopter );
-
-public:
- ~CNPC_CombineDropship();
-
- // Setup
- void Spawn( void );
- void Precache( void );
-
- void Activate( void );
-
- // Thinking/init
- void InitializeRotorSound( void );
- void StopLoopingSounds();
- void PrescheduleThink( void );
-
- // Flight/sound
- void Hunt( void );
- void Flight( void );
- float GetAltitude( void );
- void DoRotorWash( void );
- void UpdateRotorSoundPitch( int iPitch );
- void UpdatePickupNavigation( void );
- void UpdateLandTargetNavigation( void );
- void CalculateSoldierCount( int iSoldiers );
-
- // Updates the facing direction
- virtual void UpdateFacingDirection();
-
- // Combat
- void GatherEnemyConditions( CBaseEntity *pEnemy );
- void DoCombatStuff( void );
- void SpawnTroop( void );
- void DropMine( void );
- void UpdateContainerGunFacing( Vector &vecMuzzle, Vector &vecToTarget, Vector &vecAimDir, float *flTargetRange );
- bool FireCannonRound( void );
- void DoImpactEffect( trace_t &tr, int nDamageType );
- void StartCannon( void );
- void StopCannon( void );
- void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
- int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
-
- // Input handlers.
- void InputLandLeave( inputdata_t &inputdata );
- void InputLandTake( inputdata_t &inputdata );
- void InputSetLandTarget( inputdata_t &inputdata );
- void InputDropMines( inputdata_t &inputdata );
- void InputDropStrider( inputdata_t &inputdata );
- void InputDropAPC( inputdata_t &inputdata );
-
- void InputPickup( inputdata_t &inputdata );
- void InputSetGunRange( inputdata_t &inputdata );
- void InputNPCFinishDustoff( inputdata_t &inputdata );
- void InputStopWaitingForDropoff( inputdata_t &inputdata );
-
- void InputHover( inputdata_t &inputdata );
-
- // From AI_TrackPather
- virtual void InputFlyToPathTrack( inputdata_t &inputdata );
-
- Vector GetDropoffFinishPosition( Vector vecOrigin, CAI_BaseNPC *pNPC, Vector vecMins, Vector vecMaxs );
- void LandCommon( bool bHover = false );
-
- Class_T Classify( void ) { return CLASS_COMBINE_GUNSHIP; }
-
- // Drop the soldier container
- void DropSoldierContainer( );
-
- // Sounds
- virtual void UpdateRotorWashVolume();
-
-private:
- void SetLandingState( LandingState_t landingState );
- LandingState_t GetLandingState() const { return (LandingState_t)m_iLandState; }
- bool IsHovering();
- void UpdateGroundRotorWashSound( float flAltitude );
- void UpdateRotorWashVolume( CSoundPatch *pRotorSound, float flVolume, float flDeltaTime );
-
-private:
- // Timers
- float m_flTimeTakeOff;
- float m_flNextTroopSpawnAttempt;
- float m_flDropDelay; // delta between each mine
- float m_flTimeNextAttack;
- float m_flLastTime;
-
- // States and counters
- int m_iMineCount; // index for current mine # being deployed
- int m_totalMinesToDrop; // total # of mines to drop as a group (based upon triggered input)
- int m_soldiersToDrop;
- int m_iDropState;
- int m_iLandState;
- float m_engineThrust; // for tracking sound volume/pitch
- float m_existPitch;
- float m_existRoll;
- bool m_bDropMines; // signal to drop mines
- bool m_bIsFiring;
- int m_iBurstRounds;
- bool m_leaveCrate;
- bool m_bHasDroppedOff;
- int m_iCrateType;
- float m_flLandingSpeed;
- float m_flGunRange;
- bool m_bInvulnerable;
-
- QAngle m_vecAngAcceleration;
-
- // Misc Vars
- CHandle<CBaseAnimating> m_hContainer;
- EHANDLE m_hPickupTarget;
- int m_iContainerMoveType;
- bool m_bWaitForDropoffInput;
-
- DECLARE_DATADESC();
- DEFINE_CUSTOM_AI;
-
- EHANDLE m_hLandTarget;
- string_t m_iszLandTarget;
-
- string_t m_iszAPCVehicleName;
-
- // Templates for soldier's dropped off
- string_t m_sNPCTemplate[ DROPSHIP_MAX_SOLDIERS ];
- string_t m_sNPCTemplateData[ DROPSHIP_MAX_SOLDIERS ];
- string_t m_sDustoffPoints[ DROPSHIP_MAX_SOLDIERS ];
- int m_iCurrentTroopExiting;
- EHANDLE m_hLastTroopToLeave;
-
- // Template for rollermines dropped by this dropship
- string_t m_sRollermineTemplate;
- string_t m_sRollermineTemplateData;
-
- // Cached attachment points
- int m_iMuzzleAttachment;
- int m_iMachineGunBaseAttachment;
- int m_iMachineGunRefAttachment;
- int m_iAttachmentTroopDeploy;
- int m_iAttachmentDeployStart;
-
- // Sounds
- CSoundPatch *m_pCannonSound;
- CSoundPatch *m_pRotorOnGroundSound;
- CSoundPatch *m_pDescendingWarningSound;
- CSoundPatch *m_pNearRotorSound;
-
- // Outputs
- COutputEvent m_OnFinishedDropoff;
- COutputEvent m_OnFinishedPickup;
-
- COutputFloat m_OnContainerShotDownBeforeDropoff;
- COutputEvent m_OnContainerShotDownAfterDropoff;
-
-protected:
- // Because the combine dropship is a leaf class, we can use
- // static variables to store this information, and save some memory.
- // Should the dropship end up having inheritors, their activate may
- // stomp these numbers, in which case you should make these ordinary members
- // again.
- static int m_poseBody_Accel, m_poseBody_Sway, m_poseCargo_Body_Accel, m_poseCargo_Body_Sway,
- m_poseWeapon_Pitch, m_poseWeapon_Yaw;
- static bool m_sbStaticPoseParamsLoaded;
- virtual void PopulatePoseParameters( void );
-};
-
-bool CNPC_CombineDropship::m_sbStaticPoseParamsLoaded = false;
-
-int CNPC_CombineDropship::m_poseBody_Accel = 0;
-int CNPC_CombineDropship::m_poseBody_Sway = 0;
-int CNPC_CombineDropship::m_poseCargo_Body_Accel = 0;
-int CNPC_CombineDropship::m_poseCargo_Body_Sway = 0;
-int CNPC_CombineDropship::m_poseWeapon_Pitch = 0;
-int CNPC_CombineDropship::m_poseWeapon_Yaw = 0;
-
-//-----------------------------------------------------------------------------
-// Purpose: Cache whatever pose parameters we intend to use
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::PopulatePoseParameters( void )
-{
- if (!m_sbStaticPoseParamsLoaded)
- {
- m_poseBody_Accel = LookupPoseParameter( "body_accel");
- m_poseBody_Sway = LookupPoseParameter( "body_sway" );
- m_poseCargo_Body_Accel = LookupPoseParameter( "cargo_body_accel" );
- m_poseCargo_Body_Sway = LookupPoseParameter( "cargo_body_sway" );
- m_poseWeapon_Pitch = LookupPoseParameter( "weapon_pitch" );
- m_poseWeapon_Yaw = LookupPoseParameter( "weapon_yaw" );
-
- m_sbStaticPoseParamsLoaded = true;
- }
-
- BaseClass::PopulatePoseParameters();
-}
-
-//------------------------------------------------------------------------------
-//
-// Combine Dropship Container implementation:
-//
-//------------------------------------------------------------------------------
-LINK_ENTITY_TO_CLASS( prop_dropship_container, CCombineDropshipContainer )
-
-BEGIN_DATADESC( CCombineDropshipContainer )
-
- DEFINE_FIELD( m_nSmokeTrailCount, FIELD_INTEGER ),
- DEFINE_FIELD( m_hLastInflictor, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flLastHitTime, FIELD_TIME ),
-
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-// Precache
-//-----------------------------------------------------------------------------
-void CCombineDropshipContainer::Precache()
-{
- PrecacheModel( DROPSHIP_CONTAINER_MODEL );
-
- // Set this here to quiet base prop warnings
- SetModel( DROPSHIP_CONTAINER_MODEL );
-
- BaseClass::Precache();
-
- int i;
- for ( i = 0; i < DROPSHIP_CONTAINER_MAX_CHUNKS; ++i )
- {
- PrecacheModel( s_pChunkModelName[i] );
- }
-
- for ( i = 0; i < DROPSHIP_CONTAINER_MAX_GIBS; ++i )
- {
- PrecacheModel( s_pGibModelName[i] );
- }
-
- PropBreakablePrecacheAll( GetModelName() );
-}
-
-
-//-----------------------------------------------------------------------------
-// Spawn
-//-----------------------------------------------------------------------------
-void CCombineDropshipContainer::Spawn()
-{
- // NOTE: Model must be set before spawn
- SetModel( DROPSHIP_CONTAINER_MODEL );
- SetSolid( SOLID_VPHYSICS );
-
- BaseClass::Spawn();
-
-#ifdef _XBOX
- AddEffects( EF_NOSHADOW );
-#endif //_XBOX
-
- m_iHealth = m_iMaxHealth = sk_dropship_container_health.GetFloat();
-}
-
-
-//-----------------------------------------------------------------------------
-// Allows us to use vphysics
-//-----------------------------------------------------------------------------
-bool CCombineDropshipContainer::OverridePropdata( void )
-{
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Should we trigger a damage effect?
-//-----------------------------------------------------------------------------
-inline bool CCombineDropshipContainer::ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const
-{
- int nPrevRange = (int)( ((float)nPrevHealth / (float)GetMaxHealth()) * nEffectCount );
- int nRange = (int)( ((float)GetHealth() / (float)GetMaxHealth()) * nEffectCount );
- return ( nRange != nPrevRange );
-}
-
-
-//-----------------------------------------------------------------------------
-// Character killed (only fired once)
-//-----------------------------------------------------------------------------
-void CCombineDropshipContainer::CreateCorpse()
-{
- m_lifeState = LIFE_DEAD;
-
- Vector vecNormalizedMins, vecNormalizedMaxs;
- Vector vecAbsMins, vecAbsMaxs;
- CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
- CollisionProp()->WorldToNormalizedSpace( vecAbsMins, &vecNormalizedMins );
- CollisionProp()->WorldToNormalizedSpace( vecAbsMaxs, &vecNormalizedMaxs );
-
- // Explode
- Vector vecAbsPoint;
- CPASFilter filter( GetAbsOrigin() );
- CollisionProp()->RandomPointInBounds( vecNormalizedMins, vecNormalizedMaxs, &vecAbsPoint);
- te->Explosion( filter, 0.0f, &vecAbsPoint, g_sModelIndexFireball,
- random->RandomInt( 4, 10 ), random->RandomInt( 8, 15 ), TE_EXPLFLAG_NOPARTICLES, 100, 0 );
-
- // Break into chunks
- Vector angVelocity;
- QAngleToAngularImpulse( GetLocalAngularVelocity(), angVelocity );
- PropBreakableCreateAll( GetModelIndex(), VPhysicsGetObject(), GetAbsOrigin(), GetAbsAngles(), GetAbsVelocity(), angVelocity, 1.0, 250, COLLISION_GROUP_NPC, this );
-
- // Create flaming gibs
- int iChunks = random->RandomInt( 4, 6 );
- for ( int i = 0; i < iChunks; i++ )
- {
- ThrowFlamingGib();
- }
-
- AddSolidFlags( FSOLID_NOT_SOLID );
- AddEffects( EF_NODRAW );
- UTIL_Remove( this );
-}
-
-
-//-----------------------------------------------------------------------------
-// Character killed (only fired once)
-//-----------------------------------------------------------------------------
-void CCombineDropshipContainer::ThrowFlamingGib( void )
-{
- Vector vecAbsMins, vecAbsMaxs;
- CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
-
- Vector vecNormalizedMins, vecNormalizedMaxs;
- CollisionProp()->WorldToNormalizedSpace( vecAbsMins, &vecNormalizedMins );
- CollisionProp()->WorldToNormalizedSpace( vecAbsMaxs, &vecNormalizedMaxs );
-
- Vector vecAbsPoint;
- CPASFilter filter( GetAbsOrigin() );
- CollisionProp()->RandomPointInBounds( vecNormalizedMins, vecNormalizedMaxs, &vecAbsPoint);
-
- // Throw a flaming, smoking chunk.
- CGib *pChunk = CREATE_ENTITY( CGib, "gib" );
- pChunk->Spawn( "models/gibs/hgibs.mdl" );
- pChunk->SetBloodColor( DONT_BLEED );
-
- QAngle vecSpawnAngles;
- vecSpawnAngles.Random( -90, 90 );
- pChunk->SetAbsOrigin( vecAbsPoint );
- pChunk->SetAbsAngles( vecSpawnAngles );
-
- int nGib = random->RandomInt( 0, DROPSHIP_CONTAINER_MAX_CHUNKS - 1 );
- pChunk->Spawn( s_pChunkModelName[nGib] );
- pChunk->SetOwnerEntity( this );
- pChunk->m_lifeTime = random->RandomFloat( 6.0f, 8.0f );
- pChunk->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
- IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
-
- // Set the velocity
- if ( pPhysicsObject )
- {
- pPhysicsObject->EnableMotion( true );
- Vector vecVelocity;
-
- QAngle angles;
- angles.x = random->RandomFloat( -20, 20 );
- angles.y = random->RandomFloat( 0, 360 );
- angles.z = 0.0f;
- AngleVectors( angles, &vecVelocity );
-
- vecVelocity *= random->RandomFloat( 300, 900 );
- vecVelocity += GetAbsVelocity();
-
- AngularImpulse angImpulse;
- angImpulse = RandomAngularImpulse( -180, 180 );
-
- pChunk->SetAbsVelocity( vecVelocity );
- pPhysicsObject->SetVelocity(&vecVelocity, &angImpulse );
- }
-
- CEntityFlame *pFlame = CEntityFlame::Create( pChunk, false );
- if ( pFlame != NULL )
- {
- pFlame->SetLifetime( pChunk->m_lifeTime );
- }
-
- SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
- if( pSmokeTrail )
- {
- pSmokeTrail->m_SpawnRate = 80;
- pSmokeTrail->m_ParticleLifetime = 0.8f;
- pSmokeTrail->m_StartColor.Init(0.3, 0.3, 0.3);
- pSmokeTrail->m_EndColor.Init(0.5, 0.5, 0.5);
- pSmokeTrail->m_StartSize = 10;
- pSmokeTrail->m_EndSize = 40;
- pSmokeTrail->m_SpawnRadius = 5;
- pSmokeTrail->m_Opacity = 0.4;
- pSmokeTrail->m_MinSpeed = 15;
- pSmokeTrail->m_MaxSpeed = 25;
- pSmokeTrail->SetLifetime( pChunk->m_lifeTime );
- pSmokeTrail->SetParent( pChunk, 0 );
- pSmokeTrail->SetLocalOrigin( vec3_origin );
- pSmokeTrail->SetMoveType( MOVETYPE_NONE );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Character killed (only fired once)
-//-----------------------------------------------------------------------------
-void CCombineDropshipContainer::Event_Killed( const CTakeDamageInfo &info )
-{
- if ( GetOwnerEntity() )
- {
- CNPC_CombineDropship *pDropship = assert_cast<CNPC_CombineDropship *>(GetOwnerEntity() );
- pDropship->DropSoldierContainer();
- }
-
- CreateCorpse();
-}
-
-
-//-----------------------------------------------------------------------------
-// Damage effects
-//-----------------------------------------------------------------------------
-int CCombineDropshipContainer::OnTakeDamage( const CTakeDamageInfo &info )
-{
- if ( m_iHealth == 0 )
- return 0;
-
- // Airboat guns + explosive damage is all that can hurt it
- if (( info.GetDamageType() & (DMG_BLAST | DMG_AIRBOAT) ) == 0 )
- return 0;
-
- CTakeDamageInfo dmgInfo = info;
-
- int nPrevHealth = GetHealth();
-
- if ( info.GetDamageType() & DMG_BLAST )
- {
- // This check is necessary to prevent double-counting of rocket damage
- // from the blast hitting both the dropship + the container
- if ( (info.GetInflictor() != m_hLastInflictor) || (gpGlobals->curtime != m_flLastHitTime) )
- {
- m_iHealth -= (m_iMaxHealth / DROPSHIP_CRATE_ROCKET_HITS) + 1;
- m_hLastInflictor = info.GetInflictor();
- m_flLastHitTime = gpGlobals->curtime;
- }
- }
- else
- {
- m_iHealth -= dmgInfo.GetDamage();
- }
-
- if ( m_iHealth <= 0 )
- {
- m_iHealth = 0;
- Event_Killed( dmgInfo );
- return 0;
- }
-
- // Spawn damage effects
- if ( nPrevHealth != GetHealth() )
- {
- if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_SMOKE_TRAILS ) )
- {
- AddSmokeTrail( dmgInfo.GetDamagePosition() );
- }
-
- if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) )
- {
- ExplosionCreate( dmgInfo.GetDamagePosition(), vec3_angle, this, 1000, 500.0f,
- SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0 );
- UTIL_ScreenShake( dmgInfo.GetDamagePosition(), 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
-
- ThrowFlamingGib();
- }
- }
-
- return 1;
-}
-
-
-//-----------------------------------------------------------------------------
-// Add a smoke trail since we've taken more damage
-//-----------------------------------------------------------------------------
-void CCombineDropshipContainer::AddSmokeTrail( const Vector &vecPos )
-{
- // Start this trail out with a bang!
- ExplosionCreate( vecPos, vec3_angle, this, 1000, 500.0f, SF_ENVEXPLOSION_NODAMAGE |
- SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0 );
- UTIL_ScreenShake( vecPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
-
- if ( m_nSmokeTrailCount == MAX_SMOKE_TRAILS )
- return;
-
- SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
- if( !pSmokeTrail )
- return;
-
- // See if there's an attachment for this smoke trail
- char buf[32];
- Q_snprintf( buf, 32, "damage%d", m_nSmokeTrailCount );
- int nAttachment = LookupAttachment( buf );
-
- ++m_nSmokeTrailCount;
-
- pSmokeTrail->m_SpawnRate = 20;
- pSmokeTrail->m_ParticleLifetime = 4.0f;
- pSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f );
- pSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 );
- pSmokeTrail->m_StartSize = 15;
- pSmokeTrail->m_EndSize = 50;
- pSmokeTrail->m_SpawnRadius = 15;
- pSmokeTrail->m_Opacity = 0.75f;
- pSmokeTrail->m_MinSpeed = 10;
- pSmokeTrail->m_MaxSpeed = 20;
- pSmokeTrail->m_MinDirectedSpeed = 100.0f;
- pSmokeTrail->m_MaxDirectedSpeed = 120.0f;
- pSmokeTrail->SetLifetime( 5 );
- pSmokeTrail->SetParent( this, nAttachment );
- if ( nAttachment == 0 )
- {
- pSmokeTrail->SetAbsOrigin( vecPos );
- }
- else
- {
- pSmokeTrail->SetLocalOrigin( vec3_origin );
- }
-
- Vector vecForward( -1, 0, 0 );
- QAngle angles;
- VectorAngles( vecForward, angles );
- pSmokeTrail->SetAbsAngles( angles );
- pSmokeTrail->SetMoveType( MOVETYPE_NONE );
-}
-
-
-//------------------------------------------------------------------------------
-//
-// Combine Dropship implementation:
-//
-//------------------------------------------------------------------------------
-LINK_ENTITY_TO_CLASS( npc_combinedropship, CNPC_CombineDropship );
-
-BEGIN_DATADESC( CNPC_CombineDropship )
-
- DEFINE_FIELD( m_flTimeTakeOff, FIELD_TIME ),
- DEFINE_FIELD( m_flNextTroopSpawnAttempt, FIELD_TIME ),
- DEFINE_FIELD( m_flDropDelay, FIELD_TIME ),
- DEFINE_FIELD( m_flTimeNextAttack, FIELD_TIME ),
- DEFINE_FIELD( m_flLastTime, FIELD_TIME ),
- DEFINE_FIELD( m_iMineCount, FIELD_INTEGER ),
- DEFINE_FIELD( m_totalMinesToDrop, FIELD_INTEGER ),
- DEFINE_FIELD( m_soldiersToDrop, FIELD_INTEGER ),
- DEFINE_FIELD( m_iDropState, FIELD_INTEGER ),
- DEFINE_FIELD( m_bDropMines, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_iLandState, FIELD_INTEGER ),
- DEFINE_FIELD( m_engineThrust, FIELD_FLOAT ),
- DEFINE_FIELD( m_bIsFiring, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_iBurstRounds, FIELD_INTEGER ),
- DEFINE_FIELD( m_existPitch, FIELD_FLOAT ),
- DEFINE_FIELD( m_existRoll, FIELD_FLOAT ),
- DEFINE_FIELD( m_leaveCrate, FIELD_BOOLEAN ),
- DEFINE_KEYFIELD( m_iCrateType, FIELD_INTEGER, "CrateType" ),
- DEFINE_FIELD( m_flLandingSpeed, FIELD_FLOAT ),
- DEFINE_KEYFIELD( m_flGunRange, FIELD_FLOAT, "GunRange" ),
- DEFINE_FIELD( m_vecAngAcceleration,FIELD_VECTOR ),
- DEFINE_FIELD( m_hContainer, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hPickupTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_iContainerMoveType, FIELD_INTEGER ),
- DEFINE_FIELD( m_bWaitForDropoffInput, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_hLandTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_bHasDroppedOff, FIELD_BOOLEAN ),
- DEFINE_KEYFIELD( m_bInvulnerable, FIELD_BOOLEAN, "Invulnerable" ),
- DEFINE_KEYFIELD( m_iszLandTarget, FIELD_STRING, "LandTarget" ),
- DEFINE_SOUNDPATCH( m_pRotorOnGroundSound ),
- DEFINE_SOUNDPATCH( m_pDescendingWarningSound ),
- DEFINE_SOUNDPATCH( m_pNearRotorSound ),
-
- DEFINE_KEYFIELD( m_iszAPCVehicleName, FIELD_STRING, "APCVehicleName" ),
-
- DEFINE_KEYFIELD( m_sRollermineTemplate, FIELD_STRING, "RollermineTemplate" ),
- DEFINE_FIELD( m_sRollermineTemplateData, FIELD_STRING ),
-
- DEFINE_ARRAY( m_sNPCTemplateData, FIELD_STRING, DROPSHIP_MAX_SOLDIERS ),
- DEFINE_KEYFIELD( m_sNPCTemplate[0], FIELD_STRING, "NPCTemplate" ),
- DEFINE_KEYFIELD( m_sNPCTemplate[1], FIELD_STRING, "NPCTemplate2" ),
- DEFINE_KEYFIELD( m_sNPCTemplate[2], FIELD_STRING, "NPCTemplate3" ),
- DEFINE_KEYFIELD( m_sNPCTemplate[3], FIELD_STRING, "NPCTemplate4" ),
- DEFINE_KEYFIELD( m_sNPCTemplate[4], FIELD_STRING, "NPCTemplate5" ),
- DEFINE_KEYFIELD( m_sNPCTemplate[5], FIELD_STRING, "NPCTemplate6" ),
- // Here to shut classcheck up
- //DEFINE_ARRAY( m_sNPCTemplate, FIELD_STRING, DROPSHIP_MAX_SOLDIERS ),
- //DEFINE_ARRAY( m_sDustoffPoints, FIELD_STRING, DROPSHIP_MAX_SOLDIERS ),
- DEFINE_KEYFIELD( m_sDustoffPoints[0], FIELD_STRING, "Dustoff1" ),
- DEFINE_KEYFIELD( m_sDustoffPoints[1], FIELD_STRING, "Dustoff2" ),
- DEFINE_KEYFIELD( m_sDustoffPoints[2], FIELD_STRING, "Dustoff3" ),
- DEFINE_KEYFIELD( m_sDustoffPoints[3], FIELD_STRING, "Dustoff4" ),
- DEFINE_KEYFIELD( m_sDustoffPoints[4], FIELD_STRING, "Dustoff5" ),
- DEFINE_KEYFIELD( m_sDustoffPoints[5], FIELD_STRING, "Dustoff6" ),
- DEFINE_FIELD( m_iCurrentTroopExiting, FIELD_INTEGER ),
- DEFINE_FIELD( m_hLastTroopToLeave, FIELD_EHANDLE ),
-
- DEFINE_FIELD( m_iMuzzleAttachment, FIELD_INTEGER ),
- DEFINE_FIELD( m_iMachineGunBaseAttachment, FIELD_INTEGER ),
- DEFINE_FIELD( m_iMachineGunRefAttachment, FIELD_INTEGER ),
- DEFINE_FIELD( m_iAttachmentTroopDeploy, FIELD_INTEGER ),
- DEFINE_FIELD( m_iAttachmentDeployStart , FIELD_INTEGER ),
-
- DEFINE_SOUNDPATCH( m_pCannonSound ),
-
- DEFINE_INPUTFUNC( FIELD_INTEGER, "LandLeaveCrate", InputLandLeave ),
- DEFINE_INPUTFUNC( FIELD_INTEGER, "LandTakeCrate", InputLandTake ),
- DEFINE_INPUTFUNC( FIELD_STRING, "SetLandTarget", InputSetLandTarget ),
- DEFINE_INPUTFUNC( FIELD_INTEGER, "DropMines", InputDropMines ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DropStrider", InputDropStrider ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DropAPC", InputDropAPC ),
- DEFINE_INPUTFUNC( FIELD_STRING, "Pickup", InputPickup ),
- DEFINE_INPUTFUNC( FIELD_FLOAT, "SetGunRange", InputSetGunRange ),
- DEFINE_INPUTFUNC( FIELD_STRING, "NPCFinishDustoff", InputNPCFinishDustoff ),
- DEFINE_INPUTFUNC( FIELD_VOID, "StopWaitingForDropoff", InputStopWaitingForDropoff ),
- DEFINE_INPUTFUNC( FIELD_STRING, "Hover", InputHover ),
- DEFINE_INPUTFUNC( FIELD_STRING, "FlyToPathTrack", InputFlyToPathTrack ),
-
- DEFINE_OUTPUT( m_OnFinishedDropoff, "OnFinishedDropoff" ),
- DEFINE_OUTPUT( m_OnFinishedPickup, "OnFinishedPickup" ),
-
- DEFINE_OUTPUT( m_OnContainerShotDownBeforeDropoff, "OnCrateShotDownBeforeDropoff" ),
- DEFINE_OUTPUT( m_OnContainerShotDownAfterDropoff, "OnCrateShotDownAfterDropoff" ),
-
-END_DATADESC()
-
-
-//------------------------------------------------------------------------------
-// Purpose : Destructor
-//------------------------------------------------------------------------------
-CNPC_CombineDropship::~CNPC_CombineDropship(void)
-{
- if ( m_hContainer )
- {
- UTIL_Remove( m_hContainer ); // get rid of container
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::Spawn( void )
-{
- Precache( );
- SetModel( "models/combine_dropship.mdl" );
-
-#ifdef _XBOX
- AddEffects( EF_NOSHADOW );
-#endif //_XBOX
-
- InitPathingData( DROPSHIP_ARRIVE_DIST, DROPSHIP_MIN_CHASE_DIST_DIFF, DROPSHIP_AVOID_DIST );
-
- m_iContainerMoveType = MOVETYPE_NONE;
- m_iCurrentTroopExiting = 0;
- m_bHasDroppedOff = false;
- m_iMuzzleAttachment = -1;
- m_iMachineGunBaseAttachment = -1;
- m_iMachineGunRefAttachment = -1;
- m_iAttachmentTroopDeploy = -1;
- m_iAttachmentDeployStart = -1;
-
- // create the correct bin for the ship to carry
- switch ( m_iCrateType )
- {
- case CRATE_ROLLER_HOPPER:
- break;
-
- case CRATE_SOLDIER:
- m_hContainer = (CBaseAnimating*)CreateEntityByName( "prop_dropship_container" );
- if ( m_hContainer )
- {
- m_hContainer->SetName( AllocPooledString("dropship_container") );
- m_hContainer->SetAbsOrigin( GetAbsOrigin() );
- m_hContainer->SetAbsAngles( GetAbsAngles() );
- m_hContainer->SetParent(this, 0);
- m_hContainer->SetOwnerEntity(this);
- m_hContainer->Spawn();
-
- IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
- if ( pPhysicsObject )
- {
- pPhysicsObject->SetShadow( 1e4, 1e4, false, false );
- pPhysicsObject->UpdateShadow( m_hContainer->GetAbsOrigin(), m_hContainer->GetAbsAngles(), false, 0 );
- }
-
- m_hContainer->SetMoveType( MOVETYPE_PUSH );
- m_hContainer->SetGroundEntity( NULL );
-
- // Cache off container's attachment points
- m_iAttachmentTroopDeploy = m_hContainer->LookupAttachment( "deploy_landpoint" );
- m_iAttachmentDeployStart = m_hContainer->LookupAttachment( "Deploy_Start" );
- m_iMuzzleAttachment = m_hContainer->LookupAttachment( "muzzle" );
- m_iMachineGunBaseAttachment = m_hContainer->LookupAttachment( "gun_base" );
- // NOTE: gun_ref must have the same position as gun_base, but rotates with the gun
- m_iMachineGunRefAttachment = m_hContainer->LookupAttachment( "gun_ref" );
- }
- break;
-
- case CRATE_STRIDER:
- m_hContainer = (CBaseAnimating*)CreateEntityByName( "npc_strider" );
- m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector( 0, 0 , 100 ) );
- m_hContainer->SetAbsAngles( GetAbsAngles() );
- m_hContainer->SetParent(this, 0);
- m_hContainer->SetOwnerEntity(this);
- m_hContainer->Spawn();
- m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector( 0, 0 , 100 ) );
- break;
-
- case CRATE_APC:
- {
- m_soldiersToDrop = 0;
- m_hContainer = (CBaseAnimating*)gEntList.FindEntityByName( NULL, m_iszAPCVehicleName );
- if ( !m_hContainer )
- {
- Warning("Unable to find APC %s\n", STRING( m_iszAPCVehicleName ) );
- break;
- }
-
- Vector apcPosition = GetAbsOrigin() - Vector( 0, 0 , 25 );
- QAngle apcAngles = GetAbsAngles();
- VMatrix mat, rot, result;
- MatrixFromAngles( apcAngles, mat );
- MatrixBuildRotateZ( rot, -90 );
- MatrixMultiply( mat, rot, result );
- MatrixToAngles( result, apcAngles );
-
- m_hContainer->Teleport( &apcPosition, &apcAngles, NULL );
-
- m_iContainerMoveType = m_hContainer->GetMoveType();
-
- IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
- if ( pPhysicsObject )
- {
- pPhysicsObject->SetShadow( 1e4, 1e4, false, false );
- }
-
- m_hContainer->SetParent(this, 0);
- m_hContainer->SetOwnerEntity(this);
- m_hContainer->SetMoveType( MOVETYPE_PUSH );
- m_hContainer->SetGroundEntity( NULL );
- m_hContainer->UpdatePhysicsShadowToCurrentPosition(0);
- }
- break;
-
- case CRATE_JEEP:
- m_hContainer = (CBaseAnimating*)CreateEntityByName( "prop_dynamic_override" );
- if ( m_hContainer )
- {
- m_hContainer->SetModel( "models/buggy.mdl" );
- m_hContainer->SetName( AllocPooledString("dropship_jeep") );
-
- m_hContainer->SetAbsOrigin( GetAbsOrigin() );//- Vector( 0, 0 , 25 ) );
- QAngle angles = GetAbsAngles();
- VMatrix mat, rot, result;
- MatrixFromAngles( angles, mat );
- MatrixBuildRotateZ( rot, -90 );
- MatrixMultiply( mat, rot, result );
- MatrixToAngles( result, angles );
- m_hContainer->SetAbsAngles( angles );
-
- m_hContainer->SetParent(this, 0);
- m_hContainer->SetOwnerEntity(this);
- m_hContainer->SetSolid( SOLID_VPHYSICS );
- m_hContainer->Spawn();
- }
- break;
-
- case CRATE_NONE:
- default:
- break;
- }
-
- // Setup our bbox
- if ( m_hContainer )
- {
- UTIL_SetSize( this, DROPSHIP_BBOX_CRATE_MIN, DROPSHIP_BBOX_CRATE_MAX );
- SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_CARGO );
- }
- else
- {
- UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );
- SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_EXAGG );
- }
-
- m_cullBoxMins = WorldAlignMins() - Vector(300,300,200);
- m_cullBoxMaxs = WorldAlignMaxs() + Vector(300,300,200);
- BaseClass::Spawn();
-
- // Dropship ignores all damage, but can deal it to its carried container
- m_takedamage = m_bInvulnerable ? DAMAGE_NO : DAMAGE_YES;
- if ( m_bInvulnerable && m_hContainer )
- {
- m_hContainer->m_takedamage = DAMAGE_NO;
- }
-
- m_iHealth = 100;
- m_flFieldOfView = 0.5; // 60 degrees
- m_iBurstRounds = 15;
-
- InitBoneControllers();
- InitCustomSchedules();
-
- m_flMaxSpeed = DROPSHIP_MAX_SPEED;
- m_flMaxSpeedFiring = BASECHOPPER_MAX_FIRING_SPEED;
- m_hPickupTarget = NULL;
- m_hLandTarget = NULL;
-
- //!!!HACKHACK
- // This tricks the AI code that constantly complains that the vehicle has no schedule.
- SetSchedule( SCHED_IDLE_STAND );
-
- SetLandingState( LANDING_NO );
-
- if ( HasSpawnFlags( SF_DROPSHIP_WAIT_FOR_DROPOFF_INPUT ) )
- {
- m_bWaitForDropoffInput = true;
- }
- else
- {
- m_bWaitForDropoffInput = false;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Called after spawning on map load or on a load from save game.
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::Activate( void )
-{
- BaseClass::Activate();
-
- if ( !m_sRollermineTemplateData )
- {
- m_sRollermineTemplateData = NULL_STRING;
- if ( m_sRollermineTemplate != NULL_STRING )
- {
- // This must be the first time we're activated, not a load from save game.
- // Look up the template in the template database.
- m_sRollermineTemplateData = Templates_FindByTargetName(STRING(m_sRollermineTemplate));
- if ( m_sRollermineTemplateData == NULL_STRING )
- {
- Warning( "npc_combinedropship %s: Rollermine Template %s not found!\n", STRING(GetEntityName()), STRING(m_sRollermineTemplate) );
- }
- }
- }
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::Precache( void )
-{
- // Models
- PrecacheModel("models/combine_dropship.mdl");
- switch ( m_iCrateType )
- {
- case CRATE_SOLDIER:
- UTIL_PrecacheOther( "prop_dropship_container" );
-
- //
- // Precache the all templates that we are configured to spawn
- //
- for ( int i = 0; i < DROPSHIP_MAX_SOLDIERS; i++ )
- {
- if ( m_sNPCTemplate[i] != NULL_STRING )
- {
- if ( m_sNPCTemplateData[i] == NULL_STRING )
- {
- m_sNPCTemplateData[i] = Templates_FindByTargetName(STRING(m_sNPCTemplate[i]));
- }
- if ( m_sNPCTemplateData[i] != NULL_STRING )
- {
- CBaseEntity *pEntity = NULL;
- MapEntity_ParseEntity( pEntity, STRING(m_sNPCTemplateData[i]), NULL );
- if ( pEntity != NULL )
- {
- pEntity->Precache();
- UTIL_RemoveImmediate( pEntity );
- }
- }
- else
- {
- Warning( "npc_combinedropship %s: Template NPC %s not found!\n", STRING(GetEntityName()), STRING(m_sNPCTemplate[i]) );
-
- // Use the first template we've got
- m_sNPCTemplateData[i] = m_sNPCTemplateData[0];
- }
-
- // Make sure we've got a dustoff point for it
- if ( m_sDustoffPoints[i] == NULL_STRING )
- {
- Warning( "npc_combinedropship %s: Has no dustoff point for NPC %d!\n", STRING(GetEntityName()), i );
- }
- }
- else
- {
- m_sNPCTemplateData[i] = NULL_STRING;
- }
- }
- break;
-
- case CRATE_JEEP:
- PrecacheModel("models/buggy.mdl");
- break;
-
- default:
- break;
- }
-
- PrecacheScriptSound( "NPC_CombineDropship.RotorLoop" );
- PrecacheScriptSound( "NPC_CombineDropship.FireLoop" );
- PrecacheScriptSound( "NPC_CombineDropship.NearRotorLoop" );
- PrecacheScriptSound( "NPC_CombineDropship.OnGroundRotorLoop" );
- PrecacheScriptSound( "NPC_CombineDropship.DescendingWarningLoop" );
- PrecacheScriptSound( "NPC_CombineDropship.NearRotorLoop" );
-
- if ( m_sRollermineTemplate != NULL_STRING )
- {
- UTIL_PrecacheOther( "npc_rollermine" );
- }
-
- BaseClass::Precache();
-
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::Flight( void )
-{
- // Only run the flight model in some flight states
- bool bRunFlight = ( GetLandingState() == LANDING_NO ||
- GetLandingState() == LANDING_LEVEL_OUT ||
- GetLandingState() == LANDING_LIFTOFF ||
- GetLandingState() == LANDING_SWOOPING ||
- GetLandingState() == LANDING_DESCEND ||
- GetLandingState() == LANDING_HOVER_LEVEL_OUT ||
- GetLandingState() == LANDING_HOVER_DESCEND );
-
- Vector forward, right, up;
- GetVectors( &forward, &right, &up );
-
- float finspeed = 0;
- float swayspeed = 0;
- Vector vecImpulse = vec3_origin;
-
- //Adrian: Slowly lerp the orientation and position of the cargo into place...
- //We assume CRATE_NONE means the dropship just picked up some random phys object.
- if ( m_hContainer != NULL && ( m_iCrateType == CRATE_SOLDIER || m_iCrateType == CRATE_NONE ) )
- {
- if ( m_hContainer->GetLocalOrigin() != vec3_origin )
- {
- Vector vCurrentLocalOrigin = m_hContainer->GetLocalOrigin();
- Vector vLocalOrigin;
-
- VectorLerp( vCurrentLocalOrigin, vec3_origin, 0.05f, vLocalOrigin );
-
- m_hContainer->SetLocalOrigin( vLocalOrigin );
- }
-
- if ( m_hContainer->GetLocalAngles() != vec3_angle )
- {
- QAngle vCurrentLocalAngles = m_hContainer->GetLocalAngles();
- QAngle vLocalAngles;
-
- vLocalAngles = Lerp( 0.05f, vCurrentLocalAngles, vec3_angle );
-
- m_hContainer->SetLocalAngles( vLocalAngles );
- }
- }
-
- if ( bRunFlight )
- {
- if( GetFlags() & FL_ONGROUND )
- {
- // This would be really bad.
- SetGroundEntity( NULL );
- }
-
- // calc desired acceleration
- float dt = 1.0f;
-
- Vector accel;
- float accelRate = DROPSHIP_ACCEL_RATE;
- float maxSpeed = GetMaxSpeed();
-
- if ( m_lifeState == LIFE_DYING )
- {
- accelRate *= 5.0;
- maxSpeed *= 5.0;
- }
-
- float flCurrentSpeed = GetAbsVelocity().Length();
- float flDist = MIN( flCurrentSpeed + accelRate, maxSpeed );
-
- Vector deltaPos;
- if ( GetLandingState() == LANDING_SWOOPING )
- {
- // Move directly to the target point
- deltaPos = GetDesiredPosition();
- }
- else
- {
- ComputeActualTargetPosition( flDist, dt, 0.0f, &deltaPos );
- }
- deltaPos -= GetAbsOrigin();
-
- //NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + deltaPos, 0, 255, 0, true, 0.1f );
-
- // 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 ( IsOnPathTrack() && GetLandingState() == LANDING_NO )
- {
- // 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 > 200 )
- {
- // 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 - 200) / 200.0f;
- flAmount = clamp( flAmount, 0, 1 );
- VectorMA( accel, flAmount * 200.0f, vecDelta, accel );
- }
- }
-
- // don't fall faster than 0.2G or climb faster than 2G
- accel.z = clamp( accel.z, 384 * 0.2, 384 * 2.0 );
-
- 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, -60, 60 );
- goalAngAccel.y = clamp( goalAngAccel.y, -120, 120 );
- goalAngAccel.z = clamp( goalAngAccel.z, -300, 300 );
-
- // limit angular accel changes to simulate 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, -1000, 1000 );
- angAccelAccel.y = clamp( angAccelAccel.y, -1000, 1000 );
- angAccelAccel.z = clamp( angAccelAccel.z, -1000, 1000 );
-
- 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.y = clamp( angVel.y, -60, 60 );
- //angVel.y = clamp( angVel.y, -120, 120 );
- angVel.y = clamp( angVel.y, -120, 120 );
-
- SetLocalAngularVelocity( angVel );
-
- m_flForce = m_flForce * 0.8 + (accel.z + fabs( accel.x ) * 0.1 + fabs( accel.y ) * 0.1) * 0.1 * 0.2;
-
- vecImpulse = m_flForce * up;
-
- if ( m_lifeState == LIFE_DYING )
- {
- vecImpulse.z = -38.4; // 64ft/sec
- }
- else
- {
- vecImpulse.z -= 38.4; // 32ft/sec
- }
-
- // Find our current velocity
- Vector vecVelDir = GetAbsVelocity();
-
- VectorNormalize( vecVelDir );
-
- if ( flDistFromPath > 100 )
- {
- // 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
- finspeed = m_flForce * DotProduct( vecVelDir, vecAccelDir );
- swayspeed = m_flForce * DotProduct( vecVelDir, right );
- }
-
- // Use the correct pose params for the state of our container
- int poseBodyAccel;
- int poseBodySway;
- if ( m_hContainer || GetLandingState() == LANDING_SWOOPING )
- {
- poseBodyAccel = m_poseCargo_Body_Accel;
- poseBodySway = m_poseCargo_Body_Sway;
- SetPoseParameter( m_poseBody_Accel, 0 );
- SetPoseParameter( m_poseBody_Sway, 0 );
- }
- else
- {
- poseBodyAccel = m_poseBody_Accel;
- poseBodySway = m_poseBody_Sway;
- SetPoseParameter( m_poseCargo_Body_Accel, 0 );
- SetPoseParameter( m_poseCargo_Body_Sway, 0 );
- }
-
- // If we're landing, deliberately tuck in the back end
- if ( GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_TOUCHDOWN ||
- GetLandingState() == LANDING_UNLOADING || GetLandingState() == LANDING_UNLOADED || IsHovering() )
- {
- finspeed = -60;
- }
-
- // Apply the acceleration blend to the fins
- float finAccelBlend = SimpleSplineRemapVal( finspeed, -60, 60, -1, 1 );
- float curFinAccel = GetPoseParameter( poseBodyAccel );
- curFinAccel = UTIL_Approach( finAccelBlend, curFinAccel, 0.1f );
- SetPoseParameter( poseBodyAccel, EdgeLimitPoseParameter( poseBodyAccel, curFinAccel ) );
-
- // Apply the spin sway to the fins
- float finSwayBlend = SimpleSplineRemapVal( swayspeed, -60, 60, -1, 1 );
- float curFinSway = GetPoseParameter( poseBodySway );
- curFinSway = UTIL_Approach( finSwayBlend, curFinSway, 0.1f );
- SetPoseParameter( poseBodySway, EdgeLimitPoseParameter( poseBodySway, curFinSway ) );
-
- if ( bRunFlight )
- {
- // Add in our velocity pulse for this frame
- ApplyAbsVelocityImpulse( vecImpulse );
- }
-
- //DevMsg("curFinAccel: %f, curFinSway: %f\n", curFinAccel, curFinSway );
-}
-
-
-//------------------------------------------------------------------------------
-// Deals damage to what's behing carried
-//------------------------------------------------------------------------------
-int CNPC_CombineDropship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
-{
- // FIXME: To make this work for CRATE_STRIDER or CRATE_APC, we need to
- // add code to the strider + apc to make them not take double-damage from rockets
- // (owing to the blast hitting the crate + the dropship). See the dropship container
- // code above to see how to do it.
- if ( m_hContainer && !m_bInvulnerable )
- {
- if ( (inputInfo.GetDamageType() & DMG_AIRBOAT) || (m_iCrateType == CRATE_SOLDIER) )
- {
- m_hContainer->TakeDamage( inputInfo );
- }
- }
-
- // don't die
- return 0;
-}
-
-//------------------------------------------------------------------------------
-// Updates the facing direction
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::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 :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::InitializeRotorSound( void )
-{
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
-
- CPASAttenuationFilter filter( this );
- m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.RotorLoop" );
- m_pNearRotorSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.NearRotorLoop" );
- m_pRotorOnGroundSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.OnGroundRotorLoop" );
- m_pDescendingWarningSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.DescendingWarningLoop" );
- m_pCannonSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.FireLoop" );
-
- // NOTE: m_pRotorSound is started up by the base class
- if ( m_pCannonSound )
- {
- controller.Play( m_pCannonSound, 0.0, 100 );
- }
-
- if ( m_pDescendingWarningSound )
- {
- controller.Play( m_pDescendingWarningSound, 0.0, 100 );
- }
-
- if ( m_pRotorOnGroundSound )
- {
- controller.Play( m_pRotorOnGroundSound, 0.0, 100 );
- }
-
- if ( m_pNearRotorSound )
- {
- controller.Play( m_pNearRotorSound, 0.0, 100 );
- }
-
- m_engineThrust = 1.0f;
-
- BaseClass::InitializeRotorSound();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::StopLoopingSounds()
-{
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
-
- if ( m_pCannonSound )
- {
- controller.SoundDestroy( m_pCannonSound );
- m_pCannonSound = NULL;
- }
-
- if ( m_pRotorOnGroundSound )
- {
- controller.SoundDestroy( m_pRotorOnGroundSound );
- m_pRotorOnGroundSound = NULL;
- }
-
- if ( m_pDescendingWarningSound )
- {
- controller.SoundDestroy( m_pDescendingWarningSound );
- m_pDescendingWarningSound = NULL;
- }
-
- if ( m_pNearRotorSound )
- {
- controller.SoundDestroy( m_pNearRotorSound );
- m_pNearRotorSound = NULL;
- }
-
- BaseClass::StopLoopingSounds();
-}
-
-
-//------------------------------------------------------------------------------
-// Updates the rotor wash volume
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::UpdateRotorWashVolume( CSoundPatch *pRotorSound, float flVolume, float flDeltaTime )
-{
- if ( !pRotorSound )
- return;
-
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- float flVolDelta = flVolume - controller.SoundGetVolume( pRotorSound );
- if ( flVolDelta )
- {
- // We can change from 0 to 1 in 3 seconds.
- // Figure out how many seconds flVolDelta will take.
- float flRampTime = fabs( flVolDelta ) * flDeltaTime;
- controller.SoundChangeVolume( pRotorSound, flVolume, flRampTime );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Updates the rotor wash volume
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::UpdateRotorWashVolume()
-{
- float flNearFactor = 0.0f;
- CBaseEntity *pPlayer = UTIL_PlayerByIndex( 1 );
- if (pPlayer)
- {
- float flDist = pPlayer->GetAbsOrigin().DistTo( GetAbsOrigin() );
- flDist = clamp( flDist, DROPSHIP_NEAR_SOUND_MIN_DISTANCE, DROPSHIP_NEAR_SOUND_MAX_DISTANCE );
- flNearFactor = RemapVal( flDist, DROPSHIP_NEAR_SOUND_MIN_DISTANCE, DROPSHIP_NEAR_SOUND_MAX_DISTANCE, 1.0f, 0.0f );
- }
-
- if ( m_pRotorSound )
- {
- UpdateRotorWashVolume( m_pRotorSound, m_engineThrust * GetRotorVolume() * (1.0f - flNearFactor), 3.0f );
- }
-
- if ( m_pNearRotorSound )
- {
- UpdateRotorWashVolume( m_pNearRotorSound, m_engineThrust * GetRotorVolume() * flNearFactor, 3.0f );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::UpdateRotorSoundPitch( int iPitch )
-{
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
-
- float rotorPitch = 0.2 + m_engineThrust * 0.8;
- if ( m_pRotorSound )
- {
- controller.SoundChangePitch( m_pRotorSound, iPitch + rotorPitch, 0.1 );
- }
-
- if ( m_pNearRotorSound )
- {
- controller.SoundChangePitch( m_pNearRotorSound, iPitch + rotorPitch, 0.1 );
- }
-
- if (m_pRotorOnGroundSound)
- {
- controller.SoundChangePitch( m_pRotorOnGroundSound, iPitch + rotorPitch, 0.1 );
- }
-
- UpdateRotorWashVolume();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : iSoldiers -
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::CalculateSoldierCount( int iSoldiers )
-{
- if ( m_iCrateType >= 0 )
- {
- m_soldiersToDrop = clamp( iSoldiers, 0, DROPSHIP_MAX_SOLDIERS );
- }
- else
- {
- m_soldiersToDrop = 0;
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Leave crate being carried
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::InputLandLeave( inputdata_t &inputdata )
-{
- CalculateSoldierCount( inputdata.value.Int() );
- m_leaveCrate = true;
- LandCommon();
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Take crate being carried to next point
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::InputLandTake( inputdata_t &inputdata )
-{
- CalculateSoldierCount( inputdata.value.Int() );
- m_leaveCrate = false;
- LandCommon();
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input : bHover - If true, means we're landing on a hover point, not the ground
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::LandCommon( bool bHover )
-{
- // If we don't have a crate, we're not able to land
- if ( !m_hContainer && !bHover )
- return;
-
- //DevMsg( "Landing\n" );
-
- if( bHover )
- {
- SetLandingState( LANDING_HOVER_LEVEL_OUT );
- }
- else
- {
- SetLandingState( LANDING_LEVEL_OUT );
- }
-
- SetLocalAngularVelocity( vec3_angle );
-
- // Do we have a land target?
- if ( m_iszLandTarget != NULL_STRING )
- {
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_iszLandTarget );
- if ( !pTarget )
- {
- Warning("npc_combinedropship %s couldn't find land target named %s\n", STRING(GetEntityName()), STRING(m_iszLandTarget) );
- return;
- }
-
- // Start heading to the point
- m_hLandTarget = pTarget;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::InputSetLandTarget( inputdata_t &inputdata )
-{
- m_iszLandTarget = inputdata.value.StringID();
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Drop mine inputs... done this way so generic path_corners can be used
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::InputDropMines( inputdata_t &inputdata )
-{
- m_totalMinesToDrop = inputdata.value.Int();
- if ( m_totalMinesToDrop >= 1 ) // catch bogus values being passed in
- {
- m_bDropMines = true;
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::InputDropStrider( inputdata_t &inputdata )
-{
- if ( !m_hContainer || !FClassnameIs( m_hContainer, "npc_strider" ) )
- {
- Warning("npc_combinedropship %s was told to drop Strider, but isn't carrying one!\n", STRING(GetEntityName()) );
- return;
- }
-
- QAngle angles = GetAbsAngles();
-
- angles.x = 0.0;
- angles.z = 0.0;
-
- m_hContainer->SetParent(NULL, 0);
- m_hContainer->SetOwnerEntity(NULL);
- m_hContainer->SetAbsAngles( angles );
- m_hContainer->SetAbsVelocity( vec3_origin );
-
- m_hContainer = NULL;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::InputDropAPC( inputdata_t &inputdata )
-{
- if ( !m_hContainer || !FClassnameIs( m_hContainer, "prop_vehicle_apc" ) )
- {
- Warning("npc_combinedropship %s was told to drop APC, but isn't carrying one!\n", STRING(GetEntityName()) );
- return;
- }
-
- m_hContainer->SetParent(NULL, 0);
-// m_hContainer->SetOwnerEntity(NULL);
-
- Vector vecAbsVelocity = GetAbsVelocity();
- if ( vecAbsVelocity.z > 0 )
- {
- vecAbsVelocity.z = 0.0f;
- }
- if ( m_hContainer->GetHealth() > 0 )
- {
- vecAbsVelocity = vec3_origin;
- }
-
- m_hContainer->SetAbsVelocity( vecAbsVelocity );
- m_hContainer->SetMoveType( (MoveType_t)m_iContainerMoveType );
-
- // If the container has a physics object, remove it's shadow
- IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
- if ( pPhysicsObject )
- {
- pPhysicsObject->RemoveShadowController();
- }
-
- UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );
-
- m_hContainer = NULL;
- m_OnFinishedDropoff.FireOutput( this, this );
- SetLandingState( LANDING_NO );
- m_hLandTarget = NULL;
-}
-
-
-//-----------------------------------------------------------------------------
-// Drop the soldier container
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::DropSoldierContainer( )
-{
- m_hContainer->SetParent(NULL, 0);
-// m_hContainer->SetOwnerEntity(NULL);
-
- Vector vecAbsVelocity = GetAbsVelocity();
- if ( vecAbsVelocity.z > 0 )
- {
- vecAbsVelocity.z = 0.0f;
- }
-
- m_hContainer->SetAbsVelocity( vecAbsVelocity );
- m_hContainer->SetMoveType( MOVETYPE_VPHYSICS );
-
- // If we have a troop in the process of exiting, kill him.
- // We do this to avoid having to solve the AI problems resulting from it.
- if ( m_hLastTroopToLeave )
- {
- CTakeDamageInfo dmgInfo( this, this, vec3_origin, m_hContainer->GetAbsOrigin(), m_hLastTroopToLeave->GetMaxHealth(), DMG_GENERIC );
- m_hLastTroopToLeave->TakeDamage( dmgInfo );
- }
-
- // If the container has a physics object, remove it's shadow
- IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
- if ( pPhysicsObject )
- {
- pPhysicsObject->RemoveShadowController();
- pPhysicsObject->SetVelocity( &vecAbsVelocity, &vec3_origin );
- }
-
- UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );
-
- m_hContainer = NULL;
- SetLandingState( LANDING_NO );
- m_hLandTarget = NULL;
-
- if ( m_bHasDroppedOff )
- {
- m_OnContainerShotDownAfterDropoff.FireOutput( this, this );
- }
- else
- {
- int iTroopsNotUnloaded = (m_soldiersToDrop - m_iCurrentTroopExiting);
- if ( g_debug_dropship.GetInt() )
- {
- Msg("Dropship died, troops not unloaded: %d\n", iTroopsNotUnloaded );
- }
-
- m_OnContainerShotDownBeforeDropoff.Set( iTroopsNotUnloaded, this, this );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Pick up a specified object
-// Input : &inputdata -
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::InputPickup( inputdata_t &inputdata )
-{
- // Can't pickup if we're already carrying something
- if ( m_hContainer )
- {
- Warning("npc_combinedropship %s was told to pickup, but is already carrying something.\n", STRING(GetEntityName()) );
- return;
- }
-
- string_t iszTargetName = inputdata.value.StringID();
- if ( iszTargetName == NULL_STRING )
- {
- Warning("npc_combinedropship %s tried to pickup with no specified pickup target.\n", STRING(GetEntityName()) );
- return;
- }
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, iszTargetName );
- if ( !pTarget )
- {
- Warning("npc_combinedropship %s couldn't find pickup target named %s\n", STRING(GetEntityName()), STRING(iszTargetName) );
- return;
- }
-
- // Start heading to the point
- m_hPickupTarget = pTarget;
-
- m_bHasDroppedOff = false;
-
- // Disable collisions to my target
- m_hPickupTarget->SetOwnerEntity(this);
- if ( m_NPCState == NPC_STATE_IDLE )
- {
- SetState( NPC_STATE_ALERT );
- }
- SetLandingState( LANDING_SWOOPING );
- m_flLandingSpeed = GetAbsVelocity().Length();
-
- UpdatePickupNavigation();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set the range of the container's gun
-// Input : &inputdata -
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::InputSetGunRange( inputdata_t &inputdata )
-{
- m_flGunRange = inputdata.value.Float();
-}
-
-
-//------------------------------------------------------------------------------
-// Set the landing state
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::SetLandingState( LandingState_t landingState )
-{
- if ( landingState == m_iLandState )
- return;
-
- if ( m_pDescendingWarningSound )
- {
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- if ( ( landingState == LANDING_DESCEND ) || ( landingState == LANDING_TOUCHDOWN ) || ( landingState == LANDING_UNLOADING ) || ( landingState == LANDING_UNLOADED ) || ( landingState == LANDING_HOVER_DESCEND ) )
- {
- controller.SoundChangeVolume( m_pDescendingWarningSound, m_bSuppressSound ? 0.0f : 1.0f, 0.3f );
- }
- else
- {
- controller.SoundChangeVolume( m_pDescendingWarningSound, 0.0f, 0.0f );
- }
- }
-
- m_iLandState = landingState;
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-bool CNPC_CombineDropship::IsHovering()
-{
- bool bIsHovering = false;
-
- if( GetLandingState() > LANDING_START_HOVER && GetLandingState() < LANDING_END_HOVER )
- {
- bIsHovering = true;
- }
-
- return bIsHovering;
-}
-
-//------------------------------------------------------------------------------
-// Update the ground rotorwash volume
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::UpdateGroundRotorWashSound( float flAltitude )
-{
- float flVolume = RemapValClamped( flAltitude, DROPSHIP_GROUND_WASH_MIN_ALTITUDE, DROPSHIP_GROUND_WASH_MAX_ALTITUDE, 1.0f, 0.0f );
- UpdateRotorWashVolume( m_pRotorOnGroundSound, flVolume * GetRotorVolume(), 0.5f );
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::PrescheduleThink( void )
-{
- BaseClass::PrescheduleThink();
-
- // "npc_kill" destroys our container
- if (m_debugOverlays & OVERLAY_NPC_KILL_BIT)
- {
- if ( m_hContainer )
- {
- CTakeDamageInfo dmgInfo( this, this, vec3_origin, vec3_origin, 1000, DMG_BLAST );
- m_hContainer->TakeDamage( dmgInfo );
- }
- }
-
- // Update the ground rotorwash volume
- float flAltitude = GetAltitude();
- UpdateGroundRotorWashSound( flAltitude );
-
- // keep track of think time deltas for burn calc below
- float dt = gpGlobals->curtime - m_flLastTime;
- m_flLastTime = gpGlobals->curtime;
-
- switch( GetLandingState() )
- {
- case LANDING_NO:
- {
- if ( IsActivityFinished() && (GetActivity() != ACT_DROPSHIP_FLY_IDLE_EXAGG && GetActivity() != ACT_DROPSHIP_FLY_IDLE_CARGO) )
- {
- if ( m_hContainer )
- {
- SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_CARGO );
- }
- else
- {
- SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_EXAGG );
- }
- }
-
- DoRotorWash();
- }
- break;
-
- case LANDING_LEVEL_OUT:
- case LANDING_HOVER_LEVEL_OUT:
- {
- // Approach the drop point
- Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
- float flDistance = vecToTarget.Length();
-
- // Are we there yet?
- float flSpeed = GetAbsVelocity().Length();
- if ( flDistance < 70 && flSpeed < 100 )
- {
- m_flLandingSpeed = flSpeed;
-
- if( IsHovering() )
- {
- SetLandingState( LANDING_HOVER_DESCEND );
- }
- else
- {
- SetLandingState( LANDING_DESCEND );
- }
-
- // save off current angles so we can work them out over time
- QAngle angles = GetLocalAngles();
- m_existPitch = angles.x;
- m_existRoll = angles.z;
- }
-
- DoRotorWash();
- }
- break;
-
- case LANDING_DESCEND:
- case LANDING_HOVER_DESCEND:
- {
- /*
- if ( IsActivityFinished() && GetActivity() != ACT_DROPSHIP_DESCEND_IDLE )
- {
- SetActivity( (Activity)ACT_DROPSHIP_DESCEND_IDLE );
- }
- */
-
- if( IsHovering() && m_hLandTarget != NULL )
- {
- // We're trying to hover above an arbitrary point, not above the ground.
- // Recompute flAltitude to indicate the vertical distance from the land
- // target so that touchdown is correctly detected.
- flAltitude = GetAbsOrigin().z - m_hLandTarget->GetAbsOrigin().z;
- }
-
- // Orient myself to the desired direction
- bool bStillOrienting = false;
- Vector targetDir;
- if ( m_hLandTarget )
- {
- // We've got a land target, so match it's orientation
- AngleVectors( m_hLandTarget->GetAbsAngles(), &targetDir );
- }
- else
- {
- // No land target.
- targetDir = GetDesiredPosition() - GetAbsOrigin();
- }
-
- // Don't unload until we're facing the way the dropoff point specifies
- float flTargetYaw = UTIL_VecToYaw( targetDir );
- float flDeltaYaw = UTIL_AngleDiff( flTargetYaw, GetAbsAngles().y );
- if ( fabs(flDeltaYaw) > 5 )
- {
- bStillOrienting = true;
- }
-
- // Ensure we land on the drop point. Stop dropping if we're still turning.
- Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
- float flDistance = vecToTarget.Length();
- float flRampedSpeed = m_flLandingSpeed * (flDistance / 70);
- Vector vecVelocity = (flRampedSpeed / flDistance) * vecToTarget;
-
-#define MAX_LAND_VEL -300.0f
-#define MIN_LAND_VEL -75.0f
-#define ALTITUDE_CAP 512.0f
-
- float flFactor = MIN( 1.0, flAltitude / ALTITUDE_CAP );
- float flDescendVelocity = MIN( -75, MAX_LAND_VEL * flFactor );
-
- vecVelocity.z = flDescendVelocity;
-
- SetAbsVelocity( vecVelocity );
-
- if ( flAltitude < 72 )
- {
- QAngle angles = GetLocalAngles();
-
- // Level out quickly.
- angles.x = UTIL_Approach( 0.0, angles.x, 0.2 );
- angles.z = UTIL_Approach( 0.0, angles.z, 0.2 );
-
- SetLocalAngles( angles );
- }
- else
- {
- // randomly move as if buffeted by ground effects
- // gently flatten ship from starting pitch/yaw
- m_existPitch = UTIL_Approach( 0.0, m_existPitch, 1 );
- m_existRoll = UTIL_Approach( 0.0, m_existRoll, 1 );
-
- QAngle angles = GetLocalAngles();
- angles.x = m_existPitch + ( sin( gpGlobals->curtime * 3.5f ) * DROPSHIP_MAX_LAND_TILT );
- angles.z = m_existRoll + ( sin( gpGlobals->curtime * 3.75f ) * DROPSHIP_MAX_LAND_TILT );
- SetLocalAngles( angles );
- }
-
- DoRotorWash();
-
- // place danger sounds 1 foot above ground to get troops to scatter if they are below dropship
- Vector vecBottom = GetAbsOrigin();
- vecBottom.z += WorldAlignMins().z;
- Vector vecSpot = vecBottom + Vector(0, 0, -1) * (flAltitude - 12 );
- CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, 0.1, this, 0 );
- CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, 400, 0.1, this, 1 );
-// NDebugOverlay::Cross3D( vecSpot, -Vector(4,4,4), Vector(4,4,4), 255, 0, 255, false, 10.0f );
-
- // now check to see if player is below us, if so, cause heat damage to them (i.e. get them to move)
- trace_t tr;
- Vector vecBBoxMin = CRATE_BBOX_MIN; // use flat box for check
- vecBBoxMin.z = -5;
- Vector vecBBoxMax = CRATE_BBOX_MAX;
- vecBBoxMax.z = 5;
- Vector pEndPoint = vecBottom + Vector(0, 0, -1) * ( flAltitude - 12 );
- AI_TraceHull( vecBottom, pEndPoint, vecBBoxMin, vecBBoxMax, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction < 1.0f )
- {
- // Damage anything that's blocking me
- if ( tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO )
- {
- CTakeDamageInfo info( this, this, 20 * dt, DMG_BURN );
- tr.m_pEnt->TakeDamage( info );
- }
- }
-
- if ( !bStillOrienting && ((flAltitude <= 0.5f) || (m_iCrateType == CRATE_APC)) )
- {
- if( IsHovering() )
- {
- SetAbsVelocity( vec3_origin );
- SetLandingState( LANDING_HOVER_TOUCHDOWN );
- }
- else
- {
- SetLandingState( LANDING_TOUCHDOWN );
- }
-
- // upon landing, make sure ship is flat
- QAngle angles = GetLocalAngles();
- angles.x = 0;
- angles.z = 0;
- SetLocalAngles( angles );
-
- // TODO: Release cargo anim
- //SetActivity( (Activity)ACT_DROPSHIP_DESCEND_IDLE );
- return;
- }
- }
- break;
-
- case LANDING_TOUCHDOWN:
- case LANDING_HOVER_TOUCHDOWN:
- {
- /*
- if ( IsActivityFinished() && ( GetActivity() != ACT_DROPSHIP_DESCEND_IDLE ) )
- {
- SetActivity( (Activity)ACT_DROPSHIP_DESCEND_IDLE );
- }
- */
-
- // Wait here if we're supposed to wait for the dropoff input
- if ( m_bWaitForDropoffInput )
- return;
-
- // Wait here till designer tells us to get moving again.
- if ( IsHovering() )
- return;
-
- SetLandingState( LANDING_UNLOADING );
-
- // If we're dropping off troops, we'll wait for them to be done.
- // Otherwise, just pause on the ground for a few seconds and then leave.
- if ( m_soldiersToDrop && m_hContainer)
- {
- m_flTimeTakeOff = 0;
- m_flNextTroopSpawnAttempt = 0;
-
- // Open our container
- m_hContainer->SetSequence( m_hContainer->LookupSequence("open_idle") );
-
- // Start unloading troops
- m_iCurrentTroopExiting = 0;
- SpawnTroop();
- }
- else
- {
- float flHoverTime = ( m_iCrateType >= 0 ) ? DROPSHIP_LANDING_HOVER_TIME : 0.5f;
- m_flTimeTakeOff = gpGlobals->curtime + flHoverTime;
- }
- }
- break;
-
- case LANDING_UNLOADING:
- {
- // If we've got no specified takeoff time, we're still waiting for troops to exit. Idle.
- if ( !m_flTimeTakeOff )
- {
- float idleVolume = 0.2f;
- m_engineThrust = UTIL_Approach( idleVolume, m_engineThrust, 0.04f );
- if ( m_engineThrust > idleVolume )
- {
- // Make sure we're kicking up dust/water as long as engine thrust is up
- DoRotorWash();
- }
-
- // If we've lost the last troop who was leaving, he probably got killed during dustoff.
- if ( !m_hLastTroopToLeave || !m_hLastTroopToLeave->IsAlive() )
- {
- // If we still have troops onboard, spawn the next one
- if ( m_iCurrentTroopExiting < m_soldiersToDrop )
- {
- SpawnTroop();
- }
- else
- {
- // We're out of troops, time to leave
- m_flTimeTakeOff = gpGlobals->curtime + 0.5;
- }
- }
- }
- else
- {
- if( gpGlobals->curtime > m_flTimeTakeOff )
- {
- SetLandingState( LANDING_LIFTOFF );
- SetActivity( (Activity)ACT_DROPSHIP_LIFTOFF );
- m_engineThrust = 1.0f; // ensure max volume once we're airborne
- if ( m_bIsFiring )
- {
- StopCannon(); // kill cannon sounds if they are on
- }
-
- // detach container from ship
- if ( m_hContainer && m_leaveCrate )
- {
- m_hContainer->SetParent(NULL);
- m_hContainer->SetMoveType( (MoveType_t)m_iContainerMoveType );
-
- Vector vecAbsVelocity( 0, 0, GetAbsVelocity().z );
- if ( vecAbsVelocity.z > 0 )
- {
- vecAbsVelocity.z = 0.0f;
- }
-
- m_hContainer->SetAbsVelocity( vecAbsVelocity );
-
- // If the container has a physics object, remove it's shadow
- IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
- if ( pPhysicsObject )
- {
- pPhysicsObject->RemoveShadowController();
- pPhysicsObject->SetVelocity( &vecAbsVelocity, &vec3_origin );
- }
-
- m_hContainer = NULL;
- UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );
- }
- }
- else if ( (m_flTimeTakeOff - gpGlobals->curtime) < 0.5f )
- {
- // Manage engine wash and volume
- m_engineThrust = UTIL_Approach( 1.0f, m_engineThrust, 0.1f );
- DoRotorWash();
- }
- }
- }
- break;
-
- case LANDING_LIFTOFF:
- {
- // Once we're off the ground, start flying again
- if ( flAltitude > 120 )
- {
- SetLandingState( LANDING_NO );
- m_hLandTarget = NULL;
- m_bHasDroppedOff = true;
- m_OnFinishedDropoff.FireOutput( this, this );
- }
-
- if ( m_hContainer )
- {
- m_hContainer->SetSequence( m_hContainer->LookupSequence("close_idle") );
- }
- }
- break;
-
- case LANDING_SWOOPING:
- {
- // Did we lose our pickup target?
- if ( !m_hPickupTarget )
- {
- SetLandingState( LANDING_NO );
- }
- else
- {
- // Decrease altitude and speed to hit the target point.
- Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
- float flDistance = vecToTarget.Length();
-
- // Start cheating when we get near it
- if ( flDistance < 50 )
- {
- /*
- if ( flDistance > 10 )
- {
- // Cheat and ensure we touch the target
- float flSpeed = GetAbsVelocity().Length();
- Vector vecVelocity = vecToTarget;
- VectorNormalize( vecVelocity );
- SetAbsVelocity( vecVelocity * min(flSpeed,flDistance) );
- }
- else
- */
- {
- // Grab the target
- m_hContainer = m_hPickupTarget;
- m_hPickupTarget = NULL;
- m_iContainerMoveType = m_hContainer->GetMoveType();
- if ( m_bInvulnerable && m_hContainer )
- {
- m_hContainer->m_takedamage = DAMAGE_NO;
- }
-
- // If the container has a physics object, move it to shadow
- IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
- if ( pPhysicsObject )
- {
- pPhysicsObject->EnableMotion( true );
- pPhysicsObject->SetShadow( 1e4, 1e4, false, false );
- pPhysicsObject->UpdateShadow( GetAbsOrigin(), GetAbsAngles(), false, 0 );
- }
-
- m_hContainer->SetParent(this, 0);
- m_hContainer->SetMoveType( MOVETYPE_PUSH );
- m_hContainer->SetGroundEntity( NULL );
-
- m_OnFinishedPickup.FireOutput( this, this );
- SetLandingState( LANDING_NO );
- }
- }
- }
-
- DoRotorWash();
- }
- break;
- }
-
- if ( !(CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) )
- {
- DoCombatStuff();
- }
-
- if ( GetActivity() != GetIdealActivity() )
- {
- //DevMsg( "setactivity" );
- SetActivity( GetIdealActivity() );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-#define DROPSHIP_WASH_ALTITUDE 1024.0
-
-void CNPC_CombineDropship::DoRotorWash( void )
-{
- Vector vecForward;
- GetVectors( &vecForward, NULL, NULL );
-
- Vector vecRotorHub = GetAbsOrigin() + vecForward * -64;
-
- DrawRotorWash( DROPSHIP_WASH_ALTITUDE, vecRotorHub );
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose : Spawn the next NPC in our template list
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::SpawnTroop( void )
-{
- if ( !m_hContainer )
- {
- // We're done, take off.
- m_flTimeTakeOff = gpGlobals->curtime + 0.5;
- return;
- }
-
- // Are we fully unloaded? If so, take off. Otherwise, tell the next troop to exit.
- if ( m_iCurrentTroopExiting >= m_soldiersToDrop || m_sNPCTemplateData[m_iCurrentTroopExiting] == NULL_STRING )
- {
- // We're done, take off.
- m_flTimeTakeOff = gpGlobals->curtime + 0.5;
- return;
- }
-
- m_hLastTroopToLeave = NULL;
-
- // Not time to try again yet?
- if ( m_flNextTroopSpawnAttempt > gpGlobals->curtime )
- return;
-
- // HACK: This is a nasty piece of work. We want to make sure the deploy end is clear, and has enough
- // room with our deploying NPC, but we don't want to create the NPC unless it's clear, and we don't
- // know how much room he needs without spawning him.
- // So, because we know that we only ever spawn combine soldiers at the moment, we'll just use their hull.
- // HACK: Add some bloat because the endpoint isn't perfectly aligned with NPC end origin
- Vector vecNPCMins = NAI_Hull::Mins( HULL_HUMAN ) - Vector(4,4,4);
- Vector vecNPCMaxs = NAI_Hull::Maxs( HULL_HUMAN ) + Vector(4,4,4);
-
- // Scare NPCs away from our deploy endpoint to keep them away
- Vector vecDeployEndPoint;
- QAngle vecDeployEndAngles;
- m_hContainer->GetAttachment( m_iAttachmentTroopDeploy, vecDeployEndPoint, vecDeployEndAngles );
- vecDeployEndPoint = GetDropoffFinishPosition( vecDeployEndPoint, NULL, vecNPCMins, vecNPCMaxs );
- CSoundEnt::InsertSound( SOUND_DANGER, vecDeployEndPoint, 120.0f, 2.0f, this );
-
- // Make sure there are no NPCs on the spot
- trace_t tr;
- CTraceFilterOnlyNPCsAndPlayer filter( this, COLLISION_GROUP_NONE );
- AI_TraceHull( vecDeployEndPoint, vecDeployEndPoint, vecNPCMins, vecNPCMaxs, MASK_SOLID, &filter, &tr );
- if ( tr.m_pEnt )
- {
- if ( g_debug_dropship.GetInt() == 2 )
- {
- NDebugOverlay::Box( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, 255,0,0, 64, 0.5 );
- }
-
- m_flNextTroopSpawnAttempt = gpGlobals->curtime + 1;
- return;
- }
-
- if ( g_debug_dropship.GetInt() == 2 )
- {
- NDebugOverlay::Box( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, 0,255,0, 64, 0.5 );
- }
-
- // Get the spawn point inside the container
- Vector vecSpawnOrigin;
- QAngle vecSpawnAngles;
- m_hContainer->GetAttachment( m_iAttachmentDeployStart, vecSpawnOrigin, vecSpawnAngles );
-
- // Spawn the templated NPC
- CBaseEntity *pEntity = NULL;
- MapEntity_ParseEntity( pEntity, STRING(m_sNPCTemplateData[m_iCurrentTroopExiting]), NULL );
-
- // Increment troop count
- m_iCurrentTroopExiting++;
-
- if ( !pEntity )
- {
- Warning("Dropship could not create template NPC\n" );
- return;
- }
- CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
- Assert( pNPC );
-
- // Spawn an entity blocker.
- CBaseEntity *pBlocker = CEntityBlocker::Create( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, pNPC, true );
- g_EventQueue.AddEvent( pBlocker, "Kill", 2.5, this, this );
- if ( g_debug_dropship.GetInt() == 2 )
- {
- NDebugOverlay::Box( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, 255, 255, 255, 64, 2.5 );
- }
-
- // Ensure our NPCs are standing upright
- vecSpawnAngles[PITCH] = vecSpawnAngles[ROLL] = 0;
-
- // Move it to the container spawnpoint
- pNPC->SetAbsOrigin( vecSpawnOrigin );
- pNPC->SetAbsAngles( vecSpawnAngles );
- DispatchSpawn( pNPC );
- pNPC->m_NPCState = NPC_STATE_IDLE;
- pNPC->Activate();
-
- // Spawn a scripted sequence entity to make the NPC run out of the dropship
- CAI_ScriptedSequence *pSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
- pSequence->KeyValue( "m_iszEntity", STRING(pNPC->GetEntityName()) );
- pSequence->KeyValue( "m_iszPlay", "Dropship_Deploy" );
- pSequence->KeyValue( "m_fMoveTo", "4" ); // CINE_MOVETO_TELEPORT
- pSequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,NPCFinishDustoff,%s,0,-1", STRING(GetEntityName()), STRING(pNPC->GetEntityName())) );
- pSequence->SetAbsOrigin( vecSpawnOrigin );
- pSequence->SetAbsAngles( vecSpawnAngles );
- pSequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE );
- pSequence->Spawn();
- pSequence->Activate();
- variant_t emptyVariant;
- pSequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 );
-
- m_hLastTroopToLeave = pNPC;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns a safe position above/below the specified origin for the NPC to finish it's dropoff on
-// Input : vecOrigin -
-//-----------------------------------------------------------------------------
-Vector CNPC_CombineDropship::GetDropoffFinishPosition( Vector vecOrigin, CAI_BaseNPC *pNPC, Vector vecMins, Vector vecMaxs )
-{
- // Use the NPC's if they're specified
- if ( pNPC )
- {
- vecMins = NAI_Hull::Mins( pNPC->GetHullType() );
- vecMaxs = NAI_Hull::Maxs( pNPC->GetHullType() );
- }
-
- trace_t tr;
- AI_TraceHull( vecOrigin + Vector(0,0,32), vecOrigin, vecMins, vecMaxs, MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction < 1.0 )
- {
- if ( g_debug_dropship.GetInt() == 1 )
- {
- NDebugOverlay::Box( vecOrigin, vecMins, vecMaxs, 255,0,0, 8, 4.0 );
- }
-
- // Try and find the ground
- AI_TraceHull( vecOrigin + Vector(0,0,32), vecOrigin, vecMins, vecMaxs, MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr );
- if ( !tr.startsolid )
- return (tr.endpos + Vector(0,0,1));
- }
- else if ( g_debug_dropship.GetInt() == 1 )
- {
- NDebugOverlay::Box( vecOrigin, vecMins, vecMaxs, 0,255,0, 8, 4.0 );
- }
-
- return vecOrigin;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: A troop we dropped of has now finished the scripted sequence
-// Input : &inputdata -
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::InputNPCFinishDustoff( inputdata_t &inputdata )
-{
- CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, inputdata.value.StringID(), NULL, inputdata.pActivator, inputdata.pCaller );
- if ( !pEnt )
- return;
-
- CAI_BaseNPC *pNPC = pEnt->MyNPCPointer();
- Assert( pNPC );
-
- Vector vecOrigin = GetDropoffFinishPosition( pNPC->GetAbsOrigin(), pNPC, vec3_origin, vec3_origin );
- pNPC->SetAbsOrigin( vecOrigin );
-
- // Do we have a dustoff point?
- CBaseEntity *pDustoff = NULL;
- if ( m_sDustoffPoints[m_iCurrentTroopExiting-1] != NULL_STRING )
- {
- pDustoff = gEntList.FindEntityByName( NULL, m_sDustoffPoints[m_iCurrentTroopExiting-1] );
- if ( !pDustoff )
- {
- Warning("npc_combinedropship %s couldn't find dustoff target named %s\n", STRING(GetEntityName()), STRING(m_sDustoffPoints[m_iCurrentTroopExiting-1]) );
- }
- }
-
- if ( !pDustoff )
- {
- // Make a move away sound to scare the combine away from this point
- CSoundEnt::InsertSound( SOUND_MOVE_AWAY | SOUND_CONTEXT_COMBINE_ONLY, pNPC->GetAbsOrigin(), 128, 0.1 );
- }
- else
- {
- if ( g_debug_dropship.GetInt() == 1 )
- {
- NDebugOverlay::Box( pDustoff->GetAbsOrigin(), -Vector(10,10,10), Vector(10,10,10), 0,255,0, 8, 5.0 );
- }
-
- // Tell the NPC to move to the dustoff position
- pNPC->SetState( NPC_STATE_ALERT );
- pNPC->ScheduledMoveToGoalEntity( SCHED_DROPSHIP_DUSTOFF, pDustoff, ACT_RUN );
- pNPC->GetNavigator()->SetArrivalDirection( pDustoff->GetAbsAngles() );
-
- // Make sure they ignore a bunch of conditions
- static int g_Conditions[] =
- {
- COND_CAN_MELEE_ATTACK1,
- COND_CAN_MELEE_ATTACK2,
- COND_CAN_RANGE_ATTACK1,
- COND_CAN_RANGE_ATTACK2,
- COND_ENEMY_DEAD,
- COND_HEAR_BULLET_IMPACT,
- COND_HEAR_COMBAT,
- COND_HEAR_DANGER,
- COND_NEW_ENEMY,
- COND_PROVOKED,
- COND_SEE_ENEMY,
- COND_SEE_FEAR,
- COND_SMELL,
- COND_LIGHT_DAMAGE,
- COND_HEAVY_DAMAGE,
- COND_PHYSICS_DAMAGE,
- COND_REPEATED_DAMAGE,
- };
- pNPC->SetIgnoreConditions( g_Conditions, ARRAYSIZE(g_Conditions) );
- }
-
- // Unload the next troop
- SpawnTroop();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Tells the dropship to stop waiting and dustoff
-// Input : &inputdata -
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::InputStopWaitingForDropoff( inputdata_t &inputdata )
-{
- m_bWaitForDropoffInput = false;
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::InputHover( inputdata_t &inputdata )
-{
- m_iszLandTarget = inputdata.value.StringID();
- LandCommon( true );
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::InputFlyToPathTrack( inputdata_t &inputdata )
-{
- if( IsHovering() )
- {
- SetLandingState( LANDING_NO );
- m_hLandTarget = NULL;
- }
-
- CAI_TrackPather::InputFlyToPathTrack( inputdata );
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-float CNPC_CombineDropship::GetAltitude( void )
-{
- trace_t tr;
- Vector vecBottom = GetAbsOrigin();
-
- // Uneven terrain causes us problems, so trace our box down
- AI_TraceEntity( this, vecBottom, vecBottom - Vector(0,0,4096), MASK_SOLID_BRUSHONLY, &tr );
-
- float flAltitude = ( 4096 * tr.fraction );
- //DevMsg(" Altitude: %.3f\n", flAltitude );
- return flAltitude;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Drop rollermine from dropship
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::DropMine( void )
-{
- NPC_Rollermine_DropFromPoint( GetAbsOrigin(), this, STRING(m_sRollermineTemplateData) );
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Fly towards our pickup target
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::UpdatePickupNavigation( void )
-{
- // Try and touch the top of the object
- Vector vecPickup = m_hPickupTarget->WorldSpaceCenter();
- vecPickup.z += (m_hPickupTarget->CollisionProp()->OBBSize().z * 0.5);
- SetDesiredPosition( vecPickup );
-
- //NDebugOverlay::Cross3D( GetDesiredPosition(), -Vector(32,32,32), Vector(32,32,32), 0, 255, 255, true, 0.1f );
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Fly towards our land target
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::UpdateLandTargetNavigation( void )
-{
- Vector vecPickup = m_hLandTarget->WorldSpaceCenter();
- vecPickup.z += 256;
- SetDesiredPosition( vecPickup );
-
- //NDebugOverlay::Cross3D( GetDesiredPosition(), -Vector(32,32,32), Vector(32,32,32), 0, 255, 255, true, 0.1f );
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::Hunt( void )
-{
- // If we have a pickup target, fly to it
- if ( m_hPickupTarget )
- {
- UpdatePickupNavigation();
- }
- else if ( m_hLandTarget )
- {
- UpdateLandTargetNavigation();
- }
- else if ( GetLandingState() == LANDING_NO )
- {
- UpdateTrackNavigation();
- }
-
- // don't face player ever, only face nav points
- Vector desiredDir = GetDesiredPosition() - GetAbsOrigin();
- VectorNormalize( desiredDir );
- // Face our desired position.
- m_vecDesiredFaceDir = desiredDir;
-
- if ( GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_LEVEL_OUT || IsHovering() )
- {
- if ( m_hLandTarget )
- {
- // We've got a land target, so match it's orientation
- AngleVectors( m_hLandTarget->GetAbsAngles(), &m_vecDesiredFaceDir );
- }
- else
- {
- // No land target.
- m_vecDesiredFaceDir = GetDesiredPosition() - GetAbsOrigin();
- }
- }
-
- UpdateEnemy();
- Flight();
-
- UpdatePlayerDopplerShift( );
-
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::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: do all of the stuff related to having an enemy, attacking, etc.
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::DoCombatStuff( void )
-{
- // Handle mines
- if ( m_bDropMines )
- {
- switch( m_iDropState )
- {
- case DROP_IDLE:
- {
- m_iMineCount = m_totalMinesToDrop - 1;
-
- DropMine();
- // setup next individual drop time
- m_flDropDelay = gpGlobals->curtime + DROPSHIP_TIME_BETWEEN_MINES;
- // get ready to drop next mine, unless we're only supposed to drop 1
- if ( m_iMineCount )
- {
- m_iDropState = DROP_NEXT;
- }
- else
- {
- m_bDropMines = false; // no more...
- }
- break;
- }
- case DROP_NEXT:
- {
- if ( gpGlobals->curtime > m_flDropDelay ) // time to drop next mine?
- {
- DropMine();
- m_flDropDelay = gpGlobals->curtime + DROPSHIP_TIME_BETWEEN_MINES;
-
- m_iMineCount--;
- if ( !m_iMineCount )
- {
- m_iDropState = DROP_IDLE;
- m_bDropMines = false; // reset flag
- }
- }
- break;
- }
- }
- }
-
- // Handle guns
- bool bStopGun = true;
- if ( GetEnemy() )
- {
- bStopGun = !FireCannonRound();
- }
-
- if ( bStopGun && m_bIsFiring )
- {
- StopCannon();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Update the container's gun to face the enemy.
-// Input : &vecMuzzle - The gun's muzzle/firing point
-// &vecAimDir - The gun's current aim direction
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::UpdateContainerGunFacing( Vector &vecMuzzle, Vector &vecToTarget, Vector &vecAimDir, float *flTargetRange )
-{
- Assert( m_hContainer );
-
- // Get the desired aim vector
- vecToTarget = GetEnemy()->WorldSpaceCenter( );
-
- Vector vecBarrelPos, vecWorldBarrelPos;
- QAngle worldBarrelAngle, vecAngles;
- matrix3x4_t matRefToWorld;
- m_hContainer->GetAttachment( m_iMuzzleAttachment, vecMuzzle, vecAngles );
- vecWorldBarrelPos = vecMuzzle;
- worldBarrelAngle = vecAngles;
- m_hContainer->GetAttachment( m_iMachineGunRefAttachment, matRefToWorld );
- VectorITransform( vecWorldBarrelPos, matRefToWorld, vecBarrelPos );
-
- EntityMatrix parentMatrix;
- parentMatrix.InitFromEntity( m_hContainer, m_iMachineGunBaseAttachment );
- Vector target = parentMatrix.WorldToLocal( vecToTarget );
-
- float quadTarget = target.LengthSqr();
- float quadTargetXY = target.x*target.x + target.y*target.y;
-
- // Target is too close! Can't aim at it
- if ( quadTarget > vecBarrelPos.LengthSqr() )
- {
- // We're trying to aim the offset barrel at an arbitrary point.
- // To calculate this, I think of the target as being on a sphere with
- // it's center at the origin of the gun.
- // The rotation we need is the opposite of the rotation that moves the target
- // along the surface of that sphere to intersect with the gun's shooting direction
- // To calculate that rotation, we simply calculate the intersection of the ray
- // coming out of the barrel with the target sphere (that's the new target position)
- // and use atan2() to get angles
-
- // angles from target pos to center
- float targetToCenterYaw = atan2( target.y, target.x );
- float centerToGunYaw = atan2( vecBarrelPos.y, sqrt( quadTarget - (vecBarrelPos.y*vecBarrelPos.y) ) );
-
- float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) );
- float centerToGunPitch = atan2( -vecBarrelPos.z, sqrt( quadTarget - (vecBarrelPos.z*vecBarrelPos.z) ) );
-
- QAngle angles;
- angles.Init( RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw + centerToGunYaw ), 0 );
-
- float flNewAngle = AngleNormalize( UTIL_ApproachAngle( angles.x, m_hContainer->GetPoseParameter(m_poseWeapon_Pitch), DROPSHIP_GUN_SPEED));
- m_hContainer->SetPoseParameter( m_poseWeapon_Pitch, flNewAngle );
-
- flNewAngle = AngleNormalize( UTIL_ApproachAngle( angles.y, m_hContainer->GetPoseParameter(m_poseWeapon_Yaw), DROPSHIP_GUN_SPEED));
- m_hContainer->SetPoseParameter( m_poseWeapon_Yaw, flNewAngle );
- m_hContainer->StudioFrameAdvance();
- }
-
- vecToTarget -= vecMuzzle;
- *flTargetRange = VectorNormalize( vecToTarget );
- AngleVectors( vecAngles, &vecAimDir );
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose: Fire a round from the cannon
-// Notes: Only call this if you have an enemy.
-// Returns true if the cannon round was actually fired
-//------------------------------------------------------------------------------
-bool CNPC_CombineDropship::FireCannonRound( void )
-{
- // Try and aim my cannon at the enemy, if I have a container
- if ( !m_hContainer || (m_iCrateType < 0) )
- return false;
-
- // Update the container gun, and get the vector to the enemy, and the gun's current aim direction
- float flRange;
- Vector vecMuzzle, vecAimDir, vecToEnemy;
- UpdateContainerGunFacing( vecMuzzle, vecToEnemy, vecAimDir, &flRange );
-
- // Out of range?
- if ( flRange > m_flGunRange )
- return false;
-
- // Only fire if the target's close enough to our aim direction
- float flCosAngle = DotProduct( vecToEnemy, vecAimDir );
- if ( flCosAngle < DOT_15DEGREE )
- {
- m_flTimeNextAttack = gpGlobals->curtime + 0.1;
- return false;
- }
-
- // If we're out of rounds, reload
- if ( m_iBurstRounds <= 0 )
- {
- m_iBurstRounds = RandomInt( 10, 20 );
- m_flTimeNextAttack = gpGlobals->curtime + (m_iBurstRounds * 0.1);
- return false;
- }
-
- // HACK: Return true so the fire sound isn't stopped
- if ( m_flTimeNextAttack > gpGlobals->curtime )
- return true;
-
- m_iBurstRounds--;
-
- // If we're not currently firing, start it up
- if ( !m_bIsFiring )
- {
- StartCannon();
- }
-
- // Add a muzzle flash
- QAngle vecAimAngles;
- VectorAngles( vecAimDir, vecAimAngles );
- g_pEffects->MuzzleFlash( vecMuzzle, vecAimAngles, random->RandomFloat( 5.0f, 7.0f ), MUZZLEFLASH_TYPE_GUNSHIP );
- m_flTimeNextAttack = gpGlobals->curtime + 0.05;
-
- // Clamp to account for inaccuracy in aiming w/ pose parameters
- vecAimDir = vecToEnemy;
-
- // Fire the bullet
- int ammoType = GetAmmoDef()->Index("CombineCannon");
- FireBullets( 1, vecMuzzle, vecAimDir, VECTOR_CONE_2DEGREES, 8192, ammoType, 1, -1, -1, sk_npc_dmg_dropship.GetInt() );
-
- return true;
-}
-
-//------------------------------------------------------------------------------
-// Scare AIs in the area where bullets are impacting
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::DoImpactEffect( trace_t &tr, int nDamageType )
-{
- CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_REACT_TO_SOURCE, tr.endpos, 120.0f, 0.3f, this );
-
- BaseClass::DoImpactEffect( tr, nDamageType );
-}
-
-//------------------------------------------------------------------------------
-// Purpose : The proper way to begin the gunship cannon firing at the enemy.
-// Input :
-// :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::StartCannon( void )
-{
- m_bIsFiring = true;
-
- // Start up the cannon sound.
- if ( m_pCannonSound )
- {
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundChangeVolume(m_pCannonSound, 1.0, 0.0);
- }
-
-}
-
-//------------------------------------------------------------------------------
-// Purpose : The proper way to cease the gunship cannon firing.
-// Input :
-// :
-// Output :
-//------------------------------------------------------------------------------
-void CNPC_CombineDropship::StopCannon( void )
-{
- m_bIsFiring = false;
-
- // Stop the cannon sound.
- if ( m_pCannonSound )
- {
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundChangeVolume(m_pCannonSound, 0.0, 0.1);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Used the gunship's tracer for now
-//-----------------------------------------------------------------------------
-void CNPC_CombineDropship::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, 16000, true, "GunshipTracer" );
- }
- break;
-
- default:
- BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
- break;
- }
-}
-
-AI_BEGIN_CUSTOM_NPC( npc_combinedropship, CNPC_CombineDropship )
-
- DECLARE_ACTIVITY( ACT_DROPSHIP_FLY_IDLE );
- DECLARE_ACTIVITY( ACT_DROPSHIP_FLY_IDLE_EXAGG );
- DECLARE_ACTIVITY( ACT_DROPSHIP_DESCEND_IDLE );
- DECLARE_ACTIVITY( ACT_DROPSHIP_DEPLOY_IDLE );
- DECLARE_ACTIVITY( ACT_DROPSHIP_LIFTOFF );
-
- DECLARE_ACTIVITY( ACT_DROPSHIP_FLY_IDLE_CARGO );
-
-AI_END_CUSTOM_NPC()
-
-
-
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Large vehicle what delivers combine troops.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_default.h"
+#include "ai_basenpc.h"
+#include "soundenvelope.h"
+#include "cbasehelicopter.h"
+#include "ai_schedule.h"
+#include "engine/IEngineSound.h"
+#include "smoke_trail.h"
+#include "IEffects.h"
+#include "props.h"
+#include "TemplateEntities.h"
+#include "baseanimating.h"
+#include "ai_senses.h"
+#include "entitylist.h"
+#include "ammodef.h"
+#include "ndebugoverlay.h"
+#include "npc_combines.h"
+#include "soundent.h"
+#include "mapentities.h"
+#include "npc_rollermine.h"
+#include "scripted.h"
+#include "explode.h"
+#include "gib.h"
+#include "EntityFlame.h"
+#include "entityblocker.h"
+#include "eventqueue.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// Spawnflags
+#define SF_DROPSHIP_WAIT_FOR_DROPOFF_INPUT ( 1 << 15 )
+
+#define DROPSHIP_ACCEL_RATE 300
+
+// Timers
+#define DROPSHIP_LANDING_HOVER_TIME 5 // Time to spend on the ground if we have no troops to drop
+#define DROPSHIP_TIME_BETWEEN_MINES 0.5f
+
+// Special actions
+#define DROPSHIP_DEFAULT_SOLDIERS 4
+#define DROPSHIP_MAX_SOLDIERS 6
+
+// Movement
+#define DROPSHIP_BUFF_TIME 0.3f
+#define DROPSHIP_MAX_LAND_TILT 2.5f
+#define DROPSHIP_CONTAINER_HEIGHT 130.0f
+#define DROPSHIP_MAX_SPEED (60 * 17.6) // 120 miles per hour.
+
+// Pathing data
+#define DROPSHIP_LEAD_DISTANCE 800.0f
+#define DROPSHIP_MIN_CHASE_DIST_DIFF 128.0f // Distance threshold used to determine when a target has moved enough to update our navigation to it
+#define DROPSHIP_AVOID_DIST 256.0f
+#define DROPSHIP_ARRIVE_DIST 128.0f
+
+#define CRATE_BBOX_MIN (Vector( -100, -80, -60 ))
+#define CRATE_BBOX_MAX (Vector( 100, 80, 80 ))
+
+// Size
+// With crate
+#define DROPSHIP_BBOX_CRATE_MIN (-Vector(40,40,60))
+#define DROPSHIP_BBOX_CRATE_MAX (Vector(40,40,40))
+// Without crate
+#define DROPSHIP_BBOX_MIN (-Vector(40,40,0))
+#define DROPSHIP_BBOX_MAX (Vector(40,40,40))
+
+// Container gun
+#define DROPSHIP_GUN_SPEED 10 // Rotation speed
+
+#define DROPSHIP_CRATE_ROCKET_HITS 4
+
+enum DROP_STATES
+{
+ DROP_IDLE = 0,
+ DROP_NEXT,
+};
+
+enum CRATE_TYPES
+{
+ CRATE_JEEP = -3,
+ CRATE_APC = -2,
+ CRATE_STRIDER = -1,
+ CRATE_ROLLER_HOPPER,
+ CRATE_SOLDIER,
+ CRATE_NONE,
+};
+
+ConVar g_debug_dropship( "g_debug_dropship", "0" );
+ConVar sk_dropship_container_health( "sk_dropship_container_health", "750" );
+ConVar sk_npc_dmg_dropship( "sk_npc_dmg_dropship","5", FCVAR_NONE, "Dropship container cannon damage." );
+
+//=====================================
+// Animation Events
+//=====================================
+#define AE_DROPSHIP_RAMP_OPEN 1 // the tailgate is open.
+
+//=====================================
+// Custom activities
+//=====================================
+// Without Cargo
+Activity ACT_DROPSHIP_FLY_IDLE; // Flying. Vertical aspect
+Activity ACT_DROPSHIP_FLY_IDLE_EXAGG; // Exaggerated version of the flying idle
+// With Cargo
+Activity ACT_DROPSHIP_FLY_IDLE_CARGO; // Flying. Vertical aspect
+Activity ACT_DROPSHIP_DESCEND_IDLE; // waiting to touchdown
+Activity ACT_DROPSHIP_DEPLOY_IDLE; // idle on the ground with door open. Troops are leaving.
+Activity ACT_DROPSHIP_LIFTOFF; // transition back to FLY IDLE
+
+enum LandingState_t
+{
+ LANDING_NO = 0,
+
+ // Dropoff
+ LANDING_LEVEL_OUT, // Heading to a point above the dropoff point
+ LANDING_DESCEND, // Descending from to the dropoff point
+ LANDING_TOUCHDOWN,
+ LANDING_UNLOADING,
+ LANDING_UNLOADED,
+ LANDING_LIFTOFF,
+
+ // Pickup
+ LANDING_SWOOPING, // Swooping down to the target
+
+ // Hovering, which we're saying is a type of landing since there's so much landing code to leverage
+ LANDING_START_HOVER,
+ LANDING_HOVER_LEVEL_OUT,
+ LANDING_HOVER_DESCEND,
+ LANDING_HOVER_TOUCHDOWN,
+ LANDING_END_HOVER,
+};
+
+
+#define DROPSHIP_NEAR_SOUND_MIN_DISTANCE 1000
+#define DROPSHIP_NEAR_SOUND_MAX_DISTANCE 2500
+#define DROPSHIP_GROUND_WASH_MIN_ALTITUDE 100.0f
+#define DROPSHIP_GROUND_WASH_MAX_ALTITUDE 750.0f
+
+
+
+//=============================================================================
+// The combine dropship container
+//=============================================================================
+#define DROPSHIP_CONTAINER_MODEL "models/combine_dropship_container.mdl"
+
+#define DROPSHIP_CONTAINER_MAX_CHUNKS 3
+static const char *s_pChunkModelName[DROPSHIP_CONTAINER_MAX_CHUNKS] =
+{
+ "models/gibs/helicopter_brokenpiece_01.mdl",
+ "models/gibs/helicopter_brokenpiece_02.mdl",
+ "models/gibs/helicopter_brokenpiece_03.mdl",
+};
+
+#define DROPSHIP_CONTAINER_MAX_GIBS 1
+static const char *s_pGibModelName[DROPSHIP_CONTAINER_MAX_GIBS] =
+{
+ "models/combine_dropship_container.mdl",
+};
+
+class CCombineDropshipContainer : public CPhysicsProp
+{
+ DECLARE_CLASS( CCombineDropshipContainer, CPhysicsProp );
+ DECLARE_DATADESC();
+
+public:
+ void Precache();
+ virtual void Spawn();
+ virtual bool OverridePropdata( void );
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+
+private:
+ enum
+ {
+ MAX_SMOKE_TRAILS = 4,
+ MAX_EXPLOSIONS = 4,
+ };
+
+ // Should we trigger a damage effect?
+ bool ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const;
+
+ // Add a smoke trail since we've taken more damage
+ void AddSmokeTrail( const Vector &vecPos );
+
+ // Pow!
+ void ThrowFlamingGib();
+
+ // Create a corpse
+ void CreateCorpse();
+
+private:
+ int m_nSmokeTrailCount;
+ EHANDLE m_hLastInflictor;
+ float m_flLastHitTime;
+};
+
+//=============================================================================
+// The combine dropship
+//=============================================================================
+class CNPC_CombineDropship : public CBaseHelicopter
+{
+ DECLARE_CLASS( CNPC_CombineDropship, CBaseHelicopter );
+
+public:
+ ~CNPC_CombineDropship();
+
+ // Setup
+ void Spawn( void );
+ void Precache( void );
+
+ void Activate( void );
+
+ // Thinking/init
+ void InitializeRotorSound( void );
+ void StopLoopingSounds();
+ void PrescheduleThink( void );
+
+ // Flight/sound
+ void Hunt( void );
+ void Flight( void );
+ float GetAltitude( void );
+ void DoRotorWash( void );
+ void UpdateRotorSoundPitch( int iPitch );
+ void UpdatePickupNavigation( void );
+ void UpdateLandTargetNavigation( void );
+ void CalculateSoldierCount( int iSoldiers );
+
+ // Updates the facing direction
+ virtual void UpdateFacingDirection();
+
+ // Combat
+ void GatherEnemyConditions( CBaseEntity *pEnemy );
+ void DoCombatStuff( void );
+ void SpawnTroop( void );
+ void DropMine( void );
+ void UpdateContainerGunFacing( Vector &vecMuzzle, Vector &vecToTarget, Vector &vecAimDir, float *flTargetRange );
+ bool FireCannonRound( void );
+ void DoImpactEffect( trace_t &tr, int nDamageType );
+ void StartCannon( void );
+ void StopCannon( void );
+ void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+
+ // Input handlers.
+ void InputLandLeave( inputdata_t &inputdata );
+ void InputLandTake( inputdata_t &inputdata );
+ void InputSetLandTarget( inputdata_t &inputdata );
+ void InputDropMines( inputdata_t &inputdata );
+ void InputDropStrider( inputdata_t &inputdata );
+ void InputDropAPC( inputdata_t &inputdata );
+
+ void InputPickup( inputdata_t &inputdata );
+ void InputSetGunRange( inputdata_t &inputdata );
+ void InputNPCFinishDustoff( inputdata_t &inputdata );
+ void InputStopWaitingForDropoff( inputdata_t &inputdata );
+
+ void InputHover( inputdata_t &inputdata );
+
+ // From AI_TrackPather
+ virtual void InputFlyToPathTrack( inputdata_t &inputdata );
+
+ Vector GetDropoffFinishPosition( Vector vecOrigin, CAI_BaseNPC *pNPC, Vector vecMins, Vector vecMaxs );
+ void LandCommon( bool bHover = false );
+
+ Class_T Classify( void ) { return CLASS_COMBINE_GUNSHIP; }
+
+ // Drop the soldier container
+ void DropSoldierContainer( );
+
+ // Sounds
+ virtual void UpdateRotorWashVolume();
+
+private:
+ void SetLandingState( LandingState_t landingState );
+ LandingState_t GetLandingState() const { return (LandingState_t)m_iLandState; }
+ bool IsHovering();
+ void UpdateGroundRotorWashSound( float flAltitude );
+ void UpdateRotorWashVolume( CSoundPatch *pRotorSound, float flVolume, float flDeltaTime );
+
+private:
+ // Timers
+ float m_flTimeTakeOff;
+ float m_flNextTroopSpawnAttempt;
+ float m_flDropDelay; // delta between each mine
+ float m_flTimeNextAttack;
+ float m_flLastTime;
+
+ // States and counters
+ int m_iMineCount; // index for current mine # being deployed
+ int m_totalMinesToDrop; // total # of mines to drop as a group (based upon triggered input)
+ int m_soldiersToDrop;
+ int m_iDropState;
+ int m_iLandState;
+ float m_engineThrust; // for tracking sound volume/pitch
+ float m_existPitch;
+ float m_existRoll;
+ bool m_bDropMines; // signal to drop mines
+ bool m_bIsFiring;
+ int m_iBurstRounds;
+ bool m_leaveCrate;
+ bool m_bHasDroppedOff;
+ int m_iCrateType;
+ float m_flLandingSpeed;
+ float m_flGunRange;
+ bool m_bInvulnerable;
+
+ QAngle m_vecAngAcceleration;
+
+ // Misc Vars
+ CHandle<CBaseAnimating> m_hContainer;
+ EHANDLE m_hPickupTarget;
+ int m_iContainerMoveType;
+ bool m_bWaitForDropoffInput;
+
+ DECLARE_DATADESC();
+ DEFINE_CUSTOM_AI;
+
+ EHANDLE m_hLandTarget;
+ string_t m_iszLandTarget;
+
+ string_t m_iszAPCVehicleName;
+
+ // Templates for soldier's dropped off
+ string_t m_sNPCTemplate[ DROPSHIP_MAX_SOLDIERS ];
+ string_t m_sNPCTemplateData[ DROPSHIP_MAX_SOLDIERS ];
+ string_t m_sDustoffPoints[ DROPSHIP_MAX_SOLDIERS ];
+ int m_iCurrentTroopExiting;
+ EHANDLE m_hLastTroopToLeave;
+
+ // Template for rollermines dropped by this dropship
+ string_t m_sRollermineTemplate;
+ string_t m_sRollermineTemplateData;
+
+ // Cached attachment points
+ int m_iMuzzleAttachment;
+ int m_iMachineGunBaseAttachment;
+ int m_iMachineGunRefAttachment;
+ int m_iAttachmentTroopDeploy;
+ int m_iAttachmentDeployStart;
+
+ // Sounds
+ CSoundPatch *m_pCannonSound;
+ CSoundPatch *m_pRotorOnGroundSound;
+ CSoundPatch *m_pDescendingWarningSound;
+ CSoundPatch *m_pNearRotorSound;
+
+ // Outputs
+ COutputEvent m_OnFinishedDropoff;
+ COutputEvent m_OnFinishedPickup;
+
+ COutputFloat m_OnContainerShotDownBeforeDropoff;
+ COutputEvent m_OnContainerShotDownAfterDropoff;
+
+protected:
+ // Because the combine dropship is a leaf class, we can use
+ // static variables to store this information, and save some memory.
+ // Should the dropship end up having inheritors, their activate may
+ // stomp these numbers, in which case you should make these ordinary members
+ // again.
+ static int m_poseBody_Accel, m_poseBody_Sway, m_poseCargo_Body_Accel, m_poseCargo_Body_Sway,
+ m_poseWeapon_Pitch, m_poseWeapon_Yaw;
+ static bool m_sbStaticPoseParamsLoaded;
+ virtual void PopulatePoseParameters( void );
+};
+
+bool CNPC_CombineDropship::m_sbStaticPoseParamsLoaded = false;
+
+int CNPC_CombineDropship::m_poseBody_Accel = 0;
+int CNPC_CombineDropship::m_poseBody_Sway = 0;
+int CNPC_CombineDropship::m_poseCargo_Body_Accel = 0;
+int CNPC_CombineDropship::m_poseCargo_Body_Sway = 0;
+int CNPC_CombineDropship::m_poseWeapon_Pitch = 0;
+int CNPC_CombineDropship::m_poseWeapon_Yaw = 0;
+
+//-----------------------------------------------------------------------------
+// Purpose: Cache whatever pose parameters we intend to use
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::PopulatePoseParameters( void )
+{
+ if (!m_sbStaticPoseParamsLoaded)
+ {
+ m_poseBody_Accel = LookupPoseParameter( "body_accel");
+ m_poseBody_Sway = LookupPoseParameter( "body_sway" );
+ m_poseCargo_Body_Accel = LookupPoseParameter( "cargo_body_accel" );
+ m_poseCargo_Body_Sway = LookupPoseParameter( "cargo_body_sway" );
+ m_poseWeapon_Pitch = LookupPoseParameter( "weapon_pitch" );
+ m_poseWeapon_Yaw = LookupPoseParameter( "weapon_yaw" );
+
+ m_sbStaticPoseParamsLoaded = true;
+ }
+
+ BaseClass::PopulatePoseParameters();
+}
+
+//------------------------------------------------------------------------------
+//
+// Combine Dropship Container implementation:
+//
+//------------------------------------------------------------------------------
+LINK_ENTITY_TO_CLASS( prop_dropship_container, CCombineDropshipContainer )
+
+BEGIN_DATADESC( CCombineDropshipContainer )
+
+ DEFINE_FIELD( m_nSmokeTrailCount, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hLastInflictor, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flLastHitTime, FIELD_TIME ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Precache
+//-----------------------------------------------------------------------------
+void CCombineDropshipContainer::Precache()
+{
+ PrecacheModel( DROPSHIP_CONTAINER_MODEL );
+
+ // Set this here to quiet base prop warnings
+ SetModel( DROPSHIP_CONTAINER_MODEL );
+
+ BaseClass::Precache();
+
+ int i;
+ for ( i = 0; i < DROPSHIP_CONTAINER_MAX_CHUNKS; ++i )
+ {
+ PrecacheModel( s_pChunkModelName[i] );
+ }
+
+ for ( i = 0; i < DROPSHIP_CONTAINER_MAX_GIBS; ++i )
+ {
+ PrecacheModel( s_pGibModelName[i] );
+ }
+
+ PropBreakablePrecacheAll( GetModelName() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Spawn
+//-----------------------------------------------------------------------------
+void CCombineDropshipContainer::Spawn()
+{
+ // NOTE: Model must be set before spawn
+ SetModel( DROPSHIP_CONTAINER_MODEL );
+ SetSolid( SOLID_VPHYSICS );
+
+ BaseClass::Spawn();
+
+#ifdef _XBOX
+ AddEffects( EF_NOSHADOW );
+#endif //_XBOX
+
+ m_iHealth = m_iMaxHealth = sk_dropship_container_health.GetFloat();
+}
+
+
+//-----------------------------------------------------------------------------
+// Allows us to use vphysics
+//-----------------------------------------------------------------------------
+bool CCombineDropshipContainer::OverridePropdata( void )
+{
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Should we trigger a damage effect?
+//-----------------------------------------------------------------------------
+inline bool CCombineDropshipContainer::ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const
+{
+ int nPrevRange = (int)( ((float)nPrevHealth / (float)GetMaxHealth()) * nEffectCount );
+ int nRange = (int)( ((float)GetHealth() / (float)GetMaxHealth()) * nEffectCount );
+ return ( nRange != nPrevRange );
+}
+
+
+//-----------------------------------------------------------------------------
+// Character killed (only fired once)
+//-----------------------------------------------------------------------------
+void CCombineDropshipContainer::CreateCorpse()
+{
+ m_lifeState = LIFE_DEAD;
+
+ Vector vecNormalizedMins, vecNormalizedMaxs;
+ Vector vecAbsMins, vecAbsMaxs;
+ CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
+ CollisionProp()->WorldToNormalizedSpace( vecAbsMins, &vecNormalizedMins );
+ CollisionProp()->WorldToNormalizedSpace( vecAbsMaxs, &vecNormalizedMaxs );
+
+ // Explode
+ Vector vecAbsPoint;
+ CPASFilter filter( GetAbsOrigin() );
+ CollisionProp()->RandomPointInBounds( vecNormalizedMins, vecNormalizedMaxs, &vecAbsPoint);
+ te->Explosion( filter, 0.0f, &vecAbsPoint, g_sModelIndexFireball,
+ random->RandomInt( 4, 10 ), random->RandomInt( 8, 15 ), TE_EXPLFLAG_NOPARTICLES, 100, 0 );
+
+ // Break into chunks
+ Vector angVelocity;
+ QAngleToAngularImpulse( GetLocalAngularVelocity(), angVelocity );
+ PropBreakableCreateAll( GetModelIndex(), VPhysicsGetObject(), GetAbsOrigin(), GetAbsAngles(), GetAbsVelocity(), angVelocity, 1.0, 250, COLLISION_GROUP_NPC, this );
+
+ // Create flaming gibs
+ int iChunks = random->RandomInt( 4, 6 );
+ for ( int i = 0; i < iChunks; i++ )
+ {
+ ThrowFlamingGib();
+ }
+
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ AddEffects( EF_NODRAW );
+ UTIL_Remove( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Character killed (only fired once)
+//-----------------------------------------------------------------------------
+void CCombineDropshipContainer::ThrowFlamingGib( void )
+{
+ Vector vecAbsMins, vecAbsMaxs;
+ CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
+
+ Vector vecNormalizedMins, vecNormalizedMaxs;
+ CollisionProp()->WorldToNormalizedSpace( vecAbsMins, &vecNormalizedMins );
+ CollisionProp()->WorldToNormalizedSpace( vecAbsMaxs, &vecNormalizedMaxs );
+
+ Vector vecAbsPoint;
+ CPASFilter filter( GetAbsOrigin() );
+ CollisionProp()->RandomPointInBounds( vecNormalizedMins, vecNormalizedMaxs, &vecAbsPoint);
+
+ // Throw a flaming, smoking chunk.
+ CGib *pChunk = CREATE_ENTITY( CGib, "gib" );
+ pChunk->Spawn( "models/gibs/hgibs.mdl" );
+ pChunk->SetBloodColor( DONT_BLEED );
+
+ QAngle vecSpawnAngles;
+ vecSpawnAngles.Random( -90, 90 );
+ pChunk->SetAbsOrigin( vecAbsPoint );
+ pChunk->SetAbsAngles( vecSpawnAngles );
+
+ int nGib = random->RandomInt( 0, DROPSHIP_CONTAINER_MAX_CHUNKS - 1 );
+ pChunk->Spawn( s_pChunkModelName[nGib] );
+ pChunk->SetOwnerEntity( this );
+ pChunk->m_lifeTime = random->RandomFloat( 6.0f, 8.0f );
+ pChunk->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+ IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
+
+ // Set the velocity
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->EnableMotion( true );
+ Vector vecVelocity;
+
+ QAngle angles;
+ angles.x = random->RandomFloat( -20, 20 );
+ angles.y = random->RandomFloat( 0, 360 );
+ angles.z = 0.0f;
+ AngleVectors( angles, &vecVelocity );
+
+ vecVelocity *= random->RandomFloat( 300, 900 );
+ vecVelocity += GetAbsVelocity();
+
+ AngularImpulse angImpulse;
+ angImpulse = RandomAngularImpulse( -180, 180 );
+
+ pChunk->SetAbsVelocity( vecVelocity );
+ pPhysicsObject->SetVelocity(&vecVelocity, &angImpulse );
+ }
+
+ CEntityFlame *pFlame = CEntityFlame::Create( pChunk, false );
+ if ( pFlame != NULL )
+ {
+ pFlame->SetLifetime( pChunk->m_lifeTime );
+ }
+
+ SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
+ if( pSmokeTrail )
+ {
+ pSmokeTrail->m_SpawnRate = 80;
+ pSmokeTrail->m_ParticleLifetime = 0.8f;
+ pSmokeTrail->m_StartColor.Init(0.3, 0.3, 0.3);
+ pSmokeTrail->m_EndColor.Init(0.5, 0.5, 0.5);
+ pSmokeTrail->m_StartSize = 10;
+ pSmokeTrail->m_EndSize = 40;
+ pSmokeTrail->m_SpawnRadius = 5;
+ pSmokeTrail->m_Opacity = 0.4;
+ pSmokeTrail->m_MinSpeed = 15;
+ pSmokeTrail->m_MaxSpeed = 25;
+ pSmokeTrail->SetLifetime( pChunk->m_lifeTime );
+ pSmokeTrail->SetParent( pChunk, 0 );
+ pSmokeTrail->SetLocalOrigin( vec3_origin );
+ pSmokeTrail->SetMoveType( MOVETYPE_NONE );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Character killed (only fired once)
+//-----------------------------------------------------------------------------
+void CCombineDropshipContainer::Event_Killed( const CTakeDamageInfo &info )
+{
+ if ( GetOwnerEntity() )
+ {
+ CNPC_CombineDropship *pDropship = assert_cast<CNPC_CombineDropship *>(GetOwnerEntity() );
+ pDropship->DropSoldierContainer();
+ }
+
+ CreateCorpse();
+}
+
+
+//-----------------------------------------------------------------------------
+// Damage effects
+//-----------------------------------------------------------------------------
+int CCombineDropshipContainer::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ if ( m_iHealth == 0 )
+ return 0;
+
+ // Airboat guns + explosive damage is all that can hurt it
+ if (( info.GetDamageType() & (DMG_BLAST | DMG_AIRBOAT) ) == 0 )
+ return 0;
+
+ CTakeDamageInfo dmgInfo = info;
+
+ int nPrevHealth = GetHealth();
+
+ if ( info.GetDamageType() & DMG_BLAST )
+ {
+ // This check is necessary to prevent double-counting of rocket damage
+ // from the blast hitting both the dropship + the container
+ if ( (info.GetInflictor() != m_hLastInflictor) || (gpGlobals->curtime != m_flLastHitTime) )
+ {
+ m_iHealth -= (m_iMaxHealth / DROPSHIP_CRATE_ROCKET_HITS) + 1;
+ m_hLastInflictor = info.GetInflictor();
+ m_flLastHitTime = gpGlobals->curtime;
+ }
+ }
+ else
+ {
+ m_iHealth -= dmgInfo.GetDamage();
+ }
+
+ if ( m_iHealth <= 0 )
+ {
+ m_iHealth = 0;
+ Event_Killed( dmgInfo );
+ return 0;
+ }
+
+ // Spawn damage effects
+ if ( nPrevHealth != GetHealth() )
+ {
+ if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_SMOKE_TRAILS ) )
+ {
+ AddSmokeTrail( dmgInfo.GetDamagePosition() );
+ }
+
+ if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) )
+ {
+ ExplosionCreate( dmgInfo.GetDamagePosition(), vec3_angle, this, 1000, 500.0f,
+ SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0 );
+ UTIL_ScreenShake( dmgInfo.GetDamagePosition(), 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
+
+ ThrowFlamingGib();
+ }
+ }
+
+ return 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Add a smoke trail since we've taken more damage
+//-----------------------------------------------------------------------------
+void CCombineDropshipContainer::AddSmokeTrail( const Vector &vecPos )
+{
+ // Start this trail out with a bang!
+ ExplosionCreate( vecPos, vec3_angle, this, 1000, 500.0f, SF_ENVEXPLOSION_NODAMAGE |
+ SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0 );
+ UTIL_ScreenShake( vecPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
+
+ if ( m_nSmokeTrailCount == MAX_SMOKE_TRAILS )
+ return;
+
+ SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
+ if( !pSmokeTrail )
+ return;
+
+ // See if there's an attachment for this smoke trail
+ char buf[32];
+ Q_snprintf( buf, 32, "damage%d", m_nSmokeTrailCount );
+ int nAttachment = LookupAttachment( buf );
+
+ ++m_nSmokeTrailCount;
+
+ pSmokeTrail->m_SpawnRate = 20;
+ pSmokeTrail->m_ParticleLifetime = 4.0f;
+ pSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f );
+ pSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 );
+ pSmokeTrail->m_StartSize = 15;
+ pSmokeTrail->m_EndSize = 50;
+ pSmokeTrail->m_SpawnRadius = 15;
+ pSmokeTrail->m_Opacity = 0.75f;
+ pSmokeTrail->m_MinSpeed = 10;
+ pSmokeTrail->m_MaxSpeed = 20;
+ pSmokeTrail->m_MinDirectedSpeed = 100.0f;
+ pSmokeTrail->m_MaxDirectedSpeed = 120.0f;
+ pSmokeTrail->SetLifetime( 5 );
+ pSmokeTrail->SetParent( this, nAttachment );
+ if ( nAttachment == 0 )
+ {
+ pSmokeTrail->SetAbsOrigin( vecPos );
+ }
+ else
+ {
+ pSmokeTrail->SetLocalOrigin( vec3_origin );
+ }
+
+ Vector vecForward( -1, 0, 0 );
+ QAngle angles;
+ VectorAngles( vecForward, angles );
+ pSmokeTrail->SetAbsAngles( angles );
+ pSmokeTrail->SetMoveType( MOVETYPE_NONE );
+}
+
+
+//------------------------------------------------------------------------------
+//
+// Combine Dropship implementation:
+//
+//------------------------------------------------------------------------------
+LINK_ENTITY_TO_CLASS( npc_combinedropship, CNPC_CombineDropship );
+
+BEGIN_DATADESC( CNPC_CombineDropship )
+
+ DEFINE_FIELD( m_flTimeTakeOff, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextTroopSpawnAttempt, FIELD_TIME ),
+ DEFINE_FIELD( m_flDropDelay, FIELD_TIME ),
+ DEFINE_FIELD( m_flTimeNextAttack, FIELD_TIME ),
+ DEFINE_FIELD( m_flLastTime, FIELD_TIME ),
+ DEFINE_FIELD( m_iMineCount, FIELD_INTEGER ),
+ DEFINE_FIELD( m_totalMinesToDrop, FIELD_INTEGER ),
+ DEFINE_FIELD( m_soldiersToDrop, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iDropState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bDropMines, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_iLandState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_engineThrust, FIELD_FLOAT ),
+ DEFINE_FIELD( m_bIsFiring, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_iBurstRounds, FIELD_INTEGER ),
+ DEFINE_FIELD( m_existPitch, FIELD_FLOAT ),
+ DEFINE_FIELD( m_existRoll, FIELD_FLOAT ),
+ DEFINE_FIELD( m_leaveCrate, FIELD_BOOLEAN ),
+ DEFINE_KEYFIELD( m_iCrateType, FIELD_INTEGER, "CrateType" ),
+ DEFINE_FIELD( m_flLandingSpeed, FIELD_FLOAT ),
+ DEFINE_KEYFIELD( m_flGunRange, FIELD_FLOAT, "GunRange" ),
+ DEFINE_FIELD( m_vecAngAcceleration,FIELD_VECTOR ),
+ DEFINE_FIELD( m_hContainer, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hPickupTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_iContainerMoveType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bWaitForDropoffInput, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hLandTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bHasDroppedOff, FIELD_BOOLEAN ),
+ DEFINE_KEYFIELD( m_bInvulnerable, FIELD_BOOLEAN, "Invulnerable" ),
+ DEFINE_KEYFIELD( m_iszLandTarget, FIELD_STRING, "LandTarget" ),
+ DEFINE_SOUNDPATCH( m_pRotorOnGroundSound ),
+ DEFINE_SOUNDPATCH( m_pDescendingWarningSound ),
+ DEFINE_SOUNDPATCH( m_pNearRotorSound ),
+
+ DEFINE_KEYFIELD( m_iszAPCVehicleName, FIELD_STRING, "APCVehicleName" ),
+
+ DEFINE_KEYFIELD( m_sRollermineTemplate, FIELD_STRING, "RollermineTemplate" ),
+ DEFINE_FIELD( m_sRollermineTemplateData, FIELD_STRING ),
+
+ DEFINE_ARRAY( m_sNPCTemplateData, FIELD_STRING, DROPSHIP_MAX_SOLDIERS ),
+ DEFINE_KEYFIELD( m_sNPCTemplate[0], FIELD_STRING, "NPCTemplate" ),
+ DEFINE_KEYFIELD( m_sNPCTemplate[1], FIELD_STRING, "NPCTemplate2" ),
+ DEFINE_KEYFIELD( m_sNPCTemplate[2], FIELD_STRING, "NPCTemplate3" ),
+ DEFINE_KEYFIELD( m_sNPCTemplate[3], FIELD_STRING, "NPCTemplate4" ),
+ DEFINE_KEYFIELD( m_sNPCTemplate[4], FIELD_STRING, "NPCTemplate5" ),
+ DEFINE_KEYFIELD( m_sNPCTemplate[5], FIELD_STRING, "NPCTemplate6" ),
+ // Here to shut classcheck up
+ //DEFINE_ARRAY( m_sNPCTemplate, FIELD_STRING, DROPSHIP_MAX_SOLDIERS ),
+ //DEFINE_ARRAY( m_sDustoffPoints, FIELD_STRING, DROPSHIP_MAX_SOLDIERS ),
+ DEFINE_KEYFIELD( m_sDustoffPoints[0], FIELD_STRING, "Dustoff1" ),
+ DEFINE_KEYFIELD( m_sDustoffPoints[1], FIELD_STRING, "Dustoff2" ),
+ DEFINE_KEYFIELD( m_sDustoffPoints[2], FIELD_STRING, "Dustoff3" ),
+ DEFINE_KEYFIELD( m_sDustoffPoints[3], FIELD_STRING, "Dustoff4" ),
+ DEFINE_KEYFIELD( m_sDustoffPoints[4], FIELD_STRING, "Dustoff5" ),
+ DEFINE_KEYFIELD( m_sDustoffPoints[5], FIELD_STRING, "Dustoff6" ),
+ DEFINE_FIELD( m_iCurrentTroopExiting, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hLastTroopToLeave, FIELD_EHANDLE ),
+
+ DEFINE_FIELD( m_iMuzzleAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iMachineGunBaseAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iMachineGunRefAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iAttachmentTroopDeploy, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iAttachmentDeployStart , FIELD_INTEGER ),
+
+ DEFINE_SOUNDPATCH( m_pCannonSound ),
+
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "LandLeaveCrate", InputLandLeave ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "LandTakeCrate", InputLandTake ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetLandTarget", InputSetLandTarget ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "DropMines", InputDropMines ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DropStrider", InputDropStrider ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DropAPC", InputDropAPC ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "Pickup", InputPickup ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetGunRange", InputSetGunRange ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "NPCFinishDustoff", InputNPCFinishDustoff ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StopWaitingForDropoff", InputStopWaitingForDropoff ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "Hover", InputHover ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "FlyToPathTrack", InputFlyToPathTrack ),
+
+ DEFINE_OUTPUT( m_OnFinishedDropoff, "OnFinishedDropoff" ),
+ DEFINE_OUTPUT( m_OnFinishedPickup, "OnFinishedPickup" ),
+
+ DEFINE_OUTPUT( m_OnContainerShotDownBeforeDropoff, "OnCrateShotDownBeforeDropoff" ),
+ DEFINE_OUTPUT( m_OnContainerShotDownAfterDropoff, "OnCrateShotDownAfterDropoff" ),
+
+END_DATADESC()
+
+
+//------------------------------------------------------------------------------
+// Purpose : Destructor
+//------------------------------------------------------------------------------
+CNPC_CombineDropship::~CNPC_CombineDropship(void)
+{
+ if ( m_hContainer )
+ {
+ UTIL_Remove( m_hContainer ); // get rid of container
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::Spawn( void )
+{
+ Precache( );
+ SetModel( "models/combine_dropship.mdl" );
+
+#ifdef _XBOX
+ AddEffects( EF_NOSHADOW );
+#endif //_XBOX
+
+ InitPathingData( DROPSHIP_ARRIVE_DIST, DROPSHIP_MIN_CHASE_DIST_DIFF, DROPSHIP_AVOID_DIST );
+
+ m_iContainerMoveType = MOVETYPE_NONE;
+ m_iCurrentTroopExiting = 0;
+ m_bHasDroppedOff = false;
+ m_iMuzzleAttachment = -1;
+ m_iMachineGunBaseAttachment = -1;
+ m_iMachineGunRefAttachment = -1;
+ m_iAttachmentTroopDeploy = -1;
+ m_iAttachmentDeployStart = -1;
+
+ // create the correct bin for the ship to carry
+ switch ( m_iCrateType )
+ {
+ case CRATE_ROLLER_HOPPER:
+ break;
+
+ case CRATE_SOLDIER:
+ m_hContainer = (CBaseAnimating*)CreateEntityByName( "prop_dropship_container" );
+ if ( m_hContainer )
+ {
+ m_hContainer->SetName( AllocPooledString("dropship_container") );
+ m_hContainer->SetAbsOrigin( GetAbsOrigin() );
+ m_hContainer->SetAbsAngles( GetAbsAngles() );
+ m_hContainer->SetParent(this, 0);
+ m_hContainer->SetOwnerEntity(this);
+ m_hContainer->Spawn();
+
+ IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->SetShadow( 1e4, 1e4, false, false );
+ pPhysicsObject->UpdateShadow( m_hContainer->GetAbsOrigin(), m_hContainer->GetAbsAngles(), false, 0 );
+ }
+
+ m_hContainer->SetMoveType( MOVETYPE_PUSH );
+ m_hContainer->SetGroundEntity( NULL );
+
+ // Cache off container's attachment points
+ m_iAttachmentTroopDeploy = m_hContainer->LookupAttachment( "deploy_landpoint" );
+ m_iAttachmentDeployStart = m_hContainer->LookupAttachment( "Deploy_Start" );
+ m_iMuzzleAttachment = m_hContainer->LookupAttachment( "muzzle" );
+ m_iMachineGunBaseAttachment = m_hContainer->LookupAttachment( "gun_base" );
+ // NOTE: gun_ref must have the same position as gun_base, but rotates with the gun
+ m_iMachineGunRefAttachment = m_hContainer->LookupAttachment( "gun_ref" );
+ }
+ break;
+
+ case CRATE_STRIDER:
+ m_hContainer = (CBaseAnimating*)CreateEntityByName( "npc_strider" );
+ m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector( 0, 0 , 100 ) );
+ m_hContainer->SetAbsAngles( GetAbsAngles() );
+ m_hContainer->SetParent(this, 0);
+ m_hContainer->SetOwnerEntity(this);
+ m_hContainer->Spawn();
+ m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector( 0, 0 , 100 ) );
+ break;
+
+ case CRATE_APC:
+ {
+ m_soldiersToDrop = 0;
+ m_hContainer = (CBaseAnimating*)gEntList.FindEntityByName( NULL, m_iszAPCVehicleName );
+ if ( !m_hContainer )
+ {
+ Warning("Unable to find APC %s\n", STRING( m_iszAPCVehicleName ) );
+ break;
+ }
+
+ Vector apcPosition = GetAbsOrigin() - Vector( 0, 0 , 25 );
+ QAngle apcAngles = GetAbsAngles();
+ VMatrix mat, rot, result;
+ MatrixFromAngles( apcAngles, mat );
+ MatrixBuildRotateZ( rot, -90 );
+ MatrixMultiply( mat, rot, result );
+ MatrixToAngles( result, apcAngles );
+
+ m_hContainer->Teleport( &apcPosition, &apcAngles, NULL );
+
+ m_iContainerMoveType = m_hContainer->GetMoveType();
+
+ IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->SetShadow( 1e4, 1e4, false, false );
+ }
+
+ m_hContainer->SetParent(this, 0);
+ m_hContainer->SetOwnerEntity(this);
+ m_hContainer->SetMoveType( MOVETYPE_PUSH );
+ m_hContainer->SetGroundEntity( NULL );
+ m_hContainer->UpdatePhysicsShadowToCurrentPosition(0);
+ }
+ break;
+
+ case CRATE_JEEP:
+ m_hContainer = (CBaseAnimating*)CreateEntityByName( "prop_dynamic_override" );
+ if ( m_hContainer )
+ {
+ m_hContainer->SetModel( "models/buggy.mdl" );
+ m_hContainer->SetName( AllocPooledString("dropship_jeep") );
+
+ m_hContainer->SetAbsOrigin( GetAbsOrigin() );//- Vector( 0, 0 , 25 ) );
+ QAngle angles = GetAbsAngles();
+ VMatrix mat, rot, result;
+ MatrixFromAngles( angles, mat );
+ MatrixBuildRotateZ( rot, -90 );
+ MatrixMultiply( mat, rot, result );
+ MatrixToAngles( result, angles );
+ m_hContainer->SetAbsAngles( angles );
+
+ m_hContainer->SetParent(this, 0);
+ m_hContainer->SetOwnerEntity(this);
+ m_hContainer->SetSolid( SOLID_VPHYSICS );
+ m_hContainer->Spawn();
+ }
+ break;
+
+ case CRATE_NONE:
+ default:
+ break;
+ }
+
+ // Setup our bbox
+ if ( m_hContainer )
+ {
+ UTIL_SetSize( this, DROPSHIP_BBOX_CRATE_MIN, DROPSHIP_BBOX_CRATE_MAX );
+ SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_CARGO );
+ }
+ else
+ {
+ UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );
+ SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_EXAGG );
+ }
+
+ m_cullBoxMins = WorldAlignMins() - Vector(300,300,200);
+ m_cullBoxMaxs = WorldAlignMaxs() + Vector(300,300,200);
+ BaseClass::Spawn();
+
+ // Dropship ignores all damage, but can deal it to its carried container
+ m_takedamage = m_bInvulnerable ? DAMAGE_NO : DAMAGE_YES;
+ if ( m_bInvulnerable && m_hContainer )
+ {
+ m_hContainer->m_takedamage = DAMAGE_NO;
+ }
+
+ m_iHealth = 100;
+ m_flFieldOfView = 0.5; // 60 degrees
+ m_iBurstRounds = 15;
+
+ InitBoneControllers();
+ InitCustomSchedules();
+
+ m_flMaxSpeed = DROPSHIP_MAX_SPEED;
+ m_flMaxSpeedFiring = BASECHOPPER_MAX_FIRING_SPEED;
+ m_hPickupTarget = NULL;
+ m_hLandTarget = NULL;
+
+ //!!!HACKHACK
+ // This tricks the AI code that constantly complains that the vehicle has no schedule.
+ SetSchedule( SCHED_IDLE_STAND );
+
+ SetLandingState( LANDING_NO );
+
+ if ( HasSpawnFlags( SF_DROPSHIP_WAIT_FOR_DROPOFF_INPUT ) )
+ {
+ m_bWaitForDropoffInput = true;
+ }
+ else
+ {
+ m_bWaitForDropoffInput = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called after spawning on map load or on a load from save game.
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::Activate( void )
+{
+ BaseClass::Activate();
+
+ if ( !m_sRollermineTemplateData )
+ {
+ m_sRollermineTemplateData = NULL_STRING;
+ if ( m_sRollermineTemplate != NULL_STRING )
+ {
+ // This must be the first time we're activated, not a load from save game.
+ // Look up the template in the template database.
+ m_sRollermineTemplateData = Templates_FindByTargetName(STRING(m_sRollermineTemplate));
+ if ( m_sRollermineTemplateData == NULL_STRING )
+ {
+ Warning( "npc_combinedropship %s: Rollermine Template %s not found!\n", STRING(GetEntityName()), STRING(m_sRollermineTemplate) );
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::Precache( void )
+{
+ // Models
+ PrecacheModel("models/combine_dropship.mdl");
+ switch ( m_iCrateType )
+ {
+ case CRATE_SOLDIER:
+ UTIL_PrecacheOther( "prop_dropship_container" );
+
+ //
+ // Precache the all templates that we are configured to spawn
+ //
+ for ( int i = 0; i < DROPSHIP_MAX_SOLDIERS; i++ )
+ {
+ if ( m_sNPCTemplate[i] != NULL_STRING )
+ {
+ if ( m_sNPCTemplateData[i] == NULL_STRING )
+ {
+ m_sNPCTemplateData[i] = Templates_FindByTargetName(STRING(m_sNPCTemplate[i]));
+ }
+ if ( m_sNPCTemplateData[i] != NULL_STRING )
+ {
+ CBaseEntity *pEntity = NULL;
+ MapEntity_ParseEntity( pEntity, STRING(m_sNPCTemplateData[i]), NULL );
+ if ( pEntity != NULL )
+ {
+ pEntity->Precache();
+ UTIL_RemoveImmediate( pEntity );
+ }
+ }
+ else
+ {
+ Warning( "npc_combinedropship %s: Template NPC %s not found!\n", STRING(GetEntityName()), STRING(m_sNPCTemplate[i]) );
+
+ // Use the first template we've got
+ m_sNPCTemplateData[i] = m_sNPCTemplateData[0];
+ }
+
+ // Make sure we've got a dustoff point for it
+ if ( m_sDustoffPoints[i] == NULL_STRING )
+ {
+ Warning( "npc_combinedropship %s: Has no dustoff point for NPC %d!\n", STRING(GetEntityName()), i );
+ }
+ }
+ else
+ {
+ m_sNPCTemplateData[i] = NULL_STRING;
+ }
+ }
+ break;
+
+ case CRATE_JEEP:
+ PrecacheModel("models/buggy.mdl");
+ break;
+
+ default:
+ break;
+ }
+
+ PrecacheScriptSound( "NPC_CombineDropship.RotorLoop" );
+ PrecacheScriptSound( "NPC_CombineDropship.FireLoop" );
+ PrecacheScriptSound( "NPC_CombineDropship.NearRotorLoop" );
+ PrecacheScriptSound( "NPC_CombineDropship.OnGroundRotorLoop" );
+ PrecacheScriptSound( "NPC_CombineDropship.DescendingWarningLoop" );
+ PrecacheScriptSound( "NPC_CombineDropship.NearRotorLoop" );
+
+ if ( m_sRollermineTemplate != NULL_STRING )
+ {
+ UTIL_PrecacheOther( "npc_rollermine" );
+ }
+
+ BaseClass::Precache();
+
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::Flight( void )
+{
+ // Only run the flight model in some flight states
+ bool bRunFlight = ( GetLandingState() == LANDING_NO ||
+ GetLandingState() == LANDING_LEVEL_OUT ||
+ GetLandingState() == LANDING_LIFTOFF ||
+ GetLandingState() == LANDING_SWOOPING ||
+ GetLandingState() == LANDING_DESCEND ||
+ GetLandingState() == LANDING_HOVER_LEVEL_OUT ||
+ GetLandingState() == LANDING_HOVER_DESCEND );
+
+ Vector forward, right, up;
+ GetVectors( &forward, &right, &up );
+
+ float finspeed = 0;
+ float swayspeed = 0;
+ Vector vecImpulse = vec3_origin;
+
+ //Adrian: Slowly lerp the orientation and position of the cargo into place...
+ //We assume CRATE_NONE means the dropship just picked up some random phys object.
+ if ( m_hContainer != NULL && ( m_iCrateType == CRATE_SOLDIER || m_iCrateType == CRATE_NONE ) )
+ {
+ if ( m_hContainer->GetLocalOrigin() != vec3_origin )
+ {
+ Vector vCurrentLocalOrigin = m_hContainer->GetLocalOrigin();
+ Vector vLocalOrigin;
+
+ VectorLerp( vCurrentLocalOrigin, vec3_origin, 0.05f, vLocalOrigin );
+
+ m_hContainer->SetLocalOrigin( vLocalOrigin );
+ }
+
+ if ( m_hContainer->GetLocalAngles() != vec3_angle )
+ {
+ QAngle vCurrentLocalAngles = m_hContainer->GetLocalAngles();
+ QAngle vLocalAngles;
+
+ vLocalAngles = Lerp( 0.05f, vCurrentLocalAngles, vec3_angle );
+
+ m_hContainer->SetLocalAngles( vLocalAngles );
+ }
+ }
+
+ if ( bRunFlight )
+ {
+ if( GetFlags() & FL_ONGROUND )
+ {
+ // This would be really bad.
+ SetGroundEntity( NULL );
+ }
+
+ // calc desired acceleration
+ float dt = 1.0f;
+
+ Vector accel;
+ float accelRate = DROPSHIP_ACCEL_RATE;
+ float maxSpeed = GetMaxSpeed();
+
+ if ( m_lifeState == LIFE_DYING )
+ {
+ accelRate *= 5.0;
+ maxSpeed *= 5.0;
+ }
+
+ float flCurrentSpeed = GetAbsVelocity().Length();
+ float flDist = MIN( flCurrentSpeed + accelRate, maxSpeed );
+
+ Vector deltaPos;
+ if ( GetLandingState() == LANDING_SWOOPING )
+ {
+ // Move directly to the target point
+ deltaPos = GetDesiredPosition();
+ }
+ else
+ {
+ ComputeActualTargetPosition( flDist, dt, 0.0f, &deltaPos );
+ }
+ deltaPos -= GetAbsOrigin();
+
+ //NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + deltaPos, 0, 255, 0, true, 0.1f );
+
+ // 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 ( IsOnPathTrack() && GetLandingState() == LANDING_NO )
+ {
+ // 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 > 200 )
+ {
+ // 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 - 200) / 200.0f;
+ flAmount = clamp( flAmount, 0, 1 );
+ VectorMA( accel, flAmount * 200.0f, vecDelta, accel );
+ }
+ }
+
+ // don't fall faster than 0.2G or climb faster than 2G
+ accel.z = clamp( accel.z, 384 * 0.2, 384 * 2.0 );
+
+ 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, -60, 60 );
+ goalAngAccel.y = clamp( goalAngAccel.y, -120, 120 );
+ goalAngAccel.z = clamp( goalAngAccel.z, -300, 300 );
+
+ // limit angular accel changes to simulate 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, -1000, 1000 );
+ angAccelAccel.y = clamp( angAccelAccel.y, -1000, 1000 );
+ angAccelAccel.z = clamp( angAccelAccel.z, -1000, 1000 );
+
+ 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.y = clamp( angVel.y, -60, 60 );
+ //angVel.y = clamp( angVel.y, -120, 120 );
+ angVel.y = clamp( angVel.y, -120, 120 );
+
+ SetLocalAngularVelocity( angVel );
+
+ m_flForce = m_flForce * 0.8 + (accel.z + fabs( accel.x ) * 0.1 + fabs( accel.y ) * 0.1) * 0.1 * 0.2;
+
+ vecImpulse = m_flForce * up;
+
+ if ( m_lifeState == LIFE_DYING )
+ {
+ vecImpulse.z = -38.4; // 64ft/sec
+ }
+ else
+ {
+ vecImpulse.z -= 38.4; // 32ft/sec
+ }
+
+ // Find our current velocity
+ Vector vecVelDir = GetAbsVelocity();
+
+ VectorNormalize( vecVelDir );
+
+ if ( flDistFromPath > 100 )
+ {
+ // 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
+ finspeed = m_flForce * DotProduct( vecVelDir, vecAccelDir );
+ swayspeed = m_flForce * DotProduct( vecVelDir, right );
+ }
+
+ // Use the correct pose params for the state of our container
+ int poseBodyAccel;
+ int poseBodySway;
+ if ( m_hContainer || GetLandingState() == LANDING_SWOOPING )
+ {
+ poseBodyAccel = m_poseCargo_Body_Accel;
+ poseBodySway = m_poseCargo_Body_Sway;
+ SetPoseParameter( m_poseBody_Accel, 0 );
+ SetPoseParameter( m_poseBody_Sway, 0 );
+ }
+ else
+ {
+ poseBodyAccel = m_poseBody_Accel;
+ poseBodySway = m_poseBody_Sway;
+ SetPoseParameter( m_poseCargo_Body_Accel, 0 );
+ SetPoseParameter( m_poseCargo_Body_Sway, 0 );
+ }
+
+ // If we're landing, deliberately tuck in the back end
+ if ( GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_TOUCHDOWN ||
+ GetLandingState() == LANDING_UNLOADING || GetLandingState() == LANDING_UNLOADED || IsHovering() )
+ {
+ finspeed = -60;
+ }
+
+ // Apply the acceleration blend to the fins
+ float finAccelBlend = SimpleSplineRemapVal( finspeed, -60, 60, -1, 1 );
+ float curFinAccel = GetPoseParameter( poseBodyAccel );
+ curFinAccel = UTIL_Approach( finAccelBlend, curFinAccel, 0.1f );
+ SetPoseParameter( poseBodyAccel, EdgeLimitPoseParameter( poseBodyAccel, curFinAccel ) );
+
+ // Apply the spin sway to the fins
+ float finSwayBlend = SimpleSplineRemapVal( swayspeed, -60, 60, -1, 1 );
+ float curFinSway = GetPoseParameter( poseBodySway );
+ curFinSway = UTIL_Approach( finSwayBlend, curFinSway, 0.1f );
+ SetPoseParameter( poseBodySway, EdgeLimitPoseParameter( poseBodySway, curFinSway ) );
+
+ if ( bRunFlight )
+ {
+ // Add in our velocity pulse for this frame
+ ApplyAbsVelocityImpulse( vecImpulse );
+ }
+
+ //DevMsg("curFinAccel: %f, curFinSway: %f\n", curFinAccel, curFinSway );
+}
+
+
+//------------------------------------------------------------------------------
+// Deals damage to what's behing carried
+//------------------------------------------------------------------------------
+int CNPC_CombineDropship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ // FIXME: To make this work for CRATE_STRIDER or CRATE_APC, we need to
+ // add code to the strider + apc to make them not take double-damage from rockets
+ // (owing to the blast hitting the crate + the dropship). See the dropship container
+ // code above to see how to do it.
+ if ( m_hContainer && !m_bInvulnerable )
+ {
+ if ( (inputInfo.GetDamageType() & DMG_AIRBOAT) || (m_iCrateType == CRATE_SOLDIER) )
+ {
+ m_hContainer->TakeDamage( inputInfo );
+ }
+ }
+
+ // don't die
+ return 0;
+}
+
+//------------------------------------------------------------------------------
+// Updates the facing direction
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::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 :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::InitializeRotorSound( void )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+ m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.RotorLoop" );
+ m_pNearRotorSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.NearRotorLoop" );
+ m_pRotorOnGroundSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.OnGroundRotorLoop" );
+ m_pDescendingWarningSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.DescendingWarningLoop" );
+ m_pCannonSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.FireLoop" );
+
+ // NOTE: m_pRotorSound is started up by the base class
+ if ( m_pCannonSound )
+ {
+ controller.Play( m_pCannonSound, 0.0, 100 );
+ }
+
+ if ( m_pDescendingWarningSound )
+ {
+ controller.Play( m_pDescendingWarningSound, 0.0, 100 );
+ }
+
+ if ( m_pRotorOnGroundSound )
+ {
+ controller.Play( m_pRotorOnGroundSound, 0.0, 100 );
+ }
+
+ if ( m_pNearRotorSound )
+ {
+ controller.Play( m_pNearRotorSound, 0.0, 100 );
+ }
+
+ m_engineThrust = 1.0f;
+
+ BaseClass::InitializeRotorSound();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::StopLoopingSounds()
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ if ( m_pCannonSound )
+ {
+ controller.SoundDestroy( m_pCannonSound );
+ m_pCannonSound = NULL;
+ }
+
+ if ( m_pRotorOnGroundSound )
+ {
+ controller.SoundDestroy( m_pRotorOnGroundSound );
+ m_pRotorOnGroundSound = NULL;
+ }
+
+ if ( m_pDescendingWarningSound )
+ {
+ controller.SoundDestroy( m_pDescendingWarningSound );
+ m_pDescendingWarningSound = NULL;
+ }
+
+ if ( m_pNearRotorSound )
+ {
+ controller.SoundDestroy( m_pNearRotorSound );
+ m_pNearRotorSound = NULL;
+ }
+
+ BaseClass::StopLoopingSounds();
+}
+
+
+//------------------------------------------------------------------------------
+// Updates the rotor wash volume
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::UpdateRotorWashVolume( CSoundPatch *pRotorSound, float flVolume, float flDeltaTime )
+{
+ if ( !pRotorSound )
+ return;
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ float flVolDelta = flVolume - controller.SoundGetVolume( pRotorSound );
+ if ( flVolDelta )
+ {
+ // We can change from 0 to 1 in 3 seconds.
+ // Figure out how many seconds flVolDelta will take.
+ float flRampTime = fabs( flVolDelta ) * flDeltaTime;
+ controller.SoundChangeVolume( pRotorSound, flVolume, flRampTime );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Updates the rotor wash volume
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::UpdateRotorWashVolume()
+{
+ float flNearFactor = 0.0f;
+ CBaseEntity *pPlayer = UTIL_PlayerByIndex( 1 );
+ if (pPlayer)
+ {
+ float flDist = pPlayer->GetAbsOrigin().DistTo( GetAbsOrigin() );
+ flDist = clamp( flDist, DROPSHIP_NEAR_SOUND_MIN_DISTANCE, DROPSHIP_NEAR_SOUND_MAX_DISTANCE );
+ flNearFactor = RemapVal( flDist, DROPSHIP_NEAR_SOUND_MIN_DISTANCE, DROPSHIP_NEAR_SOUND_MAX_DISTANCE, 1.0f, 0.0f );
+ }
+
+ if ( m_pRotorSound )
+ {
+ UpdateRotorWashVolume( m_pRotorSound, m_engineThrust * GetRotorVolume() * (1.0f - flNearFactor), 3.0f );
+ }
+
+ if ( m_pNearRotorSound )
+ {
+ UpdateRotorWashVolume( m_pNearRotorSound, m_engineThrust * GetRotorVolume() * flNearFactor, 3.0f );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::UpdateRotorSoundPitch( int iPitch )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ float rotorPitch = 0.2 + m_engineThrust * 0.8;
+ if ( m_pRotorSound )
+ {
+ controller.SoundChangePitch( m_pRotorSound, iPitch + rotorPitch, 0.1 );
+ }
+
+ if ( m_pNearRotorSound )
+ {
+ controller.SoundChangePitch( m_pNearRotorSound, iPitch + rotorPitch, 0.1 );
+ }
+
+ if (m_pRotorOnGroundSound)
+ {
+ controller.SoundChangePitch( m_pRotorOnGroundSound, iPitch + rotorPitch, 0.1 );
+ }
+
+ UpdateRotorWashVolume();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : iSoldiers -
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::CalculateSoldierCount( int iSoldiers )
+{
+ if ( m_iCrateType >= 0 )
+ {
+ m_soldiersToDrop = clamp( iSoldiers, 0, DROPSHIP_MAX_SOLDIERS );
+ }
+ else
+ {
+ m_soldiersToDrop = 0;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Leave crate being carried
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::InputLandLeave( inputdata_t &inputdata )
+{
+ CalculateSoldierCount( inputdata.value.Int() );
+ m_leaveCrate = true;
+ LandCommon();
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Take crate being carried to next point
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::InputLandTake( inputdata_t &inputdata )
+{
+ CalculateSoldierCount( inputdata.value.Int() );
+ m_leaveCrate = false;
+ LandCommon();
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input : bHover - If true, means we're landing on a hover point, not the ground
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::LandCommon( bool bHover )
+{
+ // If we don't have a crate, we're not able to land
+ if ( !m_hContainer && !bHover )
+ return;
+
+ //DevMsg( "Landing\n" );
+
+ if( bHover )
+ {
+ SetLandingState( LANDING_HOVER_LEVEL_OUT );
+ }
+ else
+ {
+ SetLandingState( LANDING_LEVEL_OUT );
+ }
+
+ SetLocalAngularVelocity( vec3_angle );
+
+ // Do we have a land target?
+ if ( m_iszLandTarget != NULL_STRING )
+ {
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_iszLandTarget );
+ if ( !pTarget )
+ {
+ Warning("npc_combinedropship %s couldn't find land target named %s\n", STRING(GetEntityName()), STRING(m_iszLandTarget) );
+ return;
+ }
+
+ // Start heading to the point
+ m_hLandTarget = pTarget;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::InputSetLandTarget( inputdata_t &inputdata )
+{
+ m_iszLandTarget = inputdata.value.StringID();
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Drop mine inputs... done this way so generic path_corners can be used
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::InputDropMines( inputdata_t &inputdata )
+{
+ m_totalMinesToDrop = inputdata.value.Int();
+ if ( m_totalMinesToDrop >= 1 ) // catch bogus values being passed in
+ {
+ m_bDropMines = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::InputDropStrider( inputdata_t &inputdata )
+{
+ if ( !m_hContainer || !FClassnameIs( m_hContainer, "npc_strider" ) )
+ {
+ Warning("npc_combinedropship %s was told to drop Strider, but isn't carrying one!\n", STRING(GetEntityName()) );
+ return;
+ }
+
+ QAngle angles = GetAbsAngles();
+
+ angles.x = 0.0;
+ angles.z = 0.0;
+
+ m_hContainer->SetParent(NULL, 0);
+ m_hContainer->SetOwnerEntity(NULL);
+ m_hContainer->SetAbsAngles( angles );
+ m_hContainer->SetAbsVelocity( vec3_origin );
+
+ m_hContainer = NULL;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::InputDropAPC( inputdata_t &inputdata )
+{
+ if ( !m_hContainer || !FClassnameIs( m_hContainer, "prop_vehicle_apc" ) )
+ {
+ Warning("npc_combinedropship %s was told to drop APC, but isn't carrying one!\n", STRING(GetEntityName()) );
+ return;
+ }
+
+ m_hContainer->SetParent(NULL, 0);
+// m_hContainer->SetOwnerEntity(NULL);
+
+ Vector vecAbsVelocity = GetAbsVelocity();
+ if ( vecAbsVelocity.z > 0 )
+ {
+ vecAbsVelocity.z = 0.0f;
+ }
+ if ( m_hContainer->GetHealth() > 0 )
+ {
+ vecAbsVelocity = vec3_origin;
+ }
+
+ m_hContainer->SetAbsVelocity( vecAbsVelocity );
+ m_hContainer->SetMoveType( (MoveType_t)m_iContainerMoveType );
+
+ // If the container has a physics object, remove it's shadow
+ IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->RemoveShadowController();
+ }
+
+ UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );
+
+ m_hContainer = NULL;
+ m_OnFinishedDropoff.FireOutput( this, this );
+ SetLandingState( LANDING_NO );
+ m_hLandTarget = NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Drop the soldier container
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::DropSoldierContainer( )
+{
+ m_hContainer->SetParent(NULL, 0);
+// m_hContainer->SetOwnerEntity(NULL);
+
+ Vector vecAbsVelocity = GetAbsVelocity();
+ if ( vecAbsVelocity.z > 0 )
+ {
+ vecAbsVelocity.z = 0.0f;
+ }
+
+ m_hContainer->SetAbsVelocity( vecAbsVelocity );
+ m_hContainer->SetMoveType( MOVETYPE_VPHYSICS );
+
+ // If we have a troop in the process of exiting, kill him.
+ // We do this to avoid having to solve the AI problems resulting from it.
+ if ( m_hLastTroopToLeave )
+ {
+ CTakeDamageInfo dmgInfo( this, this, vec3_origin, m_hContainer->GetAbsOrigin(), m_hLastTroopToLeave->GetMaxHealth(), DMG_GENERIC );
+ m_hLastTroopToLeave->TakeDamage( dmgInfo );
+ }
+
+ // If the container has a physics object, remove it's shadow
+ IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->RemoveShadowController();
+ pPhysicsObject->SetVelocity( &vecAbsVelocity, &vec3_origin );
+ }
+
+ UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );
+
+ m_hContainer = NULL;
+ SetLandingState( LANDING_NO );
+ m_hLandTarget = NULL;
+
+ if ( m_bHasDroppedOff )
+ {
+ m_OnContainerShotDownAfterDropoff.FireOutput( this, this );
+ }
+ else
+ {
+ int iTroopsNotUnloaded = (m_soldiersToDrop - m_iCurrentTroopExiting);
+ if ( g_debug_dropship.GetInt() )
+ {
+ Msg("Dropship died, troops not unloaded: %d\n", iTroopsNotUnloaded );
+ }
+
+ m_OnContainerShotDownBeforeDropoff.Set( iTroopsNotUnloaded, this, this );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Pick up a specified object
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::InputPickup( inputdata_t &inputdata )
+{
+ // Can't pickup if we're already carrying something
+ if ( m_hContainer )
+ {
+ Warning("npc_combinedropship %s was told to pickup, but is already carrying something.\n", STRING(GetEntityName()) );
+ return;
+ }
+
+ string_t iszTargetName = inputdata.value.StringID();
+ if ( iszTargetName == NULL_STRING )
+ {
+ Warning("npc_combinedropship %s tried to pickup with no specified pickup target.\n", STRING(GetEntityName()) );
+ return;
+ }
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, iszTargetName );
+ if ( !pTarget )
+ {
+ Warning("npc_combinedropship %s couldn't find pickup target named %s\n", STRING(GetEntityName()), STRING(iszTargetName) );
+ return;
+ }
+
+ // Start heading to the point
+ m_hPickupTarget = pTarget;
+
+ m_bHasDroppedOff = false;
+
+ // Disable collisions to my target
+ m_hPickupTarget->SetOwnerEntity(this);
+ if ( m_NPCState == NPC_STATE_IDLE )
+ {
+ SetState( NPC_STATE_ALERT );
+ }
+ SetLandingState( LANDING_SWOOPING );
+ m_flLandingSpeed = GetAbsVelocity().Length();
+
+ UpdatePickupNavigation();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the range of the container's gun
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::InputSetGunRange( inputdata_t &inputdata )
+{
+ m_flGunRange = inputdata.value.Float();
+}
+
+
+//------------------------------------------------------------------------------
+// Set the landing state
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::SetLandingState( LandingState_t landingState )
+{
+ if ( landingState == m_iLandState )
+ return;
+
+ if ( m_pDescendingWarningSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ if ( ( landingState == LANDING_DESCEND ) || ( landingState == LANDING_TOUCHDOWN ) || ( landingState == LANDING_UNLOADING ) || ( landingState == LANDING_UNLOADED ) || ( landingState == LANDING_HOVER_DESCEND ) )
+ {
+ controller.SoundChangeVolume( m_pDescendingWarningSound, m_bSuppressSound ? 0.0f : 1.0f, 0.3f );
+ }
+ else
+ {
+ controller.SoundChangeVolume( m_pDescendingWarningSound, 0.0f, 0.0f );
+ }
+ }
+
+ m_iLandState = landingState;
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+bool CNPC_CombineDropship::IsHovering()
+{
+ bool bIsHovering = false;
+
+ if( GetLandingState() > LANDING_START_HOVER && GetLandingState() < LANDING_END_HOVER )
+ {
+ bIsHovering = true;
+ }
+
+ return bIsHovering;
+}
+
+//------------------------------------------------------------------------------
+// Update the ground rotorwash volume
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::UpdateGroundRotorWashSound( float flAltitude )
+{
+ float flVolume = RemapValClamped( flAltitude, DROPSHIP_GROUND_WASH_MIN_ALTITUDE, DROPSHIP_GROUND_WASH_MAX_ALTITUDE, 1.0f, 0.0f );
+ UpdateRotorWashVolume( m_pRotorOnGroundSound, flVolume * GetRotorVolume(), 0.5f );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+
+ // "npc_kill" destroys our container
+ if (m_debugOverlays & OVERLAY_NPC_KILL_BIT)
+ {
+ if ( m_hContainer )
+ {
+ CTakeDamageInfo dmgInfo( this, this, vec3_origin, vec3_origin, 1000, DMG_BLAST );
+ m_hContainer->TakeDamage( dmgInfo );
+ }
+ }
+
+ // Update the ground rotorwash volume
+ float flAltitude = GetAltitude();
+ UpdateGroundRotorWashSound( flAltitude );
+
+ // keep track of think time deltas for burn calc below
+ float dt = gpGlobals->curtime - m_flLastTime;
+ m_flLastTime = gpGlobals->curtime;
+
+ switch( GetLandingState() )
+ {
+ case LANDING_NO:
+ {
+ if ( IsActivityFinished() && (GetActivity() != ACT_DROPSHIP_FLY_IDLE_EXAGG && GetActivity() != ACT_DROPSHIP_FLY_IDLE_CARGO) )
+ {
+ if ( m_hContainer )
+ {
+ SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_CARGO );
+ }
+ else
+ {
+ SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_EXAGG );
+ }
+ }
+
+ DoRotorWash();
+ }
+ break;
+
+ case LANDING_LEVEL_OUT:
+ case LANDING_HOVER_LEVEL_OUT:
+ {
+ // Approach the drop point
+ Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
+ float flDistance = vecToTarget.Length();
+
+ // Are we there yet?
+ float flSpeed = GetAbsVelocity().Length();
+ if ( flDistance < 70 && flSpeed < 100 )
+ {
+ m_flLandingSpeed = flSpeed;
+
+ if( IsHovering() )
+ {
+ SetLandingState( LANDING_HOVER_DESCEND );
+ }
+ else
+ {
+ SetLandingState( LANDING_DESCEND );
+ }
+
+ // save off current angles so we can work them out over time
+ QAngle angles = GetLocalAngles();
+ m_existPitch = angles.x;
+ m_existRoll = angles.z;
+ }
+
+ DoRotorWash();
+ }
+ break;
+
+ case LANDING_DESCEND:
+ case LANDING_HOVER_DESCEND:
+ {
+ /*
+ if ( IsActivityFinished() && GetActivity() != ACT_DROPSHIP_DESCEND_IDLE )
+ {
+ SetActivity( (Activity)ACT_DROPSHIP_DESCEND_IDLE );
+ }
+ */
+
+ if( IsHovering() && m_hLandTarget != NULL )
+ {
+ // We're trying to hover above an arbitrary point, not above the ground.
+ // Recompute flAltitude to indicate the vertical distance from the land
+ // target so that touchdown is correctly detected.
+ flAltitude = GetAbsOrigin().z - m_hLandTarget->GetAbsOrigin().z;
+ }
+
+ // Orient myself to the desired direction
+ bool bStillOrienting = false;
+ Vector targetDir;
+ if ( m_hLandTarget )
+ {
+ // We've got a land target, so match it's orientation
+ AngleVectors( m_hLandTarget->GetAbsAngles(), &targetDir );
+ }
+ else
+ {
+ // No land target.
+ targetDir = GetDesiredPosition() - GetAbsOrigin();
+ }
+
+ // Don't unload until we're facing the way the dropoff point specifies
+ float flTargetYaw = UTIL_VecToYaw( targetDir );
+ float flDeltaYaw = UTIL_AngleDiff( flTargetYaw, GetAbsAngles().y );
+ if ( fabs(flDeltaYaw) > 5 )
+ {
+ bStillOrienting = true;
+ }
+
+ // Ensure we land on the drop point. Stop dropping if we're still turning.
+ Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
+ float flDistance = vecToTarget.Length();
+ float flRampedSpeed = m_flLandingSpeed * (flDistance / 70);
+ Vector vecVelocity = (flRampedSpeed / flDistance) * vecToTarget;
+
+#define MAX_LAND_VEL -300.0f
+#define MIN_LAND_VEL -75.0f
+#define ALTITUDE_CAP 512.0f
+
+ float flFactor = MIN( 1.0, flAltitude / ALTITUDE_CAP );
+ float flDescendVelocity = MIN( -75, MAX_LAND_VEL * flFactor );
+
+ vecVelocity.z = flDescendVelocity;
+
+ SetAbsVelocity( vecVelocity );
+
+ if ( flAltitude < 72 )
+ {
+ QAngle angles = GetLocalAngles();
+
+ // Level out quickly.
+ angles.x = UTIL_Approach( 0.0, angles.x, 0.2 );
+ angles.z = UTIL_Approach( 0.0, angles.z, 0.2 );
+
+ SetLocalAngles( angles );
+ }
+ else
+ {
+ // randomly move as if buffeted by ground effects
+ // gently flatten ship from starting pitch/yaw
+ m_existPitch = UTIL_Approach( 0.0, m_existPitch, 1 );
+ m_existRoll = UTIL_Approach( 0.0, m_existRoll, 1 );
+
+ QAngle angles = GetLocalAngles();
+ angles.x = m_existPitch + ( sin( gpGlobals->curtime * 3.5f ) * DROPSHIP_MAX_LAND_TILT );
+ angles.z = m_existRoll + ( sin( gpGlobals->curtime * 3.75f ) * DROPSHIP_MAX_LAND_TILT );
+ SetLocalAngles( angles );
+ }
+
+ DoRotorWash();
+
+ // place danger sounds 1 foot above ground to get troops to scatter if they are below dropship
+ Vector vecBottom = GetAbsOrigin();
+ vecBottom.z += WorldAlignMins().z;
+ Vector vecSpot = vecBottom + Vector(0, 0, -1) * (flAltitude - 12 );
+ CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, 0.1, this, 0 );
+ CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, 400, 0.1, this, 1 );
+// NDebugOverlay::Cross3D( vecSpot, -Vector(4,4,4), Vector(4,4,4), 255, 0, 255, false, 10.0f );
+
+ // now check to see if player is below us, if so, cause heat damage to them (i.e. get them to move)
+ trace_t tr;
+ Vector vecBBoxMin = CRATE_BBOX_MIN; // use flat box for check
+ vecBBoxMin.z = -5;
+ Vector vecBBoxMax = CRATE_BBOX_MAX;
+ vecBBoxMax.z = 5;
+ Vector pEndPoint = vecBottom + Vector(0, 0, -1) * ( flAltitude - 12 );
+ AI_TraceHull( vecBottom, pEndPoint, vecBBoxMin, vecBBoxMax, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0f )
+ {
+ // Damage anything that's blocking me
+ if ( tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO )
+ {
+ CTakeDamageInfo info( this, this, 20 * dt, DMG_BURN );
+ tr.m_pEnt->TakeDamage( info );
+ }
+ }
+
+ if ( !bStillOrienting && ((flAltitude <= 0.5f) || (m_iCrateType == CRATE_APC)) )
+ {
+ if( IsHovering() )
+ {
+ SetAbsVelocity( vec3_origin );
+ SetLandingState( LANDING_HOVER_TOUCHDOWN );
+ }
+ else
+ {
+ SetLandingState( LANDING_TOUCHDOWN );
+ }
+
+ // upon landing, make sure ship is flat
+ QAngle angles = GetLocalAngles();
+ angles.x = 0;
+ angles.z = 0;
+ SetLocalAngles( angles );
+
+ // TODO: Release cargo anim
+ //SetActivity( (Activity)ACT_DROPSHIP_DESCEND_IDLE );
+ return;
+ }
+ }
+ break;
+
+ case LANDING_TOUCHDOWN:
+ case LANDING_HOVER_TOUCHDOWN:
+ {
+ /*
+ if ( IsActivityFinished() && ( GetActivity() != ACT_DROPSHIP_DESCEND_IDLE ) )
+ {
+ SetActivity( (Activity)ACT_DROPSHIP_DESCEND_IDLE );
+ }
+ */
+
+ // Wait here if we're supposed to wait for the dropoff input
+ if ( m_bWaitForDropoffInput )
+ return;
+
+ // Wait here till designer tells us to get moving again.
+ if ( IsHovering() )
+ return;
+
+ SetLandingState( LANDING_UNLOADING );
+
+ // If we're dropping off troops, we'll wait for them to be done.
+ // Otherwise, just pause on the ground for a few seconds and then leave.
+ if ( m_soldiersToDrop && m_hContainer)
+ {
+ m_flTimeTakeOff = 0;
+ m_flNextTroopSpawnAttempt = 0;
+
+ // Open our container
+ m_hContainer->SetSequence( m_hContainer->LookupSequence("open_idle") );
+
+ // Start unloading troops
+ m_iCurrentTroopExiting = 0;
+ SpawnTroop();
+ }
+ else
+ {
+ float flHoverTime = ( m_iCrateType >= 0 ) ? DROPSHIP_LANDING_HOVER_TIME : 0.5f;
+ m_flTimeTakeOff = gpGlobals->curtime + flHoverTime;
+ }
+ }
+ break;
+
+ case LANDING_UNLOADING:
+ {
+ // If we've got no specified takeoff time, we're still waiting for troops to exit. Idle.
+ if ( !m_flTimeTakeOff )
+ {
+ float idleVolume = 0.2f;
+ m_engineThrust = UTIL_Approach( idleVolume, m_engineThrust, 0.04f );
+ if ( m_engineThrust > idleVolume )
+ {
+ // Make sure we're kicking up dust/water as long as engine thrust is up
+ DoRotorWash();
+ }
+
+ // If we've lost the last troop who was leaving, he probably got killed during dustoff.
+ if ( !m_hLastTroopToLeave || !m_hLastTroopToLeave->IsAlive() )
+ {
+ // If we still have troops onboard, spawn the next one
+ if ( m_iCurrentTroopExiting < m_soldiersToDrop )
+ {
+ SpawnTroop();
+ }
+ else
+ {
+ // We're out of troops, time to leave
+ m_flTimeTakeOff = gpGlobals->curtime + 0.5;
+ }
+ }
+ }
+ else
+ {
+ if( gpGlobals->curtime > m_flTimeTakeOff )
+ {
+ SetLandingState( LANDING_LIFTOFF );
+ SetActivity( (Activity)ACT_DROPSHIP_LIFTOFF );
+ m_engineThrust = 1.0f; // ensure max volume once we're airborne
+ if ( m_bIsFiring )
+ {
+ StopCannon(); // kill cannon sounds if they are on
+ }
+
+ // detach container from ship
+ if ( m_hContainer && m_leaveCrate )
+ {
+ m_hContainer->SetParent(NULL);
+ m_hContainer->SetMoveType( (MoveType_t)m_iContainerMoveType );
+
+ Vector vecAbsVelocity( 0, 0, GetAbsVelocity().z );
+ if ( vecAbsVelocity.z > 0 )
+ {
+ vecAbsVelocity.z = 0.0f;
+ }
+
+ m_hContainer->SetAbsVelocity( vecAbsVelocity );
+
+ // If the container has a physics object, remove it's shadow
+ IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->RemoveShadowController();
+ pPhysicsObject->SetVelocity( &vecAbsVelocity, &vec3_origin );
+ }
+
+ m_hContainer = NULL;
+ UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );
+ }
+ }
+ else if ( (m_flTimeTakeOff - gpGlobals->curtime) < 0.5f )
+ {
+ // Manage engine wash and volume
+ m_engineThrust = UTIL_Approach( 1.0f, m_engineThrust, 0.1f );
+ DoRotorWash();
+ }
+ }
+ }
+ break;
+
+ case LANDING_LIFTOFF:
+ {
+ // Once we're off the ground, start flying again
+ if ( flAltitude > 120 )
+ {
+ SetLandingState( LANDING_NO );
+ m_hLandTarget = NULL;
+ m_bHasDroppedOff = true;
+ m_OnFinishedDropoff.FireOutput( this, this );
+ }
+
+ if ( m_hContainer )
+ {
+ m_hContainer->SetSequence( m_hContainer->LookupSequence("close_idle") );
+ }
+ }
+ break;
+
+ case LANDING_SWOOPING:
+ {
+ // Did we lose our pickup target?
+ if ( !m_hPickupTarget )
+ {
+ SetLandingState( LANDING_NO );
+ }
+ else
+ {
+ // Decrease altitude and speed to hit the target point.
+ Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
+ float flDistance = vecToTarget.Length();
+
+ // Start cheating when we get near it
+ if ( flDistance < 50 )
+ {
+ /*
+ if ( flDistance > 10 )
+ {
+ // Cheat and ensure we touch the target
+ float flSpeed = GetAbsVelocity().Length();
+ Vector vecVelocity = vecToTarget;
+ VectorNormalize( vecVelocity );
+ SetAbsVelocity( vecVelocity * min(flSpeed,flDistance) );
+ }
+ else
+ */
+ {
+ // Grab the target
+ m_hContainer = m_hPickupTarget;
+ m_hPickupTarget = NULL;
+ m_iContainerMoveType = m_hContainer->GetMoveType();
+ if ( m_bInvulnerable && m_hContainer )
+ {
+ m_hContainer->m_takedamage = DAMAGE_NO;
+ }
+
+ // If the container has a physics object, move it to shadow
+ IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->EnableMotion( true );
+ pPhysicsObject->SetShadow( 1e4, 1e4, false, false );
+ pPhysicsObject->UpdateShadow( GetAbsOrigin(), GetAbsAngles(), false, 0 );
+ }
+
+ m_hContainer->SetParent(this, 0);
+ m_hContainer->SetMoveType( MOVETYPE_PUSH );
+ m_hContainer->SetGroundEntity( NULL );
+
+ m_OnFinishedPickup.FireOutput( this, this );
+ SetLandingState( LANDING_NO );
+ }
+ }
+ }
+
+ DoRotorWash();
+ }
+ break;
+ }
+
+ if ( !(CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) )
+ {
+ DoCombatStuff();
+ }
+
+ if ( GetActivity() != GetIdealActivity() )
+ {
+ //DevMsg( "setactivity" );
+ SetActivity( GetIdealActivity() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+#define DROPSHIP_WASH_ALTITUDE 1024.0
+
+void CNPC_CombineDropship::DoRotorWash( void )
+{
+ Vector vecForward;
+ GetVectors( &vecForward, NULL, NULL );
+
+ Vector vecRotorHub = GetAbsOrigin() + vecForward * -64;
+
+ DrawRotorWash( DROPSHIP_WASH_ALTITUDE, vecRotorHub );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : Spawn the next NPC in our template list
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::SpawnTroop( void )
+{
+ if ( !m_hContainer )
+ {
+ // We're done, take off.
+ m_flTimeTakeOff = gpGlobals->curtime + 0.5;
+ return;
+ }
+
+ // Are we fully unloaded? If so, take off. Otherwise, tell the next troop to exit.
+ if ( m_iCurrentTroopExiting >= m_soldiersToDrop || m_sNPCTemplateData[m_iCurrentTroopExiting] == NULL_STRING )
+ {
+ // We're done, take off.
+ m_flTimeTakeOff = gpGlobals->curtime + 0.5;
+ return;
+ }
+
+ m_hLastTroopToLeave = NULL;
+
+ // Not time to try again yet?
+ if ( m_flNextTroopSpawnAttempt > gpGlobals->curtime )
+ return;
+
+ // HACK: This is a nasty piece of work. We want to make sure the deploy end is clear, and has enough
+ // room with our deploying NPC, but we don't want to create the NPC unless it's clear, and we don't
+ // know how much room he needs without spawning him.
+ // So, because we know that we only ever spawn combine soldiers at the moment, we'll just use their hull.
+ // HACK: Add some bloat because the endpoint isn't perfectly aligned with NPC end origin
+ Vector vecNPCMins = NAI_Hull::Mins( HULL_HUMAN ) - Vector(4,4,4);
+ Vector vecNPCMaxs = NAI_Hull::Maxs( HULL_HUMAN ) + Vector(4,4,4);
+
+ // Scare NPCs away from our deploy endpoint to keep them away
+ Vector vecDeployEndPoint;
+ QAngle vecDeployEndAngles;
+ m_hContainer->GetAttachment( m_iAttachmentTroopDeploy, vecDeployEndPoint, vecDeployEndAngles );
+ vecDeployEndPoint = GetDropoffFinishPosition( vecDeployEndPoint, NULL, vecNPCMins, vecNPCMaxs );
+ CSoundEnt::InsertSound( SOUND_DANGER, vecDeployEndPoint, 120.0f, 2.0f, this );
+
+ // Make sure there are no NPCs on the spot
+ trace_t tr;
+ CTraceFilterOnlyNPCsAndPlayer filter( this, COLLISION_GROUP_NONE );
+ AI_TraceHull( vecDeployEndPoint, vecDeployEndPoint, vecNPCMins, vecNPCMaxs, MASK_SOLID, &filter, &tr );
+ if ( tr.m_pEnt )
+ {
+ if ( g_debug_dropship.GetInt() == 2 )
+ {
+ NDebugOverlay::Box( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, 255,0,0, 64, 0.5 );
+ }
+
+ m_flNextTroopSpawnAttempt = gpGlobals->curtime + 1;
+ return;
+ }
+
+ if ( g_debug_dropship.GetInt() == 2 )
+ {
+ NDebugOverlay::Box( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, 0,255,0, 64, 0.5 );
+ }
+
+ // Get the spawn point inside the container
+ Vector vecSpawnOrigin;
+ QAngle vecSpawnAngles;
+ m_hContainer->GetAttachment( m_iAttachmentDeployStart, vecSpawnOrigin, vecSpawnAngles );
+
+ // Spawn the templated NPC
+ CBaseEntity *pEntity = NULL;
+ MapEntity_ParseEntity( pEntity, STRING(m_sNPCTemplateData[m_iCurrentTroopExiting]), NULL );
+
+ // Increment troop count
+ m_iCurrentTroopExiting++;
+
+ if ( !pEntity )
+ {
+ Warning("Dropship could not create template NPC\n" );
+ return;
+ }
+ CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
+ Assert( pNPC );
+
+ // Spawn an entity blocker.
+ CBaseEntity *pBlocker = CEntityBlocker::Create( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, pNPC, true );
+ g_EventQueue.AddEvent( pBlocker, "Kill", 2.5, this, this );
+ if ( g_debug_dropship.GetInt() == 2 )
+ {
+ NDebugOverlay::Box( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, 255, 255, 255, 64, 2.5 );
+ }
+
+ // Ensure our NPCs are standing upright
+ vecSpawnAngles[PITCH] = vecSpawnAngles[ROLL] = 0;
+
+ // Move it to the container spawnpoint
+ pNPC->SetAbsOrigin( vecSpawnOrigin );
+ pNPC->SetAbsAngles( vecSpawnAngles );
+ DispatchSpawn( pNPC );
+ pNPC->m_NPCState = NPC_STATE_IDLE;
+ pNPC->Activate();
+
+ // Spawn a scripted sequence entity to make the NPC run out of the dropship
+ CAI_ScriptedSequence *pSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
+ pSequence->KeyValue( "m_iszEntity", STRING(pNPC->GetEntityName()) );
+ pSequence->KeyValue( "m_iszPlay", "Dropship_Deploy" );
+ pSequence->KeyValue( "m_fMoveTo", "4" ); // CINE_MOVETO_TELEPORT
+ pSequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,NPCFinishDustoff,%s,0,-1", STRING(GetEntityName()), STRING(pNPC->GetEntityName())) );
+ pSequence->SetAbsOrigin( vecSpawnOrigin );
+ pSequence->SetAbsAngles( vecSpawnAngles );
+ pSequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE );
+ pSequence->Spawn();
+ pSequence->Activate();
+ variant_t emptyVariant;
+ pSequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 );
+
+ m_hLastTroopToLeave = pNPC;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a safe position above/below the specified origin for the NPC to finish it's dropoff on
+// Input : vecOrigin -
+//-----------------------------------------------------------------------------
+Vector CNPC_CombineDropship::GetDropoffFinishPosition( Vector vecOrigin, CAI_BaseNPC *pNPC, Vector vecMins, Vector vecMaxs )
+{
+ // Use the NPC's if they're specified
+ if ( pNPC )
+ {
+ vecMins = NAI_Hull::Mins( pNPC->GetHullType() );
+ vecMaxs = NAI_Hull::Maxs( pNPC->GetHullType() );
+ }
+
+ trace_t tr;
+ AI_TraceHull( vecOrigin + Vector(0,0,32), vecOrigin, vecMins, vecMaxs, MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction < 1.0 )
+ {
+ if ( g_debug_dropship.GetInt() == 1 )
+ {
+ NDebugOverlay::Box( vecOrigin, vecMins, vecMaxs, 255,0,0, 8, 4.0 );
+ }
+
+ // Try and find the ground
+ AI_TraceHull( vecOrigin + Vector(0,0,32), vecOrigin, vecMins, vecMaxs, MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr );
+ if ( !tr.startsolid )
+ return (tr.endpos + Vector(0,0,1));
+ }
+ else if ( g_debug_dropship.GetInt() == 1 )
+ {
+ NDebugOverlay::Box( vecOrigin, vecMins, vecMaxs, 0,255,0, 8, 4.0 );
+ }
+
+ return vecOrigin;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: A troop we dropped of has now finished the scripted sequence
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::InputNPCFinishDustoff( inputdata_t &inputdata )
+{
+ CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, inputdata.value.StringID(), NULL, inputdata.pActivator, inputdata.pCaller );
+ if ( !pEnt )
+ return;
+
+ CAI_BaseNPC *pNPC = pEnt->MyNPCPointer();
+ Assert( pNPC );
+
+ Vector vecOrigin = GetDropoffFinishPosition( pNPC->GetAbsOrigin(), pNPC, vec3_origin, vec3_origin );
+ pNPC->SetAbsOrigin( vecOrigin );
+
+ // Do we have a dustoff point?
+ CBaseEntity *pDustoff = NULL;
+ if ( m_sDustoffPoints[m_iCurrentTroopExiting-1] != NULL_STRING )
+ {
+ pDustoff = gEntList.FindEntityByName( NULL, m_sDustoffPoints[m_iCurrentTroopExiting-1] );
+ if ( !pDustoff )
+ {
+ Warning("npc_combinedropship %s couldn't find dustoff target named %s\n", STRING(GetEntityName()), STRING(m_sDustoffPoints[m_iCurrentTroopExiting-1]) );
+ }
+ }
+
+ if ( !pDustoff )
+ {
+ // Make a move away sound to scare the combine away from this point
+ CSoundEnt::InsertSound( SOUND_MOVE_AWAY | SOUND_CONTEXT_COMBINE_ONLY, pNPC->GetAbsOrigin(), 128, 0.1 );
+ }
+ else
+ {
+ if ( g_debug_dropship.GetInt() == 1 )
+ {
+ NDebugOverlay::Box( pDustoff->GetAbsOrigin(), -Vector(10,10,10), Vector(10,10,10), 0,255,0, 8, 5.0 );
+ }
+
+ // Tell the NPC to move to the dustoff position
+ pNPC->SetState( NPC_STATE_ALERT );
+ pNPC->ScheduledMoveToGoalEntity( SCHED_DROPSHIP_DUSTOFF, pDustoff, ACT_RUN );
+ pNPC->GetNavigator()->SetArrivalDirection( pDustoff->GetAbsAngles() );
+
+ // Make sure they ignore a bunch of conditions
+ static int g_Conditions[] =
+ {
+ COND_CAN_MELEE_ATTACK1,
+ COND_CAN_MELEE_ATTACK2,
+ COND_CAN_RANGE_ATTACK1,
+ COND_CAN_RANGE_ATTACK2,
+ COND_ENEMY_DEAD,
+ COND_HEAR_BULLET_IMPACT,
+ COND_HEAR_COMBAT,
+ COND_HEAR_DANGER,
+ COND_NEW_ENEMY,
+ COND_PROVOKED,
+ COND_SEE_ENEMY,
+ COND_SEE_FEAR,
+ COND_SMELL,
+ COND_LIGHT_DAMAGE,
+ COND_HEAVY_DAMAGE,
+ COND_PHYSICS_DAMAGE,
+ COND_REPEATED_DAMAGE,
+ };
+ pNPC->SetIgnoreConditions( g_Conditions, ARRAYSIZE(g_Conditions) );
+ }
+
+ // Unload the next troop
+ SpawnTroop();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tells the dropship to stop waiting and dustoff
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::InputStopWaitingForDropoff( inputdata_t &inputdata )
+{
+ m_bWaitForDropoffInput = false;
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::InputHover( inputdata_t &inputdata )
+{
+ m_iszLandTarget = inputdata.value.StringID();
+ LandCommon( true );
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::InputFlyToPathTrack( inputdata_t &inputdata )
+{
+ if( IsHovering() )
+ {
+ SetLandingState( LANDING_NO );
+ m_hLandTarget = NULL;
+ }
+
+ CAI_TrackPather::InputFlyToPathTrack( inputdata );
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+float CNPC_CombineDropship::GetAltitude( void )
+{
+ trace_t tr;
+ Vector vecBottom = GetAbsOrigin();
+
+ // Uneven terrain causes us problems, so trace our box down
+ AI_TraceEntity( this, vecBottom, vecBottom - Vector(0,0,4096), MASK_SOLID_BRUSHONLY, &tr );
+
+ float flAltitude = ( 4096 * tr.fraction );
+ //DevMsg(" Altitude: %.3f\n", flAltitude );
+ return flAltitude;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Drop rollermine from dropship
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::DropMine( void )
+{
+ NPC_Rollermine_DropFromPoint( GetAbsOrigin(), this, STRING(m_sRollermineTemplateData) );
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Fly towards our pickup target
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::UpdatePickupNavigation( void )
+{
+ // Try and touch the top of the object
+ Vector vecPickup = m_hPickupTarget->WorldSpaceCenter();
+ vecPickup.z += (m_hPickupTarget->CollisionProp()->OBBSize().z * 0.5);
+ SetDesiredPosition( vecPickup );
+
+ //NDebugOverlay::Cross3D( GetDesiredPosition(), -Vector(32,32,32), Vector(32,32,32), 0, 255, 255, true, 0.1f );
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Fly towards our land target
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::UpdateLandTargetNavigation( void )
+{
+ Vector vecPickup = m_hLandTarget->WorldSpaceCenter();
+ vecPickup.z += 256;
+ SetDesiredPosition( vecPickup );
+
+ //NDebugOverlay::Cross3D( GetDesiredPosition(), -Vector(32,32,32), Vector(32,32,32), 0, 255, 255, true, 0.1f );
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::Hunt( void )
+{
+ // If we have a pickup target, fly to it
+ if ( m_hPickupTarget )
+ {
+ UpdatePickupNavigation();
+ }
+ else if ( m_hLandTarget )
+ {
+ UpdateLandTargetNavigation();
+ }
+ else if ( GetLandingState() == LANDING_NO )
+ {
+ UpdateTrackNavigation();
+ }
+
+ // don't face player ever, only face nav points
+ Vector desiredDir = GetDesiredPosition() - GetAbsOrigin();
+ VectorNormalize( desiredDir );
+ // Face our desired position.
+ m_vecDesiredFaceDir = desiredDir;
+
+ if ( GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_LEVEL_OUT || IsHovering() )
+ {
+ if ( m_hLandTarget )
+ {
+ // We've got a land target, so match it's orientation
+ AngleVectors( m_hLandTarget->GetAbsAngles(), &m_vecDesiredFaceDir );
+ }
+ else
+ {
+ // No land target.
+ m_vecDesiredFaceDir = GetDesiredPosition() - GetAbsOrigin();
+ }
+ }
+
+ UpdateEnemy();
+ Flight();
+
+ UpdatePlayerDopplerShift( );
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::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: do all of the stuff related to having an enemy, attacking, etc.
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::DoCombatStuff( void )
+{
+ // Handle mines
+ if ( m_bDropMines )
+ {
+ switch( m_iDropState )
+ {
+ case DROP_IDLE:
+ {
+ m_iMineCount = m_totalMinesToDrop - 1;
+
+ DropMine();
+ // setup next individual drop time
+ m_flDropDelay = gpGlobals->curtime + DROPSHIP_TIME_BETWEEN_MINES;
+ // get ready to drop next mine, unless we're only supposed to drop 1
+ if ( m_iMineCount )
+ {
+ m_iDropState = DROP_NEXT;
+ }
+ else
+ {
+ m_bDropMines = false; // no more...
+ }
+ break;
+ }
+ case DROP_NEXT:
+ {
+ if ( gpGlobals->curtime > m_flDropDelay ) // time to drop next mine?
+ {
+ DropMine();
+ m_flDropDelay = gpGlobals->curtime + DROPSHIP_TIME_BETWEEN_MINES;
+
+ m_iMineCount--;
+ if ( !m_iMineCount )
+ {
+ m_iDropState = DROP_IDLE;
+ m_bDropMines = false; // reset flag
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ // Handle guns
+ bool bStopGun = true;
+ if ( GetEnemy() )
+ {
+ bStopGun = !FireCannonRound();
+ }
+
+ if ( bStopGun && m_bIsFiring )
+ {
+ StopCannon();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update the container's gun to face the enemy.
+// Input : &vecMuzzle - The gun's muzzle/firing point
+// &vecAimDir - The gun's current aim direction
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::UpdateContainerGunFacing( Vector &vecMuzzle, Vector &vecToTarget, Vector &vecAimDir, float *flTargetRange )
+{
+ Assert( m_hContainer );
+
+ // Get the desired aim vector
+ vecToTarget = GetEnemy()->WorldSpaceCenter( );
+
+ Vector vecBarrelPos, vecWorldBarrelPos;
+ QAngle worldBarrelAngle, vecAngles;
+ matrix3x4_t matRefToWorld;
+ m_hContainer->GetAttachment( m_iMuzzleAttachment, vecMuzzle, vecAngles );
+ vecWorldBarrelPos = vecMuzzle;
+ worldBarrelAngle = vecAngles;
+ m_hContainer->GetAttachment( m_iMachineGunRefAttachment, matRefToWorld );
+ VectorITransform( vecWorldBarrelPos, matRefToWorld, vecBarrelPos );
+
+ EntityMatrix parentMatrix;
+ parentMatrix.InitFromEntity( m_hContainer, m_iMachineGunBaseAttachment );
+ Vector target = parentMatrix.WorldToLocal( vecToTarget );
+
+ float quadTarget = target.LengthSqr();
+ float quadTargetXY = target.x*target.x + target.y*target.y;
+
+ // Target is too close! Can't aim at it
+ if ( quadTarget > vecBarrelPos.LengthSqr() )
+ {
+ // We're trying to aim the offset barrel at an arbitrary point.
+ // To calculate this, I think of the target as being on a sphere with
+ // it's center at the origin of the gun.
+ // The rotation we need is the opposite of the rotation that moves the target
+ // along the surface of that sphere to intersect with the gun's shooting direction
+ // To calculate that rotation, we simply calculate the intersection of the ray
+ // coming out of the barrel with the target sphere (that's the new target position)
+ // and use atan2() to get angles
+
+ // angles from target pos to center
+ float targetToCenterYaw = atan2( target.y, target.x );
+ float centerToGunYaw = atan2( vecBarrelPos.y, sqrt( quadTarget - (vecBarrelPos.y*vecBarrelPos.y) ) );
+
+ float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) );
+ float centerToGunPitch = atan2( -vecBarrelPos.z, sqrt( quadTarget - (vecBarrelPos.z*vecBarrelPos.z) ) );
+
+ QAngle angles;
+ angles.Init( RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw + centerToGunYaw ), 0 );
+
+ float flNewAngle = AngleNormalize( UTIL_ApproachAngle( angles.x, m_hContainer->GetPoseParameter(m_poseWeapon_Pitch), DROPSHIP_GUN_SPEED));
+ m_hContainer->SetPoseParameter( m_poseWeapon_Pitch, flNewAngle );
+
+ flNewAngle = AngleNormalize( UTIL_ApproachAngle( angles.y, m_hContainer->GetPoseParameter(m_poseWeapon_Yaw), DROPSHIP_GUN_SPEED));
+ m_hContainer->SetPoseParameter( m_poseWeapon_Yaw, flNewAngle );
+ m_hContainer->StudioFrameAdvance();
+ }
+
+ vecToTarget -= vecMuzzle;
+ *flTargetRange = VectorNormalize( vecToTarget );
+ AngleVectors( vecAngles, &vecAimDir );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose: Fire a round from the cannon
+// Notes: Only call this if you have an enemy.
+// Returns true if the cannon round was actually fired
+//------------------------------------------------------------------------------
+bool CNPC_CombineDropship::FireCannonRound( void )
+{
+ // Try and aim my cannon at the enemy, if I have a container
+ if ( !m_hContainer || (m_iCrateType < 0) )
+ return false;
+
+ // Update the container gun, and get the vector to the enemy, and the gun's current aim direction
+ float flRange;
+ Vector vecMuzzle, vecAimDir, vecToEnemy;
+ UpdateContainerGunFacing( vecMuzzle, vecToEnemy, vecAimDir, &flRange );
+
+ // Out of range?
+ if ( flRange > m_flGunRange )
+ return false;
+
+ // Only fire if the target's close enough to our aim direction
+ float flCosAngle = DotProduct( vecToEnemy, vecAimDir );
+ if ( flCosAngle < DOT_15DEGREE )
+ {
+ m_flTimeNextAttack = gpGlobals->curtime + 0.1;
+ return false;
+ }
+
+ // If we're out of rounds, reload
+ if ( m_iBurstRounds <= 0 )
+ {
+ m_iBurstRounds = RandomInt( 10, 20 );
+ m_flTimeNextAttack = gpGlobals->curtime + (m_iBurstRounds * 0.1);
+ return false;
+ }
+
+ // HACK: Return true so the fire sound isn't stopped
+ if ( m_flTimeNextAttack > gpGlobals->curtime )
+ return true;
+
+ m_iBurstRounds--;
+
+ // If we're not currently firing, start it up
+ if ( !m_bIsFiring )
+ {
+ StartCannon();
+ }
+
+ // Add a muzzle flash
+ QAngle vecAimAngles;
+ VectorAngles( vecAimDir, vecAimAngles );
+ g_pEffects->MuzzleFlash( vecMuzzle, vecAimAngles, random->RandomFloat( 5.0f, 7.0f ), MUZZLEFLASH_TYPE_GUNSHIP );
+ m_flTimeNextAttack = gpGlobals->curtime + 0.05;
+
+ // Clamp to account for inaccuracy in aiming w/ pose parameters
+ vecAimDir = vecToEnemy;
+
+ // Fire the bullet
+ int ammoType = GetAmmoDef()->Index("CombineCannon");
+ FireBullets( 1, vecMuzzle, vecAimDir, VECTOR_CONE_2DEGREES, 8192, ammoType, 1, -1, -1, sk_npc_dmg_dropship.GetInt() );
+
+ return true;
+}
+
+//------------------------------------------------------------------------------
+// Scare AIs in the area where bullets are impacting
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::DoImpactEffect( trace_t &tr, int nDamageType )
+{
+ CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_REACT_TO_SOURCE, tr.endpos, 120.0f, 0.3f, this );
+
+ BaseClass::DoImpactEffect( tr, nDamageType );
+}
+
+//------------------------------------------------------------------------------
+// Purpose : The proper way to begin the gunship cannon firing at the enemy.
+// Input :
+// :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::StartCannon( void )
+{
+ m_bIsFiring = true;
+
+ // Start up the cannon sound.
+ if ( m_pCannonSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume(m_pCannonSound, 1.0, 0.0);
+ }
+
+}
+
+//------------------------------------------------------------------------------
+// Purpose : The proper way to cease the gunship cannon firing.
+// Input :
+// :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_CombineDropship::StopCannon( void )
+{
+ m_bIsFiring = false;
+
+ // Stop the cannon sound.
+ if ( m_pCannonSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume(m_pCannonSound, 0.0, 0.1);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Used the gunship's tracer for now
+//-----------------------------------------------------------------------------
+void CNPC_CombineDropship::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, 16000, true, "GunshipTracer" );
+ }
+ break;
+
+ default:
+ BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
+ break;
+ }
+}
+
+AI_BEGIN_CUSTOM_NPC( npc_combinedropship, CNPC_CombineDropship )
+
+ DECLARE_ACTIVITY( ACT_DROPSHIP_FLY_IDLE );
+ DECLARE_ACTIVITY( ACT_DROPSHIP_FLY_IDLE_EXAGG );
+ DECLARE_ACTIVITY( ACT_DROPSHIP_DESCEND_IDLE );
+ DECLARE_ACTIVITY( ACT_DROPSHIP_DEPLOY_IDLE );
+ DECLARE_ACTIVITY( ACT_DROPSHIP_LIFTOFF );
+
+ DECLARE_ACTIVITY( ACT_DROPSHIP_FLY_IDLE_CARGO );
+
+AI_END_CUSTOM_NPC()
+
+
+