cmangos / issues

This repository is used as a centralized point for all issues regarding CMaNGOS.
179 stars 47 forks source link

Idea: configuration balance bots per level bracket #3761

Open hermensbas opened 3 weeks ago

hermensbas commented 3 weeks ago

Feature Details

Set level brackets and the related percentage how the bots a balanced throughout the game. e.g. npc bots

NPC bots as source of inspiration

-----------------------------------------------------------------------------------------------------------
    NpcBot.WanderingBots.Continents.Levels
        Description: Percentage of wandering bots to spawn per level bracket:
                     0-9, 10-19, 20,29, ... , 70-79, 80+.
        Note:        Total percentage must be exactly 100.
        Note2:       Classes with minimum level requirement may ignore this parameter.
        Default:     25,15,15,10,5,10,20,0,0
------------------------------------------------------------------------------------------------------------
NpcBot.WanderingBots.Continents.Levels = 25,15,15,10,5,10,20,0,0

Example for config

Basically you make the level configuration and behaviour however you prefer through config and just based on three simple config settings. Where the 'playerbot.level-bracket-balance-lock' enforces the bots lvl to stay within a configured level bracket/scope, by randomizing the lvl within the scope of the bracket and possible teleportToLevel once the bot has reached the lvl cap of his bracket.

Random example

bot.level-bracket: 1-10,11-20,21-30,31-40,41-50,51-60,61-70,71-80
bot.level-bracket-balance: 5, 15, 10, 10, 10, 50, 0, 0
bot.level-bracket-balance-lock: false | true

Mimic current behaviour:

bot.level-bracket: 1-80
bot.level-bracket-balance: 100
bot.level-bracket-balance-lock: false | true

80% 1-60 and 20% 61-80 with lock enabled to keep the maintain the configured balance nomatter how long the server runs, or turn the lock off and let them all grow to lvl 80.

bot.level-bracket: 1-60, 61-80
bot.level-bracket-balance: 80, 20
bot.level-bracket-balance-lock: true 

Something like: https://www.programiz.com/online-compiler/7eYKqElkH88Dv

Visualizations

No response

Benefits and Purpose

Simplify and create more flexibility in regard of bot level configuration/management.

Core Versions

hermensbas commented 3 weeks ago

