1212
fix(Core): scope AddPlayerBot loading count to master account by Hokken · Pull Request #2307 · mod-playerbots/mod-playerbots · GitHub
Skip to content

fix(Core): scope AddPlayerBot loading count to master account#2307

Merged
Celandriel merged 11 commits intomod-playerbots:test-stagingfrom
Hokken:fix/addplayerbot-count-uses-global-botloading
Apr 17, 2026
Merged

fix(Core): scope AddPlayerBot loading count to master account#2307
Celandriel merged 11 commits intomod-playerbots:test-stagingfrom
Hokken:fix/addplayerbot-count-uses-global-botloading

Conversation

@Hokken
Copy link
Copy Markdown
Contributor

@Hokken Hokken commented Apr 12, 2026

Problem

AddPlayerBot() falsely rejects player bot additions with "You have added too many bots (more than 40)" even when the player has zero personal bots.

This happens because the MaxAddedBots check at PlayerbotMgr.cpp:124 adds botLoading.size() to the player's personal bot count:

uint32 count = mgr->GetPlayerbotsCount() + botLoading.size();

botLoading is a static std::unordered_set<ObjectGuid> on PlayerbotHolder — shared by both PlayerbotMgr (per-player) and RandomPlayerbotMgr (singleton). When RandomPlayerbotMgr loads random bots at startup (up to 60 per interval via RandomBotsPerInterval), their GUIDs go into the same global set. During the startup loading window, botLoading.size() can easily reach 100–300, far exceeding the default MaxAddedBots = 40 limit.

The result: any player who logs in during the random bot loading window and tries .playerbot add <name> gets blocked, even though the limit is intended to be per-player.

How to reproduce

  1. Set AiPlayerbot.RandomBotAutologin = 1 (default) with 500 random bots
  2. Start the server
  3. Log in immediately while random bots are still loading
  4. Run .playerbot add <character_name> for an offline character on your account
  5. Get "You have added too many bots (more than 40)" despite having 0 personal bots
  6. Wait 1–2 minutes for random bot loading to finish, try again — works

Root cause

  • PlayerbotHolder::botLoading is declared static at PlayerbotMgr.h:60, so both PlayerbotMgr and RandomPlayerbotMgr share the same set
  • AddPlayerBot() inserts into botLoading at line 147 for ALL callers — both player-initiated adds (masterAccountId > 0) and random bot spawns (masterAccountId = 0)
  • The count check at line 124 uses botLoading.size() (the entire global set) instead of filtering to bots being loaded for the requesting player
  • The config comment confirms the intended scope: "The maximum number of bots that a player can control simultaneously"

Fix

Change botLoading from unordered_set<ObjectGuid> to unordered_map<ObjectGuid, uint32> where the value is the masterAccountId passed to AddPlayerBot(). Random bots are loaded with masterAccountId = 0.

The count check now iterates the map and only counts entries matching the current player's masterAccountId:

uint32 loadingForMaster = 0;
for (auto const& [guid, acctId] : botLoading)
{
    if (acctId == masterAccountId)
        ++loadingForMaster;
}
uint32 count = mgr->GetPlayerbotsCount() + loadingForMaster;

Callsite compatibility

All 10 existing botLoading callsites were audited:

Callsite Operation Compatible
PlayerbotMgr.cpp:85 find() by key Yes
PlayerbotMgr.cpp:153 emplace() (was insert()) Changed
PlayerbotMgr.cpp:174 erase() by key Yes
PlayerbotMgr.cpp:209 erase() by key Yes
PlayerbotMgr.cpp:229 erase() by key Yes
PlayerbotMgr.cpp:1163 find() by key Yes
RandomPlayerbotMgr.cpp:429 empty() Yes

The six unchanged callsites use find(), erase(), and empty() which operate on keys identically for both unordered_set and unordered_map.

Files changed

File Change
src/Bot/PlayerbotMgr.h botLoading type: unordered_set<ObjectGuid>unordered_map<ObjectGuid, uint32>
src/Bot/PlayerbotMgr.cpp Definition type updated, insertemplace with masterAccountId, count check filters by masterAccountId

What is NOT changed

  • MaxAddedBots config key and default value (40) — unchanged
  • Random bot loading behavior — unchanged
  • The botLoading.empty() throttle in RandomPlayerbotMgr — unchanged
  • In-game group invite flow — unaffected (does not go through AddPlayerBot)
  • No new config keys, no schema changes, no API changes

