diff --git a/src/BgAutoQueue.cpp b/src/BgAutoQueue.cpp index 65dac1d..1a6532d 100644 --- a/src/BgAutoQueue.cpp +++ b/src/BgAutoQueue.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace { @@ -350,7 +351,7 @@ bool BgAutoQueue::BucketFitsLiveBg(Battleground* bg, BracketBucket const& bucket } BgAutoQueue::QueuedWaiters BgAutoQueue::CountUninvitedWaiters(BattlegroundTypeId bgTypeId, - BattlegroundBracketId bracketId) const + uint32 minLevel, uint32 maxLevel) const { QueuedWaiters waiters; @@ -358,6 +359,20 @@ BgAutoQueue::QueuedWaiters BgAutoQueue::CountUninvitedWaiters(BattlegroundTypeId if (bgQueueTypeId == BATTLEGROUND_QUEUE_NONE) return waiters; + Battleground* bgTemplate = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); + if (!bgTemplate) + return waiters; + + // Bracket indices are numbered per map, so the level range must be resolved + // against the candidate BG's own map. Brackets are contiguous, so the two + // endpoints cover the whole range (0, 1, or 2 distinct ids); a level with no + // bracket on this map cannot enter this BG and contributes nothing. + std::vector bracketIds; + for (uint32 level : { minLevel, maxLevel }) + if (PvPDifficultyEntry const* entry = GetBattlegroundBracketByLevel(bgTemplate->GetMapId(), level)) + if (std::find(bracketIds.begin(), bracketIds.end(), entry->GetBracketId()) == bracketIds.end()) + bracketIds.push_back(entry->GetBracketId()); + BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(bgQueueTypeId); static constexpr BattlegroundQueueGroupTypes WAITER_GROUP_TYPES[] = @@ -369,30 +384,32 @@ BgAutoQueue::QueuedWaiters BgAutoQueue::CountUninvitedWaiters(BattlegroundTypeId BG_QUEUE_CFBG }; - for (BattlegroundQueueGroupTypes groupType : WAITER_GROUP_TYPES) + for (BattlegroundBracketId bracketId : bracketIds) { - for (GroupQueueInfo const* gInfo : bgQueue.m_QueuedGroups[bracketId][groupType]) + for (BattlegroundQueueGroupTypes groupType : WAITER_GROUP_TYPES) { - 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; + 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 +bool BgAutoQueue::IsViable(Battleground* bgTemplate, BracketBucket const& bucket) const { uint32 const minPerTeam = bgTemplate->GetMinPlayersPerTeam(); - QueuedWaiters const waiters = CountUninvitedWaiters(bgTemplate->GetBgTypeID(), bracketId); + QueuedWaiters const waiters = CountUninvitedWaiters(bgTemplate->GetBgTypeID(), bucket.minLevel, bucket.maxLevel); if (_crossFaction) return (bucket.alliance + bucket.horde + waiters.total) >= (2u * minPerTeam); @@ -401,10 +418,11 @@ bool BgAutoQueue::IsViable(Battleground* bgTemplate, BracketBucket const& bucket && (bucket.horde + waiters.horde) >= minPerTeam; } -BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BattlegroundBracketId bracketId, BracketBucket const& bucket) const +BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BracketBucket const& bucket, + Optional& liveBracket) const { // (a) Live-BG reinforcement (priority; not limited to the pool). - std::vector liveTypes; + std::vector> liveTypes; for (BattlegroundTypeId bgTypeId : BG_NORMAL_TYPES) { if (sDisableMgr->IsDisabledFor(DISABLE_TYPE_BATTLEGROUND, bgTypeId, nullptr)) @@ -424,13 +442,17 @@ BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BattlegroundBracket if (!BucketFitsLiveBg(bg, bucket)) continue; - liveTypes.push_back(bgTypeId); + liveTypes.push_back({ bgTypeId, bg->GetBracketId() }); break; } } if (!liveTypes.empty()) - return Acore::Containers::SelectRandomContainerElement(liveTypes); + { + auto const& [bgTypeId, bracketId] = Acore::Containers::SelectRandomContainerElement(liveTypes); + liveBracket = bracketId; + return bgTypeId; + } // (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 @@ -445,12 +467,12 @@ BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BattlegroundBracket if (!BucketHasAnyFit(bgTypeId, bucket)) continue; - QueuedWaiters const waiters = CountUninvitedWaiters(bgTypeId, bracketId); + QueuedWaiters const waiters = CountUninvitedWaiters(bgTypeId, bucket.minLevel, bucket.maxLevel); if (waiters.total == 0) continue; Battleground* bgTemplate = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); - if (!bgTemplate || !IsViable(bgTemplate, bucket, bracketId)) + if (!bgTemplate || !IsViable(bgTemplate, bucket)) continue; if (waiters.total > bestWaiters) @@ -487,7 +509,7 @@ BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BattlegroundBracket for (BattlegroundTypeId bgTypeId : candidates) { Battleground* bgTemplate = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); - if (bgTemplate && IsViable(bgTemplate, bucket, bracketId)) + if (bgTemplate && IsViable(bgTemplate, bucket)) viable.push_back(bgTypeId); } @@ -514,7 +536,8 @@ BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BattlegroundBracket return best; } -uint32 BgAutoQueue::QueueBucket(BattlegroundTypeId bgTypeId, BracketBucket const& bucket, uint32* skippedAtQueueTime) +uint32 BgAutoQueue::QueueBucket(BattlegroundTypeId bgTypeId, BracketBucket const& bucket, + Optional liveBracket, uint32* skippedAtQueueTime) { Battleground* bgTemplate = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); if (!bgTemplate) @@ -527,7 +550,7 @@ uint32 BgAutoQueue::QueueBucket(BattlegroundTypeId bgTypeId, BracketBucket const BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(bgQueueTypeId); uint32 queued = 0; - BattlegroundBracketId scheduledBracket = BG_BRACKET_ID_FIRST; + std::vector scheduledBrackets; for (ObjectGuid guid : bucket.players) { @@ -561,6 +584,16 @@ uint32 BgAutoQueue::QueueBucket(BattlegroundTypeId bgTypeId, BracketBucket const continue; } + // Tier-a reinforcement targets one live game; a player whose own bracket + // differs would sit in a queue list that game never serves. + if (liveBracket && bracketEntry->GetBracketId() != *liveBracket) + { + LOG_DEBUG("module", "mod-bg-auto-queue: skip {}: level {} is outside the live game's bracket.", player->GetName(), player->GetLevel()); + if (skippedAtQueueTime) + ++*skippedAtQueueTime; + continue; + } + GroupQueueInfo* ginfo = bgQueue.AddGroup(player, nullptr, bgTypeId, bracketEntry, 0, false, false, 0, 0); uint32 avgWaitTime = bgQueue.GetAverageQueueWaitTime(ginfo); uint32 queueSlot = player->AddBattlegroundQueueId(bgQueueTypeId); @@ -574,15 +607,19 @@ uint32 BgAutoQueue::QueueBucket(BattlegroundTypeId bgTypeId, BracketBucket const sScriptMgr->OnPlayerJoinBG(player); - scheduledBracket = bracketEntry->GetBracketId(); + if (std::find(scheduledBrackets.begin(), scheduledBrackets.end(), bracketEntry->GetBracketId()) == scheduledBrackets.end()) + scheduledBrackets.push_back(bracketEntry->GetBracketId()); ++queued; LOG_DEBUG("module", "mod-bg-auto-queue: queued {} into bgTypeId {}.", player->GetName(), static_cast(bgTypeId)); } - // Schedule a single queue update for the bracket, not once per player. - if (queued > 0) + // One queue update per distinct bracket queued into, not once per player. + for (BattlegroundBracketId scheduledBracket : scheduledBrackets) + { sBattlegroundMgr->ScheduleQueueUpdate(0, 0, bgQueueTypeId, bgTypeId, scheduledBracket); + LOG_DEBUG("module", "mod-bg-auto-queue: scheduled queue update for bgTypeId {} bracket {}.", static_cast(bgTypeId), static_cast(scheduledBracket)); + } return queued; } @@ -627,7 +664,8 @@ BgAutoQueue::QueuePassResult BgAutoQueue::RunQueuePass() for (auto const& [bracketId, bucket] : buckets) { - BattlegroundTypeId bgTypeId = SelectBattlegroundForBracket(bracketId, bucket); + Optional liveBracket; + BattlegroundTypeId bgTypeId = SelectBattlegroundForBracket(bucket, liveBracket); if (bgTypeId == BATTLEGROUND_TYPE_NONE) { LOG_DEBUG("module", "mod-bg-auto-queue: bracket {} has no eligible battleground, skipping.", static_cast(bracketId)); @@ -635,7 +673,7 @@ BgAutoQueue::QueuePassResult BgAutoQueue::RunQueuePass() continue; } - uint32 const queued = QueueBucket(bgTypeId, bucket, &result.skippedAtQueueTime); + uint32 const queued = QueueBucket(bgTypeId, bucket, liveBracket, &result.skippedAtQueueTime); if (queued > 0) { result.players += queued; diff --git a/src/BgAutoQueue.h b/src/BgAutoQueue.h index 1b21105..49452ae 100644 --- a/src/BgAutoQueue.h +++ b/src/BgAutoQueue.h @@ -7,6 +7,7 @@ #include "DBCEnums.h" #include "ObjectGuid.h" +#include "Optional.h" #include "SharedDefines.h" #include @@ -105,7 +106,7 @@ class BgAutoQueue std::vector players; uint32 alliance = 0; uint32 horde = 0; - uint32 minLevel = 0; // bracket level range, used for logging purposes + uint32 minLevel = 0; // bracket level range, drives waiter counting and logging uint32 maxLevel = 0; }; @@ -133,31 +134,37 @@ class BgAutoQueue 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. + // Counts uninvited players already sitting in bgTypeId's core queue, in the + // candidate BG's own map-relative bracket(s) spanning [minLevel, maxLevel] + // (bracket indices are numbered per map), 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; + uint32 minLevel, uint32 maxLevel) const; // Viability per CrossFaction: cross-faction => total >= 2*min; otherwise // 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; + // the candidate BG at the bucket's levels, not just the freshly-gathered batch. + bool IsViable(Battleground* bgTemplate, BracketBucket const& bucket) const; // Selects the BG for a populated bracket: live-BG reinforcement first, // 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; - - // Queues every player in the bucket into bgTypeId, then schedules a single - // queue update for the bracket. Returns the number of players queued. When - // non-null, skippedAtQueueTime is incremented for each bucket player - // dropped by the queue-time re-check or the BG-specific veto. + // random pick from the configured pool with documented fallbacks. On a + // live-BG pick, liveBracket carries the matched game's own map-relative + // bracket id; it stays empty on every other path. + BattlegroundTypeId SelectBattlegroundForBracket(BracketBucket const& bucket, + Optional& liveBracket) const; + + // Queues every player in the bucket into bgTypeId, then schedules one + // queue update per distinct bracket queued into. When liveBracket is set + // (live-BG reinforcement), players whose own bracket differs are skipped — + // that game's queue list would never serve them. Returns the number of + // players queued. When non-null, skippedAtQueueTime is incremented for each + // bucket player dropped by the queue-time re-check, the BG-specific veto, + // or the off-bracket skip. uint32 QueueBucket(BattlegroundTypeId bgTypeId, BracketBucket const& bucket, - uint32* skippedAtQueueTime = nullptr); + Optional liveBracket, uint32* skippedAtQueueTime = nullptr); void BroadcastWarning() const; diff --git a/src/cs_bg_auto_queue.cpp b/src/cs_bg_auto_queue.cpp index 5c6d3ae..168d2c9 100644 --- a/src/cs_bg_auto_queue.cpp +++ b/src/cs_bg_auto_queue.cpp @@ -121,7 +121,7 @@ class bg_auto_queue_commandscript : public CommandScript handler->PSendSysMessage(" no eligible battleground for their level: {} bracket(s)", result.bracketsWithoutBg); if (result.skippedAtQueueTime > 0) - handler->PSendSysMessage(" dropped at queue time (state change or veto): {} player(s)", result.skippedAtQueueTime); + handler->PSendSysMessage(" dropped at queue time (state change, veto, or off-bracket): {} player(s)", result.skippedAtQueueTime); } };