Merge pull request #16 from DustinHendrickson/Dustin/SafetyChanges

Adding new Dynamic brackets feature
This commit is contained in:
Dustin Hendrickson
2025-02-20 16:26:35 -08:00
committed by GitHub
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.
Features
--------
- **Configurable Level Brackets:**
@@ -38,9 +37,23 @@ Features
- **Support for Random Bots:**
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:**
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
------------
1. **Clone the Module**
@@ -73,42 +86,43 @@ Customize the modules behavior by editing the `mod_player_bot_level_brackets.
### Global Settings
Setting | Description | Default | Valid Values
--------------------------------|----------------------------------------------------------------------------------------------|---------|--------------------
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.CheckFlaggedFrequency | Frequency (in seconds) at which the bot level reset is performed for flagged bots that failed safety checks initially. | 15 | Positive Integer
Setting | Description | Default | Valid Values
------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|---------|--------------------
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.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
*The percentages below must sum to 100.*
Setting | Description | Default | Valid Values
-------------------------------------------|--------------------------------------------------------------|---------|--------------------
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.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.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.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.Range9Pct | Desired percentage of Alliance bots at level 80. | 11 | 0-100
Setting | Description | Default | Valid Values
-------------------------------------------|---------------------------------------------------------------|---------|--------------------
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.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.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.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.Range9Pct | Desired percentage of Alliance bots at level 80. | 11 | 0-100
### Horde Level Brackets Configuration
*The percentages below must sum to 100.*
Setting | Description | Default | Valid Values
---------------------------------------|-----------------------------------------------------------|---------|--------------------
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.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.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.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.Range9Pct | Desired percentage of Horde bots at level 80. | 11 | 0-100
Setting | Description | Default | Valid Values
-------------------------------------------|---------------------------------------------------------------|---------|--------------------
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.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.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.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.Range9Pct | Desired percentage of Horde bots at level 80. | 11 | 0-100
Debugging
---------

View File

@@ -20,6 +20,20 @@ BotLevelBrackets.CheckFrequency = 300
# Default: 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
# 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_BotDistFlaggedCheckFrequency = 15; // in seconds
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.
static void LoadBotLevelBracketsConfig()
@@ -44,6 +48,8 @@ static void LoadBotLevelBracketsConfig()
g_BotDistDebugMode = sConfigMgr->GetOption<bool>("BotLevelBrackets.DebugMode", false);
g_BotDistCheckFrequency = sConfigMgr->GetOption<uint32>("BotLevelBrackets.CheckFrequency", 300);
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.
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)
{
std::string playerFaction = IsAlliancePlayerBot(bot) ? "Alliance" : "Horde";
std::string playerFaction = IsAlliancePlayerBot(bot) ? "Alliance" : "Horde";
LOG_INFO("server.loading",
"[BotLevelBrackets] AdjustBotToRange: Cannot assign {} Death Knight '{}' ({}) to range {}-{} (below level 55).",
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.");
}
// -----------------------------------------------------------------------------
@@ -198,7 +203,7 @@ static bool IsBotSafeForLevelReset(Player* bot)
if (!bot)
return false;
if (!bot->IsInWorld())
return false;
return false;
if (!bot->IsAlive())
return false;
if (bot->IsInCombat())
@@ -206,7 +211,7 @@ static bool IsBotSafeForLevelReset(Player* bot)
if (bot->InBattleground() || bot->InArena() || bot->inRandomLfgDungeon() || bot->InBattlegroundQueue())
return false;
if (bot->IsInFlight())
return false;
return false;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
@@ -272,20 +277,86 @@ public:
void OnUpdate(uint32 diff) override
{
m_timer += diff;
m_flaggedTimer += diff;
m_flaggedTimer += diff;
// Check if it's time to process pending level resets
if (m_flaggedTimer >= g_BotDistFlaggedCheckFrequency * 1000)
{
ProcessPendingLevelResets();
m_flaggedTimer = 0;
}
// Process pending level resets.
if (m_flaggedTimer >= g_BotDistFlaggedCheckFrequency * 1000)
{
ProcessPendingLevelResets();
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)
return;
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.
uint32 totalAllianceBots = 0;
int allianceActualCounts[NUM_RANGES] = {0};