mirror of
https://github.com/kadeshar/mod-player-bot-level-brackets.git
synced 2026-01-12 16:58:34 +00:00
Initial commit
This commit is contained in:
105
.gitattributes
vendored
Normal file
105
.gitattributes
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
## AUTO-DETECT
|
||||
## Handle line endings automatically for files detected as
|
||||
## text and leave all files detected as binary untouched.
|
||||
## This will handle all files NOT defined below.
|
||||
* text=auto eol=lf
|
||||
|
||||
# Text
|
||||
*.conf text
|
||||
*.conf.dist text
|
||||
*.cmake text
|
||||
|
||||
## Scripts
|
||||
*.sh text
|
||||
*.fish text
|
||||
*.lua text
|
||||
|
||||
## SQL
|
||||
*.sql text
|
||||
|
||||
## C++
|
||||
*.c text
|
||||
*.cc text
|
||||
*.cxx text
|
||||
*.cpp text
|
||||
*.c++ text
|
||||
*.hpp text
|
||||
*.h text
|
||||
*.h++ text
|
||||
*.hh text
|
||||
|
||||
|
||||
## For documentation
|
||||
|
||||
# Documents
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
## DOCUMENTATION
|
||||
*.markdown text
|
||||
*.md text
|
||||
*.mdwn text
|
||||
*.mdown text
|
||||
*.mkd text
|
||||
*.mkdn text
|
||||
*.mdtxt text
|
||||
*.mdtext text
|
||||
*.txt text
|
||||
AUTHORS text
|
||||
CHANGELOG text
|
||||
CHANGES text
|
||||
CONTRIBUTING text
|
||||
COPYING text
|
||||
copyright text
|
||||
*COPYRIGHT* text
|
||||
INSTALL text
|
||||
license text
|
||||
LICENSE text
|
||||
NEWS text
|
||||
readme text
|
||||
*README* text
|
||||
TODO text
|
||||
|
||||
## GRAPHICS
|
||||
*.ai binary
|
||||
*.bmp binary
|
||||
*.eps binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.jng binary
|
||||
*.jp2 binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.jpx binary
|
||||
*.jxr binary
|
||||
*.pdf binary
|
||||
*.png binary
|
||||
*.psb binary
|
||||
*.psd binary
|
||||
*.svg text
|
||||
*.svgz binary
|
||||
*.tif binary
|
||||
*.tiff binary
|
||||
*.wbmp binary
|
||||
*.webp binary
|
||||
|
||||
|
||||
## ARCHIVES
|
||||
*.7z binary
|
||||
*.gz binary
|
||||
*.jar binary
|
||||
*.rar binary
|
||||
*.tar binary
|
||||
*.zip binary
|
||||
|
||||
## EXECUTABLES
|
||||
*.exe binary
|
||||
*.pyc binary
|
||||
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
!.gitignore
|
||||
|
||||
#
|
||||
#Generic
|
||||
#
|
||||
|
||||
.directory
|
||||
.mailmap
|
||||
*.orig
|
||||
*.rej
|
||||
*.*~
|
||||
.hg/
|
||||
*.kdev*
|
||||
.DS_Store
|
||||
CMakeLists.txt.user
|
||||
*.bak
|
||||
*.patch
|
||||
*.diff
|
||||
*.REMOTE.*
|
||||
*.BACKUP.*
|
||||
*.BASE.*
|
||||
*.LOCAL.*
|
||||
|
||||
#
|
||||
# IDE & other softwares
|
||||
#
|
||||
/.settings/
|
||||
/.externalToolBuilders/*
|
||||
# exclude in all levels
|
||||
nbproject/
|
||||
.sync.ffs_db
|
||||
*.kate-swp
|
||||
|
||||
#
|
||||
# Eclipse
|
||||
#
|
||||
*.pydevproject
|
||||
.metadata
|
||||
.gradle
|
||||
tmp/
|
||||
*.tmp
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.settings/
|
||||
.loadpath
|
||||
.project
|
||||
.cproject
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 AzerothCore
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
91
README.md
Normal file
91
README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# AzerothCore Module: Bot Level Brackets
|
||||
==========================================
|
||||
|
||||
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, pets are removed, and auto-maintenance actions are executed.
|
||||
|
||||
Features
|
||||
--------
|
||||
• **Configurable Level Brackets:**
|
||||
Define eight distinct level brackets with customizable lower and upper bounds.
|
||||
|
||||
• **Desired Percentage Distribution:**
|
||||
Set target percentages for the number of bots within each level bracket.
|
||||
|
||||
• **Dynamic Bot Adjustment:**
|
||||
Automatically reassign bots from brackets with a surplus to those with a deficit.
|
||||
|
||||
• **Auto Maintenance Execution:**
|
||||
Executes the AutoMaintenanceOnLevelupAction after adjusting a bot’s level to ensure proper reinitialization.
|
||||
|
||||
• **Equipment and Pet Reset:**
|
||||
Destroys all equipped items and removes any pet during a level adjustment.
|
||||
|
||||
• **Support for Random Bots:**
|
||||
Applies exclusively to bots managed by RandomPlayerbotMgr.
|
||||
|
||||
• **Debug Mode:**
|
||||
Provides detailed logging to aid in monitoring and troubleshooting module operations.
|
||||
|
||||
Installation
|
||||
------------
|
||||
1. **Clone the Module**
|
||||
Ensure the AzerothCore Playerbots fork is installed and running. Clone the module into your AzerothCore modules directory:
|
||||
|
||||
cd /path/to/azerothcore/modules
|
||||
git clone https://github.com/DustinHendrickson/mod-bot-level-brackets.git
|
||||
|
||||
2. **Recompile AzerothCore**
|
||||
Rebuild the project with the new module:
|
||||
|
||||
cd /path/to/azerothcore
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
|
||||
3. **Configure the Module**
|
||||
Rename the configuration file:
|
||||
|
||||
mv /path/to/azerothcore/modules/mod-bot-level-brackets.conf.dist /path/to/azerothcore/modules/mod-bot-level-brackets.conf
|
||||
|
||||
4. **Restart the Server**
|
||||
Launch the world server:
|
||||
|
||||
./worldserver
|
||||
|
||||
Configuration Options
|
||||
---------------------
|
||||
Customize the module’s behavior by editing the `mod-bot-level-brackets.conf` file:
|
||||
|
||||
Setting | Description | Default | Valid Values
|
||||
-------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------- | --------------------
|
||||
BotDistribution.DebugMode | Enables detailed debug logging for module operations. | 0 | 0 (off) / 1 (on)
|
||||
BotDistribution.CheckFrequency | Frequency (in seconds) for performing the bot bracket distribution check. | 300 | Positive Integer
|
||||
BotDistribution.Range1Pct | Desired percentage of bots in level bracket 1-10. | 14 | 0-100
|
||||
BotDistribution.Range2Pct | Desired percentage of bots in level bracket 11-20. | 12 | 0-100
|
||||
BotDistribution.Range3Pct | Desired percentage of bots in level bracket 21-30. | 12 | 0-100
|
||||
BotDistribution.Range4Pct | Desired percentage of bots in level bracket 31-40. | 12 | 0-100
|
||||
BotDistribution.Range5Pct | Desired percentage of bots in level bracket 41-50. | 12 | 0-100
|
||||
BotDistribution.Range6Pct | Desired percentage of bots in level bracket 51-60. | 12 | 0-100
|
||||
BotDistribution.Range7Pct | Desired percentage of bots in level bracket 61-70. | 12 | 0-100
|
||||
BotDistribution.Range8Pct | Desired percentage of bots in level bracket 71-80. | 14 | 0-100
|
||||
|
||||
*Note: The sum of all bracket percentages must equal 100.*
|
||||
|
||||
Debugging
|
||||
---------
|
||||
To enable detailed debug logging, update the configuration file:
|
||||
|
||||
BotDistribution.DebugMode = 1
|
||||
|
||||
This setting outputs logs detailing bot level adjustments, item destruction, pet removal, and the execution of auto-maintenance actions.
|
||||
|
||||
License
|
||||
-------
|
||||
This module is released under the GNU GPL v2 license, consistent with AzerothCore's licensing model.
|
||||
|
||||
Contribution
|
||||
------------
|
||||
Created by Dustin Hendrickson.
|
||||
Pull requests and issues are welcome. Please ensure that contributions adhere to AzerothCore's coding standards.
|
||||
0
apps/.gitkeep
Normal file
0
apps/.gitkeep
Normal file
0
apps/ci/.gitkeep
Normal file
0
apps/ci/.gitkeep
Normal file
40
apps/ci/ci-codestyle.sh
Normal file
40
apps/ci/ci-codestyle.sh
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Codestyle check script:"
|
||||
echo
|
||||
|
||||
declare -A singleLineRegexChecks=(
|
||||
["LOG_.+GetCounter"]="Use ObjectGuid::ToString().c_str() method instead of ObjectGuid::GetCounter() when logging. Check the lines above"
|
||||
["[[:blank:]]$"]="Remove whitespace at the end of the lines above"
|
||||
["\t"]="Replace tabs with 4 spaces in the lines above"
|
||||
)
|
||||
|
||||
for check in ${!singleLineRegexChecks[@]}; do
|
||||
echo " Checking RegEx: '${check}'"
|
||||
|
||||
if grep -P -r -I -n ${check} src; then
|
||||
echo
|
||||
echo "${singleLineRegexChecks[$check]}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
declare -A multiLineRegexChecks=(
|
||||
["LOG_[^;]+GetCounter"]="Use ObjectGuid::ToString().c_str() method instead of ObjectGuid::GetCounter() when logging. Check the lines above"
|
||||
["\n\n\n"]="Multiple blank lines detected, keep only one. Check the files above"
|
||||
)
|
||||
|
||||
for check in ${!multiLineRegexChecks[@]}; do
|
||||
echo " Checking RegEx: '${check}'"
|
||||
|
||||
if grep -Pzo -r -I ${check} src; then
|
||||
echo
|
||||
echo
|
||||
echo "${multiLineRegexChecks[$check]}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Everything looks good"
|
||||
0
conf/.gitkeep
Normal file
0
conf/.gitkeep
Normal file
56
conf/mod_player_bot_level_brackets.conf.dist
Normal file
56
conf/mod_player_bot_level_brackets.conf.dist
Normal file
@@ -0,0 +1,56 @@
|
||||
[worldserver]
|
||||
|
||||
########################################
|
||||
# mod-bot-level-distribution configuration
|
||||
########################################
|
||||
#
|
||||
# BotDistribution.DebugMode
|
||||
# Description: Enables debug logging for the Bot Level Distribution module.
|
||||
# Default: 0 (disabled)
|
||||
# Valid values: 0 (off) / 1 (on)
|
||||
BotDistribution.DebugMode = 0
|
||||
|
||||
# BotDistribution.CheckFrequency
|
||||
# Description: The frequency (in seconds) at which the bot level distribution check is performed.
|
||||
# Default: 300
|
||||
BotDistribution.CheckFrequency = 300
|
||||
|
||||
# BotDistribution.Range1Pct
|
||||
# Description: Desired percentage of bots within level range 1-10.
|
||||
# Default: 14
|
||||
BotDistribution.Range1Pct = 14
|
||||
|
||||
# BotDistribution.Range2Pct
|
||||
# Description: Desired percentage of bots within level range 11-20.
|
||||
# Default: 12
|
||||
BotDistribution.Range2Pct = 12
|
||||
|
||||
# BotDistribution.Range3Pct
|
||||
# Description: Desired percentage of bots within level range 21-30.
|
||||
# Default: 12
|
||||
BotDistribution.Range3Pct = 12
|
||||
|
||||
# BotDistribution.Range4Pct
|
||||
# Description: Desired percentage of bots within level range 31-40.
|
||||
# Default: 12
|
||||
BotDistribution.Range4Pct = 12
|
||||
|
||||
# BotDistribution.Range5Pct
|
||||
# Description: Desired percentage of bots within level range 41-50.
|
||||
# Default: 12
|
||||
BotDistribution.Range5Pct = 12
|
||||
|
||||
# BotDistribution.Range6Pct
|
||||
# Description: Desired percentage of bots within level range 51-60.
|
||||
# Default: 12
|
||||
BotDistribution.Range6Pct = 12
|
||||
|
||||
# BotDistribution.Range7Pct
|
||||
# Description: Desired percentage of bots within level range 61-70.
|
||||
# Default: 12
|
||||
BotDistribution.Range7Pct = 12
|
||||
|
||||
# BotDistribution.Range8Pct
|
||||
# Description: Desired percentage of bots within level range 71-80.
|
||||
# Default: 14
|
||||
BotDistribution.Range8Pct = 14
|
||||
0
data/.gitkeep
Normal file
0
data/.gitkeep
Normal file
0
data/sql/db-auth/base/.gitkeep
Normal file
0
data/sql/db-auth/base/.gitkeep
Normal file
0
data/sql/db-auth/updates/.gitkeep
Normal file
0
data/sql/db-auth/updates/.gitkeep
Normal file
0
data/sql/db-characters/base/.gitkeep
Normal file
0
data/sql/db-characters/base/.gitkeep
Normal file
0
data/sql/db-characters/updates/.gitkeep
Normal file
0
data/sql/db-characters/updates/.gitkeep
Normal file
0
data/sql/db-world/base/.gitkeep
Normal file
0
data/sql/db-world/base/.gitkeep
Normal file
4
data/sql/db-world/base/skeleton_module_acore_string.sql
Normal file
4
data/sql/db-world/base/skeleton_module_acore_string.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
SET @ENTRY:=35410;
|
||||
DELETE FROM `acore_string` WHERE `entry`=@ENTRY;
|
||||
INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES
|
||||
(@ENTRY, 'Hello World from Skeleton-Module!', '', '', '', '', '', '¡Hola Mundo desde Skeleton-Module!', '¡Hola Mundo desde Skeleton-Module!', '');
|
||||
0
data/sql/db-world/updates/.gitkeep
Normal file
0
data/sql/db-world/updates/.gitkeep
Normal file
0
include.sh
Normal file
0
include.sh
Normal file
278
src/mod-player-bot-level-brackets.cpp
Normal file
278
src/mod-player-bot-level-brackets.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include "ScriptMgr.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Chat.h"
|
||||
#include "Log.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "Configuration/Config.h"
|
||||
#include "AutoMaintenanceOnLevelupAction.h"
|
||||
#include "Common.h"
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// LEVEL RANGE CONFIGURATION
|
||||
// -----------------------------------------------------------------------------
|
||||
struct LevelRangeConfig
|
||||
{
|
||||
uint8 lower; ///< Lower bound (inclusive)
|
||||
uint8 upper; ///< Upper bound (inclusive)
|
||||
uint8 desiredPercent;///< Desired percentage of bots in this range
|
||||
};
|
||||
|
||||
static const uint8 NUM_RANGES = 8;
|
||||
static LevelRangeConfig g_LevelRanges[NUM_RANGES];
|
||||
|
||||
static uint32 g_BotDistCheckFrequency = 300; // in seconds
|
||||
static bool g_BotDistDebugMode = false;
|
||||
|
||||
// Loads the configuration from the config file.
|
||||
// Expected keys (with example default percentages):
|
||||
// BotDistribution.Range1Pct = 14
|
||||
// BotDistribution.Range2Pct = 12
|
||||
// BotDistribution.Range3Pct = 12
|
||||
// BotDistribution.Range4Pct = 12
|
||||
// BotDistribution.Range5Pct = 12
|
||||
// BotDistribution.Range6Pct = 12
|
||||
// BotDistribution.Range7Pct = 12
|
||||
// BotDistribution.Range8Pct = 14
|
||||
// Additionally:
|
||||
// BotDistribution.CheckFrequency (in seconds)
|
||||
// BotDistribution.DebugMode (true/false)
|
||||
static void LoadBotDistributionConfig()
|
||||
{
|
||||
g_BotDistDebugMode = sConfigMgr->GetOption<bool>("BotDistribution.DebugMode", false);
|
||||
g_BotDistCheckFrequency = sConfigMgr->GetOption<uint32>("BotDistribution.CheckFrequency", 60);
|
||||
|
||||
g_LevelRanges[0] = { 1, 10, static_cast<uint8>(sConfigMgr->GetOption<uint32>("BotDistribution.Range1Pct", 14)) };
|
||||
g_LevelRanges[1] = { 11, 20, static_cast<uint8>(sConfigMgr->GetOption<uint32>("BotDistribution.Range2Pct", 12)) };
|
||||
g_LevelRanges[2] = { 21, 30, static_cast<uint8>(sConfigMgr->GetOption<uint32>("BotDistribution.Range3Pct", 12)) };
|
||||
g_LevelRanges[3] = { 31, 40, static_cast<uint8>(sConfigMgr->GetOption<uint32>("BotDistribution.Range4Pct", 12)) };
|
||||
g_LevelRanges[4] = { 41, 50, static_cast<uint8>(sConfigMgr->GetOption<uint32>("BotDistribution.Range5Pct", 12)) };
|
||||
g_LevelRanges[5] = { 51, 60, static_cast<uint8>(sConfigMgr->GetOption<uint32>("BotDistribution.Range6Pct", 12)) };
|
||||
g_LevelRanges[6] = { 61, 70, static_cast<uint8>(sConfigMgr->GetOption<uint32>("BotDistribution.Range7Pct", 12)) };
|
||||
g_LevelRanges[7] = { 71, 80, static_cast<uint8>(sConfigMgr->GetOption<uint32>("BotDistribution.Range8Pct", 14)) };
|
||||
|
||||
uint32 totalPercent = 0;
|
||||
for (uint8 i = 0; i < NUM_RANGES; ++i)
|
||||
totalPercent += g_LevelRanges[i].desiredPercent;
|
||||
if (totalPercent != 100)
|
||||
LOG_ERROR("server.loading", "[BotLevelBrackets] Sum of percentages is {} (expected 100).", totalPercent);
|
||||
}
|
||||
|
||||
// Returns the index of the level range that the given level belongs to.
|
||||
// If the level does not fall within any configured range, returns -1.
|
||||
static int GetLevelRangeIndex(uint8 level)
|
||||
{
|
||||
for (int i = 0; i < NUM_RANGES; ++i)
|
||||
{
|
||||
if (level >= g_LevelRanges[i].lower && level <= g_LevelRanges[i].upper)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Returns a random level within the provided range.
|
||||
static uint8 GetRandomLevelInRange(const LevelRangeConfig& range)
|
||||
{
|
||||
return urand(range.lower, range.upper);
|
||||
}
|
||||
|
||||
// Adjusts a bot's level by selecting a random level within the target range.
|
||||
// In addition to setting the new level and resetting XP, this function:
|
||||
// - Sends a system message indicating the reset.
|
||||
// - Destroys all equipped items.
|
||||
// - Removes the pet if present.
|
||||
// - Executes the auto maintenance action.
|
||||
static void AdjustBotToRange(Player* bot, int targetRangeIndex)
|
||||
{
|
||||
if (!bot || targetRangeIndex < 0 || targetRangeIndex >= NUM_RANGES)
|
||||
return;
|
||||
|
||||
uint8 newLevel = GetRandomLevelInRange(g_LevelRanges[targetRangeIndex]);
|
||||
bot->SetLevel(newLevel);
|
||||
bot->SetUInt32Value(PLAYER_XP, 0);
|
||||
|
||||
// Inform the bot (or player) about the level reset.
|
||||
ChatHandler(bot->GetSession()).SendSysMessage("[mod-bot-level-brackets] Your level has been reset.");
|
||||
|
||||
// Destroy equipped items.
|
||||
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
|
||||
{
|
||||
if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||
{
|
||||
std::string itemName = item->GetTemplate()->Name1;
|
||||
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the pet if present.
|
||||
if (bot->GetPet())
|
||||
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, false);
|
||||
|
||||
if (g_BotDistDebugMode)
|
||||
{
|
||||
PlayerbotAI* botAI = sPlayerbotsMgr->GetPlayerbotAI(bot);
|
||||
std::string playerClassName = botAI ? botAI->GetChatHelper()->FormatClass(bot->getClass()) : "Unknown";
|
||||
LOG_INFO("server.loading", "[BotLevelBrackets] AdjustBotToRange: Bot '{}' - {} adjusted to level {} (target range {}-{}).",
|
||||
bot->GetName(), playerClassName, newLevel, g_LevelRanges[targetRangeIndex].lower, g_LevelRanges[targetRangeIndex].upper);
|
||||
}
|
||||
|
||||
// Execute the maintenance action.
|
||||
PlayerbotAI* botAI = sPlayerbotsMgr->GetPlayerbotAI(bot);
|
||||
if (botAI)
|
||||
{
|
||||
AutoMaintenanceOnLevelupAction maintenanceAction(botAI);
|
||||
maintenanceAction.Execute(Event());
|
||||
if (g_BotDistDebugMode)
|
||||
LOG_INFO("server.loading", "[BotLevelBrackets] AdjustBotToRange: AutoMaintenanceOnLevelupAction executed for bot '{}'.", bot->GetName());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR("server.loading", "[BotLevelBrackets] AdjustBotToRange: Failed to retrieve PlayerbotAI for bot '{}'.", bot->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// BOT DETECTION HELPERS
|
||||
// -----------------------------------------------------------------------------
|
||||
static bool IsPlayerBot(Player* player)
|
||||
{
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
PlayerbotAI* botAI = sPlayerbotsMgr->GetPlayerbotAI(player);
|
||||
return botAI && botAI->IsBotAI();
|
||||
}
|
||||
|
||||
static bool IsPlayerRandomBot(Player* player)
|
||||
{
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
return sRandomPlayerbotMgr->IsRandomBot(player);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// WORLD SCRIPT: Bot Level Distribution
|
||||
// -----------------------------------------------------------------------------
|
||||
class BotLevelBracketsWorldScript : public WorldScript
|
||||
{
|
||||
public:
|
||||
BotLevelBracketsWorldScript() : WorldScript("BotLevelBracketsWorldScript"), m_timer(0) { }
|
||||
|
||||
// On server startup, load the configuration and log the settings (if debug mode is enabled).
|
||||
void OnStartup() override
|
||||
{
|
||||
LoadBotDistributionConfig();
|
||||
if (g_BotDistDebugMode)
|
||||
{
|
||||
LOG_INFO("server.loading", "[BotLevelBrackets] Module loaded. Check frequency: {} seconds.", g_BotDistCheckFrequency);
|
||||
for (uint8 i = 0; i < NUM_RANGES; ++i)
|
||||
{
|
||||
LOG_INFO("server.loading", "[BotLevelBrackets] Range {}: {}-{}, Desired Percentage: {}%",
|
||||
i + 1, g_LevelRanges[i].lower, g_LevelRanges[i].upper, g_LevelRanges[i].desiredPercent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Periodically (every g_BotDistCheckFrequency seconds) check the distribution of bot levels
|
||||
// and adjust bots from overpopulated ranges to underpopulated ranges.
|
||||
void OnUpdate(uint32 diff) override
|
||||
{
|
||||
m_timer += diff;
|
||||
if (m_timer < g_BotDistCheckFrequency * 1000)
|
||||
return;
|
||||
m_timer = 0;
|
||||
|
||||
// Build the current distribution for bots.
|
||||
uint32 totalBots = 0;
|
||||
int actualCounts[NUM_RANGES] = {0};
|
||||
std::vector<Player*> botsByRange[NUM_RANGES];
|
||||
|
||||
auto const& allPlayers = ObjectAccessor::GetPlayers();
|
||||
for (auto const& itr : allPlayers)
|
||||
{
|
||||
Player* player = itr.second;
|
||||
if (!player || !player->IsInWorld())
|
||||
continue;
|
||||
if (!IsPlayerBot(player) || !IsPlayerRandomBot(player))
|
||||
continue;
|
||||
|
||||
totalBots++;
|
||||
int rangeIndex = GetLevelRangeIndex(player->GetLevel());
|
||||
if (rangeIndex >= 0)
|
||||
{
|
||||
actualCounts[rangeIndex]++;
|
||||
botsByRange[rangeIndex].push_back(player);
|
||||
}
|
||||
else if (g_BotDistDebugMode)
|
||||
{
|
||||
LOG_INFO("server.loading", "[BotLevelBrackets] Bot '{}' with level {} does not fall into any defined range.",
|
||||
player->GetName(), player->GetLevel());
|
||||
}
|
||||
}
|
||||
|
||||
if (totalBots == 0)
|
||||
return;
|
||||
|
||||
// Compute the desired count for each range.
|
||||
int desiredCounts[NUM_RANGES] = {0};
|
||||
for (int i = 0; i < NUM_RANGES; ++i)
|
||||
{
|
||||
desiredCounts[i] = static_cast<int>(round((g_LevelRanges[i].desiredPercent / 100.0) * totalBots));
|
||||
if (g_BotDistDebugMode)
|
||||
{
|
||||
LOG_INFO("server.loading", "[BotLevelBrackets] Range {} ({}-{}): Desired = {}, Actual = {}.",
|
||||
i + 1, g_LevelRanges[i].lower, g_LevelRanges[i].upper,
|
||||
desiredCounts[i], actualCounts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// For each range that has a surplus, reassign bots to ranges that are underpopulated.
|
||||
for (int i = 0; i < NUM_RANGES; ++i)
|
||||
{
|
||||
while (actualCounts[i] > desiredCounts[i] && !botsByRange[i].empty())
|
||||
{
|
||||
// Locate a target range with a deficit.
|
||||
int targetRange = -1;
|
||||
for (int j = 0; j < NUM_RANGES; ++j)
|
||||
{
|
||||
if (actualCounts[j] < desiredCounts[j])
|
||||
{
|
||||
targetRange = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetRange == -1)
|
||||
break; // No underpopulated range found.
|
||||
|
||||
// Retrieve one bot from the current (overpopulated) range.
|
||||
Player* bot = botsByRange[i].back();
|
||||
botsByRange[i].pop_back();
|
||||
|
||||
// Adjust its level to a random level within the target range and perform cleanup.
|
||||
AdjustBotToRange(bot, targetRange);
|
||||
|
||||
actualCounts[i]--;
|
||||
actualCounts[targetRange]++;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_BotDistDebugMode)
|
||||
LOG_INFO("server.loading", "[BotLevelBrackets] Distribution adjustment complete. Total bots: {}.", totalBots);
|
||||
}
|
||||
|
||||
private:
|
||||
uint32 m_timer;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ENTRY POINT: Register the Bot Level Distribution Module
|
||||
// -----------------------------------------------------------------------------
|
||||
void Addmod_player_bot_level_bracketsScripts()
|
||||
{
|
||||
new BotLevelBracketsWorldScript();
|
||||
}
|
||||
7
src/mod-player-bot-level-brackets.h
Normal file
7
src/mod-player-bot-level-brackets.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef MOD_BOT_LEVEL_BRACKETS_H
|
||||
#define MOD_BOT_LEVEL_BRACKETS_H
|
||||
|
||||
// Registers the Bot Level Brackets module scripts.
|
||||
void Addmod_bot_level_bracketsScripts();
|
||||
|
||||
#endif // MOD_BOT_LEVEL_BRACKETS_H
|
||||
Reference in New Issue
Block a user