From 3fff58df1a2058894e9b758be07869aec87c2c70 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Tue, 12 Aug 2025 08:15:22 +0200 Subject: [PATCH] [Large server fix] #1537 Serialize playerBots/botLoading with a mutex and use snapshot-based loops to fix concurrency crashes (#1540) * MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full * Update BotMovementUtils.h * Playerbots: guard against invalid-Z teleports * Update PlayerbotMgr.cpp --- src/PlayerbotMgr.cpp | 472 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 446 insertions(+), 26 deletions(-) diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 4ac1809a..02410335 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -41,6 +41,10 @@ #include "Log.h" #include // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) #include "TravelMgr.h" +#include +#include + +static std::mutex g_botMapsMx; // protect playerBots and botLoading namespace { // [Crash fix] Centralize clearing of pointer values in the AI context @@ -105,9 +109,16 @@ public: void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId) { - // bot is loading + /*// bot is loading if (botLoading.find(playerGuid) != botLoading.end()) - return; + return;*/ + + // bot is loading (protégé) + { + std::lock_guard lk(g_botMapsMx); + if (botLoading.find(playerGuid) != botLoading.end()) + return; + } // has bot already been added? Player* bot = ObjectAccessor::FindConnectedPlayer(playerGuid); @@ -145,7 +156,16 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId LOG_DEBUG("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue()); return; } - uint32 count = mgr->GetPlayerbotsCount() + botLoading.size(); + + // read botLoading.size() locked + size_t loadingCount = 0; + { + std::lock_guard lk(g_botMapsMx); + loadingCount = botLoading.size(); + } + + // uint32 count = mgr->GetPlayerbotsCount() + botLoading.size(); + uint32 count = mgr->GetPlayerbotsCount() + static_cast(loadingCount); if (count >= sPlayerbotAIConfig->maxAddedBots) { allowed = false; @@ -161,14 +181,22 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId } return; } - std::shared_ptr holder = - std::make_shared(this, masterAccountId, accountId, playerGuid); + // std::shared_ptr holder = + // std::make_shared(this, masterAccountId, accountId, playerGuid); + auto holder = std::make_shared(this, masterAccountId, accountId, playerGuid); if (!holder->Initialize()) { return; } - botLoading.insert(playerGuid); + // botLoading.insert(playerGuid); + // Protected insert + { + std::lock_guard lk(g_botMapsMx); + if (botLoading.find(playerGuid) != botLoading.end()) + return; // already loging + botLoading.insert(playerGuid); // we reserve the GUID + } // Always login in with world session to avoid race condition sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder)) @@ -185,7 +213,11 @@ bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId) void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder) { + // Copy immediatly holder value + const ObjectGuid guid = holder.GetGuid(); + const uint32 masterAccountId = holder.GetMasterAccountId(); uint32 botAccountId = holder.GetAccountId(); + // At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this // allows channels to work as intended) WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, @@ -200,11 +232,16 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId); botSession->LogoutPlayer(true); delete botSession; - botLoading.erase(holder.GetGuid()); + // botLoading.erase(holder.GetGuid()); + { + std::lock_guard lk(g_botMapsMx); + botLoading.erase(guid); + } return; } - uint32 masterAccount = holder.GetMasterAccountId(); + // uint32 masterAccount = holder.GetMasterAccountId(); + uint32 masterAccount = masterAccountId; // Avoid read in 'holder' after login WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr; // Check if masterSession->GetPlayer() is valid @@ -217,10 +254,14 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con sRandomPlayerbotMgr->OnPlayerLogin(bot); OnBotLogin(bot); - botLoading.erase(holder.GetGuid()); + // botLoading.erase(holder.GetGuid()); + { + std::lock_guard lk(g_botMapsMx); + botLoading.erase(guid); + } } -void PlayerbotHolder::UpdateSessions() +/*void PlayerbotHolder::UpdateSessions() { for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr) { @@ -238,6 +279,29 @@ void PlayerbotHolder::UpdateSessions() HandleBotPackets(bot->GetSession()); } } +}*/ + +void PlayerbotHolder::UpdateSessions() +{ + PlayerBotMap botsCopy; + { + std::lock_guard lk(g_botMapsMx); + botsCopy = playerBots; + } + + for (const auto& kv : botsCopy) + { + Player* const bot = kv.second; + if (bot->IsBeingTeleported()) + { + if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot)) + botAI->HandleTeleportAck(); + } + else if (bot->IsInWorld()) + { + HandleBotPackets(bot->GetSession()); + } + } } /*void PlayerbotHolder::HandleBotPackets(WorldSession* session) @@ -287,8 +351,27 @@ void PlayerbotHolder::LogoutAllBots() } */ - PlayerBotMap bots = playerBots; + /*PlayerBotMap bots = playerBots; for (auto& itr : bots) + { + Player* bot = itr.second; + if (!bot) + continue; + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (!botAI || botAI->IsRealPlayer()) + continue; + + LogoutPlayerBot(bot->GetGUID()); + }*/ + // Snapshot under lock for safe iteration + PlayerBotMap botsCopy; + { + std::lock_guard lk(g_botMapsMx); + botsCopy = playerBots; + } + + for (auto& itr : botsCopy) { Player* bot = itr.second; if (!bot) @@ -302,7 +385,7 @@ void PlayerbotHolder::LogoutAllBots() } } -void PlayerbotMgr::CancelLogout() +/*void PlayerbotMgr::CancelLogout() { Player* master = GetMaster(); if (!master) @@ -323,6 +406,53 @@ void PlayerbotMgr::CancelLogout() } } + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); + it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (!botAI || botAI->IsRealPlayer()) + continue; + + if (botAI->GetMaster() != master) + continue; + + if (bot->GetSession()->isLogingOut()) + { + WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL); + bot->GetSession()->HandleLogoutCancelOpcode(data); + } + } +}*/ + +void PlayerbotMgr::CancelLogout() +{ + Player* master = GetMaster(); + if (!master) + return; + + // Snapshot of "master" bots under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (!botAI || botAI->IsRealPlayer()) + continue; + + if (bot->GetSession()->isLogingOut()) + { + WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL); + bot->GetSession()->HandleLogoutCancelOpcode(data); + botAI->TellMaster("Logout cancelled!"); + } + } + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) { @@ -489,33 +619,47 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid) } void PlayerbotHolder::RemoveFromPlayerbotsMap(ObjectGuid guid) +// { +// playerBots.erase(guid); +// } +// Protected erase { + std::lock_guard lk(g_botMapsMx); playerBots.erase(guid); } Player* PlayerbotHolder::GetPlayerBot(ObjectGuid playerGuid) const { + std::lock_guard lk(g_botMapsMx); // We protect PlayerBotMap::const_iterator it = playerBots.find(playerGuid); - return (it == playerBots.end()) ? 0 : it->second; + return (it == playerBots.end()) ? nullptr : it->second;// (nullptr) } Player* PlayerbotHolder::GetPlayerBot(ObjectGuid::LowType lowGuid) const { ObjectGuid playerGuid = ObjectGuid::Create(lowGuid); + std::lock_guard lk(g_botMapsMx); // We protect PlayerBotMap::const_iterator it = playerBots.find(playerGuid); - return (it == playerBots.end()) ? 0 : it->second; + return (it == playerBots.end()) ? nullptr : it->second; } void PlayerbotHolder::OnBotLogin(Player* const bot) { // Prevent duplicate login - if (playerBots.find(bot->GetGUID()) != playerBots.end()) + /*if (playerBots.find(bot->GetGUID()) != playerBots.end()) { return; + }*/ + { + std::lock_guard lk(g_botMapsMx); + if (playerBots.find(bot->GetGUID()) != playerBots.end()) + return; + + playerBots[bot->GetGUID()] = bot; } sPlayerbotsMgr->AddPlayerbotData(bot, true); - playerBots[bot->GetGUID()] = bot; + // playerBots[bot->GetGUID()] = bot; OnBotLoginInternal(bot); @@ -1230,8 +1374,13 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg // If the user requested a specific gender, skip any character that doesn't match. if (gender != -1 && GetOfflinePlayerGender(guid) != gender) continue; - if (botLoading.find(guid) != botLoading.end()) - continue; + /*if (botLoading.find(guid) != botLoading.end()) + continue;*/ + { + std::lock_guard lk(g_botMapsMx); + if (botLoading.find(guid) != botLoading.end()) + continue; + } if (ObjectAccessor::FindConnectedPlayer(guid)) continue; uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid); @@ -1295,12 +1444,25 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg if (charnameStr == "!" && master && master->GetSession()->GetSecurity() > SEC_GAMEMASTER) { - for (PlayerBotMap::const_iterator i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i) + /*for (PlayerBotMap::const_iterator i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i) { if (Player* bot = i->second) if (bot->IsInWorld()) bots.insert(bot->GetName()); - } + }*/ + // Snapshot under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i) + botsCopy.push_back(i->second); + } + for (Player* const bot : botsCopy) + { + if (bot && bot->IsInWorld()) + bots.insert(bot->GetName()); + } + } std::vector chars = split(charnameStr, ','); @@ -1404,7 +1566,7 @@ uint32 PlayerbotHolder::GetAccountId(ObjectGuid guid) return 0; } -std::string const PlayerbotHolder::ListBots(Player* master) +/*std::string const PlayerbotHolder::ListBots(Player* master) { std::set bots; std::map classNames; @@ -1490,6 +1652,103 @@ std::string const PlayerbotHolder::ListBots(Player* master) out << online[name] << name << " " << classes[name]; } + return out.str(); +}*/ + +std::string const PlayerbotHolder::ListBots(Player* master) +{ + std::set bots; + std::map classNames; + + classNames[CLASS_DEATH_KNIGHT] = "Death Knight"; + classNames[CLASS_DRUID] = "Druid"; + classNames[CLASS_HUNTER] = "Hunter"; + classNames[CLASS_MAGE] = "Mage"; + classNames[CLASS_PALADIN] = "Paladin"; + classNames[CLASS_PRIEST] = "Priest"; + classNames[CLASS_ROGUE] = "Rogue"; + classNames[CLASS_SHAMAN] = "Shaman"; + classNames[CLASS_WARLOCK] = "Warlock"; + classNames[CLASS_WARRIOR] = "Warrior"; + classNames[CLASS_DEATH_KNIGHT] = "DeathKnight"; + + std::map online; + std::vector names; + std::map classes; + + // Snapshot under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + std::string const name = bot->GetName(); + bots.insert(name); + + names.push_back(name); + online[name] = "+"; + classes[name] = classNames[bot->getClass()]; + } + + if (master) + { + QueryResult results = CharacterDatabase.Query( + "SELECT class, name FROM characters WHERE account = {}", + master->GetSession()->GetAccountId()); + + if (results) + { + do + { + Field* fields = results->Fetch(); + uint8 cls = fields[0].Get(); + std::string const name = fields[1].Get(); + if (bots.find(name) == bots.end() && name != master->GetSession()->GetPlayerName()) + { + names.push_back(name); + online[name] = "-"; + classes[name] = classNames[cls]; + } + } while (results->NextRow()); + } + } + + std::sort(names.begin(), names.end()); + + if (master) + { + if (Group* group = master->GetGroup()) + { + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player* member = ObjectAccessor::FindPlayer(itr->guid); + if (member && sRandomPlayerbotMgr->IsRandomBot(member)) + { + std::string const name = member->GetName(); + + names.push_back(name); + online[name] = "+"; + classes[name] = classNames[member->getClass()]; + } + } + } + } + + std::ostringstream out; + bool first = true; + out << "Bot roster: "; + for (std::vector::iterator i = names.begin(); i != names.end(); ++i) + { + if (first) first = false; else out << ", "; + std::string const name = *i; + out << online[name] << name << " " << classes[name]; + } + return out.str(); } @@ -1516,7 +1775,7 @@ std::string const PlayerbotHolder::LookupBots(Player* master) return ret_msg; } -uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) +/*uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) { uint32 count = 0; for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) @@ -1528,6 +1787,25 @@ uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) } } return count; +}*/ + +uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) +{ + uint32 count = 0; + + // Snapshot under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + if (bot && bot->IsInWorld() && bot->getClass() == cls) + ++count; + + return count; } PlayerbotMgr::PlayerbotMgr(Player* const master) : PlayerbotHolder(), master(master), lastErrorTell(0) {} @@ -1544,7 +1822,7 @@ void PlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) CheckTellErrors(elapsed); } -void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) +/*void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) { Player* master = GetMaster(); if (!master) @@ -1570,6 +1848,47 @@ void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) botAI->HandleCommand(type, text, master); } + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); + it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI && botAI->GetMaster() == master) + botAI->HandleCommand(type, text, master); + } +}*/ + +void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) +{ + Player* master = GetMaster(); + if (!master) + return; + + if (text.find(sPlayerbotAIConfig->commandSeparator) != std::string::npos) + { + std::vector commands; + split(commands, text, sPlayerbotAIConfig->commandSeparator.c_str()); + for (std::vector::iterator i = commands.begin(); i != commands.end(); ++i) + HandleCommand(type, *i); + return; + } + + // Snapshot of "master" bots under lock to avoid race conditions + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI) + botAI->HandleCommand(type, text, master); + } + + // Random bots : unchanges for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) { @@ -1580,7 +1899,7 @@ void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) } } -void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) +/*void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) { for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -1601,6 +1920,53 @@ void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) botAI->HandleMasterIncomingPacket(packet); } + switch (packet.GetOpcode()) + { + // if master is logging out, log out all bots + case CMSG_LOGOUT_REQUEST: + { + LogoutAllBots(); + break; + } + // if master cancelled logout, cancel too + case CMSG_LOGOUT_CANCEL: + { + CancelLogout(); + break; + } + } +}*/ + +void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) +{ + // Snapshot of "master" bots under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + if (!bot) + continue; + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI) + botAI->HandleMasterIncomingPacket(packet); + } + + // Boucle random bots (inchangée) + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); + it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI && botAI->GetMaster() == GetMaster()) + botAI->HandleMasterIncomingPacket(packet); + } + switch (packet.GetOpcode()) { // if master is logging out, log out all bots @@ -1618,7 +1984,8 @@ void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) } } -void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) + +/*void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) { for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -1628,6 +1995,33 @@ void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) botAI->HandleMasterOutgoingPacket(packet); } + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); + it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI && botAI->GetMaster() == GetMaster()) + botAI->HandleMasterOutgoingPacket(packet); + } +}*/ +void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) +{ + // Snapshot of "master" bots under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI) + botAI->HandleMasterOutgoingPacket(packet); + } + + // Random bots loop unchanged for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) { @@ -1638,7 +2032,7 @@ void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) } } -void PlayerbotMgr::SaveToDB() +/*void PlayerbotMgr::SaveToDB() { for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -1653,6 +2047,32 @@ void PlayerbotMgr::SaveToDB() if (GET_PLAYERBOT_AI(bot) && GET_PLAYERBOT_AI(bot)->GetMaster() == GetMaster()) bot->SaveToDB(false, false); } +}*/ + +void PlayerbotMgr::SaveToDB() +{ + // Snapshot of "master" bots under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + if (bot) + bot->SaveToDB(false, false); + } + + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); + it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + PlayerbotAI* ai = GET_PLAYERBOT_AI(bot); + if (ai && ai->GetMaster() == GetMaster()) + bot->SaveToDB(false, false); + } } void PlayerbotMgr::OnBotLoginInternal(Player* const bot)