From 09eef7ddab34c0a60db59aaf5a67f1fd774b058a Mon Sep 17 00:00:00 2001 From: rabscootle Date: Tue, 14 Jul 2026 01:29:57 -0500 Subject: [PATCH 1/3] Add MvM texture material proxy --- src/game/client/tf/c_tf_player.cpp | 67 ++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/game/client/tf/c_tf_player.cpp b/src/game/client/tf/c_tf_player.cpp index 941823fd1ec..694cd25ef36 100644 --- a/src/game/client/tf/c_tf_player.cpp +++ b/src/game/client/tf/c_tf_player.cpp @@ -60,6 +60,7 @@ #include "tf_proxyentity.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialvar.h" +#include "materialsystem/MaterialSystemUtil.h" #include "materialsystem/itexturecompositor.h" #include "c_tf_team.h" #include "tf_item_inventory.h" @@ -1764,6 +1765,72 @@ void CSpyInvisProxy::OnBindNotEntity( void *pRenderable ) EXPOSE_INTERFACE( CSpyInvisProxy, IMaterialProxy, "spy_invis" IMATERIAL_PROXY_INTERFACE_VERSION ); +//----------------------------------------------------------------------------- +// Purpose: Replaces a material's texture while playing Mann vs. Machine. +//----------------------------------------------------------------------------- +class CMvMTextureProxy : public CResultProxy +{ +public: + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ) OVERRIDE; + virtual void OnBind( void *pC_BaseEntity ) OVERRIDE; + +private: + bool CaptureOriginalTexture(); + + CTextureReference m_OriginalTexture; + CTextureReference m_MvMTexture; +}; + +bool CMvMTextureProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + if ( !CResultProxy::Init( pMaterial, pKeyValues ) ) + return false; + + const char *pszMvMTexture = pKeyValues->GetString( "mvmTexture", "" ); + if ( !pszMvMTexture[0] ) + return false; + + m_MvMTexture.Init( pszMvMTexture, TEXTURE_GROUP_MODEL ); + + // With async material loading, $basetexture may still be a string here. + // CaptureOriginalTexture will try again when the material is first bound. + CaptureOriginalTexture(); + + return m_MvMTexture.IsValid(); +} + +bool CMvMTextureProxy::CaptureOriginalTexture() +{ + if ( m_OriginalTexture.IsValid() ) + return true; + + if ( !m_pResult || !m_pResult->IsTexture() ) + return false; + + ITexture *pOriginalTexture = m_pResult->GetTextureValue(); + if ( !pOriginalTexture ) + return false; + + m_OriginalTexture.Init( pOriginalTexture ); + return true; +} + +void CMvMTextureProxy::OnBind( void *pC_BaseEntity ) +{ + if ( !CaptureOriginalTexture() ) + return; + + const bool bUseMvMTexture = TFGameRules() && TFGameRules()->IsMannVsMachineMode(); + m_pResult->SetTextureValue( bUseMvMTexture ? m_MvMTexture : m_OriginalTexture ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +EXPOSE_INTERFACE( CMvMTextureProxy, IMaterialProxy, "MvMTexture" IMATERIAL_PROXY_INTERFACE_VERSION ); + //----------------------------------------------------------------------------- // Purpose: Used for invulnerability material // Returns 1 if the player is invulnerable, and 0 if the player is losing / doesn't have invuln. From a40de108fcea8a1306ddf5ed623e7c87bfa3e8e9 Mon Sep 17 00:00:00 2001 From: rabscootle Date: Tue, 21 Jul 2026 13:31:22 -0500 Subject: [PATCH 2/3] Spy masks will now check disguised team --- src/game/client/tf/c_tf_player.cpp | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/game/client/tf/c_tf_player.cpp b/src/game/client/tf/c_tf_player.cpp index 694cd25ef36..29fc705f7e7 100644 --- a/src/game/client/tf/c_tf_player.cpp +++ b/src/game/client/tf/c_tf_player.cpp @@ -1771,6 +1771,8 @@ EXPOSE_INTERFACE( CSpyInvisProxy, IMaterialProxy, "spy_invis" IMATERIAL_PROXY_IN class CMvMTextureProxy : public CResultProxy { public: + CMvMTextureProxy() : m_bEnemyDisguiseOnly( false ) {} + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ) OVERRIDE; virtual void OnBind( void *pC_BaseEntity ) OVERRIDE; @@ -1779,6 +1781,7 @@ class CMvMTextureProxy : public CResultProxy CTextureReference m_OriginalTexture; CTextureReference m_MvMTexture; + bool m_bEnemyDisguiseOnly; }; bool CMvMTextureProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) @@ -1791,6 +1794,8 @@ bool CMvMTextureProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) return false; m_MvMTexture.Init( pszMvMTexture, TEXTURE_GROUP_MODEL ); + KeyValues *pEnemyDisguiseOnly = pKeyValues->FindKey( "enemyDisguiseOnly", false ); + m_bEnemyDisguiseOnly = pEnemyDisguiseOnly && pEnemyDisguiseOnly->GetBool(); // With async material loading, $basetexture may still be a string here. // CaptureOriginalTexture will try again when the material is first bound. @@ -1820,7 +1825,26 @@ void CMvMTextureProxy::OnBind( void *pC_BaseEntity ) if ( !CaptureOriginalTexture() ) return; - const bool bUseMvMTexture = TFGameRules() && TFGameRules()->IsMannVsMachineMode(); + bool bUseMvMTexture = TFGameRules() && TFGameRules()->IsMannVsMachineMode(); + if ( bUseMvMTexture && m_bEnemyDisguiseOnly ) + { + C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity ); + C_TFPlayer *pPlayer = ToTFPlayer( pEntity ); + if ( !pPlayer && pEntity ) + { + IHasOwner *pOwnerInterface = dynamic_cast< IHasOwner* >( pEntity ); + if ( pOwnerInterface ) + { + pPlayer = ToTFPlayer( pOwnerInterface->GetOwnerViaInterface() ); + } + } + + bUseMvMTexture = pPlayer && + pPlayer->IsPlayerClass( TF_CLASS_SPY ) && + pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && + pPlayer->m_Shared.GetDisguiseTeam() != pPlayer->GetTeamNumber(); + } + m_pResult->SetTextureValue( bUseMvMTexture ? m_MvMTexture : m_OriginalTexture ); if ( ToolsEnabled() ) From 3c2ceb581bd1bf0a8ea9055ff5a27b574e11b18a Mon Sep 17 00:00:00 2001 From: rabscootle Date: Tue, 21 Jul 2026 13:48:47 -0500 Subject: [PATCH 3/3] Updated disguise team check --- src/game/client/tf/c_tf_player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/client/tf/c_tf_player.cpp b/src/game/client/tf/c_tf_player.cpp index 29fc705f7e7..62da142c6e9 100644 --- a/src/game/client/tf/c_tf_player.cpp +++ b/src/game/client/tf/c_tf_player.cpp @@ -1842,7 +1842,7 @@ void CMvMTextureProxy::OnBind( void *pC_BaseEntity ) bUseMvMTexture = pPlayer && pPlayer->IsPlayerClass( TF_CLASS_SPY ) && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && - pPlayer->m_Shared.GetDisguiseTeam() != pPlayer->GetTeamNumber(); + pPlayer->m_Shared.GetDisguiseTeam() == TF_TEAM_PVE_INVADERS; } m_pResult->SetTextureValue( bUseMvMTexture ? m_MvMTexture : m_OriginalTexture );