psuedo code for the general idea (i'm not c++ developer though):

#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <stdexcept>

// Define a structure to represent level brackets
struct LevelBracket {
    int minLevel;
    int maxLevel;
    int percentage;
};

// Function to generate bot levels based on brackets and percentages
std::vector<std::pair<LevelBracket, std::pair<std::string, int>>> generateBotLevels(
    std::vector<LevelBracket> brackets, 
    const std::vector<int>& percentages, 
    int totalBots, 
    const std::vector<std::string>& classes) {

    std::vector<std::pair<LevelBracket, std::pair<std::string, int>>> levelList;
    std::random_device rd;
    std::mt19937 gen(rd());

    // Ensure the total percentages sum to 100
    int totalPercentage = 0;
    for (int percent : percentages) {
        totalPercentage += percent;
    }

    if (totalPercentage != 100) {
        throw std::invalid_argument("Total percentage must be 100.");
    }

    // Sort brackets in descending order by maxLevel
    std::sort(brackets.begin(), brackets.end(), [](const LevelBracket& a, const LevelBracket& b) {
        return a.maxLevel > b.maxLevel;
    });

    // Calculate the number of bots per bracket
    std::vector<int> botsPerBracket;
    for (int percent : percentages) {
        botsPerBracket.push_back((totalBots * percent) / 100);
    }

    // Assign levels to each bracket
    std::vector<std::pair<LevelBracket, int>> assignedLevels;
    for (size_t i = 0; i < brackets.size(); ++i) {
        const LevelBracket& bracket = brackets[i];
        int numBots = botsPerBracket[i];

        std::vector<int> levels;
        for (int level = bracket.minLevel; level <= bracket.maxLevel; ++level) {
            levels.push_back(level);
        }

        // Shuffle levels to ensure randomness
        std::shuffle(levels.begin(), levels.end(), gen);

        // Randomize the level assignment within the bracket
        for (int j = 0; j < numBots; ++j) {
            int level = levels[j % levels.size()];
            assignedLevels.push_back({bracket, level});
        }
    }

    // Shuffle the assigned levels to ensure randomness in class assignment
    std::shuffle(assignedLevels.begin(), assignedLevels.end(), gen);

    // Now assign classes to the levels
    for (const auto& assigned : assignedLevels) {
        const LevelBracket& bracket = assigned.first;
        int level = assigned.second;

        // Filter out "Death Knight" if the level is below 55
        std::vector<std::string> availableClasses;
        for (const std::string& cls : classes) {
            if (cls == "Death Knight" && level < 55) {
                continue;
            }
            availableClasses.push_back(cls);
        }

        // Shuffle classes to ensure randomness
        std::shuffle(availableClasses.begin(), availableClasses.end(), gen);

        // Assign a random class to this level
        std::string botClass = availableClasses[gen() % availableClasses.size()];

        // Add to the result vector
        levelList.push_back({bracket, {botClass, level}});
    }

    // Shuffle the entire list before sorting
    std::shuffle(levelList.begin(), levelList.end(), gen);

    // Sort the list by level in ascending order
    std::sort(levelList.begin(), levelList.end(), [](const auto& a, const auto& b) {
        return a.second.second < b.second.second;
    });

    return levelList;
}

int main() {
    // Define level brackets
    std::vector<LevelBracket> brackets = {
        {1, 10},
        {11, 20},
        {21, 30},
        {31, 40},
        {41, 50},
        {51, 60},
        {61, 70},
        {71, 80}
    };

    // Define percentages for each bracket
    std::vector<int> percentages = {5, 10, 15, 10, 10, 20, 15, 15};

    // Define total number of bots
    int totalBots = 200;

    // Define available classes
    std::vector<std::string> classes = {
        "Warrior", "Paladin", "Hunter", "Rogue", "Priest", 
        "Death Knight", "Shaman", "Mage", "Warlock", "Druid"
    };

    try {
        auto levelList = generateBotLevels(brackets, percentages, totalBots, classes);

        // Output levels with their brackets and classes
        std::cout << "\nGenerated bot levels (size " << levelList.size() << "):\n\n";
        for (const auto& pair : levelList) {
            const LevelBracket& bracket = pair.first;
            const auto& botInfo = pair.second;
            std::string botClass = botInfo.first;
            int level = botInfo.second;
            std::cout << "Bracket [" << bracket.minLevel << "-" << bracket.maxLevel << "]: " 
                      << "Class: " << botClass << ", Level: " << level << "\n";
        }
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    return 0;
}
mostlikely4r commented 3 weeks ago

Would need adjustment to the level bots get when initially randomized here: https://github.com/cmangos/playerbots/blob/d954090232046527f296f0a76e2d1669052732d9/playerbot/RandomPlayerbotMgr.cpp#L2644 Afaik this will also be called periodically for bots already randomized so can increase and lower levels.

And the part that logs in the bots of the correct level here: https://github.com/cmangos/playerbots/blob/master/playerbot/RandomPlayerbotMgr.cpp#L868

Currently bots are randomized equally between minLevel and maxLevel. (code location 1) However the system that determines which bots are allowed to log (code location 2) in tries to keep the average level of all bots exactly between min and max. It does this by selecting proper levels or non-randomized bots by making assumptions what the post-randomized level will be.

Code location 1 would need to look at all bots to see what level brackets fall below the selected percentages and randomize to those bracket (perhaps equally distributed over the bracket seletected?) Disabling level-lock would require this method to not randomize already randomized bots.

Code location 2 would also need to look at current brackets (it current looks at min-maxlevel bracket) and stop bot from logging in if it doesn't fall into a bracket that requires more bots. Levellock disabled would totally remove this limitation?

Keep in mind currently level takes priority over class/race distribution. Ie. if there's not enough gnome/warlock at < lvl10 it'll log anything else at lvl10 before looking at higher level. To simplify things this should be maintained for level brackets. This will mean you could in theory have a lvl50-60 bracket with only horde and a lvl40-50 bracket with only alliance (if activity for one faction was disproportionate compared to the other.).