From e8d1ad69c330fa321463753651f9bb63d71375aa Mon Sep 17 00:00:00 2001 From: Dustin Hendrickson Date: Thu, 20 Feb 2025 16:24:54 -0800 Subject: [PATCH] Adding new Dynamic brackets feature --- README.md | 72 +++++++++------ conf/mod_player_bot_level_brackets.conf.dist | 14 +++ src/mod-player-bot-level-brackets.cpp | 95 +++++++++++++++++--- 3 files changed, 140 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 3c69388..56cfc2d 100644 --- a/README.md +++ b/README.md @@ -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 bracket’s 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 module’s 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 --------- diff --git a/conf/mod_player_bot_level_brackets.conf.dist b/conf/mod_player_bot_level_brackets.conf.dist index 448c5d2..9f0c9e4 100644 --- a/conf/mod_player_bot_level_brackets.conf.dist +++ b/conf/mod_player_bot_level_brackets.conf.dist @@ -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. diff --git a/src/mod-player-bot-level-brackets.cpp b/src/mod-player-bot-level-brackets.cpp index cdc34f7..e9c19d6 100644 --- a/src/mod-player-bot-level-brackets.cpp +++ b/src/mod-player-bot-level-brackets.cpp @@ -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("BotLevelBrackets.DebugMode", false); g_BotDistCheckFrequency = sConfigMgr->GetOption("BotLevelBrackets.CheckFrequency", 300); g_BotDistFlaggedCheckFrequency = sConfigMgr->GetOption("BotLevelBrackets.CheckFlaggedFrequency", 15); + g_UseDynamicDistribution = sConfigMgr->GetOption("BotLevelBrackets.UseDynamicDistribution", false); + g_RealPlayerWeight = sConfigMgr->GetOption("BotLevelBrackets.RealPlayerWeight", 1.0f); // Alliance configuration. g_AllianceLevelRanges[0] = { 1, 9, static_cast(sConfigMgr->GetOption("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(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(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};