From aedcd91da1505aaea1621583ddb88816c5622f9e Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 13 Nov 2025 00:44:49 -0600 Subject: [PATCH 1/5] Prioritize solo players as auto-balance candidates --- src/game/server/tf/tf_autobalance.cpp | 29 +++++++++++++++++++++++++-- src/game/server/tf/tf_autobalance.h | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/game/server/tf/tf_autobalance.cpp b/src/game/server/tf/tf_autobalance.cpp index d88515a98f4..ad85f813120 100644 --- a/src/game/server/tf/tf_autobalance.cpp +++ b/src/game/server/tf/tf_autobalance.cpp @@ -13,6 +13,7 @@ #include "minigames/tf_duel.h" #include "player_resource.h" #include "tf_player_resource.h" +#include "tf_party.h" // memdbgon must be the last include file in a .cpp file!!! #include @@ -299,7 +300,7 @@ bool CTFAutobalance::ValidateCandidates() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -CTFPlayer *CTFAutobalance::FindNextCandidate() +CTFPlayer *CTFAutobalance::FindNextCandidate( bool bPrioritizeSoloPlayers ) { CTFPlayer *pRetVal = NULL; @@ -313,6 +314,22 @@ CTFPlayer *CTFAutobalance::FindNextCandidate() CTFPlayer *pTFPlayer = ToTFPlayer( pTeam->GetPlayer( i ) ); if ( pTFPlayer && !IsAlreadyCandidate( pTFPlayer ) && pTFPlayer->CanBeAutobalanced() ) { + if ( bPrioritizeSoloPlayers ) + { + // disqualify this player if they are in a non-solo party + CSteamID steamId; + + if ( pTFPlayer->GetSteamID( &steamId ) ) + { + CTFParty* pTFParty = GTFGCClientSystem()->GetPartyForPlayer( steamId ); + + if ( pTFParty && pTFParty->GetNumMembers() > 1 ) + { + continue; + } + } + } + vecCandidates.AddToTail( pTFPlayer ); } } @@ -369,7 +386,15 @@ bool CTFAutobalance::FindCandidates() while ( nNumFound < nTotal ) { - CTFPlayer *pTFPlayer = FindNextCandidate(); + // find a candidate that is not currently in a party + CTFPlayer *pTFPlayer = FindNextCandidate(true); + + if ( pTFPlayer == nullptr ) + { + // everyone is in a party, we have no choice but to split + pTFPlayer = FindNextCandidate(false); + } + if ( pTFPlayer ) { // the best candidates are towards the tail of the list so diff --git a/src/game/server/tf/tf_autobalance.h b/src/game/server/tf/tf_autobalance.h index 3329fa5b529..1d44ea97206 100644 --- a/src/game/server/tf/tf_autobalance.h +++ b/src/game/server/tf/tf_autobalance.h @@ -55,7 +55,7 @@ class CTFAutobalance : public CAutoGameSystemPerFrame bool IsAlreadyCandidate( CTFPlayer *pTFPlayer ) const; double GetTeamAutoBalanceScore( int nTeam ) const; double GetPlayerAutoBalanceScore( CTFPlayer *pTFPlayer ) const; - CTFPlayer *FindNextCandidate(); + CTFPlayer *FindNextCandidate( bool bPrioritizeSoloPlayers ); bool FindCandidates(); bool ValidateCandidates(); From 184bd58a8a35a90e6ff66b60dda0469868520efd Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 13 Nov 2025 01:00:28 -0600 Subject: [PATCH 2/5] Use NULL instead of nullptr for consistency. --- src/game/server/tf/tf_autobalance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/server/tf/tf_autobalance.cpp b/src/game/server/tf/tf_autobalance.cpp index ad85f813120..35edb241253 100644 --- a/src/game/server/tf/tf_autobalance.cpp +++ b/src/game/server/tf/tf_autobalance.cpp @@ -389,7 +389,7 @@ bool CTFAutobalance::FindCandidates() // find a candidate that is not currently in a party CTFPlayer *pTFPlayer = FindNextCandidate(true); - if ( pTFPlayer == nullptr ) + if ( pTFPlayer == NULL ) { // everyone is in a party, we have no choice but to split pTFPlayer = FindNextCandidate(false); From 437147702db7dee28f1e4808fa44724378da2c89 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 13 Nov 2025 01:01:45 -0600 Subject: [PATCH 3/5] Or better yet... this! --- src/game/server/tf/tf_autobalance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/server/tf/tf_autobalance.cpp b/src/game/server/tf/tf_autobalance.cpp index 35edb241253..70712f6db35 100644 --- a/src/game/server/tf/tf_autobalance.cpp +++ b/src/game/server/tf/tf_autobalance.cpp @@ -389,7 +389,7 @@ bool CTFAutobalance::FindCandidates() // find a candidate that is not currently in a party CTFPlayer *pTFPlayer = FindNextCandidate(true); - if ( pTFPlayer == NULL ) + if ( !pTFPlayer ) { // everyone is in a party, we have no choice but to split pTFPlayer = FindNextCandidate(false); From ec058e5b59a4a051cf8163347d206f0057fbcfca Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 13 Nov 2025 15:14:26 -0600 Subject: [PATCH 4/5] Integrate party leader control as intended. --- src/game/client/tf/tf_partyclient.cpp | 26 +++++++++++++++++++++-- src/game/client/tf/tf_partyclient.h | 7 ++++++ src/game/client/tf/vgui/tf_ping_panel.cpp | 7 ------ src/game/server/tf/tf_autobalance.cpp | 10 ++++----- src/game/server/tf/tf_autobalance.h | 2 +- src/game/shared/tf/tf_gcmessages.proto | 1 + src/game/shared/tf/tf_matchcriteria.cpp | 9 ++++++++ src/game/shared/tf/tf_matchcriteria.h | 1 + src/game/shared/tf/tf_party.h | 1 + 9 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/game/client/tf/tf_partyclient.cpp b/src/game/client/tf/tf_partyclient.cpp index b54143b8bf9..da865801d96 100644 --- a/src/game/client/tf/tf_partyclient.cpp +++ b/src/game/client/tf/tf_partyclient.cpp @@ -103,8 +103,7 @@ ConVar tf_party_join_request_mode( "tf_party_join_request_mode", "-1", FCVAR_ARC ConVar tf_party_ignore_invites( "tf_party_ignore_invites", "0", FCVAR_ARCHIVE, "If set, ignore incoming party invites", OnPartyClientPrefConVarChanged ); -// TODO(Universal Parties): Hook up and enable in UI -ConVar tf_party_keep_on_same_team( "tf_party_keep_on_same_team", "0", FCVAR_ARCHIVE ); +ConVar tf_party_keep_on_same_team( "tf_party_keep_on_same_team", "1", FCVAR_ARCHIVE ); //----------------------------------------------------------------------------- // Popup dialog for MM errors with an optional clickable url @@ -1019,6 +1018,9 @@ void CTFPartyClient::ReadConVarPreferences() // Ignore incoming invites SetIgnorePartyInvites( tf_party_ignore_invites.GetBool() ); + + // Keep party on same team + SetKeepPartyOnSameTeam( tf_party_keep_on_same_team.GetBool() ); } //----------------------------------------------------------------------------- @@ -1057,6 +1059,26 @@ void CTFPartyClient::SetIgnorePartyInvites( bool bIgnore ) } } +//----------------------------------------------------------------------------- +bool CTFPartyClient::GetKeepPartyOnSameTeam() const +{ + return m_bKeepPartyOnSameTeam; +} + +//----------------------------------------------------------------------------- +void CTFPartyClient::SetKeepPartyOnSameTeam( bool bKeepPartyOnSameTeam ) +{ + if ( m_bKeepPartyOnSameTeam != bKeepPartyOnSameTeam ) + { + m_bKeepPartyOnSameTeam = bKeepPartyOnSameTeam; + MutLocalGroupCriteria().SetKeepPartyOnSameTeam( bKeepPartyOnSameTeam ); + + // This will trigger a OnChanged and call us again, make sure we set the mode first so we don't get in a loop. + tf_party_keep_on_same_team.SetValue( bKeepPartyOnSameTeam ); + OnPartyPrefChanged(); + } +} + //----------------------------------------------------------------------------- bool CTFPartyClient::BInQueueForMatchGroup( ETFMatchGroup eMatchGroup ) const { diff --git a/src/game/client/tf/tf_partyclient.h b/src/game/client/tf/tf_partyclient.h index eec48865f81..a1d012449ac 100644 --- a/src/game/client/tf/tf_partyclient.h +++ b/src/game/client/tf/tf_partyclient.h @@ -110,6 +110,10 @@ class CTFPartyClient : private CAutoGameSystemPerFrame, private CLocalSteamShare EPartyJoinRequestMode GetPartyJoinRequestMode() const; void SetPartyJoinRequestMode( EPartyJoinRequestMode ); + + bool GetKeepPartyOnSameTeam() const; + void SetKeepPartyOnSameTeam( bool bKeepPartyOnSameTeam ); + bool GetIgnorePartyInvites() const; void SetIgnorePartyInvites( bool bIgnore ); @@ -438,6 +442,9 @@ class CTFPartyClient : private CAutoGameSystemPerFrame, private CLocalSteamShare // (new session, leader changed, etc) bool m_bSentInitialCriteria = false; + // Whether auto-balance should try to keep members of this party on the same team. + bool m_bKeepPartyOnSameTeam = true; + // Cached criteria object from party updates CTFGroupMatchCriteria m_activePartyCriteria; diff --git a/src/game/client/tf/vgui/tf_ping_panel.cpp b/src/game/client/tf/vgui/tf_ping_panel.cpp index 19117bd1148..b0ca2539026 100644 --- a/src/game/client/tf/vgui/tf_ping_panel.cpp +++ b/src/game/client/tf/vgui/tf_ping_panel.cpp @@ -69,13 +69,6 @@ void CTFPingPanel::ApplySchemeSettings( IScheme *pScheme ) m_pIgnoreInvitesCheckBox = FindControl< CvarToggleCheckButton >( "IgnorePartyInvites", true ); m_pKeepTeamTogetherCheckBox = FindControl< CvarToggleCheckButton >( "KeepPartyOnSameTeam", true ); - if ( m_pKeepTeamTogetherCheckBox ) - { - m_pKeepTeamTogetherCheckBox->SetSelected( true ); - m_pKeepTeamTogetherCheckBox->SetEnabled( false ); - m_pKeepTeamTogetherCheckBox->SetTooltip( GetDashboardTooltip( k_eMediumFont ), "#TF_MM_ComingSoon" ); - } - RegeneratePingPanels(); m_pPingSlider->AddActionSignalTarget( this ); } diff --git a/src/game/server/tf/tf_autobalance.cpp b/src/game/server/tf/tf_autobalance.cpp index 70712f6db35..9c9731dfd5d 100644 --- a/src/game/server/tf/tf_autobalance.cpp +++ b/src/game/server/tf/tf_autobalance.cpp @@ -300,7 +300,7 @@ bool CTFAutobalance::ValidateCandidates() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -CTFPlayer *CTFAutobalance::FindNextCandidate( bool bPrioritizeSoloPlayers ) +CTFPlayer *CTFAutobalance::FindNextCandidate( bool bKeepPartiesOnSameTeam ) { CTFPlayer *pRetVal = NULL; @@ -314,16 +314,16 @@ CTFPlayer *CTFAutobalance::FindNextCandidate( bool bPrioritizeSoloPlayers ) CTFPlayer *pTFPlayer = ToTFPlayer( pTeam->GetPlayer( i ) ); if ( pTFPlayer && !IsAlreadyCandidate( pTFPlayer ) && pTFPlayer->CanBeAutobalanced() ) { - if ( bPrioritizeSoloPlayers ) + if ( bKeepPartiesOnSameTeam ) { - // disqualify this player if they are in a non-solo party + // disqualify this player if they are in a party of 2 or more that wants to be kept on the same team. CSteamID steamId; if ( pTFPlayer->GetSteamID( &steamId ) ) { CTFParty* pTFParty = GTFGCClientSystem()->GetPartyForPlayer( steamId ); - if ( pTFParty && pTFParty->GetNumMembers() > 1 ) + if ( pTFParty && pTFParty->GetNumMembers() > 1 && pTFParty->GetKeepPartyOnSameTeam() ) { continue; } @@ -391,7 +391,7 @@ bool CTFAutobalance::FindCandidates() if ( !pTFPlayer ) { - // everyone is in a party, we have no choice but to split + // everyone is in a party, we have no choice but to split (?) pTFPlayer = FindNextCandidate(false); } diff --git a/src/game/server/tf/tf_autobalance.h b/src/game/server/tf/tf_autobalance.h index 1d44ea97206..22a0426ebad 100644 --- a/src/game/server/tf/tf_autobalance.h +++ b/src/game/server/tf/tf_autobalance.h @@ -55,7 +55,7 @@ class CTFAutobalance : public CAutoGameSystemPerFrame bool IsAlreadyCandidate( CTFPlayer *pTFPlayer ) const; double GetTeamAutoBalanceScore( int nTeam ) const; double GetPlayerAutoBalanceScore( CTFPlayer *pTFPlayer ) const; - CTFPlayer *FindNextCandidate( bool bPrioritizeSoloPlayers ); + CTFPlayer *FindNextCandidate( bool bKeepPartiesOnSameTeam ); bool FindCandidates(); bool ValidateCandidates(); diff --git a/src/game/shared/tf/tf_gcmessages.proto b/src/game/shared/tf/tf_gcmessages.proto index 568afe55712..2538483554a 100644 --- a/src/game/shared/tf/tf_gcmessages.proto +++ b/src/game/shared/tf/tf_gcmessages.proto @@ -1033,6 +1033,7 @@ message CTFGroupMatchCriteriaProto // optional TF_MatchmakingMode matchmaking_mode = 7; optional bool late_join_ok = 5; // Is my party willing to join late? optional uint32 custom_ping_tolerance = 13 [ default = 0 ]; // 0 == no custom tolerance + optional bool keep_party_on_same_team = 17 [ default = true ]; // Should auto-balance try to avoid splitting party members up? // Deprecated - with multi-queue, you are no longer locked to one match group, and criteria is universal (not one // per match-group) diff --git a/src/game/shared/tf/tf_matchcriteria.cpp b/src/game/shared/tf/tf_matchcriteria.cpp index 4a6ddbb5353..6f22847b39f 100644 --- a/src/game/shared/tf/tf_matchcriteria.cpp +++ b/src/game/shared/tf/tf_matchcriteria.cpp @@ -124,6 +124,15 @@ void ITFGroupMatchCriteria::SetCustomPingTolerance( uint32_t unCustomPingToleran } } +//----------------------------------------------------------------------------- +void ITFGroupMatchCriteria::SetKeepPartyOnSameTeam(bool bKeepPartyOnSameTeam) +{ + if ( Proto().keep_party_on_same_team() != bKeepPartyOnSameTeam ) + { + MutProto().set_keep_party_on_same_team( bKeepPartyOnSameTeam ); + } +} + //----------------------------------------------------------------------------- void ITFGroupMatchCriteria::SetCasualMapSelected( uint32 nMapDefIndex, bool bSelected ) { diff --git a/src/game/shared/tf/tf_matchcriteria.h b/src/game/shared/tf/tf_matchcriteria.h index 9c52831d203..14c4b4b77dd 100644 --- a/src/game/shared/tf/tf_matchcriteria.h +++ b/src/game/shared/tf/tf_matchcriteria.h @@ -120,6 +120,7 @@ class ITFGroupMatchCriteria : public ITFGroupMatchCriteriaReader void SetLateJoin( bool bLateJoin ); void SetCustomPingTolerance( uint32_t unCustomPingTolerance ); + void SetKeepPartyOnSameTeam( bool bKeepPartyOnSameTeam ); void SetCasualMapSelected( uint32 nMapDefIndex, bool bSelected ); void SetCasualGroupSelected( EMatchmakingGroupType eGroup, bool bSelected ); void SetCasualCategorySelected( EGameCategory eCategory, bool bSelected ); diff --git a/src/game/shared/tf/tf_party.h b/src/game/shared/tf/tf_party.h index 61b429741ab..6da1ff83521 100644 --- a/src/game/shared/tf/tf_party.h +++ b/src/game/shared/tf/tf_party.h @@ -42,6 +42,7 @@ class CTFParty : public GCSDK::CProtoBufSharedObject Date: Thu, 13 Nov 2025 15:35:03 -0600 Subject: [PATCH 5/5] styling fixes --- src/game/server/tf/tf_autobalance.cpp | 4 ++-- src/game/shared/tf/tf_matchcriteria.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/game/server/tf/tf_autobalance.cpp b/src/game/server/tf/tf_autobalance.cpp index 9c9731dfd5d..d6c7aa5a039 100644 --- a/src/game/server/tf/tf_autobalance.cpp +++ b/src/game/server/tf/tf_autobalance.cpp @@ -387,12 +387,12 @@ bool CTFAutobalance::FindCandidates() while ( nNumFound < nTotal ) { // find a candidate that is not currently in a party - CTFPlayer *pTFPlayer = FindNextCandidate(true); + CTFPlayer *pTFPlayer = FindNextCandidate( true ); if ( !pTFPlayer ) { // everyone is in a party, we have no choice but to split (?) - pTFPlayer = FindNextCandidate(false); + pTFPlayer = FindNextCandidate( false ); } if ( pTFPlayer ) diff --git a/src/game/shared/tf/tf_matchcriteria.cpp b/src/game/shared/tf/tf_matchcriteria.cpp index 6f22847b39f..304d24c118e 100644 --- a/src/game/shared/tf/tf_matchcriteria.cpp +++ b/src/game/shared/tf/tf_matchcriteria.cpp @@ -125,7 +125,7 @@ void ITFGroupMatchCriteria::SetCustomPingTolerance( uint32_t unCustomPingToleran } //----------------------------------------------------------------------------- -void ITFGroupMatchCriteria::SetKeepPartyOnSameTeam(bool bKeepPartyOnSameTeam) +void ITFGroupMatchCriteria::SetKeepPartyOnSameTeam( bool bKeepPartyOnSameTeam ) { if ( Proto().keep_party_on_same_team() != bKeepPartyOnSameTeam ) {