diff --git a/sp/src/game/server/hl2/npc_attackchopper.cpp b/sp/src/game/server/hl2/npc_attackchopper.cpp index 24c43369645..6d5102d121d 100644 --- a/sp/src/game/server/hl2/npc_attackchopper.cpp +++ b/sp/src/game/server/hl2/npc_attackchopper.cpp @@ -170,6 +170,15 @@ ConVar g_helicopter_bullrush_mega_bomb_health( "g_helicopter_bullrush_mega_bomb_ ConVar g_helicopter_bomb_danger_radius( "g_helicopter_bomb_danger_radius", "120" ); +#ifdef MAPBASE +ConVar g_helicopter_crashpoint_nearest( "g_helicopter_crashpoint_nearest", "1", 0, "Selects the nearest crash point instead of just the first in the entity list." ); + +#ifdef HL2_EPISODIC +ConVar g_helicopter_phys_follow_while_crashing( "g_helicopter_phys_follow_while_crashing", "0", 0, "Allows the phys_bone_followers to follow the helicopter while flying to crash point" ); +#endif + +#endif + Activity ACT_HELICOPTER_DROP_BOMB; Activity ACT_HELICOPTER_CRASHING; @@ -197,7 +206,60 @@ enum #define GRENADE_HELICOPTER_MODEL "models/combine_helicopter/helicopter_bomb01.mdl" +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTargetHelicopterCrash : public CPointEntity +{ + DECLARE_CLASS( CTargetHelicopterCrash, CPointEntity ); +public: + DECLARE_DATADESC(); + + void InputEnable( inputdata_t &inputdata ) + { + m_bDisabled = false; + } + void InputDisable( inputdata_t &inputdata ) + { + m_bDisabled = true; + } + bool IsDisabled( void ) + { + return m_bDisabled; + } + void HelicopterCrashedOnTarget( CBaseHelicopter *pChopper ) + { + m_OnCrashed.FireOutput( pChopper, this ); + } + void HelicopterAcquiredCrashTarget( CBaseHelicopter *pChopper ) + { + m_OnBeginCrash.FireOutput( pChopper, this ); + } + +private: + bool m_bDisabled; + + COutputEvent m_OnCrashed; + COutputEvent m_OnBeginCrash; +}; + +LINK_ENTITY_TO_CLASS( info_target_helicopter_crash, CTargetHelicopterCrash ); + +BEGIN_DATADESC( CTargetHelicopterCrash ) + DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + // Outputs + DEFINE_OUTPUT( m_OnCrashed, "OnCrashed" ), + DEFINE_OUTPUT( m_OnBeginCrash, "OnBeginCrash" ), +END_DATADESC() +#else LINK_ENTITY_TO_CLASS( info_target_helicopter_crash, CPointEntity ); +#endif //------------------------------------------------------------------------------ @@ -663,6 +725,8 @@ class CNPC_AttackHelicopter : public CBaseHelicopter protected: #endif + CBaseEntity *FindCrashPoint(); + CBaseEntity *GetCrashPoint() { return m_hCrashPoint.Get(); } private: @@ -3806,6 +3870,46 @@ void Chopper_BecomeChunks( CBaseEntity *pChopper ) pBodyChunk->m_pCockpitConstraint = physenv->CreateFixedConstraint( pBodyObject, pCockpitObject, pGroup, fixed ); } +//----------------------------------------------------------------------------- +// Purpose: Find a valid crash point +//----------------------------------------------------------------------------- +CBaseEntity *CNPC_AttackHelicopter::FindCrashPoint() +{ +#ifdef MAPBASE + float flNearest = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH; + CTargetHelicopterCrash *pNearest = NULL; + CBaseEntity *pEnt = NULL; + while( (pEnt = gEntList.FindEntityByClassname(pEnt, "info_target_helicopter_crash")) != NULL ) + { + CTargetHelicopterCrash *pCrashTarget = assert_cast(pEnt); + if ( pCrashTarget->IsDisabled() ) + continue; + + if (g_helicopter_crashpoint_nearest.GetBool()) + { + float flDist = ( pEnt->WorldSpaceCenter() - WorldSpaceCenter() ).LengthSqr(); + if( flDist < flNearest ) + { + pNearest = pCrashTarget; + flNearest = flDist; + } + } + else + { + pNearest = pCrashTarget; + break; + } + } + + if (pNearest) + pNearest->HelicopterAcquiredCrashTarget( this ); + + return pNearest; +#else + return gEntList.FindEntityByClassname( NULL, "info_target_helicopter_crash" ); +#endif +} + //----------------------------------------------------------------------------- // Purpose: Start us crashing //----------------------------------------------------------------------------- @@ -3823,7 +3927,7 @@ void CNPC_AttackHelicopter::Event_Killed( const CTakeDamageInfo &info ) if( GetCrashPoint() == NULL ) { - CBaseEntity *pCrashPoint = gEntList.FindEntityByClassname( NULL, "info_target_helicopter_crash" ); + CBaseEntity *pCrashPoint = FindCrashPoint(); if( pCrashPoint != NULL ) { m_hCrashPoint.Set( pCrashPoint ); @@ -3844,6 +3948,12 @@ void CNPC_AttackHelicopter::Event_Killed( const CTakeDamageInfo &info ) return; } } +#ifdef MAPBASE + else + { + assert_cast( GetCrashPoint() )->HelicopterCrashedOnTarget( this ); + } +#endif Chopper_BecomeChunks( this ); StopLoopingSounds(); @@ -4853,6 +4963,15 @@ void CNPC_AttackHelicopter::Hunt( void ) { Flight(); UpdatePlayerDopplerShift( ); + +#if defined(MAPBASE) && defined(HL2_EPISODIC) + if (g_helicopter_phys_follow_while_crashing.GetBool()) + { + // Update our bone followers + m_BoneFollowerManager.UpdateBoneFollowers( this ); + } +#endif // HL2_EPISODIC + return; } @@ -6389,6 +6508,10 @@ class CNPC_ArbeitHelicopter : public CNPC_AttackHelicopter bool m_bDoorsOpen; float m_flDoorTransitionTime; + // Crashing + string_t m_iszBodyGibModel; + string_t m_iszTailGibModel; + // Attachments int m_nGunnerAttachments[ ARBEIT_HELI_MAX_GUNNERS ]; int m_nGunnerJumpAttachments[ ARBEIT_HELI_MAX_GUNNERS ]; @@ -6423,6 +6546,9 @@ BEGIN_DATADESC( CNPC_ArbeitHelicopter ) DEFINE_KEYFIELD( m_bDoorsOpen, FIELD_BOOLEAN, "DoorsOpen" ), DEFINE_FIELD( m_flDoorTransitionTime, FIELD_TIME ), + DEFINE_KEYFIELD( m_iszBodyGibModel, FIELD_STRING, "BodyGibModel" ), + DEFINE_KEYFIELD( m_iszTailGibModel, FIELD_STRING, "TailGibModel" ), + DEFINE_INPUTFUNC( FIELD_VOID, "RespawnAllGunners", InputRespawnAllGunners ), DEFINE_INPUTFUNC( FIELD_VOID, "RespawnRightGunner", InputRespawnRightGunner ), DEFINE_INPUTFUNC( FIELD_VOID, "RespawnLeftGunner", InputRespawnLeftGunner ), @@ -6514,7 +6640,13 @@ void CNPC_ArbeitHelicopter::Precache( void ) if (!m_bNonCombat) { - PrecacheModel( "models/props_vehicles/arbeit_heli_stationary.mdl" ); + if (m_iszBodyGibModel == NULL_STRING) + m_iszBodyGibModel = MAKE_STRING( "models/props_vehicles/arbeit_heli_stationary.mdl" ); // TODO: Unique crash model + + PrecacheModel( STRING( m_iszBodyGibModel ) ); + + if (m_iszTailGibModel != NULL_STRING) + PrecacheModel( STRING( m_iszTailGibModel ) ); } PrecacheScriptSound( "NPC_ArbeitHelicopter.RotorBlast" ); @@ -6723,9 +6855,9 @@ void CNPC_ArbeitHelicopter::PrescheduleThink( void ) RefreshGunnerState(); // TODO - if (m_hGunners[0]) + if (m_hGunners[0] && m_hGunners[0]->IsAlive()) m_iSideFacing = GUNNER_RIGHT_ONLY; - else if (m_hGunners[1]) + else if (m_hGunners[1] && m_hGunners[1]->IsAlive()) m_iSideFacing = GUNNER_LEFT_ONLY; else m_iSideFacing = GUNNER_NONE; @@ -6783,7 +6915,7 @@ void CNPC_ArbeitHelicopter::Event_Killed( const CTakeDamageInfo &info ) if( GetCrashPoint() == NULL ) { - CBaseEntity *pCrashPoint = gEntList.FindEntityByClassname( NULL, "info_target_helicopter_crash" ); + CBaseEntity *pCrashPoint = FindCrashPoint(); if( pCrashPoint != NULL ) { m_hCrashPoint.Set( pCrashPoint ); @@ -6807,6 +6939,10 @@ void CNPC_ArbeitHelicopter::Event_Killed( const CTakeDamageInfo &info ) return; } } + else + { + assert_cast( GetCrashPoint() )->HelicopterCrashedOnTarget( this ); + } // Kill our gunners for (int i = 0; i < ARBEIT_HELI_MAX_GUNNERS; i++) @@ -6820,14 +6956,73 @@ void CNPC_ArbeitHelicopter::Event_Killed( const CTakeDamageInfo &info ) m_hGunners[i] = NULL; } + Vector vecChunkPos, vecChunkVelocity, vecForward, vecRight, vecUp; + QAngle vecChunkAngles; + + int nBodyAttachment = LookupAttachment( "damage0" ); + if (nBodyAttachment > 0) + { + //GetAttachment( nBodyAttachment, vecChunkPos, vecChunkAngles ); + + matrix3x4_t attachmentToWorld; + GetAttachment( nBodyAttachment, attachmentToWorld ); + MatrixAngles( attachmentToWorld, vecChunkAngles, vecChunkPos ); + MatrixGetColumn( attachmentToWorld, 0, vecForward ); + //MatrixGetColumn( attachmentToWorld, 1, vecRight ); + MatrixGetColumn( attachmentToWorld, 2, vecUp ); + } + else + { + vecChunkPos = GetAbsOrigin(); + vecChunkAngles = GetAbsAngles(); + vecChunkVelocity = GetAbsVelocity(); + } + + // We need to get a right hand vector to toss the cockpit and tail pieces + // so their motion looks like a continuation of the tailspin animation + // that the chopper plays before crashing. + GetVectors( NULL, &vecRight, NULL ); + // For now, create a charred stationary version of ourselves - // TODO: Unique crash model - CHelicopterChunk *pBodyChunk = CHelicopterChunk::CreateHelicopterChunk( GetAbsOrigin(), GetAbsAngles(), GetAbsVelocity(), "models/props_vehicles/arbeit_heli_stationary.mdl", CHUNK_BODY ); + CHelicopterChunk *pBodyChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, vecChunkVelocity, STRING( m_iszBodyGibModel ), CHUNK_BODY ); if (pBodyChunk) { pBodyChunk->SetRenderColor( 64, 64, 64 ); } + CHelicopterChunk *pTailChunk = NULL; + if ( m_iszTailGibModel != NULL_STRING ) + { + // Tail + vecChunkPos += (vecForward * -175.0f) + (vecUp * 75.0f); + pTailChunk = CHelicopterChunk::CreateHelicopterChunk( vecChunkPos, vecChunkAngles, vecChunkVelocity + (vecRight * -600.0f), STRING( m_iszTailGibModel ), CHUNK_TAIL ); + if (pTailChunk) + { + pTailChunk->SetRenderColor( 64, 64, 64 ); + pTailChunk->m_hMaster = pBodyChunk; + } + } + + if (pBodyChunk && pTailChunk) + { + // Constrain all the pieces together loosely + IPhysicsObject *pBodyObject = pBodyChunk->VPhysicsGetObject(); + Assert( pBodyObject ); + + IPhysicsObject *pTailObject = pTailChunk->VPhysicsGetObject(); + Assert( pTailObject ); + + IPhysicsConstraintGroup *pGroup = NULL; + + // Create the constraint + constraint_fixedparams_t fixed; + fixed.Defaults(); + fixed.InitWithCurrentObjectState( pBodyObject, pTailObject ); + fixed.constraint.Defaults(); + + pBodyChunk->m_pTailConstraint = physenv->CreateFixedConstraint( pBodyObject, pTailObject, pGroup, fixed ); + } + StopLoopingSounds(); m_lifeState = LIFE_DEAD; @@ -7063,7 +7258,7 @@ void CNPC_ArbeitHelicopter::InputOpenDoors( inputdata_t &inputdata ) { m_bDoorsOpen = true; - if (m_flDoorTransitionTime != -1.0f && m_iGunnerSpawnState == GUNNER_NONE) + if (m_flDoorTransitionTime == -1.0f /*&& m_iGunnerSpawnState == GUNNER_NONE*/) { m_flDoorTransitionTime = gpGlobals->curtime + ARBEIT_HELI_DOOR_TIME; SetContextThink( &CNPC_ArbeitHelicopter::OpenDoorsThink, gpGlobals->curtime + TICK_INTERVAL, g_pszGunnerSpawnContext ); @@ -7076,7 +7271,7 @@ void CNPC_ArbeitHelicopter::InputCloseDoors( inputdata_t &inputdata ) { m_bDoorsOpen = false; - if (m_flDoorTransitionTime != -1.0f && m_iGunnerSpawnState == GUNNER_NONE) + if (m_flDoorTransitionTime == -1.0f /*&& m_iGunnerSpawnState == GUNNER_NONE*/) { m_flDoorTransitionTime = gpGlobals->curtime + ARBEIT_HELI_DOOR_TIME; SetContextThink( &CNPC_ArbeitHelicopter::CloseDoorsThink, gpGlobals->curtime + TICK_INTERVAL, g_pszGunnerSpawnContext ); diff --git a/sp/src/game/server/hl2/npc_combinegunship.cpp b/sp/src/game/server/hl2/npc_combinegunship.cpp index b36216d9dc2..f1c857e20c0 100644 --- a/sp/src/game/server/hl2/npc_combinegunship.cpp +++ b/sp/src/game/server/hl2/npc_combinegunship.cpp @@ -187,15 +187,29 @@ class CTargetGunshipCrash : public CPointEntity { return m_bDisabled; } +#ifdef MAPBASE + void GunshipCrashedOnTarget( CBaseHelicopter *pGunship ) + { + m_OnCrashed.FireOutput( pGunship, this ); + } + void GunshipAcquiredCrashTarget( CBaseHelicopter *pGunship ) + { + m_OnBeginCrash.FireOutput( pGunship, this ); + } +#else void GunshipCrashedOnTarget( void ) { m_OnCrashed.FireOutput( this, this ); } +#endif private: bool m_bDisabled; COutputEvent m_OnCrashed; +#ifdef MAPBASE + COutputEvent m_OnBeginCrash; +#endif }; LINK_ENTITY_TO_CLASS( info_target_gunshipcrash, CTargetGunshipCrash ); @@ -209,6 +223,9 @@ BEGIN_DATADESC( CTargetGunshipCrash ) // Outputs DEFINE_OUTPUT( m_OnCrashed, "OnCrashed" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnBeginCrash, "OnBeginCrash" ), +#endif END_DATADESC() @@ -1564,7 +1581,11 @@ void CNPC_CombineGunship::PrescheduleThink( void ) { BeginDestruct(); m_OnCrashed.FireOutput( this, this ); +#ifdef MAPBASE + m_hCrashTarget->GunshipCrashedOnTarget( this ); +#else m_hCrashTarget->GunshipCrashedOnTarget(); +#endif return; } } @@ -1976,6 +1997,10 @@ bool CNPC_CombineGunship::FindNearestGunshipCrash( void ) m_flNextGunshipCrashFind = gpGlobals->curtime + 0.5; m_flEndDestructTime = 0; +#ifdef MAPBASE + m_hCrashTarget->GunshipAcquiredCrashTarget( this ); +#endif + if ( g_debug_gunship.GetInt() ) { NDebugOverlay::Line(GetAbsOrigin(), m_hCrashTarget->GetAbsOrigin(), 0,255,0, true, 0.5);