diff --git a/src/game/client/swarm/c_asw_sentry_base.cpp b/src/game/client/swarm/c_asw_sentry_base.cpp index 7608c3b7e..8511a94cf 100644 --- a/src/game/client/swarm/c_asw_sentry_base.cpp +++ b/src/game/client/swarm/c_asw_sentry_base.cpp @@ -33,6 +33,7 @@ IMPLEMENT_CLIENTCLASS_DT( C_ASW_Sentry_Base, DT_ASW_Sentry_Base, CASW_Sentry_Bas RecvPropInt( RECVINFO( m_nGunType ) ), RecvPropEHandle( RECVINFO( m_hOriginalOwnerPlayer ) ), RecvPropIntWithMinusOneFlag( RECVINFO( m_iInventoryEquipSlot ) ), + RecvPropEHandle( RECVINFO( m_hLastDisassembler ) ), END_RECV_TABLE() BEGIN_PREDICTION_DATA( C_ASW_Sentry_Base ) @@ -41,6 +42,17 @@ END_PREDICTION_DATA() CUtlVector g_SentryGuns; +static const char *const s_szSentryTopBuildModels[] = +{ + "models/sentry_gun/machinegun_top.mdl", + "models/sentry_gun/grenade_top.mdl", + "models/sentry_gun/flame_top.mdl", + "models/sentry_gun/freeze_top.mdl", +#ifdef RD_7A_WEAPONS + "models/sentry_gun/railgun_top.mdl", +#endif +}; + vgui::HFont C_ASW_Sentry_Base::s_hAmmoFont = vgui::INVALID_FONT; C_ASW_Sentry_Base::C_ASW_Sentry_Base() @@ -53,24 +65,80 @@ C_ASW_Sentry_Base::C_ASW_Sentry_Base() m_nUseIconTextureID = -1; m_hOriginalOwnerPlayer = NULL; m_iInventoryEquipSlot = -1; + m_hLastDisassembler = NULL; } - C_ASW_Sentry_Base::~C_ASW_Sentry_Base() { + if ( m_hBuildTop.Get() ) + { + UTIL_Remove( m_hBuildTop.Get() ); + m_hBuildTop = NULL; + } + g_SentryGuns.FindAndRemove( this ); } -bool C_ASW_Sentry_Base::ShouldDraw() +void C_ASW_Sentry_Base::OnDataChanged( DataUpdateType_t updateType ) { - return true; + BaseClass::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } } -int C_ASW_Sentry_Base::DrawModel( int flags, const RenderableInstance_t &instance ) +void C_ASW_Sentry_Base::ClientThink() { - int d = BaseClass::DrawModel(flags, instance); + BaseClass::ClientThink(); + + if ( m_bAssembled ) + { + if ( m_hBuildTop.Get() ) + { + UTIL_Remove( m_hBuildTop.Get() ); + m_hBuildTop = NULL; + } + + SetNextClientThink( CLIENT_THINK_NEVER ); - return d; + return; + } + + if ( !m_hBuildTop.Get() ) + { + C_BaseAnimating *pBuildTop = new C_BaseAnimating; + Assert( pBuildTop ); + if ( !pBuildTop ) + { + return; + } + + if ( !pBuildTop->InitializeAsClientEntity( s_szSentryTopBuildModels[m_nGunType], false ) ) + { + Assert( !"sentry top build model failed to spawn" ); + UTIL_Remove( pBuildTop ); + SetNextClientThink( CLIENT_THINK_NEVER ); + return; + } + + pBuildTop->SetOwnerEntity( this ); + pBuildTop->SetParent( this ); + pBuildTop->SetLocalOrigin( vec3_origin ); + pBuildTop->SetLocalAngles( vec3_angle ); + pBuildTop->SetSolid( SOLID_NONE ); + pBuildTop->SetPoseParameter( "aim_pitch", -30.0f ); // should match the spawn value for m_fAimPitch on both client and server + pBuildTop->SetSequence( pBuildTop->SelectWeightedSequence( ACT_OBJ_ASSEMBLING ) ); + pBuildTop->SetPlaybackRate( 0.0f ); + + m_hBuildTop = pBuildTop; + } + + if ( m_hBuildTop ) + { + m_hBuildTop->SetCycle( m_fAssembleProgress ); + } } int C_ASW_Sentry_Base::GetSentryIconTextureID() @@ -123,7 +191,7 @@ bool C_ASW_Sentry_Base::GetUseAction(ASWUseAction &action, C_ASW_Inhabitable_NPC action.bShowHoldButtonUseKey = true; action.bShowUseKey = true; } - action.iUseIconTexture = GetSentryIconTextureID(); + action.iUseIconTexture = GetSentryIconTextureID(); action.UseTarget = this; } else @@ -139,7 +207,7 @@ bool C_ASW_Sentry_Base::GetUseAction(ASWUseAction &action, C_ASW_Inhabitable_NPC TryLocalize( "#asw_assemble_sentry", action.wszText, sizeof( action.wszText ) ); action.bShowUseKey = true; } - action.iUseIconTexture = GetSentryIconTextureID(); + action.iUseIconTexture = GetSentryIconTextureID(); action.UseTarget = this; action.fProgress = GetAssembleProgress(); } @@ -184,14 +252,17 @@ void C_ASW_Sentry_Base::CustomPaint( int ix, int iy, int alpha, vgui::Panel *pUs } } -const char* C_ASW_Sentry_Base::GetWeaponClass() +const char *C_ASW_Sentry_Base::GetWeaponClass() { - switch( m_nGunType.Get() ) + switch ( m_nGunType.Get() ) { - case 0: return "asw_weapon_sentry"; - case 1: return "asw_weapon_sentry_cannon"; - case 2: return "asw_weapon_sentry_flamer"; - case 3: return "asw_weapon_sentry_freeze"; + case 0: return "asw_weapon_sentry"; + case 1: return "asw_weapon_sentry_cannon"; + case 2: return "asw_weapon_sentry_flamer"; + case 3: return "asw_weapon_sentry_freeze"; +#ifdef RD_7A_WEAPONS + case 4: return "asw_weapon_sentry_railgun"; +#endif } return "asw_weapon_sentry"; } diff --git a/src/game/client/swarm/c_asw_sentry_base.h b/src/game/client/swarm/c_asw_sentry_base.h index e7bd07200..a6aee892b 100644 --- a/src/game/client/swarm/c_asw_sentry_base.h +++ b/src/game/client/swarm/c_asw_sentry_base.h @@ -15,9 +15,8 @@ class C_ASW_Sentry_Base : public C_BaseAnimating, public IASW_Client_Usable_Enti C_ASW_Sentry_Base(); virtual ~C_ASW_Sentry_Base(); - bool ShouldDraw(); - - virtual int DrawModel( int flags, const RenderableInstance_t &instance ); + void OnDataChanged( DataUpdateType_t updateType ) override; + void ClientThink() override; bool IsAssembled() const { return m_bAssembled; } bool IsInUse() const { return m_bIsInUse; } @@ -44,9 +43,14 @@ class C_ASW_Sentry_Base : public C_BaseAnimating, public IASW_Client_Usable_Enti CNetworkVar( int, m_iInventoryEquipSlot ); bool IsInventoryEquipSlotValid() const { return !!m_hOriginalOwnerPlayer && m_iInventoryEquipSlot != -1; } + CNetworkHandle( C_ASW_Inhabitable_NPC, m_hLastDisassembler ); + // class of the weapon that created us const char* GetWeaponClass(); + // fake sentry top for building animation + CHandle m_hBuildTop; + // IASW_Client_Usable_Entity virtual C_BaseEntity* GetEntity() { return this; } virtual bool IsUsable(C_BaseEntity *pUser); diff --git a/src/game/client/swarm/c_asw_sentry_top.cpp b/src/game/client/swarm/c_asw_sentry_top.cpp index 53080cf37..f6d9e1a5d 100644 --- a/src/game/client/swarm/c_asw_sentry_top.cpp +++ b/src/game/client/swarm/c_asw_sentry_top.cpp @@ -18,6 +18,8 @@ IMPLEMENT_CLIENTCLASS_DT( C_ASW_Sentry_Top, DT_ASW_Sentry_Top, CASW_Sentry_Top ) RecvPropInt( RECVINFO( m_iSentryAngle ) ), RecvPropFloat( RECVINFO( m_fDeployYaw ) ), RecvPropFloat( RECVINFO( m_fCenterAimYaw ) ), + RecvPropFloat( RECVINFO( m_fGoalPitch ) ), + RecvPropFloat( RECVINFO( m_fGoalYaw ) ), RecvPropBool( RECVINFO( m_bLowAmmo ) ), END_RECV_TABLE() @@ -26,11 +28,21 @@ BEGIN_PREDICTION_DATA( C_ASW_Sentry_Top ) END_PREDICTION_DATA() C_ASW_Sentry_Top::C_ASW_Sentry_Top() : - m_iv_fCenterAimYaw( "C_ASW_Sentry_Top::m_iv_fCenterAimYaw" ) + m_iv_fCenterAimYaw( "C_ASW_Sentry_Top::m_iv_fCenterAimYaw" ), + m_iv_fGoalYaw( "C_ASW_Sentry_Top::m_iv_fGoalYaw" ), + m_iv_fGoalPitch( "C_ASW_Sentry_Top::m_iv_fGoalPitch" ) { + UseClientSideAnimation(); + AddVar( &m_fCenterAimYaw, &m_iv_fCenterAimYaw, LATCH_SIMULATION_VAR ); + AddVar( &m_fGoalYaw, &m_iv_fGoalYaw, LATCH_SIMULATION_VAR ); + AddVar( &m_fGoalPitch, &m_iv_fGoalPitch, LATCH_SIMULATION_VAR ); m_bSpawnedDisplayEffects = false; + + m_iPoseParamPitch = -2; + m_iPoseParamYaw = -2; + m_iPoseParamFireRate = -2; } C_ASW_Sentry_Top::~C_ASW_Sentry_Top() @@ -85,7 +97,10 @@ void C_ASW_Sentry_Top::OnDataChanged( DataUpdateType_t updateType ) C_RD_Weapon_Accessory::CreateWeaponAccessories( this, pBase->m_hOriginalOwnerPlayer->m_EquippedItemDataDynamic[pBase->m_iInventoryEquipSlot], m_hWeaponAccessory, s_pKVAccessoryPosition[pBase->m_nGunType], s_szAccessoryPositionFiles[pBase->m_nGunType] ); } - SetNextClientThink( gpGlobals->curtime ); + m_fAimPitch = -30.0f; // should match the angle that the build animation uses + m_fCameraYaw = 0.0f; + + SetNextClientThink( CLIENT_THINK_ALWAYS ); } if ( m_bLowAmmo && !m_hWarningLight ) @@ -136,9 +151,8 @@ void C_ASW_Sentry_Top::ClientThink() { BaseClass::ClientThink(); + UpdatePose(); Scan(); - - SetNextClientThink( gpGlobals->curtime + 0.05f ); } int C_ASW_Sentry_Top::GetMuzzleAttachment( void ) @@ -209,11 +223,6 @@ C_ASW_Sentry_Base *C_ASW_Sentry_Top::GetSentryBase() return m_hSentryBase.Get(); } -int C_ASW_Sentry_Top::GetSentryAngle( void ) -{ - return m_iSentryAngle; -} - void C_ASW_Sentry_Top::Scan() { C_ASW_Sentry_Base *RESTRICT pBase = GetSentryBase(); @@ -311,7 +320,7 @@ void C_ASW_Sentry_Top::Scan() AdjustRadiusBeamEdges( vecEye, forward, 3 ); scanAngle = baseAngle; - scanAngle.y += GetSentryAngle() * sin( gpGlobals->curtime * 3.0f ); + scanAngle.y += GetScanAngle(); Vector vecBase = pBase->GetAbsOrigin() + Vector( 0, 0, 2 );// + m_vecLightOffset; AngleVectors( scanAngle, &forward, NULL, NULL ); diff --git a/src/game/client/swarm/c_asw_sentry_top.h b/src/game/client/swarm/c_asw_sentry_top.h index 3400dccb5..ba802d300 100644 --- a/src/game/client/swarm/c_asw_sentry_top.h +++ b/src/game/client/swarm/c_asw_sentry_top.h @@ -25,6 +25,8 @@ class C_ASW_Sentry_Top : public C_BaseCombatCharacter virtual bool HasPilotLight() { return false; } int GetSentryAngle( void ); + float GetScanAngle(); + void UpdatePose(); void Scan( void ); void CreateRadiusBeamEdges( const Vector &vecStart, const Vector &vecDir, int iControlPoint ); @@ -44,10 +46,20 @@ class C_ASW_Sentry_Top : public C_BaseCombatCharacter CNetworkVar( int, m_iSentryAngle ); CNetworkVar( float, m_fDeployYaw ); CNetworkVar( float, m_fCenterAimYaw ); + float m_fAimPitch; + float m_fCameraYaw; + CNetworkVar( float, m_fGoalPitch ); + CNetworkVar( float, m_fGoalYaw ); CNetworkVar( bool, m_bLowAmmo ); float GetDeployYaw(); CInterpolatedVar m_iv_fCenterAimYaw; + CInterpolatedVar m_iv_fGoalYaw; + CInterpolatedVar m_iv_fGoalPitch; + + int m_iPoseParamPitch; + int m_iPoseParamYaw; + int m_iPoseParamFireRate; bool m_bSpawnedDisplayEffects; CUtlReference m_hRadiusDisplay; diff --git a/src/game/client/swarm/c_asw_sentry_top_flamer.cpp b/src/game/client/swarm/c_asw_sentry_top_flamer.cpp index d7b24d378..cc3ad36fe 100644 --- a/src/game/client/swarm/c_asw_sentry_top_flamer.cpp +++ b/src/game/client/swarm/c_asw_sentry_top_flamer.cpp @@ -48,12 +48,6 @@ void C_ASW_Sentry_Top_Flamer::OnDataChanged( DataUpdateType_t updateType ) if ( updateType == DATA_UPDATE_CREATED ) { - //CreateObsoleteEmitters(); - - SetNextClientThink(gpGlobals->curtime); - // We might want to think every frame. - // SetNextClientThink( CLIENT_THINK_ALWAYS ); - m_bFiringShadow = m_bFiring; if ( HasPilotLight() && !m_hPilotLight ) @@ -83,91 +77,6 @@ void C_ASW_Sentry_Top_Flamer::OnDataChanged( DataUpdateType_t updateType ) } } -void C_ASW_Sentry_Top_Flamer::ClientThink( void ) -{ - BaseClass::ClientThink(); -/* - // walk emitters and turn them on or off as appropriate, then call their thinks. - if ( m_OldStyleEmitters.Count() > 0 ) - { - // a turret with old school emitters must think every frame. - // we need to forcibly reset this because the base class sets a longer - // think interval. - SetNextClientThink( CLIENT_THINK_ALWAYS ); - - bool bWantEmitters = ShouldEmittersBeOn(); - - Vector vecMuzzle = GetAbsOrigin()+Vector(0,0,30); - QAngle angMuzzle; - - GetAttachment( GetMuzzleAttachment(), vecMuzzle, angMuzzle ); - - Vector vForward; AngleVectors(angMuzzle, &vForward); - angMuzzle.x = m_flPitchHack; - vecMuzzle += vForward * 36; - - for ( int i = 0 ; i < m_OldStyleEmitters.Count() ; ++i ) - { - CSmartPtr &hEmitter = m_OldStyleEmitters[i]; - Assert( hEmitter.IsValid() ); - if ( hEmitter.IsValid() ) - { - if ( hEmitter->GetActive() != bWantEmitters ) - { - hEmitter->SetActive( bWantEmitters ); - } - - // now think them so they have the correct orientation - hEmitter->Think(gpGlobals->frametime, vecMuzzle, angMuzzle); - } - } - } - */ -} - -/* -void C_ASW_Sentry_Top_Flamer::CreateObsoleteEmitters( void ) -{ - AssertMsg( m_OldStyleEmitters.Count() == 0, "Flame turret tried to create emitters when it already had some!" ); - - // create two emitter handles (use default constructor to pull them to NULL to begin with) - m_OldStyleEmitters.EnsureCount(2); - - CSmartPtr &m_hFlameEmitter = m_OldStyleEmitters[0]; - CSmartPtr &m_hFlameStreamEmitter = m_OldStyleEmitters[1]; - - m_hFlameEmitter = CASWGenericEmitter::Create( "asw_emitter" ); - if ( m_hFlameEmitter.IsValid() ) - { - m_hFlameEmitter->UseTemplate("flamer5"); - m_hFlameEmitter->SetActive(false); - m_hFlameEmitter->m_hCollisionIgnoreEntity = this; - m_hFlameEmitter->SetCustomCollisionGroup(ASW_COLLISION_GROUP_IGNORE_NPCS); - //m_hFlameEmitter->SetGlowMaterial("swarm/sprites/aswredglow2"); - } - else - { - AssertMsg( false, "m_hFlameEmitter.IsValid()" ); - Warning("Failed to create a turret's flame emitter\n"); - } - - m_hFlameStreamEmitter = CASWGenericEmitter::Create( "asw_emitter" ); - if ( m_hFlameStreamEmitter.IsValid() ) - { - m_hFlameStreamEmitter->UseTemplate("flamerstream1"); - m_hFlameStreamEmitter->SetActive(false); - m_hFlameStreamEmitter->m_hCollisionIgnoreEntity = this; - m_hFlameStreamEmitter->SetCustomCollisionGroup(ASW_COLLISION_GROUP_IGNORE_NPCS); - //m_hFlameEmitter->SetGlowMaterial("swarm/sprites/aswredglow2"); - } - else - { - AssertMsg( false, "m_hFlameStreamEmitter.IsValid()" ); - Warning("Failed to create a turret's flame stream emitter\n"); - } -} -*/ - void C_ASW_Sentry_Top_Flamer::OnStartFiring() { if ( m_szBeginFireSoundScriptName ) @@ -221,4 +130,4 @@ void C_ASW_Sentry_Top_Flamer::UpdateOnRemove() OnStopFiring(); BaseClass::UpdateOnRemove(); -} \ No newline at end of file +} diff --git a/src/game/client/swarm/c_asw_sentry_top_flamer.h b/src/game/client/swarm/c_asw_sentry_top_flamer.h index 9e2b40c45..1a63513cf 100644 --- a/src/game/client/swarm/c_asw_sentry_top_flamer.h +++ b/src/game/client/swarm/c_asw_sentry_top_flamer.h @@ -16,12 +16,8 @@ class C_ASW_Sentry_Top_Flamer : public C_ASW_Sentry_Top DECLARE_CLIENTCLASS(); DECLARE_PREDICTABLE(); - C_ASW_Sentry_Top_Flamer(); - // virtual ~C_ASW_Sentry_Top_Flamer(); - - virtual void ClientThink( void ); virtual void OnDataChanged( DataUpdateType_t updateType ); inline bool IsPlayingFiringLoopSound( ); @@ -30,37 +26,24 @@ class C_ASW_Sentry_Top_Flamer : public C_ASW_Sentry_Top virtual void UpdateOnRemove(); protected: - /////////////// init functions - /// Create any old-school emitters associated with weapon fx - /// Override in child classes ( do not call to base ) - //virtual void CreateObsoleteEmitters( void ); - + void OnStartFiring(); + void OnStopFiring(); - /////////////// internal functions - //inline bool ShouldEmittersBeOn( void ) { return m_bFiring || asw_sentry_emitter_test.GetBool(); } - void OnStartFiring(); // can be made virtual if necessary - void OnStopFiring(); // can be made virtual if necessary + CUtlReference m_hFiringEffect; + CNetworkVar( bool, m_bFiring ); + CNetworkVar( float, m_flPitchHack ); // just for a second to deal with stairs until I get proper pitch gimballing on base class + CSoundPatch *m_pFlamerLoopSound; - /// A vector of old-school particle effects associated with this - /// turret (the old flamer had two, the icer has one, who knows - /// how many others might have) - //CUtlVector< CSmartPtr > m_OldStyleEmitters; - CUtlReference m_hFiringEffect; - CNetworkVar(bool, m_bFiring); - CNetworkVar(float, m_flPitchHack); // just for a second to deal with stairs until I get proper pitch gimballing on base class - CSoundPatch* m_pFlamerLoopSound; - - const char * m_szParticleEffectFireName; + const char *m_szParticleEffectFireName; // sound scripts for begin, during, end (initialize in your constructor) // allowed to be NULL - const char * m_szBeginFireSoundScriptName; - const char * m_szDuringFireSoundScriptName; - const char * m_szEndFireSoundScriptName; + const char *m_szBeginFireSoundScriptName; + const char *m_szDuringFireSoundScriptName; + const char *m_szEndFireSoundScriptName; // shadows of network vars so we can detect their change in OnDataChanged bool m_bFiringShadow : 1; - }; diff --git a/src/game/client/swarm_sdk_client.vcxproj b/src/game/client/swarm_sdk_client.vcxproj index 9f7f27ea8..51af04d01 100644 --- a/src/game/client/swarm_sdk_client.vcxproj +++ b/src/game/client/swarm_sdk_client.vcxproj @@ -794,6 +794,7 @@ if exist ..\..\devtools\bin\postbuild.cmd ..\..\devtools\bin\postbuild.cmd clien + diff --git a/src/game/server/swarm/asw_sentry_base.cpp b/src/game/server/swarm/asw_sentry_base.cpp index d1c1724ac..432aa591b 100644 --- a/src/game/server/swarm/asw_sentry_base.cpp +++ b/src/game/server/swarm/asw_sentry_base.cpp @@ -44,6 +44,7 @@ IMPLEMENT_SERVERCLASS_ST( CASW_Sentry_Base, DT_ASW_Sentry_Base ) SendPropInt( SENDINFO( m_nGunType ), NumBitsForCount( kGUNTYPE_MAX ), SPROP_UNSIGNED ), SendPropEHandle( SENDINFO( m_hOriginalOwnerPlayer ) ), SendPropIntWithMinusOneFlag( SENDINFO( m_iInventoryEquipSlot ), NumBitsForCount( RD_NUM_STEAM_INVENTORY_EQUIP_SLOTS_DYNAMIC + 1 ) ), + SendPropEHandle( SENDINFO( m_hLastDisassembler ) ), END_SEND_TABLE() BEGIN_DATADESC( CASW_Sentry_Base ) @@ -54,6 +55,7 @@ BEGIN_DATADESC( CASW_Sentry_Base ) DEFINE_FIELD( m_fAssembleProgress, FIELD_FLOAT ), DEFINE_FIELD( m_fAssembleCompleteTime, FIELD_TIME ), DEFINE_FIELD( m_hDeployer, FIELD_EHANDLE ), + DEFINE_FIELD( m_hLastDisassembler, FIELD_EHANDLE ), END_DATADESC() BEGIN_ENT_SCRIPTDESC( CASW_Sentry_Base, CBaseAnimating, "sentry" ) @@ -89,6 +91,7 @@ CASW_Sentry_Base::CASW_Sentry_Base() } m_hOriginalOwnerPlayer = NULL; m_iInventoryEquipSlot = -1; + m_hLastDisassembler = NULL; } @@ -217,8 +220,13 @@ void CASW_Sentry_Base::ActivateUseIcon( CASW_Inhabitable_NPC *pNPC, int nHoldTyp { if ( nHoldType == ASW_USE_HOLD_START ) { + bool bAlreadyUsing = pMarine->m_hUsingEntity.Get() == this; pMarine->StartUsing( this ); - pMarine->GetMarineSpeech()->Chatter( CHATTER_USE ); + + m_hLastDisassembler = pMarine; + + if ( !bAlreadyUsing ) + pMarine->GetMarineSpeech()->Chatter( CHATTER_USE ); } else if ( nHoldType == ASW_USE_HOLD_RELEASE_FULL ) { @@ -226,7 +234,6 @@ void CASW_Sentry_Base::ActivateUseIcon( CASW_Inhabitable_NPC *pNPC, int nHoldTyp if ( !m_bAlreadyTaken ) { - //Msg( "Disassembling sentry gun!\n" ); IGameEvent *event = gameeventmanager->CreateEvent( "sentry_dismantled" ); if ( event ) { @@ -250,20 +257,11 @@ void CASW_Sentry_Base::ActivateUseIcon( CASW_Inhabitable_NPC *pNPC, int nHoldTyp UTIL_Remove( this ); m_bAlreadyTaken = true; } - - // TODO: just have the marine pick it up now and let that logic deal with the slot? - - // TODO: Find an empty inv slot. Or default to 2nd. - // Drop whatever's in that slot currently - // Create a new sentry gun weapon with our ammo amount and give it to the marine - // Destroy ourselves } else if ( nHoldType == ASW_USE_RELEASE_QUICK ) { pMarine->StopUsing(); - pMarine->GetMarineSpeech()->Chatter( CHATTER_USE ); - IGameEvent *event = gameeventmanager->CreateEvent( "sentry_rotated" ); if ( event ) { diff --git a/src/game/server/swarm/asw_sentry_base.h b/src/game/server/swarm/asw_sentry_base.h index 6d105edc5..56ff5e226 100644 --- a/src/game/server/swarm/asw_sentry_base.h +++ b/src/game/server/swarm/asw_sentry_base.h @@ -87,6 +87,8 @@ class CASW_Sentry_Base : public CBaseAnimating, public IASW_Server_Usable_Entity // so this is not something we should spend resources trying to prevent. bool IsInventoryEquipSlotValid() const { return !!m_hOriginalOwnerPlayer && m_iInventoryEquipSlot != -1; } + CNetworkHandle( CASW_Inhabitable_NPC, m_hLastDisassembler ); + protected: CNetworkVar( int, m_nGunType ); diff --git a/src/game/server/swarm/asw_sentry_top.cpp b/src/game/server/swarm/asw_sentry_top.cpp index 5e25d7bae..6ba43ef01 100644 --- a/src/game/server/swarm/asw_sentry_top.cpp +++ b/src/game/server/swarm/asw_sentry_top.cpp @@ -25,6 +25,8 @@ IMPLEMENT_SERVERCLASS_ST( CASW_Sentry_Top, DT_ASW_Sentry_Top ) SendPropInt( SENDINFO( m_iSentryAngle ) ), SendPropAngle( SENDINFO( m_fDeployYaw ), 11 ), SendPropAngle( SENDINFO( m_fCenterAimYaw ), 11, SPROP_CHANGES_OFTEN ), + SendPropAngle( SENDINFO( m_fGoalPitch ), 11, SPROP_CHANGES_OFTEN ), + SendPropAngle( SENDINFO( m_fGoalYaw ), 11, SPROP_CHANGES_OFTEN ), SendPropBool( SENDINFO( m_bLowAmmo ) ), END_SEND_TABLE() @@ -39,11 +41,12 @@ extern ConVar asw_sentry_friendly_fire_scale; BEGIN_DATADESC( CASW_Sentry_Top ) DEFINE_THINKFUNC( AnimThink ), - DEFINE_FIELD( m_fTurnRate, FIELD_FLOAT ), DEFINE_FIELD( m_flTimeFirstFired, FIELD_TIME ), DEFINE_FIELD( m_fLastThinkTime, FIELD_TIME ), DEFINE_FIELD( m_fNextFireTime, FIELD_TIME ), + DEFINE_FIELD( m_fGoalPitch, FIELD_FLOAT ), DEFINE_FIELD( m_fGoalYaw, FIELD_FLOAT ), + DEFINE_FIELD( m_fAimPitch, FIELD_FLOAT ), DEFINE_FIELD( m_fDeployYaw, FIELD_FLOAT ), DEFINE_FIELD( m_fCenterAimYaw, FIELD_FLOAT ), DEFINE_FIELD( m_fCurrentYaw, FIELD_FLOAT ), @@ -53,10 +56,9 @@ BEGIN_DATADESC( CASW_Sentry_Top ) DEFINE_FIELD( m_flNextTurnSound, FIELD_TIME ), DEFINE_FIELD( m_hSentryBase, FIELD_EHANDLE ), DEFINE_FIELD( m_iBaseTurnRate, FIELD_INTEGER ), + DEFINE_FIELD( m_iEnemyTurnRate, FIELD_INTEGER ), DEFINE_FIELD( m_iSentryAngle, FIELD_INTEGER ), - DEFINE_FIELD( m_fTurnRate, FIELD_FLOAT ), DEFINE_KEYFIELD( m_flShootRange, FIELD_FLOAT, "TurretRange" ), - DEFINE_FIELD( m_bHasHysteresis, FIELD_BOOLEAN ), DEFINE_FIELD( m_bLowAmmo, FIELD_BOOLEAN ), END_DATADESC() @@ -69,10 +71,18 @@ END_SCRIPTDESC() CASW_Sentry_Top::CASW_Sentry_Top() { + UseClientSideAnimation(); + m_flShootRange = ASW_SENTRY_RANGE; m_iAmmoType = GetAmmoDef()->Index( "ASW_R" ); - m_iBaseTurnRate = ASW_SENTRY_TURNRATE; + m_iBaseTurnRate = ASW_SENTRY_TURNRATE / 2; + m_iEnemyTurnRate = ASW_SENTRY_TURNRATE; m_iSentryAngle = ASW_SENTRY_ANGLE; + + m_iPoseParamPitch = -2; + m_iPoseParamYaw = -2; + m_iPoseParamFireRate = -2; + m_bLowAmmo = false; } @@ -104,11 +114,15 @@ void CASW_Sentry_Top::Spawn( void ) m_fCenterAimYaw = GetAbsAngles().y; m_fCurrentYaw = GetAbsAngles().y; + m_fAimPitch = -30.0f; // should match the angle that the build animation uses + m_fCameraYaw = 0.0f; + if ( GetMoveParent() ) { m_fDeployYaw -= GetMoveParent()->GetAbsAngles().y; } + m_fLastThinkTime = gpGlobals->curtime; SetThink( &CASW_Sentry_Top::AnimThink ); SetNextThink( gpGlobals->curtime + 0.01f ); } @@ -169,6 +183,7 @@ void CASW_Sentry_Top::AnimThink( void ) UpdateGoal(); TurnToGoal(deltatime); + UpdatePose(); CheckFiring(); StudioFrameAdvance(); } @@ -207,12 +222,20 @@ void CASW_Sentry_Top::UpdateGoal() { if ( !m_hEnemy.IsValid() || !m_hEnemy.Get() ) { + if ( HasHysteresis() ) + { + // leave goal unchanged during over-shoot + return; + } + m_fGoalYaw = GetDeployYaw(); + m_fGoalPitch = 0.0f; } else { // set our goal yaw to point at the enemy m_fGoalYaw = GetYawTo( m_hEnemy ); + m_fGoalPitch = UTIL_VecToPitch( m_hEnemy->WorldSpaceCenter() - WorldSpaceCenter() ); } } @@ -234,9 +257,9 @@ void CASW_Sentry_Top::TurnToGoal( float deltatime ) } // set our turn rate depending on if we have an enemy or not - float fTurnRate = ASW_SENTRY_TURNRATE * 0.5f; + float fTurnRate = m_iBaseTurnRate; if ( m_hEnemy.IsValid() && m_hEnemy.Get() ) - fTurnRate = ASW_SENTRY_TURNRATE; + fTurnRate = m_iEnemyTurnRate; if ( fabsf( fDist ) < deltatime * fTurnRate ) { @@ -244,7 +267,7 @@ void CASW_Sentry_Top::TurnToGoal( float deltatime ) } else { - // turn it + // turn it m_fCurrentYaw += deltatime * fTurnRate * ( fDist > 0.0f ? 1.0f : -1.0f ); if ( m_fCurrentYaw < -180.0f ) @@ -433,7 +456,7 @@ bool CASW_Sentry_Top::CanSee( CBaseEntity *pEnt ) if ( fYawDiff > 360.0f ) fYawDiff -= 360.0f; - if ( fabs( fYawDiff ) > ASW_SENTRY_ANGLE ) + if ( fabs( fYawDiff ) > GetSentryAngle() ) { m_iCanSeeError = 1; return false; @@ -505,12 +528,12 @@ bool CASW_Sentry_Top::IsValidEnemy( CAI_BaseNPC *pNPC ) void CASW_Sentry_Top::CheckFiring() { - if ( gpGlobals->curtime > m_fNextFireTime && HasAmmo() && ( m_bHasHysteresis || m_hEnemy.Get() ) ) + if ( gpGlobals->curtime > m_fNextFireTime && HasAmmo() && ( HasHysteresis() || m_hEnemy.Get() ) ) { float flDist = fabs( m_fGoalYaw - m_fCurrentYaw ); flDist = fsel( flDist - 180, 360 - flDist, flDist ); - if ( ( flDist < ASW_SENTRY_FIRE_ANGLE_THRESHOLD ) || ( m_bHasHysteresis && !m_hEnemy ) ) + if ( ( flDist < ASW_SENTRY_FIRE_ANGLE_THRESHOLD ) || ( HasHysteresis() && !m_hEnemy ) ) { Fire(); } diff --git a/src/game/server/swarm/asw_sentry_top.h b/src/game/server/swarm/asw_sentry_top.h index 31a15f83b..6bf4ce81c 100644 --- a/src/game/server/swarm/asw_sentry_top.h +++ b/src/game/server/swarm/asw_sentry_top.h @@ -32,10 +32,14 @@ class CASW_Sentry_Top : public CBaseCombatCharacter void PlayTurnSound(); void UpdateGoal(); + int GetSentryAngle(); + float GetScanAngle(); + void UpdatePose(); void TurnToGoal( float deltatime ); void FindEnemy(); virtual bool IsValidEnemy( CAI_BaseNPC *pNPC ); virtual void CheckFiring(); + virtual bool HasHysteresis() { return false; } // if true, CheckFiring() will be called even if there is no m_hEnemy virtual void Fire( void ); virtual void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ); virtual void OnUsedQuarterAmmo( void ); @@ -50,8 +54,11 @@ class CASW_Sentry_Top : public CBaseCombatCharacter float m_fLastThinkTime; float m_fNextFireTime; - float m_fGoalYaw, m_fCurrentYaw; - + float m_fCurrentYaw; + float m_fAimPitch; + float m_fCameraYaw; + CNetworkVar( float, m_fGoalPitch ); + CNetworkVar( float, m_fGoalYaw ); CNetworkVar( float, m_fDeployYaw ); CNetworkVar( float, m_fCenterAimYaw ); CNetworkVar( bool, m_bLowAmmo ); @@ -61,14 +68,18 @@ class CASW_Sentry_Top : public CBaseCombatCharacter int m_iCanSeeError; int m_iAmmoType; int m_iBaseTurnRate; // angles per second + int m_iEnemyTurnRate; CNetworkVar( int, m_iSentryAngle ); - float m_fTurnRate; // actual turn rate float m_flTimeFirstFired; // sound stuff float m_flNextTurnSound; + int m_iPoseParamPitch; + int m_iPoseParamYaw; + int m_iPoseParamFireRate; + virtual const Vector &GetBulletSpread( void ) { static Vector cone; @@ -99,7 +110,6 @@ class CASW_Sentry_Top : public CBaseCombatCharacter virtual CAI_BaseNPC *SelectOptimalEnemy() ; float m_flShootRange; - bool m_bHasHysteresis; // if true, CheckFiring() will be called even if there is no m_hEnemy }; @@ -108,5 +118,4 @@ inline float CASW_Sentry_Top::GetRange() return m_flShootRange; } - #endif /* _DEFINED_ASW_SENTRY_TOP_H */ diff --git a/src/game/server/swarm/asw_sentry_top_flamer.cpp b/src/game/server/swarm/asw_sentry_top_flamer.cpp index c55cf07ff..b840a9e3a 100644 --- a/src/game/server/swarm/asw_sentry_top_flamer.cpp +++ b/src/game/server/swarm/asw_sentry_top_flamer.cpp @@ -49,7 +49,7 @@ CASW_Sentry_Top_Flamer::CASW_Sentry_Top_Flamer( int projectileVelocity ) : m_b m_flShootRange = 375.0f; // increase turn rate until I get better leading code in (so it can actually hit something) - m_fTurnRate *= 3.0f; + m_iEnemyTurnRate *= 3; m_flFireHysteresisTime = gpGlobals->curtime; } @@ -111,6 +111,10 @@ void CASW_Sentry_Top_Flamer::CheckFiring() } } +bool CASW_Sentry_Top_Flamer::HasHysteresis() +{ + return m_flFireHysteresisTime > gpGlobals->curtime; +} //ITraceFilter *CASW_Sentry_Top_Flamer::GetVisibilityTraceFilter() //{ @@ -285,7 +289,7 @@ Vector ProjectileIntercept( const Vector &vProjectileOrigin, const float fProjec const Vector &vTargetOrigin, const Vector &vTargetDirection, float * RESTRICT pflTime ) { -#pragma message("TODO: make SIMD") +//#pragma message("TODO: make SIMD") /* math: let B = (A - S) |P| = Q diff --git a/src/game/server/swarm/asw_sentry_top_flamer.h b/src/game/server/swarm/asw_sentry_top_flamer.h index eef02f0b3..2b1c0210c 100644 --- a/src/game/server/swarm/asw_sentry_top_flamer.h +++ b/src/game/server/swarm/asw_sentry_top_flamer.h @@ -18,6 +18,7 @@ class CASW_Sentry_Top_Flamer : public CASW_Sentry_Top /// Override this because flamer-style turrets don't have discrete fire, /// but rather start and stop firing continuously. virtual void CheckFiring(); + bool HasHysteresis() override; virtual float GetYawTo(CBaseEntity* pEnt); diff --git a/src/game/server/swarm/asw_sentry_top_icer.cpp b/src/game/server/swarm/asw_sentry_top_icer.cpp index c6450c064..dffd3a9c8 100644 --- a/src/game/server/swarm/asw_sentry_top_icer.cpp +++ b/src/game/server/swarm/asw_sentry_top_icer.cpp @@ -47,7 +47,7 @@ CASW_Sentry_Top_Icer::CASW_Sentry_Top_Icer() : CASW_Sentry_Top_Flamer(CASW_Weapo { m_flShootRange = 300; // increase turn rate until I get better leading code in (so it can actually hit something) - m_fTurnRate *= 3.0f; + m_iEnemyTurnRate *= 3; m_flEnemyOverfreezePermittedUntil = gpGlobals->curtime; } diff --git a/src/game/server/swarm/asw_sentry_top_machinegun.cpp b/src/game/server/swarm/asw_sentry_top_machinegun.cpp index 1ac20d0f0..d36ddbd0e 100644 --- a/src/game/server/swarm/asw_sentry_top_machinegun.cpp +++ b/src/game/server/swarm/asw_sentry_top_machinegun.cpp @@ -21,11 +21,6 @@ ConVar asw_sentry_top_machinegun_fire_rate( "asw_sentry_top_machinegun_fire_rate LINK_ENTITY_TO_CLASS( asw_sentry_top_machinegun, CASW_Sentry_Top_Machinegun ); PRECACHE_REGISTER( asw_sentry_top_machinegun ); -/* -IMPLEMENT_SERVERCLASS_ST(CASW_Sentry_Top_Machinegun, DT_ASW_Sentry_Top_Machinegun ) -END_SEND_TABLE() -*/ - BEGIN_DATADESC( CASW_Sentry_Top_Machinegun ) END_DATADESC() @@ -37,11 +32,6 @@ void CASW_Sentry_Top_Machinegun::Spawn( void ) { BaseClass::Spawn(); - if ( GetSentryBase() ) - { - m_bHasHysteresis = true; - } - m_flFireHysteresisTime = gpGlobals->curtime; } @@ -50,6 +40,11 @@ void CASW_Sentry_Top_Machinegun::SetTopModel() SetModel(SENTRY_TOP_MODEL); } +bool CASW_Sentry_Top_Machinegun::HasHysteresis() +{ + return m_flFireHysteresisTime > gpGlobals->curtime; +} + void CASW_Sentry_Top_Machinegun::Fire() { if ( !HasAmmo() ) @@ -57,7 +52,7 @@ void CASW_Sentry_Top_Machinegun::Fire() BaseClass::Fire(); - Vector diff; + Vector diff; if ( !m_hEnemy ) { if ( gpGlobals->curtime > m_flFireHysteresisTime ) @@ -71,40 +66,40 @@ void CASW_Sentry_Top_Machinegun::Fire() else { diff = m_hEnemy->WorldSpaceCenter() - GetFiringPosition(); - m_flFireHysteresisTime = gpGlobals->curtime + ASW_SENTRY_OVERFIRE ; + m_flFireHysteresisTime = gpGlobals->curtime + ASW_SENTRY_OVERFIRE; } // if we haven't fired in a few ticks, assume this is the first bullet in a salvo, // and reset the next fire time such that we fire only one bullet this tick. // m_fNextFireTime = curtime - m_fNextFireTime >= gpGlobals->interval_per_tick * 3 ? curtime : m_fNextFireTime - m_fNextFireTime = fsel( gpGlobals->curtime - m_fNextFireTime - gpGlobals->interval_per_tick * 3.0f, gpGlobals->curtime, m_fNextFireTime ); - CASW_Sentry_Base* const pBase = GetSentryBase(); + m_fNextFireTime = fsel( gpGlobals->curtime - m_fNextFireTime - gpGlobals->interval_per_tick * 3.0f, gpGlobals->curtime, m_fNextFireTime ); + CASW_Sentry_Base *const pBase = GetSentryBase(); const float fPriorTickTime = gpGlobals->curtime - gpGlobals->interval_per_tick; - do + do { - FireBulletsInfo_t info(1, GetFiringPosition(), diff, GetBulletSpread(), - GetRange(), m_iAmmoType); + FireBulletsInfo_t info( 1, GetFiringPosition(), diff, GetBulletSpread(), + GetRange(), m_iAmmoType ); info.m_pAttacker = this; - info.m_pAdditionalIgnoreEnt = GetSentryBase(); + info.m_pAdditionalIgnoreEnt = GetSentryBase(); if ( asw_sentry_top_machinegun_dmg_override.GetFloat() > 0 ) info.m_flDamage = asw_sentry_top_machinegun_dmg_override.GetFloat(); else info.m_flDamage = GetSentryDamage(); info.m_iTracerFreq = 1; - FireBullets(info); + FireBullets( info ); // because we may emit more than one bullet per server tick, space the play time // of the sounds out so they are at a regular interval. technically what's happening // here is that we are "queuing up" the bullets that are supposed to be fired this // frame. EmitSound( "ASW_Sentry.Fire", gpGlobals->curtime + m_fNextFireTime - fPriorTickTime ); - + CEffectData data; data.m_vOrigin = GetAbsOrigin(); //data.m_vNormal = dir; //data.m_flScale = (float)amount; CPASFilter filter( data.m_vOrigin ); - filter.SetIgnorePredictionCull(true); + filter.SetIgnorePredictionCull( true ); DispatchParticleEffect( "muzzle_sentrygun", PATTACH_POINT_FOLLOW, this, "muzzle", false, -1, &filter ); // advance by consistent interval (may cause more than one bullet to be fired per frame) @@ -116,18 +111,4 @@ void CASW_Sentry_Top_Machinegun::Fire() pBase->OnFiredShots(); } } while ( m_fNextFireTime < gpGlobals->curtime ); - -} - -/* -int CASW_Sentry_Top_Machinegun::GetSentryDamage() -{ - float flDamage = 10.0f; - if ( ASWGameRules() ) - { - ASWGameRules()->ModifyAlienDamageBySkillLevel( flDamage ); - } - - return flDamage * GetSentryBase()->m_fDamageScale; } -*/ \ No newline at end of file diff --git a/src/game/server/swarm/asw_sentry_top_machinegun.h b/src/game/server/swarm/asw_sentry_top_machinegun.h index 062f93025..7836d5be6 100644 --- a/src/game/server/swarm/asw_sentry_top_machinegun.h +++ b/src/game/server/swarm/asw_sentry_top_machinegun.h @@ -8,16 +8,16 @@ class CASW_Sentry_Top_Machinegun : public CASW_Sentry_Top { public: DECLARE_CLASS( CASW_Sentry_Top_Machinegun, CASW_Sentry_Top ); - // DECLARE_SERVERCLASS(); DECLARE_DATADESC(); - virtual void Spawn( void ); + void Spawn( void ) override; - virtual void Fire(); - virtual void SetTopModel(); + void SetTopModel() override; + bool HasHysteresis() override; + void Fire() override; // Classification - virtual Class_T Classify( void ) { return (Class_T) CLASS_ASW_SENTRY_GUN; } + Class_T Classify( void ) override { return (Class_T) CLASS_ASW_SENTRY_GUN; } protected: float m_flFireHysteresisTime; // some turrets have a mechanism to continue shooting without an enemy diff --git a/src/game/server/swarm_sdk_server.vcxproj b/src/game/server/swarm_sdk_server.vcxproj index 20885f9f5..9f2a6ac84 100644 --- a/src/game/server/swarm_sdk_server.vcxproj +++ b/src/game/server/swarm_sdk_server.vcxproj @@ -715,6 +715,7 @@ if exist ..\..\devtools\bin\postbuild.cmd ..\..\devtools\bin\postbuild.cmd serve + diff --git a/src/game/shared/swarm/asw_sentry_top_shared.cpp b/src/game/shared/swarm/asw_sentry_top_shared.cpp new file mode 100644 index 000000000..09ea303cd --- /dev/null +++ b/src/game/shared/swarm/asw_sentry_top_shared.cpp @@ -0,0 +1,68 @@ +#include "cbase.h" +#ifdef CLIENT_DLL +#include "c_asw_sentry_top.h" +#include "c_asw_sentry_base.h" +#include "c_asw_marine.h" +#include "c_asw_player.h" +#define CASW_Sentry_Top C_ASW_Sentry_Top +#define CASW_Sentry_Base C_ASW_Sentry_Base +#else +#include "asw_sentry_top.h" +#include "asw_sentry_base.h" +#include "asw_marine.h" +#include "asw_player.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +int CASW_Sentry_Top::GetSentryAngle() +{ + return m_iSentryAngle; +} + +float CASW_Sentry_Top::GetScanAngle() +{ + return GetSentryAngle() * sin( gpGlobals->curtime * 3.0f ); +} + +void CASW_Sentry_Top::UpdatePose() +{ + if ( m_iPoseParamPitch == -2 ) + m_iPoseParamPitch = LookupPoseParameter( "aim_pitch" ); + if ( m_iPoseParamYaw == -2 ) + m_iPoseParamYaw = LookupPoseParameter( "aim_yaw" ); + if ( m_iPoseParamFireRate == -2 ) + m_iPoseParamFireRate = LookupPoseParameter( "fire_rate" ); + + bool bReturning = AlmostEqual( m_fGoalYaw, m_fCenterAimYaw ); + QAngle angles = GetAbsAngles(); + + float flTargetPitch = bReturning ? 0.0f : ( m_fGoalPitch - angles.x ); + float flTargetYaw = bReturning ? 0.0f : ( m_fGoalYaw - angles.y ); + + if ( bReturning ) + { + CASW_Sentry_Base *pBase = GetSentryBase(); + CASW_Marine *pLastDisassembler = pBase ? CASW_Marine::AsMarine( pBase->m_hLastDisassembler ) : NULL; + CASW_Player *pDisassemblePlayer = pLastDisassembler && pLastDisassembler->IsInhabited() && pLastDisassembler->GetUsingEntity() == pBase ? pLastDisassembler->GetCommander() : NULL; + if ( pDisassemblePlayer ) + { + float flDisassembleProgress = ( ( gpGlobals->curtime - pDisassemblePlayer->m_flUseKeyDownTime ) - 0.2f ) / ( ASW_USE_KEY_HOLD_SENTRY_TIME - 0.2f ); + flTargetPitch = flDisassembleProgress * -90.0f; + } + } + + float flPitchSpeed = ( bReturning ? 15.0f : 150.0f ) * gpGlobals->frametime; + float flYawSpeed = ( bReturning ? 15.0f : 150.0f ) * gpGlobals->frametime; + + m_fAimPitch = ApproachAngle( flTargetPitch, m_fAimPitch, flPitchSpeed ); + m_fCameraYaw = ApproachAngle( flTargetYaw, m_fCameraYaw, flYawSpeed ); + + if ( m_iPoseParamPitch != -1 ) + SetPoseParameter( m_iPoseParamPitch, m_fAimPitch ); + if ( m_iPoseParamYaw != -1 ) + SetPoseParameter( m_iPoseParamYaw, m_fCameraYaw ); + if ( m_iPoseParamFireRate != -1 ) + SetPoseParameter( m_iPoseParamFireRate, bReturning || fabsf( flTargetYaw ) > 10.0f ? 0.0f : 1.0f ); +} diff --git a/src/game/shared/swarm/rd_inventory_shared.cpp b/src/game/shared/swarm/rd_inventory_shared.cpp index 3f694a3c3..cae7427ac 100644 --- a/src/game/shared/swarm/rd_inventory_shared.cpp +++ b/src/game/shared/swarm/rd_inventory_shared.cpp @@ -22,6 +22,7 @@ #include "c_asw_marine.h" #include "c_asw_weapon.h" #include "c_asw_sentry_base.h" +#include "c_asw_sentry_top.h" #include "c_asw_game_resource.h" #include "asw_equipment_list.h" #include "rd_workshop.h" @@ -35,6 +36,7 @@ #include "asw_hud_3dmarinenames.h" #include "rd_collections.h" #define CASW_Sentry_Base C_ASW_Sentry_Base +#define CASW_Sentry_Top C_ASW_Sentry_Top #else #include "asw_player.h" #include "asw_marine_resource.h" @@ -1058,6 +1060,13 @@ static class CRD_Inventory_Manager final : public CAutoGameSystem, public CGameE { s_RD_Inventory_Manager.IncrementStrangePropertyOnWeaponAndGlobals( pNPC, pSentry, iAccessoryID, iAmount, iPropertyIndex, bRelative ); } + else if ( CASW_Sentry_Top *pSentry = dynamic_cast< CASW_Sentry_Top * >( pWeapon ) ) + { + if ( CASW_Sentry_Base *pBase = pSentry->GetSentryBase() ) + { + s_RD_Inventory_Manager.IncrementStrangePropertyOnWeaponAndGlobals( pNPC, pBase, iAccessoryID, iAmount, iPropertyIndex, bRelative ); + } + } else if ( IRD_Has_Projectile_Data *pProjectile = dynamic_cast< IRD_Has_Projectile_Data * >( pWeapon ) ) { CRD_ProjectileData *pData = const_cast< CRD_ProjectileData * >( pProjectile->GetProjectileData() );