From 48bc9ed6270956abc8aa1d3277c84b806b13e1d4 Mon Sep 17 00:00:00 2001 From: FrancescoBorzi Date: Thu, 2 Jul 2026 16:47:53 +0200 Subject: [PATCH 1/2] fix: read the invited ledger in BalanceTeamsOnEntry The corrector compared live head counts, which exclude every invitee still porting: accept deletes the queue-side GroupQueueInfo without decrementing the BG's invited count, so a porting premade is invisible to GetPlayersCountByTeam. A solo entering before the premade landed got flipped onto the premade's side, starting the match 1v3. Compare the invited ledger (entered + accepted-in-flight + pending) instead, minus the entrant's own still-ledgered reservation, and update the conf.dist description accordingly. Fixes #172 --- conf/CFBG.conf.dist | 9 +++++---- src/CFBG.cpp | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/conf/CFBG.conf.dist b/conf/CFBG.conf.dist index e9c91b1..5b1fabc 100644 --- a/conf/CFBG.conf.dist +++ b/conf/CFBG.conf.dist @@ -110,10 +110,11 @@ # # CFBG.BalanceTeamsOnEntry.Enabled # Description: When a solo player enters a battleground while the teams are -# uneven, move them to the smaller team according to the live -# in-BG head counts at that moment. If the teams are already -# even they keep the team chosen at invite time, and grouped -# players are never moved. +# uneven, move them to the smaller team according to the +# invited counts at that moment (players inside plus accepted +# or pending invitees still on their way). If the teams are +# already even they keep the team chosen at invite time, and +# grouped players are never moved. # Default: 1 - (Enabled) # 0 - (Disabled) diff --git a/src/CFBG.cpp b/src/CFBG.cpp index b2d9b97..3ce5f84 100644 --- a/src/CFBG.cpp +++ b/src/CFBG.cpp @@ -376,10 +376,19 @@ void CFBG::BalanceTeamsOnEntry(Battleground* bg, Player* player) TeamId provisional = player->GetBgTeamId(); - // Live head counts correctly EXCLUDE the entering player (counted later in - // Battleground::AddPlayer). - int32 countA = bg->GetPlayersCountByTeam(TEAM_ALLIANCE); - int32 countH = bg->GetPlayersCountByTeam(TEAM_HORDE); + // The invited ledger (entered + accepted-in-flight + pending-invited) sees + // reservations that live head counts miss while invitees are still porting + // (issue #172: a solo flipped onto a porting premade's side -> 1v3). + int32 countA = bg->GetInvitedCount(TEAM_ALLIANCE); + int32 countH = bg->GetInvitedCount(TEAM_HORDE); + + // Accept does not release the reservation (RemovePlayer with + // decreaseInvitedCount=false), so the entrant is still ledgered on the + // provisional side: exclude them from the comparison. + if (provisional == TEAM_ALLIANCE) + --countA; + else + --countH; // Sides already balanced: keep the provisional team, no morph / count churn. if (countA == countH) From ecfe9550ce417c0bf484a57bdcfde10d3e6a3b20 Mon Sep 17 00:00:00 2001 From: FrancescoBorzi Date: Thu, 2 Jul 2026 16:48:13 +0200 Subject: [PATCH 2/2] fix: clamp projected base counts to the invited ledger A player between accept and worldport ack is in neither term of the projection: the accept deleted their GroupQueueInfo and AddPlayer has not run yet. Reinforcement during that window saw the side as short and over-invited onto it, producing 3v2 (or 6v5) starts under strict EvenTeams. Clamp each team's projection up to the BG's invited ledger, which keeps tracking porting players until RemovePlayerAtLeave or invite expiry. Note: a separate core defect can leak the invited count by +1 when an accepted port never lands on the BG map (fix proposed upstream in azerothcore/azerothcore-wotlk). Under max() a stale +1 converts to under-reinforcement of that side for the instance's lifetime, which fails safe and resets at BG end. --- src/CFBG.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/CFBG.cpp b/src/CFBG.cpp index 3ce5f84..d47c889 100644 --- a/src/CFBG.cpp +++ b/src/CFBG.cpp @@ -744,6 +744,13 @@ std::array CFBG::GetProjectedBaseCounts(Battleground* bg, Battlegroun if (gInfo->IsInvitedToBGInstanceGUID == bg->GetInstanceID()) counts[gInfo->teamId] += gInfo->Players.size(); + // A player between accept and worldport ack is in neither term above (the + // accept deleted their ginfo; AddPlayer has not run yet). The BG's invited + // ledger still holds every reservation, so clamp up to it; max() degrades + // gracefully if either register is skewed. + counts[TEAM_ALLIANCE] = std::max(counts[TEAM_ALLIANCE], bg->GetInvitedCount(TEAM_ALLIANCE)); + counts[TEAM_HORDE] = std::max(counts[TEAM_HORDE], bg->GetInvitedCount(TEAM_HORDE)); + return counts; }