mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
## 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>
130 lines
4.3 KiB
C++
130 lines
4.3 KiB
C++
/*
|
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
*/
|
|
|
|
#ifndef _PLAYERBOT_PLAYERBOTMGR_H
|
|
#define _PLAYERBOT_PLAYERBOTMGR_H
|
|
|
|
#include "ObjectGuid.h"
|
|
#include "Player.h"
|
|
#include "PlayerbotAIBase.h"
|
|
|
|
class ChatHandler;
|
|
class PlayerbotAI;
|
|
class PlayerbotLoginQueryHolder;
|
|
class WorldPacket;
|
|
|
|
typedef std::map<ObjectGuid, Player*> PlayerBotMap;
|
|
typedef std::map<std::string, std::set<std::string> > PlayerBotErrorMap;
|
|
|
|
class PlayerbotHolder : public PlayerbotAIBase
|
|
{
|
|
public:
|
|
PlayerbotHolder();
|
|
virtual ~PlayerbotHolder(){};
|
|
|
|
void AddPlayerBot(ObjectGuid guid, uint32 masterAccountId);
|
|
bool IsAccountLinked(uint32 accountId, uint32 masterAccountId);
|
|
void HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder);
|
|
|
|
void LogoutPlayerBot(ObjectGuid guid);
|
|
void DisablePlayerBot(ObjectGuid guid);
|
|
void RemoveFromPlayerbotsMap(ObjectGuid guid);
|
|
Player* GetPlayerBot(ObjectGuid guid) const;
|
|
Player* GetPlayerBot(ObjectGuid::LowType lowGuid) const;
|
|
PlayerBotMap::const_iterator GetPlayerBotsBegin() const { return playerBots.begin(); }
|
|
PlayerBotMap::const_iterator GetPlayerBotsEnd() const { return playerBots.end(); }
|
|
|
|
void UpdateAIInternal([[maybe_unused]] uint32 elapsed, [[maybe_unused]] bool minimal = false) override{};
|
|
void UpdateSessions();
|
|
void HandleBotPackets(WorldSession* session);
|
|
|
|
void LogoutAllBots();
|
|
void OnBotLogin(Player* const bot);
|
|
|
|
std::vector<std::string> HandlePlayerbotCommand(char const* args, Player* master = nullptr);
|
|
std::string const ProcessBotCommand(std::string const cmd, ObjectGuid guid, ObjectGuid masterguid, bool admin,
|
|
uint32 masterAccountId, uint32 masterGuildId);
|
|
uint32 GetAccountId(std::string const name);
|
|
uint32 GetAccountId(ObjectGuid guid);
|
|
std::string const ListBots(Player* master);
|
|
std::string const LookupBots(Player* master);
|
|
uint32 GetPlayerbotsCount() { return playerBots.size(); }
|
|
uint32 GetPlayerbotsCountByClass(uint32 cls);
|
|
|
|
protected:
|
|
virtual void OnBotLoginInternal(Player* const bot) = 0;
|
|
|
|
PlayerBotMap playerBots;
|
|
static std::unordered_map<ObjectGuid, uint32> botLoading;
|
|
};
|
|
|
|
class PlayerbotMgr : public PlayerbotHolder
|
|
{
|
|
public:
|
|
PlayerbotMgr(Player* const master);
|
|
virtual ~PlayerbotMgr();
|
|
|
|
static bool HandlePlayerbotMgrCommand(ChatHandler* handler, char const* args);
|
|
void HandleMasterIncomingPacket(WorldPacket const& packet);
|
|
void HandleMasterOutgoingPacket(WorldPacket const& packet);
|
|
void HandleCommand(uint32 type, std::string const text);
|
|
void OnPlayerLogin(Player* player);
|
|
void CancelLogout();
|
|
|
|
void UpdateAIInternal(uint32 elapsed, bool minimal = false) override;
|
|
void TellError(std::string const botName, std::string const text);
|
|
|
|
Player* GetMaster() const { return master; };
|
|
|
|
void SaveToDB();
|
|
|
|
void HandleSetSecurityKeyCommand(Player* player, const std::string& key);
|
|
void HandleLinkAccountCommand(Player* player, const std::string& accountName, const std::string& key);
|
|
void HandleViewLinkedAccountsCommand(Player* player);
|
|
void HandleUnlinkAccountCommand(Player* player, const std::string& accountName);
|
|
|
|
protected:
|
|
void OnBotLoginInternal(Player* const bot) override;
|
|
void CheckTellErrors(uint32 elapsed);
|
|
|
|
private:
|
|
Player* const master;
|
|
PlayerBotErrorMap errors;
|
|
time_t lastErrorTell;
|
|
};
|
|
|
|
class PlayerbotsMgr
|
|
{
|
|
public:
|
|
static PlayerbotsMgr& instance()
|
|
{
|
|
static PlayerbotsMgr instance;
|
|
return instance;
|
|
}
|
|
|
|
void AddPlayerbotData(Player* player, bool isBotAI);
|
|
void RemovePlayerBotData(ObjectGuid const& guid, bool is_AI);
|
|
|
|
PlayerbotAI* GetPlayerbotAI(Player* player);
|
|
PlayerbotMgr* GetPlayerbotMgr(Player* player);
|
|
|
|
private:
|
|
PlayerbotsMgr() = default;
|
|
~PlayerbotsMgr() = default;
|
|
|
|
PlayerbotsMgr(const PlayerbotsMgr&) = delete;
|
|
PlayerbotsMgr& operator=(const PlayerbotsMgr&) = delete;
|
|
|
|
PlayerbotsMgr(PlayerbotsMgr&&) = delete;
|
|
PlayerbotsMgr& operator=(PlayerbotsMgr&&) = delete;
|
|
|
|
std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsAIMap;
|
|
std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsMgrMap;
|
|
};
|
|
|
|
#define sPlayerbotsMgr PlayerbotsMgr::instance()
|
|
|
|
#endif
|