mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
fix(Core): scope AddPlayerBot loading count to master account (#2307)
## 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:
```cpp
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`:
```cpp
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, `insert` →
`emplace` 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
---------
Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
Co-authored-by: Hokken <Hokken@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
937b4903bb
commit
ce1adebc78
@ -64,7 +64,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
|
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
|
||||||
std::unordered_set<ObjectGuid> PlayerbotHolder::botLoading;
|
std::unordered_map<ObjectGuid, uint32> PlayerbotHolder::botLoading;
|
||||||
|
|
||||||
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
|
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
|
||||||
class PlayerbotLoginQueryHolder : public LoginQueryHolder
|
class PlayerbotLoginQueryHolder : public LoginQueryHolder
|
||||||
@ -121,7 +121,13 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
|||||||
LOG_DEBUG("playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
|
LOG_DEBUG("playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint32 count = mgr->GetPlayerbotsCount() + botLoading.size();
|
uint32 loadingForMaster = 0;
|
||||||
|
for (auto const& [guid, acctId] : botLoading)
|
||||||
|
{
|
||||||
|
if (acctId == masterAccountId)
|
||||||
|
++loadingForMaster;
|
||||||
|
}
|
||||||
|
uint32 count = mgr->GetPlayerbotsCount() + loadingForMaster;
|
||||||
if (count >= PlayerbotAIConfig::instance().maxAddedBots)
|
if (count >= PlayerbotAIConfig::instance().maxAddedBots)
|
||||||
{
|
{
|
||||||
allowed = false;
|
allowed = false;
|
||||||
@ -144,7 +150,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
botLoading.insert(playerGuid);
|
botLoading.emplace(playerGuid, masterAccountId);
|
||||||
|
|
||||||
// Always login in with world session to avoid race condition
|
// Always login in with world session to avoid race condition
|
||||||
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
|
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
|
||||||
|
|||||||
@ -57,7 +57,7 @@ protected:
|
|||||||
virtual void OnBotLoginInternal(Player* const bot) = 0;
|
virtual void OnBotLoginInternal(Player* const bot) = 0;
|
||||||
|
|
||||||
PlayerBotMap playerBots;
|
PlayerBotMap playerBots;
|
||||||
static std::unordered_set<ObjectGuid> botLoading;
|
static std::unordered_map<ObjectGuid, uint32> botLoading;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PlayerbotMgr : public PlayerbotHolder
|
class PlayerbotMgr : public PlayerbotHolder
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user