diff --git a/src/BgAutoQueue.cpp b/src/BgAutoQueue.cpp index b44617b..65dac1d 100644 --- a/src/BgAutoQueue.cpp +++ b/src/BgAutoQueue.cpp @@ -320,26 +320,85 @@ bool BgAutoQueue::CanEnter(Player* player, BattlegroundTypeId bgTypeId) const return GetBattlegroundBracketByLevel(bgTemplate->GetMapId(), player->GetLevel()) != nullptr; } -bool BgAutoQueue::IsBracketEligible(BattlegroundTypeId bgTypeId, BracketBucket const& bucket) const +bool BgAutoQueue::BucketHasAnyFit(BattlegroundTypeId bgTypeId, BracketBucket const& bucket) const { for (ObjectGuid guid : bucket.players) { Player* player = ObjectAccessor::FindPlayer(guid); - if (!player || !CanEnter(player, bgTypeId)) - return false; + if (player && CanEnter(player, bgTypeId)) + return true; } - return true; + return false; +} + +bool BgAutoQueue::BucketFitsLiveBg(Battleground* bg, BracketBucket const& bucket) const +{ + BattlegroundTypeId const bgTypeId = bg->GetBgTypeID(); + for (ObjectGuid guid : bucket.players) + { + Player* player = ObjectAccessor::FindPlayer(guid); + if (!player || !player->GetBGAccessByLevel(bgTypeId)) + continue; + + PvPDifficultyEntry const* entry = GetBattlegroundBracketByLevel(bg->GetMapId(), player->GetLevel()); + if (entry && entry->GetBracketId() == bg->GetBracketId()) + return true; + } + + return false; } -bool BgAutoQueue::IsViable(Battleground* bgTemplate, BracketBucket const& bucket) const +BgAutoQueue::QueuedWaiters BgAutoQueue::CountUninvitedWaiters(BattlegroundTypeId bgTypeId, + BattlegroundBracketId bracketId) const +{ + QueuedWaiters waiters; + + BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(bgTypeId, 0); + if (bgQueueTypeId == BATTLEGROUND_QUEUE_NONE) + return waiters; + + BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(bgQueueTypeId); + + static constexpr BattlegroundQueueGroupTypes WAITER_GROUP_TYPES[] = + { + BG_QUEUE_PREMADE_ALLIANCE, + BG_QUEUE_PREMADE_HORDE, + BG_QUEUE_NORMAL_ALLIANCE, + BG_QUEUE_NORMAL_HORDE, + BG_QUEUE_CFBG + }; + + for (BattlegroundQueueGroupTypes groupType : WAITER_GROUP_TYPES) + { + for (GroupQueueInfo const* gInfo : bgQueue.m_QueuedGroups[bracketId][groupType]) + { + if (gInfo->IsInvitedToBGInstanceGUID != 0) + continue; + + uint32 const count = static_cast(gInfo->Players.size()); + waiters.total += count; + if (gInfo->teamId == TEAM_ALLIANCE) + waiters.alliance += count; + else + waiters.horde += count; + } + } + + return waiters; +} + +bool BgAutoQueue::IsViable(Battleground* bgTemplate, BracketBucket const& bucket, + BattlegroundBracketId bracketId) const { uint32 const minPerTeam = bgTemplate->GetMinPlayersPerTeam(); + QueuedWaiters const waiters = CountUninvitedWaiters(bgTemplate->GetBgTypeID(), bracketId); if (_crossFaction) - return (bucket.alliance + bucket.horde) >= (2u * minPerTeam); + return (bucket.alliance + bucket.horde + waiters.total) >= (2u * minPerTeam); - return bucket.alliance >= minPerTeam && bucket.horde >= minPerTeam; + return (bucket.alliance + waiters.alliance) >= minPerTeam + && (bucket.horde + waiters.horde) >= minPerTeam; } BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BattlegroundBracketId bracketId, BracketBucket const& bucket) const @@ -351,20 +410,20 @@ BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BattlegroundBracket if (sDisableMgr->IsDisabledFor(DISABLE_TYPE_BATTLEGROUND, bgTypeId, nullptr)) continue; - if (!IsBracketEligible(bgTypeId, bucket)) - continue; - for (Battleground* bg : sBattlegroundMgr->GetBGFreeSlotQueueStore(bgTypeId)) { - if (bg->GetBracketId() != bracketId) - continue; - if (!(bg->GetStatus() > STATUS_WAIT_QUEUE && bg->GetStatus() < STATUS_WAIT_LEAVE)) continue; if (!bg->HasFreeSlots()) continue; + // Match each live game by its own map-relative bracket, reinforced by + // whichever subset of bucket players fits it (off-boundary players are + // skipped at queue time, not blocking the whole game). + if (!BucketFitsLiveBg(bg, bucket)) + continue; + liveTypes.push_back(bgTypeId); break; } @@ -373,6 +432,40 @@ BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BattlegroundBracket if (!liveTypes.empty()) return Acore::Containers::SelectRandomContainerElement(liveTypes); + // (a2) Prefer a not-yet-running BG that already has uninvited queuers, so a + // manual queuer's chosen BG is reinforced instead of bypassed. Among viable + // candidates pick the one closest to popping (most waiters); ties at random. + std::vector waiterLeaders; + uint32 bestWaiters = 0; + for (BattlegroundTypeId bgTypeId : BG_NORMAL_TYPES) + { + if (sDisableMgr->IsDisabledFor(DISABLE_TYPE_BATTLEGROUND, bgTypeId, nullptr)) + continue; + + if (!BucketHasAnyFit(bgTypeId, bucket)) + continue; + + QueuedWaiters const waiters = CountUninvitedWaiters(bgTypeId, bracketId); + if (waiters.total == 0) + continue; + + Battleground* bgTemplate = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); + if (!bgTemplate || !IsViable(bgTemplate, bucket, bracketId)) + continue; + + if (waiters.total > bestWaiters) + { + bestWaiters = waiters.total; + waiterLeaders.clear(); + waiterLeaders.push_back(bgTypeId); + } + else if (waiters.total == bestWaiters) + waiterLeaders.push_back(bgTypeId); + } + + if (!waiterLeaders.empty()) + return Acore::Containers::SelectRandomContainerElement(waiterLeaders); + // (b) Random pick from the configured pool. std::vector candidates; for (BattlegroundTypeId bgTypeId : _pool) @@ -380,7 +473,7 @@ BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BattlegroundBracket if (sDisableMgr->IsDisabledFor(DISABLE_TYPE_BATTLEGROUND, bgTypeId, nullptr)) continue; - if (IsBracketEligible(bgTypeId, bucket)) + if (BucketHasAnyFit(bgTypeId, bucket)) candidates.push_back(bgTypeId); } @@ -394,7 +487,7 @@ BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BattlegroundBracket for (BattlegroundTypeId bgTypeId : candidates) { Battleground* bgTemplate = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); - if (bgTemplate && IsViable(bgTemplate, bucket)) + if (bgTemplate && IsViable(bgTemplate, bucket, bracketId)) viable.push_back(bgTypeId); } diff --git a/src/BgAutoQueue.h b/src/BgAutoQueue.h index 2157049..1b21105 100644 --- a/src/BgAutoQueue.h +++ b/src/BgAutoQueue.h @@ -118,15 +118,37 @@ class BgAutoQueue // True when the player can be queued into bgTypeId at their level. bool CanEnter(Player* player, BattlegroundTypeId bgTypeId) const; - // True when every player in the bucket passes CanEnter for bgTypeId. - bool IsBracketEligible(BattlegroundTypeId bgTypeId, BracketBucket const& bucket) const; + // True when at least one bucket player can be queued into bgTypeId at their + // level (subset-friendly; QueueBucket skips the rest at queue time). + bool BucketHasAnyFit(BattlegroundTypeId bgTypeId, BracketBucket const& bucket) const; + + // True when at least one bucket player resolves, on this live game's own map, + // to this game's own bracket and has BG access for its type. + bool BucketFitsLiveBg(Battleground* bg, BracketBucket const& bucket) const; + + struct QueuedWaiters + { + uint32 total = 0; + uint32 alliance = 0; + uint32 horde = 0; + }; + + // Counts uninvited players already sitting in bgTypeId's core queue for this + // bracket, split by faction. Scans every solo/premade/cross-faction group + // bucket because which bucket a manual queuer lands in depends on whether + // mod-cfbg is active. Groups already invited to a forming instance are skipped. + QueuedWaiters CountUninvitedWaiters(BattlegroundTypeId bgTypeId, + BattlegroundBracketId bracketId) const; // Viability per CrossFaction: cross-faction => total >= 2*min; otherwise - // each faction tally >= min. - bool IsViable(Battleground* bgTemplate, BracketBucket const& bucket) const; + // each faction tally >= min. Includes uninvited players already queued for + // the candidate BG in this bracket, not just the freshly-gathered batch. + bool IsViable(Battleground* bgTemplate, BracketBucket const& bucket, + BattlegroundBracketId bracketId) const; // Selects the BG for a populated bracket: live-BG reinforcement first, - // then a random pick from the configured pool with documented fallbacks. + // then a not-yet-running BG that already has uninvited queuers, then a + // random pick from the configured pool with documented fallbacks. BattlegroundTypeId SelectBattlegroundForBracket(BattlegroundBracketId bracketId, BracketBucket const& bucket) const;