From 3900237ffdd74a605b910bce3f351e2a38ff474d Mon Sep 17 00:00:00 2001 From: Your Name <50133316+NoxMax@users.noreply.github.com> Date: Sun, 3 Aug 2025 17:36:02 -0600 Subject: [PATCH] Filter bot logins by level range --- conf/playerbots.conf.dist | 20 +++- src/PlayerbotAIConfig.cpp | 5 +- src/PlayerbotAIConfig.h | 2 + src/Playerbots.cpp | 36 +++++- src/RandomPlayerbotMgr.cpp | 229 ++++++++++++++++++++++++++++++------- src/RandomPlayerbotMgr.h | 18 +++ 6 files changed, 260 insertions(+), 50 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 344a969b..cfb45149 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -491,7 +491,7 @@ AiPlayerbot.AutoGearQualityLimit = 3 # Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 1.5(ZA) = 138 | Phase 2(SC, TK) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164 # Wotlk # Max iLVL Tier 7(10/25) = 200/213 | Tier 8(10/25) = 225/232 | Tier 9(10/25) = 232/245 | Tier 10(10/25/HC) = 251/264/290 -# Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290 +# Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290 # Default: 0 (no limit) AiPlayerbot.AutoGearScoreLimit = 0 @@ -500,11 +500,11 @@ AiPlayerbot.AutoGearScoreLimit = 0 # "health" (bots have infinite health) # "mana" (bots have infinite mana) # "power" (bots have infinite energy, rage, and runic power) -# "taxi" (bots may use all flight paths, though they will not actually learn them) -# "raid" (bots use cheats implemented into raid strategies) +# "taxi" (bots may use all flight paths, though they will not actually learn them) +# "raid" (bots use cheats implemented into raid strategies) # To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,taxi") # Default: taxi is enabled -AiPlayerbot.BotCheats = "taxi,raid" +AiPlayerbot.BotCheats = "taxi,raid" # # @@ -550,10 +550,18 @@ AiPlayerbot.RandomBotRandomPassword = 0 # Prefix for account names to create for randombots AiPlayerbot.RandomBotAccountPrefix = "rndbot" -# Minimum and maximum levels for randombots +# Minimum and maximum initialization levels for randombots AiPlayerbot.RandomBotMinLevel = 1 AiPlayerbot.RandomBotMaxLevel = 80 +# Minimum and maximum level range for randombots allowed to login +AiPlayerbot.RandomBotMinLoginLevel = 1 +AiPlayerbot.RandomBotMaxLoginLevel = 80 + +# log-out randombots if they they level outside the allowed login range +# Default: 0 (disabled) +AiPlayerbot.RandomBotLogoutOutsideLoginRange = 0 + # Sync max randombot level with max level of online players # Default: 0 (disabled) AiPlayerbot.SyncLevelWithPlayers = 0 @@ -654,7 +662,7 @@ AiPlayerbot.RandomGearQualityLimit = 3 # Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 1.5(ZA) = 138 | Phase 2(SC, TK) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164 # Wotlk # Max iLVL Tier 7(10/25) = 200/213 | Tier 8(10/25) = 225/232 | Tier 9(10/25) = 232/245 | Tier 10(10/25/HC) = 251/264/290 -# Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290 +# Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290 # Default: 0 (no limit) AiPlayerbot.RandomGearScoreLimit = 0 diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 4d9f3935..e332ddb2 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -83,6 +83,9 @@ bool PlayerbotAIConfig::Initialize() lootDelay = sConfigMgr->GetOption("AiPlayerbot.LootDelay", 1000); disabledWithoutRealPlayerLoginDelay = sConfigMgr->GetOption("AiPlayerbot.DisabledWithoutRealPlayerLoginDelay", 30); disabledWithoutRealPlayerLogoutDelay = sConfigMgr->GetOption("AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay", 300); + randomBotMinLoginLevel = sConfigMgr->GetOption("AiPlayerbot.RandomBotMinLoginLevel", 1); + randomBotMaxLoginLevel = sConfigMgr->GetOption("AiPlayerbot.RandomBotMaxLoginLevel", 80); + randomBotLogoutOutsideLoginRange = sConfigMgr->GetOption("AiPlayerbot.RandomBotLogoutOutsideLoginRange", false); farDistance = sConfigMgr->GetOption("AiPlayerbot.FarDistance", 20.0f); sightDistance = sConfigMgr->GetOption("AiPlayerbot.SightDistance", 75.0f); @@ -198,7 +201,7 @@ bool PlayerbotAIConfig::Initialize() "575,576,578,595,599,600,601,602,604,608,619,632,650,658,668,409,469,509," "531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"), restrictedHealerDPSMaps); - + //////////////////////////// ICC EnableICCBuffs = sConfigMgr->GetOption("AiPlayerbot.EnableICCBuffs", true); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 54d8b007..63e11063 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -229,6 +229,8 @@ public: std::string randomBotCombatStrategies, randomBotNonCombatStrategies; bool applyInstanceStrategies; uint32 randomBotMinLevel, randomBotMaxLevel; + uint32 randomBotMinLoginLevel, randomBotMaxLoginLevel; + bool randomBotLogoutOutsideLoginRange; float randomChangeMultiplier; // std::string premadeLevelSpec[MAX_CLASSES][10][91]; //lvl 10 - 100 diff --git a/src/Playerbots.cpp b/src/Playerbots.cpp index 07b2bf94..3b3e5726 100644 --- a/src/Playerbots.cpp +++ b/src/Playerbots.cpp @@ -87,7 +87,8 @@ public: PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS, PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE, PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT, - PLAYERHOOK_ON_GIVE_EXP + PLAYERHOOK_ON_GIVE_EXP, + PLAYERHOOK_ON_LEVEL_CHANGED }) {} void OnPlayerLogin(Player* player) override @@ -115,12 +116,39 @@ public: roundedTime = roundedTime.substr(0, roundedTime.find('.') + 2); ChatHandler(player->GetSession()).SendSysMessage( - "|cff00ff00Playerbots:|r bot initialization at server startup takes about '" + "|cff00ff00Playerbots:|r bot initialization at server startup takes about '" + roundedTime + "' minutes."); } } } + void OnPlayerLevelChanged(Player* player, uint8 oldLevel) override + { + // Check if feature is enabled and required objects are valid + if (!sPlayerbotAIConfig || !sPlayerbotAIConfig->randomBotLogoutOutsideLoginRange || !sRandomPlayerbotMgr) + return; + + // Only apply to bots from rndBotTypeAccounts (type 1) + uint32 accountId = player->GetSession()->GetAccountId(); + if (!sRandomPlayerbotMgr->IsAccountType(accountId, 1)) + return; + + uint32 newLevel = player->GetLevel(); + + // Check if the new level is outside the allowed login range + if (newLevel < sPlayerbotAIConfig->randomBotMinLoginLevel || + newLevel > sPlayerbotAIConfig->randomBotMaxLoginLevel) + { + LOG_INFO("playerbots", "Bot {} changed levels from {} to {}, outside login range ({}-{}). Marking for logout", + player->GetName(), oldLevel, newLevel, + sPlayerbotAIConfig->randomBotMinLoginLevel, sPlayerbotAIConfig->randomBotMaxLoginLevel); + + // Mark the bot for removal in the next update cycle + sRandomPlayerbotMgr->MarkBotForLogout(player->GetGUID().GetCounter()); + sRandomPlayerbotMgr->ForceRecount(); + } + } + void OnPlayerAfterUpdate(Player* player, uint32 diff) override { if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player)) @@ -211,7 +239,7 @@ public: { if (!player->GetSession()->IsBot()) return; - + if (!sRandomPlayerbotMgr->IsRandomBot(player)) return; @@ -281,7 +309,7 @@ public: LOG_INFO("server.loading", "╚══════════════════════════════════════════════════════════╝"); uint32 oldMSTime = getMSTime(); - + LOG_INFO("server.loading", " "); LOG_INFO("server.loading", "Load Playerbots Config..."); diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index fd29b1df..ba1d8f5f 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -332,13 +332,73 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) }*/ uint32 maxAllowedBotCount = GetEventValue(0, "bot_count"); - if (!maxAllowedBotCount || (maxAllowedBotCount < sPlayerbotAIConfig->minRandomBots || - maxAllowedBotCount > sPlayerbotAIConfig->maxRandomBots)) + + // Check if level filtering is active and populate eligible bots + if (!levelFilterAdjusted && IsLevelFilterActive()) { - maxAllowedBotCount = urand(sPlayerbotAIConfig->minRandomBots, sPlayerbotAIConfig->maxRandomBots); - SetEventValue(0, "bot_count", maxAllowedBotCount, - urand(sPlayerbotAIConfig->randomBotCountChangeMinInterval, - sPlayerbotAIConfig->randomBotCountChangeMaxInterval)); + PopulateEligibleBots(); + + // Count total eligible bots from RNDbot accounts + uint32 eligibleBotCount = 0; + + for (uint32 accountId : rndBotTypeAccounts) + { + QueryResult result = CharacterDatabase.Query( + "SELECT COUNT(*) FROM characters WHERE account = {} AND level >= {} AND level <= {}", + accountId, sPlayerbotAIConfig->randomBotMinLoginLevel, sPlayerbotAIConfig->randomBotMaxLoginLevel + ); + + if (result) + { + Field* fields = result->Fetch(); + eligibleBotCount += fields[0].Get(); + } + } + + if (eligibleBotCount > 0) + { + // Cap eligible bots by maxRandomBots + uint32 effectiveMaxBots = std::min(eligibleBotCount, sPlayerbotAIConfig->maxRandomBots); + + LOG_INFO("playerbots", "Level filter active: {} eligible bots found in range {}-{}. Effective maximum: {} bots", + eligibleBotCount, sPlayerbotAIConfig->randomBotMinLoginLevel, + sPlayerbotAIConfig->randomBotMaxLoginLevel, effectiveMaxBots); + + if (effectiveMaxBots >= sPlayerbotAIConfig->minRandomBots) + { + maxAllowedBotCount = urand(sPlayerbotAIConfig->minRandomBots, effectiveMaxBots); + } + else + { + maxAllowedBotCount = effectiveMaxBots; + } + + SetEventValue(0, "bot_count", maxAllowedBotCount, + urand(sPlayerbotAIConfig->randomBotCountChangeMinInterval, + sPlayerbotAIConfig->randomBotCountChangeMaxInterval)); + + currentBots.clear(); + levelFilterAdjusted = true; + } + else + { + LOG_ERROR("playerbots", "No eligible bots found with level filter {}-{}", + sPlayerbotAIConfig->randomBotMinLoginLevel, sPlayerbotAIConfig->randomBotMaxLoginLevel); + levelFilterAdjusted = true; + } + } + else if (!levelFilterAdjusted) + { + // Normal bot count logic (no level filtering) + if (!maxAllowedBotCount || (maxAllowedBotCount < sPlayerbotAIConfig->minRandomBots || + maxAllowedBotCount > sPlayerbotAIConfig->maxRandomBots)) + { + maxAllowedBotCount = urand(sPlayerbotAIConfig->minRandomBots, sPlayerbotAIConfig->maxRandomBots); + SetEventValue(0, "bot_count", maxAllowedBotCount, + urand(sPlayerbotAIConfig->randomBotCountChangeMinInterval, + sPlayerbotAIConfig->randomBotCountChangeMaxInterval)); + } + levelFilterAdjusted = true; } GetBots(); @@ -730,23 +790,41 @@ uint32 RandomPlayerbotMgr::AddRandomBots() for (uint32 accountId : accountsToUse) { - CharacterDatabasePreparedStatement* stmt = - CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID); - stmt->SetData(0, accountId); - PreparedQueryResult result = CharacterDatabase.Query(stmt); - if (!result) - continue; - - do + // Lambda to process results regardless of query type + auto processResults = [&](auto result) { - Field* fields = result->Fetch(); - CharacterInfo info; - info.guid = fields[0].Get(); - info.rClass = fields[1].Get(); - info.rRace = fields[2].Get(); - info.accountId = accountId; - allCharacters.push_back(info); - } while (result->NextRow()); + if (!result) + return; + + do + { + Field* fields = result->Fetch(); + CharacterInfo info; + info.guid = fields[0].Get(); + info.rClass = fields[1].Get(); + info.rRace = fields[2].Get(); + info.accountId = accountId; + allCharacters.push_back(info); + } while (result->NextRow()); + }; + + if (IsLevelFilterActive()) + { + // Custom query with level filtering + auto result = CharacterDatabase.Query( + "SELECT guid, class, race FROM characters WHERE account = {} AND level >= {} AND level <= {}", + accountId, sPlayerbotAIConfig->randomBotMinLoginLevel, sPlayerbotAIConfig->randomBotMaxLoginLevel + ); + processResults(result); + } + else + { + CharacterDatabasePreparedStatement* stmt = + CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID); + stmt->SetData(0, accountId); + auto result = CharacterDatabase.Query(stmt); + processResults(result); + } } // Shuffle for class balance @@ -2548,17 +2626,55 @@ void RandomPlayerbotMgr::GetBots() stmt->SetData(0, 0); stmt->SetData(1, "add"); uint32 maxAllowedBotCount = GetEventValue(0, "bot_count"); + if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt)) { do { Field* fields = result->Fetch(); uint32 bot = fields[0].Get(); + if (GetEventValue(bot, "add")) + { + // Single query to get both account and level + QueryResult charResult = CharacterDatabase.Query("SELECT account, level FROM characters WHERE guid = {}", bot); + if (!charResult) + continue; + + Field* charFields = charResult->Fetch(); + uint32 botAccountId = charFields[0].Get(); + uint32 botLevel = charFields[1].Get(); + + // Skip if not an RNDbot account + bool isRndBotAccount = false; + for (uint32 accountId : rndBotTypeAccounts) + { + if (accountId == botAccountId) + { + isRndBotAccount = true; + break; + } + } + + if (!isRndBotAccount) + continue; + + // If level filtering is active, check bot level + if (IsLevelFilterActive()) + { + // Skip bots outside the allowed level range + if (botLevel < sPlayerbotAIConfig->randomBotMinLoginLevel || + botLevel > sPlayerbotAIConfig->randomBotMaxLoginLevel) + { + continue; // Skip this bot + } + } + currentBots.push_back(bot); - if (currentBots.size() >= maxAllowedBotCount) - break; + if (currentBots.size() >= maxAllowedBotCount) + break; + } } while (result->NextRow()); } } @@ -2586,6 +2702,51 @@ std::vector RandomPlayerbotMgr::GetBgBots(uint32 bracket) return std::move(BgBots); } +void RandomPlayerbotMgr::PopulateEligibleBots() +{ + if (!sPlayerbotAIConfig || !(sPlayerbotAIConfig->randomBotMinLoginLevel > 1 || + sPlayerbotAIConfig->randomBotMaxLoginLevel < 80)) + return; + + LOG_INFO("playerbots", "Populating eligible bots for level filter ({}-{})...", + sPlayerbotAIConfig->randomBotMinLoginLevel, sPlayerbotAIConfig->randomBotMaxLoginLevel); + + bool botsAdded = false; + + // Use only RNDbot type accounts (type 1) + for (uint32 accountId : rndBotTypeAccounts) + { + QueryResult result = CharacterDatabase.Query( + "SELECT guid, level, online FROM characters WHERE account = {} AND level >= {} AND level <= {}", + accountId, sPlayerbotAIConfig->randomBotMinLoginLevel, sPlayerbotAIConfig->randomBotMaxLoginLevel + ); + + if (result) + { + do + { + Field* fields = result->Fetch(); + uint32 botGuid = fields[0].Get(); + bool isOnline = fields[2].Get(); + + // Skip bots that are already online or already have "add" event + if (isOnline || GetEventValue(botGuid, "add")) + continue; + + uint32 add_time = urand(sPlayerbotAIConfig->minRandomBotInWorldTime, + sPlayerbotAIConfig->maxRandomBotInWorldTime); + SetEventValue(botGuid, "add", 1, add_time); + botsAdded = true; + } while (result->NextRow()); + } + } + + if (botsAdded) + { + currentBots.clear(); // Force reload + } +} + uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event) { // load all events at once on first event load @@ -2613,23 +2774,13 @@ uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event) } CachedEvent& e = eventCache[bot][event]; - /*if (e.IsEmpty()) - { - QueryResult results = PlayerbotsDatabase.Query("SELECT `value`, `time`, validIn, `data` FROM - playerbots_random_bots WHERE owner = 0 AND bot = {} AND event = {}", bot, event.c_str()); - if (results) - { - Field* fields = results->Fetch(); - e.value = fields[0].Get(); - e.lastChangeTime = fields[1].Get(); - e.validIn = fields[2].Get(); - e.data = fields[3].Get(); - } - } - */ + bool shouldExpire = (time(0) - e.lastChangeTime) >= e.validIn && + event != "specNo" && + event != "specLink" && + !(event == "bot_count" && IsLevelFilterActive()); // Don't expire bot_count when level filtering is active - if ((time(0) - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink") + if (shouldExpire) e.value = 0; return e.value; diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index 6a62a68b..ebf40be8 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -189,6 +189,14 @@ public: void AssignAccountTypes(); bool IsAccountType(uint32 accountId, uint8 accountType); + // Allowed login range management + void ForceRecount() { SetEventValue(0, "bot_count", 0, 0); } + void MarkBotForLogout(uint32 bot) + { + SetEventValue(bot, "add", 0, 0); // Clear the "add" event to trigger logout + SetEventValue(bot, "logout", 1, 1); // Also set logout for clarity + } + protected: void OnBotLoginInternal(Player* const bot) override; @@ -233,6 +241,16 @@ private: std::vector rndBotTypeAccounts; // Accounts marked as RNDbot (type 1) std::vector addClassTypeAccounts; // Accounts marked as AddClass (type 2) + // Login level filtering + bool levelFilterAdjusted = false; + void PopulateEligibleBots(); + bool IsLevelFilterActive() const + { + return sPlayerbotAIConfig && + (sPlayerbotAIConfig->randomBotMinLoginLevel > 1 || + sPlayerbotAIConfig->randomBotMaxLoginLevel < 80); + } + //void ScaleBotActivity(); // Deprecated function };