Adding new Dynamic brackets feature

This commit is contained in:
Dustin Hendrickson
2025-02-20 16:24:54 -08:00
parent 82d293c8a0
commit e8d1ad69c3
3 changed files with 140 additions and 41 deletions

View File

@@ -5,7 +5,6 @@ Overview
-------- --------
The Bot Level Brackets module for AzerothCore ensures an even spread of player bots across configurable level ranges (brackets). It periodically monitors bot levels and automatically adjusts them by transferring bots from overpopulated brackets to those with a deficit. During adjustments, bot levels are reset, equipped items are destroyed, trade skills are removed, quests are cleared, active auras are removed, pets are dismissed, and auto-maintenance actions are executed. Bots that are not immediately safe for level reset (for example, those in combat or engaged in other activities) are flagged for pending adjustment and processed later when they become safe. Additionally, Death Knight bots are safeguarded to never be assigned a level below 55. The Bot Level Brackets module for AzerothCore ensures an even spread of player bots across configurable level ranges (brackets). It periodically monitors bot levels and automatically adjusts them by transferring bots from overpopulated brackets to those with a deficit. During adjustments, bot levels are reset, equipped items are destroyed, trade skills are removed, quests are cleared, active auras are removed, pets are dismissed, and auto-maintenance actions are executed. Bots that are not immediately safe for level reset (for example, those in combat or engaged in other activities) are flagged for pending adjustment and processed later when they become safe. Additionally, Death Knight bots are safeguarded to never be assigned a level below 55.
Features Features
-------- --------
- **Configurable Level Brackets:** - **Configurable Level Brackets:**
@@ -38,9 +37,23 @@ Features
- **Support for Random Bots:** - **Support for Random Bots:**
The module applies exclusively to bots managed by RandomPlayerbotMgr. The module applies exclusively to bots managed by RandomPlayerbotMgr.
- **Dynamic Real Player Weighting with Inverse Scaling:**
When dynamic distribution is enabled, the module uses a configurable weight multiplier (set via `BotLevelBrackets.RealPlayerWeight`) to boost each real player's contribution to the desired distribution. This weight is further scaled inversely by the total number of real players online, ensuring that when few players are active, each player's impact on the bot distribution is significantly increased.
**Note:** The `RealPlayerWeight` option only takes effect when `BotLevelBrackets.UseDynamicDistribution` is enabled.
- **Dynamic Distribution Toggle:**
Enable or disable the dynamic recalculation of bot distribution percentages based on the number of non-bot players in each level bracket via the `BotLevelBrackets.UseDynamicDistribution` option.
- **Debug Mode:** - **Debug Mode:**
An optional debug mode provides detailed logging for monitoring bot adjustments and troubleshooting module operations. An optional debug mode provides detailed logging for monitoring bot adjustments and troubleshooting module operations.
### Dynamic Real Player Weighting and Scaling
When dynamic distribution is enabled (`BotLevelBrackets.UseDynamicDistribution`), the module recalculates the desired bot percentages for each level bracket based on the number of non-bot players present.
The total weight across all brackets is computed, and each brackets desired percentage is derived from its share of that total. This ensures that while more players in a bracket increase its weight, each additional player contributes progressively less.
Installation Installation
------------ ------------
1. **Clone the Module** 1. **Clone the Module**
@@ -73,42 +86,43 @@ Customize the modules behavior by editing the `mod_player_bot_level_brackets.
### Global Settings ### Global Settings
Setting | Description | Default | Valid Values Setting | Description | Default | Valid Values
--------------------------------|----------------------------------------------------------------------------------------------|---------|-------------------- ------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|---------|--------------------
BotLevelBrackets.DebugMode | Enables detailed debug logging for module operations. | 0 | 0 (off) / 1 (on) BotLevelBrackets.DebugMode | Enables detailed debug logging for module operations. | 0 | 0 (off) / 1 (on)
BotLevelBrackets.CheckFrequency | Frequency (in seconds) for performing the bot bracket distribution check. | 300 | Positive Integer BotLevelBrackets.CheckFrequency | Frequency (in seconds) for performing the bot bracket distribution check. | 300 | Positive Integer
BotLevelBrackets.CheckFlaggedFrequency | Frequency (in seconds) at which the bot level reset is performed for flagged bots that failed safety checks initially. | 15 | Positive Integer BotLevelBrackets.CheckFlaggedFrequency | Frequency (in seconds) at which the bot level reset is performed for flagged bots that failed safety checks initially. | 15 | Positive Integer
BotLevelBrackets.RealPlayerWeight | Multiplier applied to each real player's contribution in their level bracket. This value is further scaled inversely by the total number of real players online. **Only active if dynamic distribution is enabled.** | 1.0 | Floating point number
BotLevelBrackets.UseDynamicDistribution | Enables dynamic recalculation of bot distribution percentages based on the number of non-bot players in each bracket. | 0 | 0 (off) / 1 (on)
### Alliance Level Brackets Configuration ### Alliance Level Brackets Configuration
*The percentages below must sum to 100.* *The percentages below must sum to 100.*
Setting | Description | Default | Valid Values Setting | Description | Default | Valid Values
-------------------------------------------|--------------------------------------------------------------|---------|-------------------- -------------------------------------------|---------------------------------------------------------------|---------|--------------------
BotLevelBrackets.Alliance.Range1Pct | Desired percentage of Alliance bots within level range 1-9. | 12 | 0-100 BotLevelBrackets.Alliance.Range1Pct | Desired percentage of Alliance bots within level range 1-9. | 12 | 0-100
BotLevelBrackets.Alliance.Range2Pct | Desired percentage of Alliance bots within level range 10-19. | 11 | 0-100 BotLevelBrackets.Alliance.Range2Pct | Desired percentage of Alliance bots within level range 10-19. | 11 | 0-100
BotLevelBrackets.Alliance.Range3Pct | Desired percentage of Alliance bots within level range 20-29. | 11 | 0-100 BotLevelBrackets.Alliance.Range3Pct | Desired percentage of Alliance bots within level range 20-29. | 11 | 0-100
BotLevelBrackets.Alliance.Range4Pct | Desired percentage of Alliance bots within level range 30-39. | 11 | 0-100 BotLevelBrackets.Alliance.Range4Pct | Desired percentage of Alliance bots within level range 30-39. | 11 | 0-100
BotLevelBrackets.Alliance.Range5Pct | Desired percentage of Alliance bots within level range 40-49. | 11 | 0-100 BotLevelBrackets.Alliance.Range5Pct | Desired percentage of Alliance bots within level range 40-49. | 11 | 0-100
BotLevelBrackets.Alliance.Range6Pct | Desired percentage of Alliance bots within level range 50-59. | 11 | 0-100 BotLevelBrackets.Alliance.Range6Pct | Desired percentage of Alliance bots within level range 50-59. | 11 | 0-100
BotLevelBrackets.Alliance.Range7Pct | Desired percentage of Alliance bots within level range 60-69. | 11 | 0-100 BotLevelBrackets.Alliance.Range7Pct | Desired percentage of Alliance bots within level range 60-69. | 11 | 0-100
BotLevelBrackets.Alliance.Range8Pct | Desired percentage of Alliance bots within level range 70-79. | 11 | 0-100 BotLevelBrackets.Alliance.Range8Pct | Desired percentage of Alliance bots within level range 70-79. | 11 | 0-100
BotLevelBrackets.Alliance.Range9Pct | Desired percentage of Alliance bots at level 80. | 11 | 0-100 BotLevelBrackets.Alliance.Range9Pct | Desired percentage of Alliance bots at level 80. | 11 | 0-100
### Horde Level Brackets Configuration ### Horde Level Brackets Configuration
*The percentages below must sum to 100.* *The percentages below must sum to 100.*
Setting | Description | Default | Valid Values Setting | Description | Default | Valid Values
---------------------------------------|-----------------------------------------------------------|---------|-------------------- -------------------------------------------|---------------------------------------------------------------|---------|--------------------
BotLevelBrackets.Horde.Range1Pct | Desired percentage of Horde bots within level range 1-9. | 12 | 0-100 BotLevelBrackets.Horde.Range1Pct | Desired percentage of Horde bots within level range 1-9. | 12 | 0-100
BotLevelBrackets.Horde.Range2Pct | Desired percentage of Horde bots within level range 10-19.| 11 | 0-100 BotLevelBrackets.Horde.Range2Pct | Desired percentage of Horde bots within level range 10-19. | 11 | 0-100
BotLevelBrackets.Horde.Range3Pct | Desired percentage of Horde bots within level range 20-29.| 11 | 0-100 BotLevelBrackets.Horde.Range3Pct | Desired percentage of Horde bots within level range 20-29. | 11 | 0-100
BotLevelBrackets.Horde.Range4Pct | Desired percentage of Horde bots within level range 30-39.| 11 | 0-100 BotLevelBrackets.Horde.Range4Pct | Desired percentage of Horde bots within level range 30-39. | 11 | 0-100
BotLevelBrackets.Horde.Range5Pct | Desired percentage of Horde bots within level range 40-49.| 11 | 0-100 BotLevelBrackets.Horde.Range5Pct | Desired percentage of Horde bots within level range 40-49. | 11 | 0-100
BotLevelBrackets.Horde.Range6Pct | Desired percentage of Horde bots within level range 50-59.| 11 | 0-100 BotLevelBrackets.Horde.Range6Pct | Desired percentage of Horde bots within level range 50-59. | 11 | 0-100
BotLevelBrackets.Horde.Range7Pct | Desired percentage of Horde bots within level range 60-69.| 11 | 0-100 BotLevelBrackets.Horde.Range7Pct | Desired percentage of Horde bots within level range 60-69. | 11 | 0-100
BotLevelBrackets.Horde.Range8Pct | Desired percentage of Horde bots within level range 70-79.| 11 | 0-100 BotLevelBrackets.Horde.Range8Pct | Desired percentage of Horde bots within level range 70-79. | 11 | 0-100
BotLevelBrackets.Horde.Range9Pct | Desired percentage of Horde bots at level 80. | 11 | 0-100 BotLevelBrackets.Horde.Range9Pct | Desired percentage of Horde bots at level 80. | 11 | 0-100
Debugging Debugging
--------- ---------

View File

@@ -20,6 +20,20 @@ BotLevelBrackets.CheckFrequency = 300
# Default: 15 # Default: 15
BotLevelBrackets.CheckFlaggedFrequency = 15 BotLevelBrackets.CheckFlaggedFrequency = 15
# BotLevelBrackets.UseDynamicDistribution
# Description: Enables dynamic recalculation of bot distribution percentages based on the number of non-bot players present in each level bracket.
# Default: 0 (disabled)
# Valid values: 0 (off) / 1 (on)
BotLevelBrackets.UseDynamicDistribution = 0
#
# BotLevelBrackets.RealPlayerWeight
# Description: A multiplier applied to each real player's contribution in their level bracket.
# When combined with inverse scaling by total player count, a higher value significantly boosts the effective weight
# of a real player when few are online.
# Default: 1.0
BotLevelBrackets.RealPlayerWeight = 1.0
# #
# Alliance Level Brackets Configuration # Alliance Level Brackets Configuration
# The percentages below must sum to 100. # The percentages below must sum to 100.

View File

@@ -37,6 +37,10 @@ static LevelRangeConfig g_HordeLevelRanges[NUM_RANGES];
static uint32 g_BotDistCheckFrequency = 300; // in seconds static uint32 g_BotDistCheckFrequency = 300; // in seconds
static uint32 g_BotDistFlaggedCheckFrequency = 15; // in seconds static uint32 g_BotDistFlaggedCheckFrequency = 15; // in seconds
static bool g_BotDistDebugMode = false; static bool g_BotDistDebugMode = false;
static bool g_UseDynamicDistribution = false;
// New configuration: real player weight to boost bracket contributions.
static float g_RealPlayerWeight = 1.0f;
// Loads the configuration from the config file. // Loads the configuration from the config file.
static void LoadBotLevelBracketsConfig() static void LoadBotLevelBracketsConfig()
@@ -44,6 +48,8 @@ static void LoadBotLevelBracketsConfig()
g_BotDistDebugMode = sConfigMgr->GetOption<bool>("BotLevelBrackets.DebugMode", false); g_BotDistDebugMode = sConfigMgr->GetOption<bool>("BotLevelBrackets.DebugMode", false);
g_BotDistCheckFrequency = sConfigMgr->GetOption<uint32>("BotLevelBrackets.CheckFrequency", 300); g_BotDistCheckFrequency = sConfigMgr->GetOption<uint32>("BotLevelBrackets.CheckFrequency", 300);
g_BotDistFlaggedCheckFrequency = sConfigMgr->GetOption<uint32>("BotLevelBrackets.CheckFlaggedFrequency", 15); g_BotDistFlaggedCheckFrequency = sConfigMgr->GetOption<uint32>("BotLevelBrackets.CheckFlaggedFrequency", 15);
g_UseDynamicDistribution = sConfigMgr->GetOption<bool>("BotLevelBrackets.UseDynamicDistribution", false);
g_RealPlayerWeight = sConfigMgr->GetOption<float>("BotLevelBrackets.RealPlayerWeight", 1.0f);
// Alliance configuration. // Alliance configuration.
g_AllianceLevelRanges[0] = { 1, 9, static_cast<uint8>(sConfigMgr->GetOption<uint32>("BotLevelBrackets.Alliance.Range1Pct", 12)) }; g_AllianceLevelRanges[0] = { 1, 9, static_cast<uint8>(sConfigMgr->GetOption<uint32>("BotLevelBrackets.Alliance.Range1Pct", 12)) };
@@ -124,7 +130,7 @@ static void AdjustBotToRange(Player* bot, int targetRangeIndex, const LevelRange
{ {
if (g_BotDistDebugMode) if (g_BotDistDebugMode)
{ {
std::string playerFaction = IsAlliancePlayerBot(bot) ? "Alliance" : "Horde"; std::string playerFaction = IsAlliancePlayerBot(bot) ? "Alliance" : "Horde";
LOG_INFO("server.loading", LOG_INFO("server.loading",
"[BotLevelBrackets] AdjustBotToRange: Cannot assign {} Death Knight '{}' ({}) to range {}-{} (below level 55).", "[BotLevelBrackets] AdjustBotToRange: Cannot assign {} Death Knight '{}' ({}) to range {}-{} (below level 55).",
playerFaction, bot->GetName(), botOriginalLevel, lowerBound, upperBound); playerFaction, bot->GetName(), botOriginalLevel, lowerBound, upperBound);
@@ -156,7 +162,6 @@ static void AdjustBotToRange(Player* bot, int targetRangeIndex, const LevelRange
} }
ChatHandler(bot->GetSession()).SendSysMessage("[mod-bot-level-brackets] Your level has been reset."); ChatHandler(bot->GetSession()).SendSysMessage("[mod-bot-level-brackets] Your level has been reset.");
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -198,7 +203,7 @@ static bool IsBotSafeForLevelReset(Player* bot)
if (!bot) if (!bot)
return false; return false;
if (!bot->IsInWorld()) if (!bot->IsInWorld())
return false; return false;
if (!bot->IsAlive()) if (!bot->IsAlive())
return false; return false;
if (bot->IsInCombat()) if (bot->IsInCombat())
@@ -206,7 +211,7 @@ static bool IsBotSafeForLevelReset(Player* bot)
if (bot->InBattleground() || bot->InArena() || bot->inRandomLfgDungeon() || bot->InBattlegroundQueue()) if (bot->InBattleground() || bot->InArena() || bot->inRandomLfgDungeon() || bot->InBattlegroundQueue())
return false; return false;
if (bot->IsInFlight()) if (bot->IsInFlight())
return false; return false;
if (Group* group = bot->GetGroup()) if (Group* group = bot->GetGroup())
{ {
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
@@ -272,20 +277,86 @@ public:
void OnUpdate(uint32 diff) override void OnUpdate(uint32 diff) override
{ {
m_timer += diff; m_timer += diff;
m_flaggedTimer += diff; m_flaggedTimer += diff;
// Check if it's time to process pending level resets // Process pending level resets.
if (m_flaggedTimer >= g_BotDistFlaggedCheckFrequency * 1000) if (m_flaggedTimer >= g_BotDistFlaggedCheckFrequency * 1000)
{ {
ProcessPendingLevelResets(); ProcessPendingLevelResets();
m_flaggedTimer = 0; m_flaggedTimer = 0;
} }
// Continue with distribution adjustments once its timer expires // Continue with distribution adjustments once the timer expires.
if (m_timer < g_BotDistCheckFrequency * 1000) if (m_timer < g_BotDistCheckFrequency * 1000)
return; return;
m_timer = 0; m_timer = 0;
// Dynamic distribution: recalc desired percentages based on non-bot players.
if (g_UseDynamicDistribution)
{
int allianceRealCounts[NUM_RANGES] = {0};
int hordeRealCounts[NUM_RANGES] = {0};
uint32 totalAllianceReal = 0;
uint32 totalHordeReal = 0;
// Iterate over all players and count non-bot players.
for (auto const& itr : ObjectAccessor::GetPlayers())
{
Player* player = itr.second;
if (!player || !player->IsInWorld())
continue;
if (IsPlayerBot(player))
continue; // Skip bots.
int rangeIndex = GetLevelRangeIndex(player->GetLevel());
if (rangeIndex < 0)
continue;
if (player->GetTeamId() == TEAM_ALLIANCE)
{
allianceRealCounts[rangeIndex]++;
totalAllianceReal++;
}
else if (player->GetTeamId() == TEAM_HORDE)
{
hordeRealCounts[rangeIndex]++;
totalHordeReal++;
}
}
// Use a baseline weight to ensure an equal share when no real players are present.
const float baseline = 1.0f;
float allianceTotalWeight = 0.0f;
float hordeTotalWeight = 0.0f;
float allianceWeights[NUM_RANGES] = {0};
float hordeWeights[NUM_RANGES] = {0};
for (int i = 0; i < NUM_RANGES; ++i)
{
allianceWeights[i] = baseline + g_RealPlayerWeight * log(1 + allianceRealCounts[i]);
hordeWeights[i] = baseline + g_RealPlayerWeight * log(1 + hordeRealCounts[i]);
allianceTotalWeight += allianceWeights[i];
hordeTotalWeight += hordeWeights[i];
}
// Recalculate desired percentages for each range.
for (int i = 0; i < NUM_RANGES; ++i)
{
g_AllianceLevelRanges[i].desiredPercent = static_cast<uint8>(round((allianceWeights[i] / allianceTotalWeight) * 100));
if (g_BotDistDebugMode)
{
LOG_INFO("server.loading", "[BotLevelBrackets] Dynamic Distribution - Alliance Range {}: {}-{}, Real Players: {} (weight: {:.2f}), New Desired: {}%",
i + 1, g_AllianceLevelRanges[i].lower, g_AllianceLevelRanges[i].upper, allianceRealCounts[i], allianceWeights[i], g_AllianceLevelRanges[i].desiredPercent);
}
}
for (int i = 0; i < NUM_RANGES; ++i)
{
g_HordeLevelRanges[i].desiredPercent = static_cast<uint8>(round((hordeWeights[i] / hordeTotalWeight) * 100));
if (g_BotDistDebugMode)
{
LOG_INFO("server.loading", "[BotLevelBrackets] Dynamic Distribution - Horde Range {}: {}-{}, Real Players: {} (weight: {:.2f}), New Desired: {}%",
i + 1, g_HordeLevelRanges[i].lower, g_HordeLevelRanges[i].upper, hordeRealCounts[i], hordeWeights[i], g_HordeLevelRanges[i].desiredPercent);
}
}
}
// Containers for Alliance bots. // Containers for Alliance bots.
uint32 totalAllianceBots = 0; uint32 totalAllianceBots = 0;
int allianceActualCounts[NUM_RANGES] = {0}; int allianceActualCounts[NUM_RANGES] = {0};