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
5 changes: 5 additions & 0 deletions conf/mod-bg-auto-queue.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ BgAutoQueue.Level.Min = 10
# BgAutoQueue.Level.Max
# Description: Maximum character level eligible for the automatic queue.
# Players above this level are skipped.
# On sub-max brackets, batch viability follows the core
# Battleground.Override.LowLevels.MinPlayers override when
# set. A max-level bracket (reachable only with
# Level.Max = 80) keeps the raw template minimums,
# matching core.
# Default: 79
#

Expand Down
57 changes: 51 additions & 6 deletions src/BgAutoQueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "Battleground.h"
#include "BattlegroundMgr.h"
#include "BattlegroundQueue.h"
#include "BattlegroundUtils.h"
#include "Chat.h"
#include "Config.h"
#include "Containers.h"
Expand Down Expand Up @@ -110,6 +111,15 @@ void BgAutoQueue::LoadConfig()
LOG_WARN("module", "BgAutoQueue.WarningLeadTime ({} s) >= Interval ({} min); the warning will not fire.", warningLeadSec, intervalMin);

_crossFaction = sConfigMgr->GetOption<bool>("BgAutoQueue.CrossFaction", true);

// Mirror mod-cfbg's defaults (CFBG.cpp LoadConfig). Only CFBG on + EvenTeams
// on + threshold 0 pins CFBG's allowedDiff to 0 in both match formation and
// reinforcement; every other combination tolerates odd totals, so the parity
// trim in QueueBucket self-disables (including installs without mod-cfbg).
_evenTeamsStrict = sConfigMgr->GetOption<bool>("CFBG.Enable", false)
&& sConfigMgr->GetOption<bool>("CFBG.EvenTeams.Enabled", false)
&& sConfigMgr->GetOption<uint32>("CFBG.EvenTeams.MaxPlayersThreshold", 0) == 0;

_skipGameMasters = sConfigMgr->GetOption<bool>("BgAutoQueue.SkipGameMasters", true);
_skipAfk = sConfigMgr->GetOption<bool>("BgAutoQueue.SkipAFK", true);
_broadcastMessage = sConfigMgr->GetOption<std::string>("BgAutoQueue.BroadcastMessage", BG_AUTO_QUEUE_DEFAULT_BROADCAST);
Expand All @@ -133,8 +143,8 @@ void BgAutoQueue::LoadConfig()
_warningSent = false;
_firstPass = true;

LOG_INFO("module", "mod-bg-auto-queue: enabled={}, levels=[{}-{}], pool size={}, interval={} min, initialDelay={} s, warningLead={} s, crossFaction={}, skipGM={}, skipAFK={}, skipAuras={}.",
_enabled, _levelMin, _levelMax, _poolRaw.size(), intervalMin, initialDelaySec, warningLeadSec, _crossFaction, _skipGameMasters, _skipAfk, _skipAuras.size());
LOG_INFO("module", "mod-bg-auto-queue: enabled={}, levels=[{}-{}], pool size={}, interval={} min, initialDelay={} s, warningLead={} s, crossFaction={}, evenTeamsStrict={}, skipGM={}, skipAFK={}, skipAuras={}.",
_enabled, _levelMin, _levelMax, _poolRaw.size(), intervalMin, initialDelaySec, warningLeadSec, _crossFaction, _evenTeamsStrict, _skipGameMasters, _skipAfk, _skipAuras.size());

// Opt-out is stored via the core PlayerSettings system, which only persists
// across logins when EnablePlayerSettings is on. Without it, .bgevents
Expand Down Expand Up @@ -406,9 +416,21 @@ BgAutoQueue::QueuedWaiters BgAutoQueue::CountUninvitedWaiters(BattlegroundTypeId
return waiters;
}

uint32 BgAutoQueue::GetEffectiveMinPlayersPerTeam(Battleground* bgTemplate, BracketBucket const& bucket) const
{
PvPDifficultyEntry const* bracketEntry = GetBattlegroundBracketByLevel(bgTemplate->GetMapId(), bucket.minLevel);
if (!bracketEntry)
bracketEntry = GetBattlegroundBracketByLevel(bgTemplate->GetMapId(), bucket.maxLevel);

if (!bracketEntry)
return bgTemplate->GetMinPlayersPerTeam();

return ::GetMinPlayersPerTeam(bgTemplate, bracketEntry);
}

