From 178c9c69e6b1b9efb16aa2b9946762c90d42eab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Borz=C3=AC?= Date: Tue, 14 Jul 2026 17:45:48 +0200 Subject: [PATCH] feat(Core/Battlegrounds): rework LowLevelsMinPlayersOverride (#26601) --- .../apps/worldserver/worldserver.conf.dist | 25 +++ .../game/Battlegrounds/Battleground.cpp | 7 + src/server/game/Battlegrounds/Battleground.h | 6 +- .../game/Battlegrounds/BattlegroundUtils.cpp | 18 +- .../game/Battlegrounds/BattlegroundUtils.h | 6 + src/server/game/World/WorldConfig.cpp | 6 + src/server/game/World/WorldConfig.h | 6 + .../LowLevelsMinPlayersOverrideTest.cpp | 204 ++++++++++++++++++ 8 files changed, 272 insertions(+), 6 deletions(-) create mode 100644 src/test/server/game/Battlegrounds/LowLevelsMinPlayersOverrideTest.cpp diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index dd6fe04a3..da7a62806 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -3976,10 +3976,35 @@ Battleground.SpeedBuffRespawn = 150 # # Battleground.Override.LowLevels.MinPlayers # Description: Overrides the minimum number of required players per team for all levels < MaxPlayerLevel +# Used as fallback for any battleground without a per-battleground override (see below) # Default: 0 (Disabled) Battleground.Override.LowLevels.MinPlayers = 0 +# +# Battleground.Override.LowLevels.MinPlayers.AV +# Battleground.Override.LowLevels.MinPlayers.WS +# Battleground.Override.LowLevels.MinPlayers.AB +# Battleground.Override.LowLevels.MinPlayers.EY +# Battleground.Override.LowLevels.MinPlayers.SA +# Battleground.Override.LowLevels.MinPlayers.IC +# Description: Per-battleground overrides of the minimum number of required players per team +# for all levels < MaxPlayerLevel. +# AV = Alterac Valley, WS = Warsong Gulch, AB = Arathi Basin, +# EY = Eye of the Storm, SA = Strand of the Ancients, IC = Isle of Conquest +# Resolution order: per-battleground key, then Battleground.Override.LowLevels.MinPlayers, +# then the battleground_template DB value. +# Matches entered through the Random Battleground queue always use the global +# Battleground.Override.LowLevels.MinPlayers, never the per-battleground keys. +# Default: 0 (Disabled, use Battleground.Override.LowLevels.MinPlayers) + +Battleground.Override.LowLevels.MinPlayers.AV = 0 +Battleground.Override.LowLevels.MinPlayers.WS = 0 +Battleground.Override.LowLevels.MinPlayers.AB = 0 +Battleground.Override.LowLevels.MinPlayers.EY = 0 +Battleground.Override.LowLevels.MinPlayers.SA = 0 +Battleground.Override.LowLevels.MinPlayers.IC = 0 + # # Battleground.Warsong.Flags # Description: Set the number of flags required for a team to win in Warsong battleground diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp index 828f1f5fa..caa95df99 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -19,6 +19,7 @@ #include "ArenaSpectator.h" #include "ArenaTeam.h" #include "BattlegroundMgr.h" +#include "BattlegroundUtils.h" #include "Chat.h" #include "ChatTextBuilder.h" #include "Creature.h" @@ -242,6 +243,12 @@ Battleground::~Battleground() delete itr.second; } +uint32 Battleground::GetMinPlayersPerTeam() const +{ + uint32 lowLevelsOverride = GetLowLevelsMinPlayersOverride(GetBgTypeID()); + return (lowLevelsOverride && !isTemplate() && !isMaxLevel() && !isArena()) ? lowLevelsOverride : m_MinPlayersPerTeam; +} + void Battleground::Update(uint32 diff) { // pussywizard: diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h index 34f349af3..3c42603c6 100644 --- a/src/server/game/Battlegrounds/Battleground.h +++ b/src/server/game/Battlegrounds/Battleground.h @@ -347,11 +347,7 @@ public: } [[nodiscard]] uint32 GetMaxPlayersPerTeam() const { return m_MaxPlayersPerTeam; } - [[nodiscard]] uint32 GetMinPlayersPerTeam() const - { - auto lowLevelsOverride = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS); - return (lowLevelsOverride && !isTemplate() && !isMaxLevel() && !isArena()) ? lowLevelsOverride : m_MinPlayersPerTeam; - } + [[nodiscard]] uint32 GetMinPlayersPerTeam() const; [[nodiscard]] int32 GetStartDelayTime() const { return m_StartDelayTime; } [[nodiscard]] uint8 GetArenaType() const { return m_ArenaType; } diff --git a/src/server/game/Battlegrounds/BattlegroundUtils.cpp b/src/server/game/Battlegrounds/BattlegroundUtils.cpp index 54703472b..f0c4b476c 100644 --- a/src/server/game/Battlegrounds/BattlegroundUtils.cpp +++ b/src/server/game/Battlegrounds/BattlegroundUtils.cpp @@ -14,7 +14,23 @@ uint32 GetMinPlayersPerTeam(Battleground* bg, PvPDifficultyEntry const* bracketE auto maxPlayerLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); auto isMaxLevel = bracketEntry->minLevel <= maxPlayerLevel && maxPlayerLevel <= bracketEntry->maxLevel; - auto lowLevelsOverride = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS); + auto lowLevelsOverride = GetLowLevelsMinPlayersOverride(bg->GetBgTypeID()); return (lowLevelsOverride && !isMaxLevel) ? lowLevelsOverride : bg->GetMinPlayersPerTeam(); } + +uint32 GetLowLevelsMinPlayersOverride(BattlegroundTypeId bgTypeId) +{ + uint32 perBg = 0; + switch (bgTypeId) + { + case BATTLEGROUND_AV: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AV); break; + case BATTLEGROUND_WS: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS); break; + case BATTLEGROUND_AB: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AB); break; + case BATTLEGROUND_EY: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_EY); break; + case BATTLEGROUND_SA: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_SA); break; + case BATTLEGROUND_IC: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_IC); break; + default: break; + } + return perBg ? perBg : sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS); +} diff --git a/src/server/game/Battlegrounds/BattlegroundUtils.h b/src/server/game/Battlegrounds/BattlegroundUtils.h index 9d18cbb82..ed54548f7 100644 --- a/src/server/game/Battlegrounds/BattlegroundUtils.h +++ b/src/server/game/Battlegrounds/BattlegroundUtils.h @@ -5,4 +5,10 @@ uint32 GetMinPlayersPerTeam(Battleground* bg, PvPDifficultyEntry const* bracketEntry); +// Effective low-levels MinPlayersPerTeam override for a BG type: +// the per-BG config if set, else the global one. 0 = no override. +// BGs entered through the Random BG queue have BATTLEGROUND_RB as +// their type, so they always resolve through the global key. +uint32 GetLowLevelsMinPlayersOverride(BattlegroundTypeId bgTypeId); + #endif // BATTLEGROUNDUTILS_H diff --git a/src/server/game/World/WorldConfig.cpp b/src/server/game/World/WorldConfig.cpp index 5233d6e48..26ca792c0 100644 --- a/src/server/game/World/WorldConfig.cpp +++ b/src/server/game/World/WorldConfig.cpp @@ -426,6 +426,12 @@ void WorldConfig::BuildConfigCache() SetConfigValue(CONFIG_BATTLEGROUND_PREP_TIME, "Battleground.PrepTime", 120); SetConfigValue(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS, "Battleground.Override.LowLevels.MinPlayers", 0); + SetConfigValue(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AV, "Battleground.Override.LowLevels.MinPlayers.AV", 0); + SetConfigValue(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS, "Battleground.Override.LowLevels.MinPlayers.WS", 0); + SetConfigValue(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AB, "Battleground.Override.LowLevels.MinPlayers.AB", 0); + SetConfigValue(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_EY, "Battleground.Override.LowLevels.MinPlayers.EY", 0); + SetConfigValue(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_SA, "Battleground.Override.LowLevels.MinPlayers.SA", 0); + SetConfigValue(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_IC, "Battleground.Override.LowLevels.MinPlayers.IC", 0); SetConfigValue(CONFIG_BATTLEGROUND_DISABLE_QUEST_SHARE_IN_BG, "Battleground.DisableQuestShareInBG", false); SetConfigValue(CONFIG_BATTLEGROUND_DISABLE_READY_CHECK_IN_BG, "Battleground.DisableReadyCheckInBG", false); SetConfigValue(CONFIG_BATTLEGROUND_CAST_DESERTER, "Battleground.CastDeserter", true); diff --git a/src/server/game/World/WorldConfig.h b/src/server/game/World/WorldConfig.h index 7e3ef1319..e869d8b3b 100644 --- a/src/server/game/World/WorldConfig.h +++ b/src/server/game/World/WorldConfig.h @@ -264,6 +264,12 @@ enum ServerConfigs CONFIG_DISABLE_BREATHING, CONFIG_BATTLEGROUND_PREP_TIME, CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS, + CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AV, + CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS, + CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AB, + CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_EY, + CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_SA, + CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_IC, CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_SPAM_DELAY, CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_TIMER, CONFIG_BATTLEGROUND_PREMATURE_FINISH_TIMER, diff --git a/src/test/server/game/Battlegrounds/LowLevelsMinPlayersOverrideTest.cpp b/src/test/server/game/Battlegrounds/LowLevelsMinPlayersOverrideTest.cpp new file mode 100644 index 000000000..3073f1374 --- /dev/null +++ b/src/test/server/game/Battlegrounds/LowLevelsMinPlayersOverrideTest.cpp @@ -0,0 +1,204 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "Battleground.h" +#include "BattlegroundUtils.h" +#include "DBCStructure.h" +#include "ScriptMgr.h" +#include "ScriptDefines/AllBattlegroundScript.h" +#include "WorldMock.h" +#include "gtest/gtest.h" + +/** + * Tests the resolution order of the low-levels MinPlayersPerTeam override: + * per-BG config key > global config key > battleground_template value, + * and the guards that keep it away from arenas, templates (member function) + * and max-level brackets. + */ +class LowLevelsMinPlayersOverrideTest : public ::testing::Test +{ +protected: + static constexpr uint32 TemplateMinPlayers = 5; + + void SetUp() override + { + // Battleground's destructor fires OnBattlegroundDestroy; the hook + // registry must be sized before any hook is invoked. + ScriptRegistry::InitEnabledHooksIfNeeded(ALLBATTLEGROUNDHOOK_END); + + previousWorld_ = std::move(sWorld); + worldMock_ = new ::testing::NiceMock(); + ON_CALL(*worldMock_, getIntConfig(::testing::_)) + .WillByDefault(::testing::Return(0)); + ON_CALL(*worldMock_, getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) + .WillByDefault(::testing::Return(80)); + sWorld.reset(worldMock_); + } + + void TearDown() override + { + sWorld = std::move(previousWorld_); + } + + void SetIntConfig(ServerConfigs index, uint32 value) + { + ON_CALL(*worldMock_, getIntConfig(index)) + .WillByDefault(::testing::Return(value)); + } + + static PvPDifficultyEntry MakeBracket(uint32 minLevel, uint32 maxLevel) + { + return PvPDifficultyEntry(/*mapId*/ 0, /*bracketId*/ 0, minLevel, maxLevel, /*difficulty*/ 0); + } + + // A template BG as loaded from battleground_template + static void SetupTemplate(Battleground& bg, BattlegroundTypeId bgTypeId) + { + bg.SetBgTypeID(bgTypeId); + bg.SetMinPlayersPerTeam(TemplateMinPlayers); + } + + // A real BG instance bound to a level bracket + static void SetupRealBg(Battleground& bg, BattlegroundTypeId bgTypeId, PvPDifficultyEntry const& bracket) + { + SetupTemplate(bg, bgTypeId); + bg.SetBracket(&bracket); + } + + ::testing::NiceMock* worldMock_ = nullptr; + std::unique_ptr previousWorld_; +}; + +TEST_F(LowLevelsMinPlayersOverrideTest, PerBgKeyWinsOverGlobalAndTemplate) +{ + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS, 4); + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS, 2); + + Battleground bg; + PvPDifficultyEntry bracket = MakeBracket(10, 19); + SetupRealBg(bg, BATTLEGROUND_WS, bracket); + + EXPECT_EQ(bg.GetMinPlayersPerTeam(), 2u); + EXPECT_EQ(GetMinPlayersPerTeam(&bg, &bracket), 2u); +} + +TEST_F(LowLevelsMinPlayersOverrideTest, GlobalKeyAppliesWhenPerBgUnset) +{ + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS, 4); + + Battleground bg; + PvPDifficultyEntry bracket = MakeBracket(10, 19); + SetupRealBg(bg, BATTLEGROUND_WS, bracket); + + EXPECT_EQ(bg.GetMinPlayersPerTeam(), 4u); + EXPECT_EQ(GetMinPlayersPerTeam(&bg, &bracket), 4u); +} + +TEST_F(LowLevelsMinPlayersOverrideTest, TemplateValueAppliesWhenNoOverrideSet) +{ + Battleground bg; + PvPDifficultyEntry bracket = MakeBracket(10, 19); + SetupRealBg(bg, BATTLEGROUND_WS, bracket); + + EXPECT_EQ(bg.GetMinPlayersPerTeam(), TemplateMinPlayers); + EXPECT_EQ(GetMinPlayersPerTeam(&bg, &bracket), TemplateMinPlayers); +} + +TEST_F(LowLevelsMinPlayersOverrideTest, MaxLevelBracketIgnoresOverride) +{ + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS, 4); + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS, 2); + + Battleground bg; + PvPDifficultyEntry bracket = MakeBracket(71, 80); + SetupRealBg(bg, BATTLEGROUND_WS, bracket); + + EXPECT_EQ(bg.GetMinPlayersPerTeam(), TemplateMinPlayers); + EXPECT_EQ(GetMinPlayersPerTeam(&bg, &bracket), TemplateMinPlayers); + + // same via the template path of the free helper + Battleground templateBg; + SetupTemplate(templateBg, BATTLEGROUND_WS); + EXPECT_EQ(GetMinPlayersPerTeam(&templateBg, &bracket), TemplateMinPlayers); +} + +TEST_F(LowLevelsMinPlayersOverrideTest, ArenaIgnoresOverride) +{ + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS, 4); + + Battleground bg; + PvPDifficultyEntry bracket = MakeBracket(10, 19); + SetupRealBg(bg, BATTLEGROUND_NA, bracket); + bg.SetArenaorBGType(true); + + EXPECT_EQ(bg.GetMinPlayersPerTeam(), TemplateMinPlayers); + EXPECT_EQ(GetMinPlayersPerTeam(&bg, &bracket), TemplateMinPlayers); +} + +TEST_F(LowLevelsMinPlayersOverrideTest, PerBgKeyDoesNotLeakToOtherBgs) +{ + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS, 2); + + EXPECT_EQ(GetLowLevelsMinPlayersOverride(BATTLEGROUND_WS), 2u); + EXPECT_EQ(GetLowLevelsMinPlayersOverride(BATTLEGROUND_AB), 0u); + + Battleground bg; + PvPDifficultyEntry bracket = MakeBracket(10, 19); + SetupRealBg(bg, BATTLEGROUND_AB, bracket); + + EXPECT_EQ(bg.GetMinPlayersPerTeam(), TemplateMinPlayers); +} + +TEST_F(LowLevelsMinPlayersOverrideTest, EachPerBgKeyResolvesIndependently) +{ + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AV, 10); + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS, 2); + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AB, 3); + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_EY, 4); + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_SA, 5); + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_IC, 6); + + EXPECT_EQ(GetLowLevelsMinPlayersOverride(BATTLEGROUND_AV), 10u); + EXPECT_EQ(GetLowLevelsMinPlayersOverride(BATTLEGROUND_WS), 2u); + EXPECT_EQ(GetLowLevelsMinPlayersOverride(BATTLEGROUND_AB), 3u); + EXPECT_EQ(GetLowLevelsMinPlayersOverride(BATTLEGROUND_EY), 4u); + EXPECT_EQ(GetLowLevelsMinPlayersOverride(BATTLEGROUND_SA), 5u); + EXPECT_EQ(GetLowLevelsMinPlayersOverride(BATTLEGROUND_IC), 6u); +} + +TEST_F(LowLevelsMinPlayersOverrideTest, UnmappedBgTypeFallsBackToGlobal) +{ + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS, 4); + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS, 2); + + EXPECT_EQ(GetLowLevelsMinPlayersOverride(BATTLEGROUND_RB), 4u); +} + +TEST_F(LowLevelsMinPlayersOverrideTest, FreeHelperOnTemplateUsesPerBgValue) +{ + SetIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS, 2); + + Battleground templateBg; + SetupTemplate(templateBg, BATTLEGROUND_WS); + PvPDifficultyEntry bracket = MakeBracket(10, 19); + + // the free helper resolves via the bracket entry for templates + EXPECT_EQ(GetMinPlayersPerTeam(&templateBg, &bracket), 2u); + + // the member function never applies overrides to templates + EXPECT_EQ(templateBg.GetMinPlayersPerTeam(), TemplateMinPlayers); +}