From facc9078e8ef29e0dfdb9dcbcba641bb385e4742 Mon Sep 17 00:00:00 2001 From: doclic Date: Sat, 8 Mar 2025 14:34:33 +0100 Subject: [PATCH 1/3] tf2: Allow vscript to set damage for force calc This fixes https://github.com/ValveSoftware/Source-1-Games/issues/6056 --- src/game/shared/tf/tf_gamerules.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/shared/tf/tf_gamerules.cpp b/src/game/shared/tf/tf_gamerules.cpp index cce13ada8e2..01640475c4a 100644 --- a/src/game/shared/tf/tf_gamerules.cpp +++ b/src/game/shared/tf/tf_gamerules.cpp @@ -5913,7 +5913,7 @@ void CTFGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecS //----------------------------------------------------------------------------- bool CTFGameRules::ApplyOnDamageModifyRules( CTakeDamageInfo &info, CBaseEntity *pVictimBaseEntity, bool bAllowDamage ) { - info.SetDamageForForceCalc( info.GetDamage() ); + if (!info.GetDamageForForceCalc()) info.SetDamageForForceCalc( info.GetDamage() ); bool bDebug = tf_debug_damage.GetBool(); CTFPlayer *pVictim = ToTFPlayer( pVictimBaseEntity ); From 0d557e6073537a00970be78c72df15eac5a4f1e1 Mon Sep 17 00:00:00 2001 From: Sean McGeehan Date: Wed, 4 Mar 2026 16:47:50 -0500 Subject: [PATCH 2/3] Fix most broken spy disguise weapons. --- src/game/shared/tf/tf_item_wearable.cpp | 56 ++++++++++++----- src/game/shared/tf/tf_item_wearable.h | 6 +- src/game/shared/tf/tf_player_shared.cpp | 47 +++++++++++++- src/game/shared/tf/tf_weaponbase.cpp | 84 ++++++++++++++++++++++++- src/game/shared/tf/tf_weaponbase.h | 1 + 5 files changed, 176 insertions(+), 18 deletions(-) diff --git a/src/game/shared/tf/tf_item_wearable.cpp b/src/game/shared/tf/tf_item_wearable.cpp index db20eb12bd4..c50fad9fdcc 100644 --- a/src/game/shared/tf/tf_item_wearable.cpp +++ b/src/game/shared/tf/tf_item_wearable.cpp @@ -516,6 +516,32 @@ void CTFWearable::ValidateModelIndex( void ) #endif +//----------------------------------------------------------------------------- +// Purpose: Helper to apply bodygroups from an item to a disguise target. +//----------------------------------------------------------------------------- +void CTFWearable::UpdateDisguiseBodygroups( CTFPlayer *pTFOwner, CTFPlayer *pDisguiseTarget, CEconItemView *pItem, int iTeam, int iState ) +{ + if ( !pTFOwner || !pDisguiseTarget || !pItem ) + return; + + int iDisguiseBody = pTFOwner->m_Shared.GetDisguiseBody(); + int iNumBodyGroups = pItem->GetStaticData()->GetNumModifiedBodyGroups( iTeam ); + + for ( int i = 0; i < iNumBodyGroups; ++i ) + { + int iBody = 0; + const char *pszBodyGroup = pItem->GetStaticData()->GetModifiedBodyGroup( iTeam, i, iBody ); + int iBodyGroup = pDisguiseTarget->FindBodygroupByName( pszBodyGroup ); + + if ( iBodyGroup == -1 ) + continue; + + ::SetBodygroup( pDisguiseTarget->GetModelPtr(), iDisguiseBody, iBodyGroup, iState ); + } + + pTFOwner->m_Shared.SetDisguiseBody( iDisguiseBody ); +} + //----------------------------------------------------------------------------- // Purpose: Hides or shows masked bodygroups associated with this item. //----------------------------------------------------------------------------- @@ -529,28 +555,30 @@ bool CTFWearable::UpdateBodygroups( CBaseCombatCharacter* pOwner, int iState ) if ( bBaseUpdate && m_bDisguiseWearable ) { CEconItemView *pItem = GetAttributeContainer()->GetItem(); // Safe. Checked in base class call. - CTFPlayer *pDisguiseTarget = pTFOwner->m_Shared.GetDisguiseTarget(); if ( !pDisguiseTarget ) return false; - // Update our disguise bodygroup. - int iDisguiseBody = pTFOwner->m_Shared.GetDisguiseBody(); int iTeam = pTFOwner->m_Shared.GetDisguiseTeam(); - int iNumBodyGroups = pItem->GetStaticData()->GetNumModifiedBodyGroups( iTeam ); - for ( int i=0; iGetStaticData()->GetModifiedBodyGroup( iTeam, i, iBody ); - int iBodyGroup = pDisguiseTarget->FindBodygroupByName( pszBodyGroup ); + UpdateDisguiseBodygroups( pTFOwner, pDisguiseTarget, pItem, iTeam, iState ); + } - if ( iBodyGroup == -1 ) - continue; + // CEconEntity::UpdateBodygroups is broken for disguise weapons and wearables. + // Additionally it is missing most of the infrastructure needed to set up this function there. + // As such, we set up the disguise weapon along with the other wearables since this is the only place + // they are actually handled correctly. + CTFWeaponBase *pDisguiseWeapon = pTFOwner->m_Shared.GetDisguiseWeapon(); + if ( pDisguiseWeapon ) + { + CAttributeContainer *pCont = pDisguiseWeapon->GetAttributeContainer(); + CEconItemView *pItem = pCont ? pCont->GetItem() : NULL; + CTFPlayer *pDisguiseTarget = pTFOwner->m_Shared.GetDisguiseTarget(); - ::SetBodygroup( pDisguiseTarget->GetModelPtr(), iDisguiseBody, iBodyGroup, iState ); + if ( pItem && pDisguiseTarget ) + { + // We must use team 0 for disguise weapons. + UpdateDisguiseBodygroups( pTFOwner, pDisguiseTarget, pItem, 0, iState ); } - - pTFOwner->m_Shared.SetDisguiseBody( iDisguiseBody ); } CEconItemView *pItem = GetAttributeContainer() ? GetAttributeContainer()->GetItem() : NULL; diff --git a/src/game/shared/tf/tf_item_wearable.h b/src/game/shared/tf/tf_item_wearable.h index f89c0c956a9..d5efa15614b 100644 --- a/src/game/shared/tf/tf_item_wearable.h +++ b/src/game/shared/tf/tf_item_wearable.h @@ -18,6 +18,10 @@ #if defined( CLIENT_DLL ) #define CTFWearable C_TFWearable #define CTFWearableVM C_TFWearableVM +class C_TFPlayer; +#define CTFPlayer C_TFPlayer +#else +class CTFPlayer; #endif @@ -74,8 +78,8 @@ class CTFWearable : public CEconWearable, public IHasGenericMeter protected: virtual void InternalSetPlayerDisplayModel( void ); - private: + void UpdateDisguiseBodygroups( CTFPlayer *pTFOwner, CTFPlayer *pDisguiseTarget, CEconItemView *pItem, int iTeam, int iState ); CNetworkVar( bool, m_bDisguiseWearable ); CNetworkHandle( CBaseEntity, m_hWeaponAssociatedWith ); diff --git a/src/game/shared/tf/tf_player_shared.cpp b/src/game/shared/tf/tf_player_shared.cpp index 6fd3052ee5a..42d0b829b55 100644 --- a/src/game/shared/tf/tf_player_shared.cpp +++ b/src/game/shared/tf/tf_player_shared.cpp @@ -1479,6 +1479,7 @@ void CTFPlayerShared::OnDataChanged( void ) { m_hDisguiseWeapon->UpdateVisibility(); m_hDisguiseWeapon->UpdateParticleSystems(); + m_hDisguiseWeapon->UpdateAttachmentModels(); } // XXX(JohnS): This is not the right place to do these things, SetWeaponVisible on the *client* is just stomping @@ -7229,6 +7230,8 @@ void CTFPlayerShared::OnRemoveDisguising( void ) void CTFPlayerShared::OnRemoveDisguised( void ) { #ifdef CLIENT_DLL + // Save the disguise target before clearing it, so we can mark bodygroups dirty. + CTFPlayer *pOldDisguiseTarget = ToTFPlayer( m_hDisguiseTarget.Get() ); if ( m_pOuter->GetPredictable() && ( !prediction->IsFirstTimePredicted() || m_bSyncingConditions ) ) return; @@ -7253,7 +7256,14 @@ void CTFPlayerShared::OnRemoveDisguised( void ) UpdateCritBoostEffect( kCritBoost_ForceRefresh ); m_pOuter->UpdateSpyStateChange(); + // Mark the old disguise target's bodygroups as dirty so they'll be recalculated. + if ( pOldDisguiseTarget ) + { + pOldDisguiseTarget->SetBodygroupsDirty(); + } + #else + m_nDisguiseTeam = TF_SPY_UNDEFINED; m_nDisguiseClass.Set( TF_CLASS_UNDEFINED ); m_nDisguiseSkinOverride = 0; @@ -8223,6 +8233,15 @@ void CTFPlayerShared::Disguise( int nTeam, int nClass, CTFPlayer* pDesiredTarget } } +#ifdef CLIENT_DLL + // Save the old disguise target before changing disguise, so we can clean up bodygroups. + CTFPlayer *pOldDisguiseTarget = ToTFPlayer( m_hDisguiseTarget.Get() ); + if ( pOldDisguiseTarget ) + { + pOldDisguiseTarget->SetBodygroupsDirty(); + } +#endif + m_hDesiredDisguiseTarget.Set( pDesiredTarget ); m_nDesiredDisguiseClass = nClass; m_nDesiredDisguiseTeam = nTeam; @@ -8402,6 +8421,7 @@ void CTFPlayerShared::DetermineDisguiseWeapon( bool bForcePrimary ) { CTFWeaponBase *pLastDisguiseWeapon = m_hDisguiseWeapon; CTFWeaponBase *pFirstValidWeapon = NULL; + // Cycle through the target's weapons and see if we have a match. // Note that it's possible the disguise target doesn't have a weapon in the slot we want, // for example if they have replaced it with an unlockable that isn't a weapon (wearable). @@ -8505,6 +8525,7 @@ void CTFPlayerShared::DetermineDisguiseWeapon( bool bForcePrimary ) m_hDisguiseWeapon->m_bDisguiseWeapon = true; m_hDisguiseWeapon->SetContextThink( &CTFWeaponBase::DisguiseWeaponThink, gpGlobals->curtime + 0.5, "DisguiseWeaponThink" ); + m_hDisguiseWeapon->UpdateExtraWearables(); // Ammo/clip state is displayed to attached medics m_iDisguiseAmmo = 0; @@ -8539,14 +8560,31 @@ void CTFPlayerShared::DetermineDisguiseWeapon( bool bForcePrimary ) void CTFPlayerShared::DetermineDisguiseWearables() { CTFPlayer *pDisguiseTarget = ToTFPlayer( m_hDisguiseTarget.Get() ); - if ( !pDisguiseTarget ) - return; // Remove any existing disguise wearables. RemoveDisguiseWearables(); + if ( !pDisguiseTarget ) + { + // No target exists, reset disguise body to default state. + SetDisguiseBody( 0 ); + return; + } + if ( GetDisguiseClass() != pDisguiseTarget->GetPlayerClass()->GetClassIndex() ) + { + // Class mismatch, reset disguise body to default. + SetDisguiseBody( 0 ); +#ifdef CLIENT_DLL + // Mark bodygroups dirty even when not copying wearables (class mismatch). + pDisguiseTarget->SetBodygroupsDirty(); +#endif return; + } + + // Reset disguise body to default before applying new wearables. + // This ensures old bodygroup modifications don't carry over. + SetDisguiseBody( 0 ); // Equip us with copies of our disguise target's wearables. int iPlayerSkinOverride = 0; @@ -8592,6 +8630,11 @@ void CTFPlayerShared::DetermineDisguiseWearables() } m_nDisguiseSkinOverride = iPlayerSkinOverride; + +#ifdef CLIENT_DLL + // Mark bodygroups dirty after creating disguise wearables. + pDisguiseTarget->SetBodygroupsDirty(); +#endif } void CTFPlayerShared::RemoveDisguiseWearables() diff --git a/src/game/shared/tf/tf_weaponbase.cpp b/src/game/shared/tf/tf_weaponbase.cpp index d33d92359df..7385394f114 100644 --- a/src/game/shared/tf/tf_weaponbase.cpp +++ b/src/game/shared/tf/tf_weaponbase.cpp @@ -982,7 +982,7 @@ void CTFWeaponBase::UpdateExtraWearables() // Precaching may be needed here, because we allow virtually everything to be loaded on demand now. pExtraWearableItem->PrecacheModel( pEconItemView->GetExtraWearableViewModel() ); } - + pExtraWearableItem->SetDisguiseWearable(m_bDisguiseWeapon); pExtraWearableItem->AddSpawnFlags( SF_NORESPAWN ); pExtraWearableItem->SetAlwaysAllow( true ); DispatchSpawn( pExtraWearableItem ); @@ -1008,6 +1008,7 @@ void CTFWeaponBase::UpdateExtraWearables() pExtraWearableItem->PrecacheModel( pEconItemView->GetExtraWearableModel() ); } + pExtraWearableItem->SetDisguiseWearable(m_bDisguiseWeapon); pExtraWearableItem->AddSpawnFlags( SF_NORESPAWN ); pExtraWearableItem->SetAlwaysAllow( true ); DispatchSpawn( pExtraWearableItem ); @@ -3300,6 +3301,87 @@ bool CTFWeaponBase::OnInternalDrawModel( ClientModelRenderInfo_t *pInfo ) return BaseClass::OnInternalDrawModel( pInfo ); } +//----------------------------------------------------------------------------- +// Purpose: Override for disguise weapons to use team 0 for attachment lookups +//----------------------------------------------------------------------------- +void CTFWeaponBase::UpdateAttachmentModels( void ) +{ +#ifdef CLIENT_DLL + // For disguise weapons, we need to use the disguise target's team when fetching attachment models + // because GetTeamNumber() returns the spy's team, not the disguised target's team. + if ( m_bDisguiseWeapon ) + { + C_TFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); + if ( !pOwner ) + { + BaseClass::UpdateAttachmentModels(); + return; + } + + C_TFPlayer *pDisguiseTarget = pOwner->m_Shared.GetDisguiseTarget(); + int iTeamNumber = pDisguiseTarget ? pDisguiseTarget->GetTeamNumber() : 0; + + CEconItemView *pItem = GetAttributeContainer()->GetItem(); + GameItemDefinition_t *pItemDef = pItem && pItem->IsValid() ? pItem->GetStaticData() : NULL; + + // Update the state of additional model attachments + m_vecAttachedModels.Purge(); + if ( pItemDef && AttachmentModelsShouldBeVisible() ) + { + { + int iAttachedModels = pItemDef->GetNumAttachedModels( iTeamNumber ); + for ( int i = 0; i < iAttachedModels; i++ ) + { + attachedmodel_t *pModel = pItemDef->GetAttachedModelData( iTeamNumber, i ); + + int iModelIndex = modelinfo->GetModelIndex( pModel->m_pszModelName ); + if ( iModelIndex >= 0 ) + { + AttachedModelData_t attachedModelData; + attachedModelData.m_pModel = modelinfo->GetModel( iModelIndex ); + attachedModelData.m_iModelDisplayFlags = pModel->m_iModelDisplayFlags; + m_vecAttachedModels.AddToTail( attachedModelData ); + } + } + } + + // Check for Festive attachedmodels for festivized weapons + { + int iAttachedModels = pItemDef->GetNumAttachedModelsFestivized( iTeamNumber ); + if ( iAttachedModels ) + { + int iFestivized = 0; + CALL_ATTRIB_HOOK_INT( iFestivized, is_festivized ); + if ( iFestivized ) + { + for ( int i = 0; i < iAttachedModels; i++ ) + { + attachedmodel_t *pModel = pItemDef->GetAttachedModelDataFestivized( iTeamNumber, i ); + + int iModelIndex = modelinfo->GetModelIndex( pModel->m_pszModelName ); + if ( iModelIndex >= 0 ) + { + AttachedModelData_t attachedModelData; + attachedModelData.m_pModel = modelinfo->GetModel( iModelIndex ); + attachedModelData.m_iModelDisplayFlags = pModel->m_iModelDisplayFlags; + m_vecAttachedModels.AddToTail( attachedModelData ); + } + } + } + } + } + } + // Note: We skip the viewmodel attachment section (ShouldAttachToHands) because + // disguise weapons are world models only and don't need viewmodel attachments. + } + else + { + // Normal weapons use the base class implementation + BaseClass::UpdateAttachmentModels(); + } +#endif +} + void CTFWeaponBase::ProcessMuzzleFlashEvent( void ) { C_BaseAnimating *pAttachEnt = GetAppropriateWorldOrViewModel(); diff --git a/src/game/shared/tf/tf_weaponbase.h b/src/game/shared/tf/tf_weaponbase.h index 1060367aeda..22b82e8f9ad 100644 --- a/src/game/shared/tf/tf_weaponbase.h +++ b/src/game/shared/tf/tf_weaponbase.h @@ -586,6 +586,7 @@ class CTFWeaponBase : public CBaseCombatWeapon, public IHasOwner, public IHasGen virtual float CalcViewmodelBob( void ); BobState_t *GetBobState(); virtual bool AttachmentModelsShouldBeVisible( void ) OVERRIDE { return (m_iState == WEAPON_IS_ACTIVE) && !IsBeingRepurposedForTaunt(); } + virtual void UpdateAttachmentModels( void ) OVERRIDE; virtual bool ShouldEjectBrass() { return true; } From 3300848d8a25ef6403c91f82a4cd97d6daefbc06 Mon Sep 17 00:00:00 2001 From: senmcgen Date: Thu, 12 Mar 2026 19:15:11 -0400 Subject: [PATCH 3/3] fix Short circuit for disguised spies --- src/game/shared/tf/tf_item_wearable.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/game/shared/tf/tf_item_wearable.cpp b/src/game/shared/tf/tf_item_wearable.cpp index c50fad9fdcc..dcf7db00ddd 100644 --- a/src/game/shared/tf/tf_item_wearable.cpp +++ b/src/game/shared/tf/tf_item_wearable.cpp @@ -10,6 +10,7 @@ #include "tf_gamerules.h" #include "animation.h" #include "basecombatweapon_shared.h" +#include "tf_weapon_mechanical_arm.h" #ifdef CLIENT_DLL #include "c_tf_player.h" #include "model_types.h" @@ -578,6 +579,19 @@ bool CTFWearable::UpdateBodygroups( CBaseCombatCharacter* pOwner, int iState ) { // We must use team 0 for disguise weapons. UpdateDisguiseBodygroups( pTFOwner, pDisguiseTarget, pItem, 0, iState ); + + CTFMechanicalArm* pMechArm = dynamic_cast(pDisguiseWeapon); + if (pMechArm) { + // Hack, Short circuit is special case and should be off if it is the disguise weapon. + // Directly set the bodygroup since UpdateDisguiseBodygroups doesn't work for this weapon + int iDisguiseBody = pTFOwner->m_Shared.GetDisguiseBody(); + int iBodyGroup = pDisguiseTarget->FindBodygroupByName("rightarm"); + if (iBodyGroup != -1) + { + ::SetBodygroup(pDisguiseTarget->GetModelPtr(), iDisguiseBody, iBodyGroup, 2); + pTFOwner->m_Shared.SetDisguiseBody(iDisguiseBody); + } + } } }