bool BgAutoQueue::IsViable(Battleground* bgTemplate, BracketBucket const& bucket) const
{
uint32 const minPerTeam = bgTemplate->GetMinPlayersPerTeam();
uint32 const minPerTeam = GetEffectiveMinPlayersPerTeam(bgTemplate, bucket);
QueuedWaiters const waiters = CountUninvitedWaiters(bgTemplate->GetBgTypeID(), bucket.minLevel, bucket.maxLevel);

if (_crossFaction)
Expand Down Expand Up @@ -525,7 +547,7 @@ BattlegroundTypeId BgAutoQueue::SelectBattlegroundForBracket(BracketBucket const
if (!bgTemplate)
continue;

uint32 const minPerTeam = bgTemplate->GetMinPlayersPerTeam();
uint32 const minPerTeam = GetEffectiveMinPlayersPerTeam(bgTemplate, bucket);
if (minPerTeam < bestMin || (minPerTeam == bestMin && bgTypeId < best))
{
bestMin = minPerTeam;
Expand All @@ -549,8 +571,9 @@ uint32 BgAutoQueue::QueueBucket(BattlegroundTypeId bgTypeId, BracketBucket const

BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(bgQueueTypeId);

uint32 queued = 0;
std::vector<BattlegroundBracketId> scheduledBrackets;
// Phase 1: re-validate and collect. Holding Player* between the two phases
// is safe: both run inside this one synchronous call, no tick boundary.
std::vector<std::pair<Player*, PvPDifficultyEntry const*>> verified;

for (ObjectGuid guid : bucket.players)
{
Expand Down Expand Up @@ -594,6 +617,28 @@ uint32 BgAutoQueue::QueueBucket(BattlegroundTypeId bgTypeId, BracketBucket const
continue;
}

verified.push_back({ player, bracketEntry });
}

// Strict EvenTeams (allowedDiff 0) can never invite an odd total: one solo
// would be stranded until an external parity break. Leave the newest
// unqueued instead; the next pass picks them up.
if (_crossFaction && _evenTeamsStrict && !verified.empty())
{
QueuedWaiters const waiters = CountUninvitedWaiters(bgTypeId, bucket.minLevel, bucket.maxLevel);
if ((verified.size() + waiters.total) % 2 != 0)
{
LOG_DEBUG("module", "mod-bg-auto-queue: parity trim: leaving {} unqueued to keep the wave even for strict EvenTeams.", verified.back().first->GetName());
verified.pop_back();
}
}

// Phase 2: queue everyone that survived.
uint32 queued = 0;
std::vector<BattlegroundBracketId> scheduledBrackets;

for (auto const& [player, bracketEntry] : verified)
{
GroupQueueInfo* ginfo = bgQueue.AddGroup(player, nullptr, bgTypeId, bracketEntry, 0, false, false, 0, 0);
uint32 avgWaitTime = bgQueue.GetAverageQueueWaitTime(ginfo);
uint32 queueSlot = player->AddBattlegroundQueueId(bgQueueTypeId);
Expand Down
22 changes: 18 additions & 4 deletions src/BgAutoQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ class BgAutoQueue
QueuedWaiters CountUninvitedWaiters(BattlegroundTypeId bgTypeId,
uint32 minLevel, uint32 maxLevel) const;

// Bracket-aware MinPlayersPerTeam for viability math: resolves the bucket's
// bracket on the candidate BG's own map and returns the same override-aware
// minimum core matching uses (GetMinPlayersPerTeam, BattlegroundUtils.h), so
// Battleground.Override.LowLevels.MinPlayers is honoured on sub-max brackets.
// Bucket levels come from the WSG reference map, so the candidate may have no
// bracket at minLevel (hence the maxLevel retry) or none at all, in which
// case the raw template minimum is returned.
uint32 GetEffectiveMinPlayersPerTeam(Battleground* bgTemplate, BracketBucket const& bucket) const;

// Viability per CrossFaction: cross-faction => total >= 2*min; otherwise
// each faction tally >= min. Includes uninvited players already queued for
// the candidate BG at the bucket's levels, not just the freshly-gathered batch.
Expand All @@ -159,10 +168,14 @@ class BgAutoQueue
// 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.
// that game's queue list would never serve them. Under strict CFBG
// EvenTeams (see _evenTeamsStrict), an odd wave (re-validated players plus
// uninvited waiters) leaves its newest player unqueued for this pass so no
// solo is stranded behind an allowedDiff of 0; the next pass picks them up.
// 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 (not for the parity trim —
// a deferral, not a drop).
uint32 QueueBucket(BattlegroundTypeId bgTypeId, BracketBucket const& bucket,
Optional<BattlegroundBracketId> liveBracket, uint32* skippedAtQueueTime = nullptr);

Expand All @@ -183,6 +196,7 @@ class BgAutoQueue
uint32 _initialDelayMs = 0;
uint32 _warningLeadMs = 60u * 1000u;
bool _crossFaction = true;
bool _evenTeamsStrict = false; // mod-cfbg EvenTeams with threshold 0 (see LoadConfig)
bool _skipGameMasters = true;
bool _skipAfk = true;
std::vector<uint32> _skipAuras; // aura ids that exclude a player from a pass
Expand Down
Loading