Celandriel and others added 11 commits February 13, 2026 09:16
Master update from Test-staging: Fix ObjectAccessor retrieval, optimize EquipActions, and implement RaidBossHelpers
Update master from Test staging and Core Update
The MaxAddedBots check in AddPlayerBot() uses botLoading.size() to
include bots currently in the async login pipeline. However,
botLoading is a global static set shared by both PlayerbotMgr
(per-player) and RandomPlayerbotMgr (singleton). When
RandomPlayerbotMgr loads hundreds of random bots at startup, their
entries inflate the count for every player, causing false "You have
added too many bots" rejections even when the player has zero
personal bots.

Fix: change botLoading from unordered_set<ObjectGuid> to
unordered_map<ObjectGuid, uint32> where the value is the
masterAccountId passed to AddPlayerBot(). Random bots are loaded
with masterAccountId=0. The count check now filters to only count
entries matching the requesting player's master account.

All existing callsites (find, erase, empty) are compatible with
the map type and required no changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@NoxMax
Copy link
Copy Markdown
Contributor

NoxMax commented Apr 13, 2026

How common is this issue for other people? I do recall running into it before but it's incredibly rare, even when I login and add alts while randoms are still logging in. I login 4000 to 8000 bots, DisableWithoutRealPlayer = 1, and RandomBotsPerInterval = 500

@brighton-chi
Copy link
Copy Markdown
Contributor

How common is this issue for other people? I do recall running into it before but it's incredibly rare, even when I login and add alts while randoms are still logging in. I login 4000 to 8000 bots, DisableWithoutRealPlayer = 1, and RandomBotsPerInterval = 500

Very common for me. Basically whenever I use rndbots.

@NoxMax
Copy link
Copy Markdown
Contributor

NoxMax commented Apr 14, 2026

Interesting. What is your bot population and DisableWithoutRealPlayer/RandomBotsPerInterval values?

@Hokken
Copy link
Copy Markdown
Contributor Author

Hokken commented Apr 14, 2026

Interesting. What is your bot population and DisableWithoutRealPlayer/RandomBotsPerInterval values?

AiPlayerbot.MinRandomBots = 2000
AiPlayerbot.MaxRandomBots = 2000

AiPlayerbot.DisabledWithoutRealPlayer = 0
AiPlayerbot.DisabledWithoutRealPlayerLoginDelay = 30
AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay = 300

@brighton-chi
Copy link
Copy Markdown
Contributor

I run 1500 and the default RandomBotsPerInterval. I think that is 60?

I do use DisabledWithoutRealPlayer. I've moved the value around some, but I think I have to be in the process of random bots logging in for the issue to occur. It's been a long time since I used random bots so I don't recall exactly. For sure sometimes I would not be able to log in altbots immediately upon entering the world (because I use Multibot, which immediately logs in all bots in your party when you log in, and logging them in would be blocked). I'm not sure if the configured delay starts from actually entering the world though or from logging into your account (even if you remain at the character selection screen), though I figure you know the answer to that.

I can actually sometimes get a few altbot invites in during the process of random bots being logged in, but I haven't noticed a trend as to when that works and doesn't, other than it seems to be more effective as more random bots have been logged on.

@Hokken
Copy link
Copy Markdown
Contributor Author

Hokken commented Apr 14, 2026

I run 1500 and the default RandomBotsPerInterval. I think that is 60?

I do use DisabledWithoutRealPlayer. I've moved the value around some, but I think I have to be in the process of random bots logging in for the issue to occur. It's been a long time since I used random bots so I don't recall exactly. For sure sometimes I would not be able to log in altbots immediately upon entering the world (because I use Multibot, which immediately logs in all bots in your party when you log in, and logging them in would be blocked). I'm not sure if the configured delay starts from actually entering the world though or from logging into your account (even if you remain at the character selection screen), though I figure you know the answer to that.

I can actually sometimes get a few altbot invites in during the process of random bots being logged in, but I haven't noticed a trend as to when that works and doesn't, other than it seems to be more effective as more random bots have been logged on.

Yes the patterns are similar on my side.

@Hokken
Copy link
Copy Markdown
Contributor Author

Hokken commented Apr 14, 2026

Quick note on the timing behavior for anyone confused by it:

botLoading isn't a count of loaded bots, it's a buffer of bots currently mid-load. An entry is added right before the async DB query fires, and removed once the bot has finished logging in.

At startup, RandomPlayerbotMgr queues dozens of bots per tick. Adding entries is instant, but each DB load takes a few hundred ms to resolve, so the set fills faster than it drains and easily climbs over 40 during the initial flood. Once the queue stops feeding, the callbacks catch up and the set drains back to zero.

That's why .playerbot add fails at login and works a minute later, same check, different snapshot of the buffer.

This PR doesn't change that pipeline; it just filters the count by masterAccountId so the randombot flood (allmasterAccountId = 0) stops counting against a real player's personal limit.

@Celandriel Celandriel merged commit ce1adeb into mod-playerbots:test-staging Apr 17, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants