From c2e6eb069ecac2f81fa3b61ec909763896e571d1 Mon Sep 17 00:00:00 2001 From: rabscootle Date: Tue, 21 Jul 2026 10:09:26 -0500 Subject: [PATCH] Draw robot disguises in the 3D player HUD --- src/game/client/tf/tf_hud_playerstatus.cpp | 42 +++++++++++++++++-- .../client/tf/vgui/tf_playermodelpanel.cpp | 28 +++++++------ src/game/client/tf/vgui/tf_playermodelpanel.h | 10 ++--- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/game/client/tf/tf_hud_playerstatus.cpp b/src/game/client/tf/tf_hud_playerstatus.cpp index 12bcf4376ae..9d97ff34b1f 100644 --- a/src/game/client/tf/tf_hud_playerstatus.cpp +++ b/src/game/client/tf/tf_hud_playerstatus.cpp @@ -178,6 +178,15 @@ void CTFHudPlayerClass::ApplySchemeSettings( IScheme *pScheme ) BaseClass::ApplySchemeSettings( pScheme ); } +static bool IsMvMRobotDisguise( C_TFPlayer *pPlayer ) +{ + return pPlayer && + TFGameRules() && TFGameRules()->IsMannVsMachineMode() && + pPlayer->GetTeamNumber() == TF_TEAM_PVE_DEFENDERS && + pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && + pPlayer->m_Shared.GetDisguiseTeam() != pPlayer->GetTeamNumber(); +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -191,6 +200,18 @@ void CTFHudPlayerClass::OnThink() if ( !pPlayer ) return; + // Hold the playerhud update for a brief moment so that the weapon can properly update before potentially changing classes + // Without a brief stall, past weapon will briefly persist with new disguise + CTFWeaponBase *pDisguiseWeapon = pPlayer->m_Shared.GetDisguiseWeapon(); + bool bDisguiseIdentityChanged = m_nDisguiseClass != pPlayer->m_Shared.GetDisguiseClass() || m_nDisguiseTeam != pPlayer->m_Shared.GetDisguiseTeam(); + bool bWaitingForDisguiseWeapon = pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && + ( !pDisguiseWeapon || ( bDisguiseIdentityChanged && m_hDisguiseWeapon.Get() == pDisguiseWeapon ) ); + if ( bWaitingForDisguiseWeapon ) + { + m_flNextThink = gpGlobals->curtime + 0.05f; + return; + } + bool bTeamChange = false; // set our background colors if ( m_nTeam != pPlayer->GetTeamNumber() ) @@ -449,14 +470,27 @@ void CTFHudPlayerClass::UpdateModelPanel() CEconItemView *pWeapon = NULL; bool bDisguised = pPlayer->m_Shared.InCond( TF_COND_DISGUISED ); + bool bRobotDisguise = IsMvMRobotDisguise( pPlayer ); // Are we disguised as a robot? + CTFWeaponBase *pDisguiseWeapon = pPlayer->m_Shared.GetDisguiseWeapon(); + bool bDisguiseIdentityChanged = m_nDisguiseClass != pPlayer->m_Shared.GetDisguiseClass() || m_nDisguiseTeam != pPlayer->m_Shared.GetDisguiseTeam(); + + // Hold the playerhud update for a brief moment so that the weapon can properly update before potentially changing classes + // Without a brief stall, past weapon will briefly persist with new disguise + if ( bDisguised && + ( !pDisguiseWeapon || ( bDisguiseIdentityChanged && m_hDisguiseWeapon.Get() == pDisguiseWeapon ) ) ) + { + m_flNextThink = gpGlobals->curtime + 0.05f; + return; + } + if ( bDisguised ) { nClass = pPlayer->m_Shared.GetDisguiseClass(); nTeam = pPlayer->m_Shared.GetDisguiseTeam(); - if ( pPlayer->m_Shared.GetDisguiseWeapon() ) + if ( pDisguiseWeapon ) { - CAttributeContainer *pCont = pPlayer->m_Shared.GetDisguiseWeapon()->GetAttributeContainer(); + CAttributeContainer *pCont = pDisguiseWeapon->GetAttributeContainer(); pWeapon = pCont ? pCont->GetItem() : NULL; if ( pWeapon ) { @@ -477,7 +511,7 @@ void CTFHudPlayerClass::UpdateModelPanel() } m_pPlayerModelPanel->ClearCarriedItems(); - m_pPlayerModelPanel->SetToPlayerClass( nClass ); + m_pPlayerModelPanel->SetToPlayerClass( nClass, false, bRobotDisguise ? g_szBotModels[nClass] : NULL, bRobotDisguise ); m_pPlayerModelPanel->SetTeam( nTeam ); if ( pWeapon ) @@ -509,7 +543,7 @@ void CTFHudPlayerClass::UpdateModelPanel() } } - m_pPlayerModelPanel->HoldItemInSlot( nItemSlot ); + m_pPlayerModelPanel->HoldItemInSlot( nItemSlot, bRobotDisguise ); } } diff --git a/src/game/client/tf/vgui/tf_playermodelpanel.cpp b/src/game/client/tf/vgui/tf_playermodelpanel.cpp index 53715b62e66..a68846ce0ce 100644 --- a/src/game/client/tf/vgui/tf_playermodelpanel.cpp +++ b/src/game/client/tf/vgui/tf_playermodelpanel.cpp @@ -195,7 +195,7 @@ void CTFPlayerModelPanel::ApplySettings( KeyValues *inResourceData ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CTFPlayerModelPanel::SetToPlayerClass( int iClass, bool bForceRefresh /*= false*/, const char *pszPlayerModelOverride /*= NULL*/ ) +void CTFPlayerModelPanel::SetToPlayerClass( int iClass, bool bForceRefresh /*= false*/, const char *pszPlayerModelOverride /*= NULL*/, bool bOverrideUsesClassAnimations /*= false*/ ) { if ( !m_strPlayerModelOverride.IsEqual_CaseInsensitive( pszPlayerModelOverride ) ) { @@ -219,6 +219,10 @@ void CTFPlayerModelPanel::SetToPlayerClass( int iClass, bool bForceRefresh /*= f if ( !m_strPlayerModelOverride.IsEmpty() ) { SetMDL( m_strPlayerModelOverride.Get() ); + if ( bOverrideUsesClassAnimations ) + { + HoldFirstValidItem( true ); + } } else { @@ -255,7 +259,7 @@ void CTFPlayerModelPanel::SetToPlayerClass( int iClass, bool bForceRefresh /*= f //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CTFPlayerModelPanel::HoldFirstValidItem( void ) +void CTFPlayerModelPanel::HoldFirstValidItem( bool bPreserveModelOverride /*= false*/ ) { RemoveAdditionalModels(); @@ -283,7 +287,7 @@ void CTFPlayerModelPanel::HoldFirstValidItem( void ) if ( iDesiredSlot != -1 ) { - UpdateHeldItem( iDesiredSlot ); + UpdateHeldItem( iDesiredSlot, bPreserveModelOverride ); return; } @@ -297,19 +301,19 @@ void CTFPlayerModelPanel::HoldFirstValidItem( void ) if ( pItem && pItem->IsValid() ) { - SwitchHeldItemTo( pItem ); + SwitchHeldItemTo( pItem, bPreserveModelOverride ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -bool CTFPlayerModelPanel::HoldItemInSlot( int iSlot ) +bool CTFPlayerModelPanel::HoldItemInSlot( int iSlot, bool bPreserveModelOverride /*= false*/ ) { if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED ) return false; - return UpdateHeldItem( iSlot ); + return UpdateHeldItem( iSlot, bPreserveModelOverride ); } //----------------------------------------------------------------------------- @@ -351,7 +355,7 @@ bool CTFPlayerModelPanel::HoldItem( int iItemNumber ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -bool CTFPlayerModelPanel::UpdateHeldItem( int iDesiredSlot ) +bool CTFPlayerModelPanel::UpdateHeldItem( int iDesiredSlot, bool bPreserveModelOverride /*= false*/ ) { m_pHeldItem = NULL; @@ -363,7 +367,7 @@ bool CTFPlayerModelPanel::UpdateHeldItem( int iDesiredSlot ) // Also ignore requests to equip non-wearables that are never actively equipped if ( bIsTauntItem || ( !pItem->GetStaticData()->IsAWearable() && pItem->GetAnimationSlot() != -2 ) ) { - SwitchHeldItemTo( pItem ); + SwitchHeldItemTo( pItem, bPreserveModelOverride ); return true; } } @@ -371,12 +375,12 @@ bool CTFPlayerModelPanel::UpdateHeldItem( int iDesiredSlot ) // If we were trying to switch to a new item, and it's not valid, stick to our current if ( iDesiredSlot != m_iCurrentSlotIndex ) { - UpdateHeldItem( m_iCurrentSlotIndex ); + UpdateHeldItem( m_iCurrentSlotIndex, bPreserveModelOverride ); return false; } // We were trying to stay on the current weapon, and it's not valid. Find anything. - HoldFirstValidItem(); + HoldFirstValidItem( bPreserveModelOverride ); return false; } @@ -514,7 +518,7 @@ void CTFPlayerModelPanel::FireEvent( const char *pszEventName, const char *pszEv //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CTFPlayerModelPanel::SwitchHeldItemTo( CEconItemView *pItem ) +void CTFPlayerModelPanel::SwitchHeldItemTo( CEconItemView *pItem, bool bPreserveModelOverride /*= false*/ ) { m_nBody = 0; @@ -532,7 +536,7 @@ void CTFPlayerModelPanel::SwitchHeldItemTo( CEconItemView *pItem ) } else { - SetToPlayerClass( m_iCurrentClassIndex ); + SetToPlayerClass( m_iCurrentClassIndex, false, bPreserveModelOverride ? m_strPlayerModelOverride.Get() : NULL, bPreserveModelOverride ); } } diff --git a/src/game/client/tf/vgui/tf_playermodelpanel.h b/src/game/client/tf/vgui/tf_playermodelpanel.h index 4300e365439..5ab39e8deca 100644 --- a/src/game/client/tf/vgui/tf_playermodelpanel.h +++ b/src/game/client/tf/vgui/tf_playermodelpanel.h @@ -28,10 +28,10 @@ class CTFPlayerModelPanel : public CBaseModelPanel, public IChoreoEventCallback, void ApplySettings( KeyValues *inResourceData ); - void SetToPlayerClass( int iClass, bool bForceRefresh = false, const char *pszPlayerModelOverride = NULL ); - bool HoldItemInSlot( int iSlot ); + void SetToPlayerClass( int iClass, bool bForceRefresh = false, const char *pszPlayerModelOverride = NULL, bool bOverrideUsesClassAnimations = false ); + bool HoldItemInSlot( int iSlot, bool bPreserveModelOverride = false ); bool HoldItem( int iItemNumber ); - void SwitchHeldItemTo( CEconItemView *pItem ); + void SwitchHeldItemTo( CEconItemView *pItem, bool bPreserveModelOverride = false ); void EquipRequiredLoadoutSlot( int iRequiredLoadoutSlot ); CEconItemView *GetHeldItem() { return m_pHeldItem; } @@ -126,10 +126,10 @@ class CTFPlayerModelPanel : public CBaseModelPanel, public IChoreoEventCallback, void InitPhonemeMappings( void ); void SetupMappings( char const *pchFileRoot ); - void HoldFirstValidItem( void ); + void HoldFirstValidItem( bool bPreserveModelOverride = false ); void EquipAllWearables( CEconItemView *pHeldItem ); void EquipItem( CEconItemView *pItem ); - bool UpdateHeldItem( int iDesiredSlot ); + bool UpdateHeldItem( int iDesiredSlot, bool bPreserveModelOverride = false ); void UpdateWeaponBodygroups( bool bModifyDeployedOnlyBodygroups ); void UpdateHiddenBodyGroups( CEconItemView* pItem ); CEconItemView *GetItemInSlot( int iSlot );