Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 67 additions & 29 deletions src/BgAutoQueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <algorithm>
#include <limits>
#include <unordered_map>
#include <utility>

namespace
{
Expand Down Expand Up @@ -350,14 +351,28 @@ bool BgAutoQueue::BucketFitsLiveBg(Battleground* bg, BracketBucket const& bucket
}

BgAutoQueue::QueuedWaiters BgAutoQueue::CountUninvitedWaiters(BattlegroundTypeId bgTypeId,
BattlegroundBracketId bracketId) const
uint32 minLevel, uint32 maxLevel) const
{
QueuedWaiters waiters;

BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(bgTypeId, 0);
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<BattlegroundBracketId> 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[] =
Expand All @@ -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<uint32>(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<uint32>(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);
Expand All @@ -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<BattlegroundBracketId>& liveBracket) const
{
// (a) Live-BG reinforcement (priority; not limited to the pool).
std::vector<BattlegroundTypeId> liveTypes;
std::vector<std::pair<BattlegroundTypeId, BattlegroundBracketId>> liveTypes;
for (BattlegroundTypeId bgTypeId : BG_NORMAL_TYPES)
{
if (sDisableMgr->IsDisabledFor(DISABLE_TYPE_BATTLEGROUND, bgTypeId, nullptr))
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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);
}

Expand All @@ -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<BattlegroundBracketId> liveBracket, uint32* skippedAtQueueTime)
{
Battleground* bgTemplate = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId);
if (!bgTemplate)
Expand All @@ -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<BattlegroundBracketId> scheduledBrackets;

for (ObjectGuid guid : bucket.players)
{
Expand Down Expand Up @@ -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);
Expand All @@ -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<uint32>(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<uint32>(bgTypeId), static_cast<uint32>(scheduledBracket));
}

return queued;
}
Expand Down Expand Up @@ -627,15 +664,16 @@ BgAutoQueue::QueuePassResult BgAutoQueue::RunQueuePass()

for (auto const& [bracketId, bucket] : buckets)
{
BattlegroundTypeId bgTypeId = SelectBattlegroundForBracket(bracketId, bucket);
Optional<BattlegroundBracketId> 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<uint32>(bracketId));
++result.bracketsWithoutBg;
continue;
}

uint32 const queued = QueueBucket(bgTypeId, bucket, &result.skippedAtQueueTime);
uint32 const queued = QueueBucket(bgTypeId, bucket, liveBracket, &result.skippedAtQueueTime);
if (queued > 0)
{
result.players += queued;
Expand Down
43 changes: 25 additions & 18 deletions src/BgAutoQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "DBCEnums.h"
#include "ObjectGuid.h"
#include "Optional.h"
#include "SharedDefines.h"

#include <array>
Expand Down Expand Up @@ -105,7 +106,7 @@ class BgAutoQueue
std::vector<ObjectGuid> 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;
};

Expand Down Expand Up @@ -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<BattlegroundBracketId>& 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<BattlegroundBracketId> liveBracket, uint32* skippedAtQueueTime = nullptr);

void BroadcastWarning() const;

Expand Down
2 changes: 1 addition & 1 deletion src/cs_bg_auto_queue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};

Expand Down
Loading