//========= 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 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; int m_poseWeapon_Pitch; int m_poseWeapon_Yaw; // 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; 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; //----------------------------------------------------------------------------- // 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_sbStaticPoseParamsLoaded = true; } if( m_hContainer ) { m_poseWeapon_Pitch = m_hContainer->LookupPoseParameter( "weapon_pitch" ); m_poseWeapon_Yaw = m_hContainer->LookupPoseParameter( "weapon_yaw" ); } 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(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; m_poseWeapon_Pitch = -1; m_poseWeapon_Yaw = -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" ); m_poseWeapon_Pitch = m_hContainer->LookupPoseParameter( "weapon_pitch" ); m_poseWeapon_Yaw = m_hContainer->LookupPoseParameter( "weapon_yaw" ); } 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()