From 6be860c967c0ce7eecaf67e0773a9361591e9467 Mon Sep 17 00:00:00 2001 From: bashermens <31279994+hermensbas@users.noreply.github.com> Date: Wed, 24 Dec 2025 13:24:29 +0100 Subject: [PATCH] [Stability] Various crash fixes based on Regrad fixes and crashlogs. (#1928) These contains various fixes, fixes that have history worked one in past more then once as person as group, aswell @Wishmaster117. But due various reasons we had to drop them due priority or simply timewise. These fixes have recollected again by @Regrad based on his crash logs. Most crash logs we have, i am talking 30+ of them, to many to post here. @Regrad running a larger server 100+ real players with bots, which means he will walk into issues that most of us wont or are extremely difficult to reproduce. @Regrad used LLM to solve them based on crash log and mentioned his server crashes almost disappeared, instead of redoing every single PR and pull them apart. I tried to keep his bunch of changes together as whole, reviewed them, some redone, verified again etc etc. This is not how would normally do this. But since i want @Regrad being able to confirm, we need this in a package as a whole. Pulling them apart in the current situation is simply to much, to complicated in the verification process. So this PR is open and in my opinion has priority above others, but @Regrad is only person who can give the green light for the mod-playerbot changes for now. I, we spend huge amount of time into these issues over last couple of months. I will put other PR's on hold for abit. --------- Signed-off-by: Engardium Co-authored-by: Engardium --- src/PlayerbotAI.cpp | 155 ++++++++------ src/PlayerbotMgr.cpp | 59 ++++-- src/PlayerbotMgr.h | 2 +- src/PlayerbotOperations.h | 31 ++- src/PlayerbotSecurity.cpp | 151 +++++++------ src/Playerbots.cpp | 53 +++-- src/RandomPlayerbotMgr.cpp | 124 ++++++----- src/RandomPlayerbotMgr.h | 54 +++-- src/strategy/actions/BuyAction.cpp | 54 +++-- src/strategy/actions/ReadyCheckAction.cpp | 47 +++-- src/strategy/values/Formations.cpp | 246 ++++++++++++---------- src/strategy/values/ItemUsageValue.cpp | 6 +- 12 files changed, 549 insertions(+), 433 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index c1878632..3dc85e0b 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -242,8 +242,8 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) nextAICheckDelay = 0; // Early return if bot is in invalid state - if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() || - bot->IsDuringRemoveFromWorld()) + if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() || + bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld()) return; // Handle cheat options (set bot health and power if cheats are enabled) @@ -713,39 +713,59 @@ void PlayerbotAI::HandleTeleportAck() if (IsRealPlayer()) return; + // Clearing motion generators and stopping movement which prevents + // conflicts between teleport and any active motion (walk, run, swim, flight, etc.) bot->GetMotionMaster()->Clear(true); bot->StopMoving(); + + // Near teleport (within map/instance) if (bot->IsBeingTeleportedNear()) { - // Temporary fix for instance can not enter - if (!bot->IsInWorld()) - { - bot->GetMap()->AddPlayerToMap(bot); - } - while (bot->IsInWorld() && bot->IsBeingTeleportedNear()) - { - Player* plMover = bot->m_mover->ToPlayer(); - if (!plMover) - return; - WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 20); - p << plMover->GetPackGUID(); - p << (uint32)0; // supposed to be flags? not used currently - p << (uint32)0; // time - not currently used - bot->GetSession()->HandleMoveTeleportAck(p); - }; + // Previous versions manually added the bot to the map if it was not in the world. + // not needed: HandleMoveTeleportAckOpcode() safely attaches the player to the map + // and clears IsBeingTeleportedNear(). + + Player* plMover = bot->m_mover->ToPlayer(); + if (!plMover) + return; + + // Send the near teleport ACK packet + WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20); + p << plMover->GetPackGUID(); + p << uint32(0); + p << uint32(0); + bot->GetSession()->HandleMoveTeleportAck(p); + + // Simulate teleport latency and prevent AI from running too early (used cmangos delays) + SetNextCheckDelay(urand(1000, 2000)); } + + // Far teleport (worldport / different map) if (bot->IsBeingTeleportedFar()) { - while (bot->IsBeingTeleportedFar()) + // Handle far teleport ACK: + // Moves the bot to the new map, clears IsBeingTeleportedFar(), updates session/map references + bot->GetSession()->HandleMoveWorldportAck(); + + // Ensure bot now has a valid map. If this fails, there is a core/session bug? + if (!bot->GetMap()) { - bot->GetSession()->HandleMoveWorldportAck(); + LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString()); } - // SetNextCheckDelay(urand(2000, 5000)); + + // Instance strategies after teleport if (sPlayerbotAIConfig->applyInstanceStrategies) ApplyInstanceStrategies(bot->GetMapId(), true); + + // healer DPS strategies if restrictions are enabled if (sPlayerbotAIConfig->restrictHealerDPS) EvaluateHealerDpsStrategy(); + + // Reset AI state to to before teleport conditions Reset(true); + + // Slightly longer delay to simulate far teleport latency (used cmangos delays) + SetNextCheckDelay(urand(2000, 5000)); } SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown); @@ -988,10 +1008,10 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) { if (packet.empty()) return; + if (!bot || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld()) - { return; - } + switch (packet.GetOpcode()) { case SMSG_SPELL_FAILURE: @@ -4103,8 +4123,7 @@ Player* PlayerbotAI::FindNewMaster() for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - if (!member || member == bot || !member->IsInWorld() || - !member->IsInSameRaidWith(bot)) + if (!member || member == bot || !member->IsInWorld() || !member->IsInSameRaidWith(bot)) continue; PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member); @@ -4339,6 +4358,11 @@ inline bool ZoneHasRealPlayers(Player* bot) bool PlayerbotAI::AllowActive(ActivityType activityType) { + // Early return if bot is in invalid state + if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() || + bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld()) + return false; + // when botActiveAlone is 100% and smartScale disabled if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale) { @@ -4429,10 +4453,8 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - if ((!member || !member->IsInWorld()) && member->GetMapId() != bot->GetMapId()) - { + if (!member || !member->IsInWorld() || member->GetMapId() != bot->GetMapId()) continue; - } if (member == bot) { @@ -4483,23 +4505,23 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) // HasFriend if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend) { - if (!bot || !bot->IsInWorld() || !bot->GetGUID()) + // shouldnt be needed analyse in future + if (!bot->GetGUID()) return false; for (auto& player : sRandomPlayerbotMgr->GetPlayers()) { - if (!player || !player->IsInWorld()) + if (!player || !player->GetSession() || !player->IsInWorld() || player->IsDuringRemoveFromWorld() || + player->GetSession()->isLogingOut()) continue; - Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID()); - if (!connectedPlayer) + PlayerbotAI* playerAI = GET_PLAYERBOT_AI(player); + if (!playerAI || !playerAI->IsRealPlayer()) continue; + // if a real player has the bot as a friend PlayerSocial* social = player->GetSocial(); - if (!social) - continue; - - if (social->HasFriend(bot->GetGUID())) + if (social && social->HasFriend(bot->GetGUID())) return true; } } @@ -4513,7 +4535,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) } } - // Bots don't need to move using PathGenerator. + // Bots don't need react to PathGenerator activities if (activityType == DETAILED_MOVE_ACTIVITY) { return false; @@ -4549,15 +4571,25 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow) { - if (!allowActiveCheckTimer[activityType]) - allowActiveCheckTimer[activityType] = time(nullptr); + const int activityIndex = static_cast(activityType); - if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityType] + 5)) - return allowActive[activityType]; + // Unknown/out-of-range avoid blocking, added logging for further analysing should not happen in the first place. + if (activityIndex <= 0 || activityIndex >= MAX_ACTIVITY_TYPE) + { + LOG_ERROR("playerbots", "AllowActivity received invalid activity type value: {}", activityIndex); + return true; + } + + if (!allowActiveCheckTimer[activityIndex]) + allowActiveCheckTimer[activityIndex] = time(nullptr); + + if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityIndex] + 5)) + return allowActive[activityIndex]; + + const bool allowed = AllowActive(activityType); + allowActive[activityIndex] = allowed; + allowActiveCheckTimer[activityIndex] = time(nullptr); - bool allowed = AllowActive(activityType); - allowActive[activityType] = allowed; - allowActiveCheckTimer[activityType] = time(nullptr); return allowed; } @@ -5341,15 +5373,13 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const if (!item_template) return nullptr; -static const std::vector uPrioritizedSharpStoneIds = { - ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE, - SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE -}; + static const std::vector uPrioritizedSharpStoneIds = { + ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE, + SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE}; -static const std::vector uPrioritizedWeightStoneIds = { - ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, - HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE -}; + static const std::vector uPrioritizedWeightStoneIds = { + ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, + HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE}; Item* stone = nullptr; ItemTemplate const* pProto = weapon->GetTemplate(); @@ -5385,7 +5415,6 @@ static const std::vector uPrioritizedWeightStoneIds = { Item* PlayerbotAI::FindOilFor(Item* weapon) const { - if (!weapon) return nullptr; @@ -5394,12 +5423,12 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const return nullptr; static const std::vector uPrioritizedWizardOilIds = { - BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL, - BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL}; + BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL, + BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL}; static const std::vector uPrioritizedManaOilIds = { - BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL, - BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL}; + BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL, BRILLIANT_WIZARD_OIL, + SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL}; Item* oil = nullptr; int botClass = bot->getClass(); @@ -5415,22 +5444,22 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const prioritizedOils = &uPrioritizedWizardOilIds; break; case CLASS_DRUID: - if (specTab == 0) // Balance + if (specTab == 0) // Balance prioritizedOils = &uPrioritizedWizardOilIds; - else if (specTab == 1) // Feral + else if (specTab == 1) // Feral prioritizedOils = nullptr; - else // Restoration (specTab == 2) or any other/unspecified spec + else // Restoration (specTab == 2) or any other/unspecified spec prioritizedOils = &uPrioritizedManaOilIds; break; case CLASS_HUNTER: prioritizedOils = &uPrioritizedManaOilIds; break; case CLASS_PALADIN: - if (specTab == 1) // Protection + if (specTab == 1) // Protection prioritizedOils = &uPrioritizedWizardOilIds; - else if (specTab == 2) // Retribution + else if (specTab == 2) // Retribution prioritizedOils = nullptr; - else // Holy (specTab == 0) or any other/unspecified spec + else // Holy (specTab == 0) or any other/unspecified spec prioritizedOils = &uPrioritizedManaOilIds; break; default: diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index d96851da..13a6acff 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -9,9 +9,10 @@ #include #include #include -#include #include +#include #include +#include #include "ChannelMgr.h" #include "CharacterCache.h" @@ -34,12 +35,9 @@ #include "RandomPlayerbotMgr.h" #include "SharedDefines.h" #include "WorldSession.h" -#include "ChannelMgr.h" #include "BroadcastHelper.h" -#include "PlayerbotDbStore.h" #include "WorldSessionMgr.h" -#include "DatabaseEnv.h" // Added for gender choice -#include // Added for gender choice +#include "DatabaseEnv.h" class BotInitGuard { @@ -68,6 +66,7 @@ private: }; std::unordered_set BotInitGuard::botsBeingInitialized; +std::unordered_set PlayerbotHolder::botLoading; PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {} class PlayerbotLoginQueryHolder : public LoginQueryHolder @@ -76,13 +75,12 @@ private: uint32 masterAccountId; PlayerbotHolder* playerbotHolder; public: - PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid) - : LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder) + PlayerbotLoginQueryHolder(uint32 masterAccount, uint32 accountId, ObjectGuid guid) + : LoginQueryHolder(accountId, guid), masterAccountId(masterAccount) { } uint32 GetMasterAccountId() const { return masterAccountId; } - PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; } }; void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId) @@ -143,7 +141,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId return; } std::shared_ptr holder = - std::make_shared(this, masterAccountId, accountId, playerGuid); + std::make_shared(masterAccountId, accountId, playerGuid); if (!holder->Initialize()) { return; @@ -153,8 +151,27 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId // Always login in with world session to avoid race condition sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder)) - .AfterComplete([this](SQLQueryHolderBase const& holder) - { HandlePlayerBotLoginCallback(static_cast(holder)); }); + .AfterComplete( + [](SQLQueryHolderBase const& queryHolder) + { + PlayerbotLoginQueryHolder const& holder = static_cast(queryHolder); + PlayerbotHolder* mgr = sRandomPlayerbotMgr; // could be null + uint32 masterAccountId = holder.GetMasterAccountId(); + + if (masterAccountId) + { + // verify and find current world session of master + WorldSession* masterSession = sWorldSessionMgr->FindSession(masterAccountId); + Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr; + if (masterPlayer) + mgr = GET_PLAYERBOT_MGR(masterPlayer); + } + + if (mgr) + mgr->HandlePlayerBotLoginCallback(holder); + else + PlayerbotHolder::botLoading.erase(holder.GetGuid()); + }); } bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId) @@ -169,8 +186,9 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con 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, - time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true); + WorldSession* botSession = + new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0), + sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true); botSession->HandlePlayerLoginFromDB(holder); // will delete lqh @@ -181,26 +199,27 @@ 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()); + PlayerbotHolder::botLoading.erase(holder.GetGuid()); + return; } - uint32 masterAccount = holder.GetMasterAccountId(); - WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr; + uint32 masterAccountId = holder.GetMasterAccountId(); + WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr; // Check if masterSession->GetPlayer() is valid Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr; if (masterSession && !masterPlayer) { - LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount); + LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", + masterAccountId); } sRandomPlayerbotMgr->OnPlayerLogin(bot); - - auto op = std::make_unique(bot->GetGUID(), this); + auto op = std::make_unique(bot->GetGUID(), masterAccountId); sPlayerbotWorldProcessor->QueueOperation(std::move(op)); - botLoading.erase(holder.GetGuid()); + PlayerbotHolder::botLoading.erase(holder.GetGuid()); } void PlayerbotHolder::UpdateSessions() diff --git a/src/PlayerbotMgr.h b/src/PlayerbotMgr.h index 7e1cc528..7ee57600 100644 --- a/src/PlayerbotMgr.h +++ b/src/PlayerbotMgr.h @@ -60,7 +60,7 @@ protected: virtual void OnBotLoginInternal(Player* const bot) = 0; PlayerBotMap playerBots; - std::unordered_set botLoading; + static std::unordered_set botLoading; }; class PlayerbotMgr : public PlayerbotHolder diff --git a/src/PlayerbotOperations.h b/src/PlayerbotOperations.h index d7c2b47b..8711f346 100644 --- a/src/PlayerbotOperations.h +++ b/src/PlayerbotOperations.h @@ -9,6 +9,7 @@ #include "Group.h" #include "GroupMgr.h" #include "GuildMgr.h" +#include "Playerbots.h" #include "ObjectAccessor.h" #include "PlayerbotOperation.h" #include "Player.h" @@ -16,6 +17,8 @@ #include "PlayerbotMgr.h" #include "PlayerbotDbStore.h" #include "RandomPlayerbotMgr.h" +#include "WorldSession.h" +#include "WorldSessionMgr.h" // Group invite operation class GroupInviteOperation : public PlayerbotOperation @@ -468,18 +471,31 @@ private: class OnBotLoginOperation : public PlayerbotOperation { public: - OnBotLoginOperation(ObjectGuid botGuid, PlayerbotHolder* holder) - : m_botGuid(botGuid), m_holder(holder) + OnBotLoginOperation(ObjectGuid botGuid, uint32 masterAccountId) + : m_botGuid(botGuid), m_masterAccountId(masterAccountId) { } bool Execute() override { + // find and verify bot still exists Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid); - if (!bot || !m_holder) + if (!bot) return false; - m_holder->OnBotLogin(bot); + PlayerbotHolder* holder = sRandomPlayerbotMgr; + if (m_masterAccountId) + { + WorldSession* masterSession = sWorldSessionMgr->FindSession(m_masterAccountId); + Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr; + if (masterPlayer) + holder = GET_PLAYERBOT_MGR(masterPlayer); + } + + if (!holder) + return false; + + holder->OnBotLogin(bot); return true; } @@ -487,14 +503,11 @@ public: uint32 GetPriority() const override { return 100; } std::string GetName() const override { return "OnBotLogin"; } - bool IsValid() const override - { - return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr; - } + bool IsValid() const override { return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr; } private: ObjectGuid m_botGuid; - PlayerbotHolder* m_holder; + uint32 m_masterAccountId = 0; }; #endif diff --git a/src/PlayerbotSecurity.cpp b/src/PlayerbotSecurity.cpp index 822f40e6..68bb2db2 100644 --- a/src/PlayerbotSecurity.cpp +++ b/src/PlayerbotSecurity.cpp @@ -17,14 +17,28 @@ PlayerbotSecurity::PlayerbotSecurity(Player* const bot) : bot(bot) PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* reason, bool ignoreGroup) { + // Basic pointer validity checks + if (!bot || !from || !from->GetSession()) + { + if (reason) + *reason = PLAYERBOT_DENY_NONE; + + return PLAYERBOT_SECURITY_DENY_ALL; + } + + // GMs always have full access if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER) return PLAYERBOT_SECURITY_ALLOW_ALL; PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (!botAI) { + if (reason) + *reason = PLAYERBOT_DENY_NONE; + return PLAYERBOT_SECURITY_DENY_ALL; } + if (botAI->IsOpposing(from)) { if (reason) @@ -35,6 +49,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea if (sPlayerbotAIConfig->IsInRandomAccountList(account)) { + // (duplicate check in case of faction change) if (botAI->IsOpposing(from)) { if (reason) @@ -43,27 +58,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea return PLAYERBOT_SECURITY_DENY_ALL; } - // if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE) - // { - // if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId()) - // { - // if (reason) - // *reason = PLAYERBOT_DENY_LFG; + Group* fromGroup = from->GetGroup(); + Group* botGroup = bot->GetGroup(); - // return PLAYERBOT_SECURITY_TALK; - // } - // } - - Group* group = from->GetGroup(); - if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() == from) + if (fromGroup && botGroup && fromGroup == botGroup && !ignoreGroup) { - return PLAYERBOT_SECURITY_ALLOW_ALL; - } + if (botAI->GetMaster() == from) + return PLAYERBOT_SECURITY_ALLOW_ALL; - if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() != from) - { if (reason) *reason = PLAYERBOT_DENY_NOT_YOURS; + return PLAYERBOT_SECURITY_TALK; } @@ -75,27 +80,34 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea return PLAYERBOT_SECURITY_TALK; } - if (sPlayerbotAIConfig->groupInvitationPermission <= 1 && (int32)bot->GetLevel() - (int8)from->GetLevel() > 5) + if (sPlayerbotAIConfig->groupInvitationPermission <= 1) { - if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId()) + int32 levelDiff = int32(bot->GetLevel()) - int32(from->GetLevel()); + if (levelDiff > 5) { - if (reason) - *reason = PLAYERBOT_DENY_LOW_LEVEL; + if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId()) + { + if (reason) + *reason = PLAYERBOT_DENY_LOW_LEVEL; - return PLAYERBOT_SECURITY_TALK; + return PLAYERBOT_SECURITY_TALK; + } } } - int32 botGS = (int32)botAI->GetEquipGearScore(bot/*, false, false*/); - int32 fromGS = (int32)botAI->GetEquipGearScore(from/*, false, false*/); - if (sPlayerbotAIConfig->gearscorecheck) + int32 botGS = static_cast(botAI->GetEquipGearScore(bot)); + int32 fromGS = static_cast(botAI->GetEquipGearScore(from)); + + if (sPlayerbotAIConfig->gearscorecheck && botGS && bot->GetLevel() > 15 && botGS > fromGS) { - if (botGS && bot->GetLevel() > 15 && botGS > fromGS && - static_cast(100 * (botGS - fromGS) / botGS) >= - static_cast(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel())) + uint32 diffPct = uint32(100 * (botGS - fromGS) / botGS); + uint32 reqPct = uint32(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel()); + + if (diffPct >= reqPct) { if (reason) *reason = PLAYERBOT_DENY_GEARSCORE; + return PLAYERBOT_SECURITY_TALK; } } @@ -111,35 +123,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea } } - /*if (bot->isDead()) + // If the bot is not in the group, we offer an invite + botGroup = bot->GetGroup(); + if (!botGroup) { - if (reason) - *reason = PLAYERBOT_DENY_DEAD; - - return PLAYERBOT_SECURITY_TALK; - }*/ - - group = bot->GetGroup(); - if (!group) - { - /*if (bot->GetMapId() != from->GetMapId() || bot->GetDistance(from) > sPlayerbotAIConfig->whisperDistance) - { - if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId()) - { - if (reason) - *reason = PLAYERBOT_DENY_FAR; - - return PLAYERBOT_SECURITY_TALK; - } - }*/ - if (reason) *reason = PLAYERBOT_DENY_INVITE; return PLAYERBOT_SECURITY_INVITE; } - if (!ignoreGroup && group->IsFull()) + if (!ignoreGroup && botGroup->IsFull()) { if (reason) *reason = PLAYERBOT_DENY_FULL_GROUP; @@ -147,27 +141,22 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea return PLAYERBOT_SECURITY_TALK; } - if (!ignoreGroup && group->GetLeaderGUID() != bot->GetGUID()) + if (!ignoreGroup && botGroup->GetLeaderGUID() != bot->GetGUID()) { if (reason) *reason = PLAYERBOT_DENY_NOT_LEADER; return PLAYERBOT_SECURITY_TALK; } - else - { - if (reason) - *reason = PLAYERBOT_DENY_IS_LEADER; - - return PLAYERBOT_SECURITY_INVITE; - } + // The bot is the group leader, you can invite the initiator if (reason) - *reason = PLAYERBOT_DENY_INVITE; + *reason = PLAYERBOT_DENY_IS_LEADER; return PLAYERBOT_SECURITY_INVITE; } + // Non-random bots: only their master has full access if (botAI->GetMaster() == from) return PLAYERBOT_SECURITY_ALLOW_ALL; @@ -179,8 +168,13 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup) { + // If something is wrong with the pointers, we silently refuse + if (!bot || !from || !from->GetSession()) + return false; + DenyReason reason = PLAYERBOT_DENY_NONE; PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup); + if (realLevel >= level || from == bot) return true; @@ -189,11 +183,17 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, return false; PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - Player* master = botAI->GetMaster(); - if (master && botAI && botAI->IsOpposing(master) && master->GetSession()->GetSecurity() < SEC_GAMEMASTER) + if (!botAI) return false; + Player* master = botAI->GetMaster(); + if (master && botAI->IsOpposing(master)) + if (WorldSession* session = master->GetSession()) + if (session->GetSecurity() < SEC_GAMEMASTER) + return false; + std::ostringstream out; + switch (realLevel) { case PLAYERBOT_SECURITY_DENY_ALL: @@ -206,19 +206,20 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, out << "I'll do it later"; break; case PLAYERBOT_DENY_LOW_LEVEL: - out << "You are too low level: |cffff0000" << (uint32)from->GetLevel() << "|cffffffff/|cff00ff00" - << (uint32)bot->GetLevel(); + out << "You are too low level: |cffff0000" << uint32(from->GetLevel()) << "|cffffffff/|cff00ff00" + << uint32(bot->GetLevel()); break; case PLAYERBOT_DENY_GEARSCORE: { - int botGS = (int)botAI->GetEquipGearScore(bot/*, false, false*/); - int fromGS = (int)botAI->GetEquipGearScore(from/*, false, false*/); + int botGS = int(botAI->GetEquipGearScore(bot)); + int fromGS = int(botAI->GetEquipGearScore(from)); int diff = (100 * (botGS - fromGS) / botGS); int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel(); + out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS << " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%"; + break; } - break; case PLAYERBOT_DENY_NOT_YOURS: out << "I have a master already"; break; @@ -237,13 +238,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, case PLAYERBOT_DENY_FAR: { out << "You must be closer to invite me to your group. I am in "; - if (AreaTableEntry const* entry = sAreaTableStore.LookupEntry(bot->GetAreaId())) - { out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)"; - } + break; } - break; case PLAYERBOT_DENY_FULL_GROUP: out << "I am in a full group. Will do it later"; break; @@ -251,15 +249,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, out << "I am currently leading a group. I can invite you if you want."; break; case PLAYERBOT_DENY_NOT_LEADER: - if (botAI->GetGroupLeader()) - { - out << "I am in a group with " << botAI->GetGroupLeader()->GetName() - << ". You can ask him for invite."; - } + if (Player* leader = botAI->GetGroupLeader()) + out << "I am in a group with " << leader->GetName() << ". You can ask him for invite."; else - { out << "I am in a group with someone else. You can ask him for invite."; - } break; case PLAYERBOT_DENY_BG: out << "I am in a queue for BG. Will do it later"; @@ -283,10 +276,14 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, std::string const text = out.str(); ObjectGuid guid = from->GetGUID(); time_t lastSaid = whispers[guid][text]; + if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000) { whispers[guid][text] = time(nullptr); - bot->Whisper(text, LANG_UNIVERSAL, from); + + // Additional protection against crashes during logout + if (bot->IsInWorld() && from->IsInWorld()) + bot->Whisper(text, LANG_UNIVERSAL, from); } return false; diff --git a/src/Playerbots.cpp b/src/Playerbots.cpp index e13e079c..c9c26ee8 100644 --- a/src/Playerbots.cpp +++ b/src/Playerbots.cpp @@ -124,24 +124,49 @@ public: } } - bool OnPlayerBeforeTeleport(Player* player, uint32 mapid, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override + bool OnPlayerBeforeTeleport(Player* /*player*/, uint32 /*mapid*/, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override { - // Only apply to bots to prevent affecting real players - if (!player || !player->GetSession()->IsBot()) + /* for now commmented out until proven its actually required + * havent seen any proof CleanVisibilityReferences() is needed + + // If the player is not safe to touch, do nothing + if (!player) return true; - // If changing maps, proactively clean visibility references to prevent - // stale pointers in other players' visibility maps during the teleport. - // This fixes a race condition where: - // 1. Bot A teleports and its visible objects start getting cleaned up - // 2. Bot B is simultaneously updating visibility and tries to access objects in Bot A's old visibility map - // 3. Those objects may already be freed, causing a segmentation fault - if (player->GetMapId() != mapid && player->IsInWorld()) - { - player->GetObjectVisibilityContainer().CleanVisibilityReferences(); - } + // If same map or not in world do nothing + if (!player->IsInWorld() || player->GetMapId() == mapid) + return true; - return true; // Allow teleport to continue + // If real player do nothing + PlayerbotAI* ai = GET_PLAYERBOT_AI(player); + if (!ai || ai->IsRealPlayer()) + return true; + + // Cross-map bot teleport: defer visibility reference cleanup. + // CleanVisibilityReferences() erases this bot's GUID from other objects' visibility containers. + // This is intentionally done via the event queue (instead of directly here) because erasing + // from other players' visibility maps inside the teleport call stack can hit unsafe re-entrancy + // or iterator invalidation while visibility updates are in progress + ObjectGuid guid = player->GetGUID(); + player->m_Events.AddEventAtOffset( + [guid, mapid]() + { + // do nothing, if the player is not safe to touch + Player* p = ObjectAccessor::FindPlayer(guid); + if (!p || !p->IsInWorld() || p->IsDuringRemoveFromWorld()) + return; + + // do nothing if we are already on the target map + if (p->GetMapId() == mapid) + return; + + p->GetObjectVisibilityContainer().CleanVisibilityReferences(); + }, + Milliseconds(0)); + + */ + + return true; } void OnPlayerAfterUpdate(Player* player, uint32 diff) override diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 49851f8c..c76bbd71 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -26,7 +26,6 @@ #include "DatabaseEnv.h" #include "Define.h" #include "FleeManager.h" -#include "GameTime.h" #include "GridNotifiers.h" #include "GridNotifiersImpl.h" #include "GuildMgr.h" @@ -670,9 +669,9 @@ void RandomPlayerbotMgr::AssignAccountTypes() uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts; uint32 assigned = 0; - for (int i = allRandomBotAccounts.size() - 1; i >= 0 && assigned < toAssign; i--) + for (size_t idx = allRandomBotAccounts.size(); idx-- > 0 && assigned < toAssign;) { - uint32 accountId = allRandomBotAccounts[i]; + uint32 accountId = allRandomBotAccounts[idx]; if (currentAssignments[accountId] == 0) // Unassigned { PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId); @@ -1425,7 +1424,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) LOG_DEBUG("playerbots", "Bot #{}: log out", bot); SetEventValue(bot, "add", 0, 0); - currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end()); + currentBots.remove(bot); if (player) LogoutPlayerBot(botGUID); @@ -2709,69 +2708,73 @@ std::vector RandomPlayerbotMgr::GetBgBots(uint32 bracket) return std::move(BgBots); } -uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event) +CachedEvent* RandomPlayerbotMgr::FindEvent(uint32 bot, std::string const& event) { - // load all events at once on first event load - if (eventCache[bot].empty()) + BotEventCache& cache = eventCache[bot]; + + // Load once + if (!cache.loaded) { + cache.events.clear(); + PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT); stmt->SetData(0, 0); stmt->SetData(1, bot); + if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt)) { do { Field* fields = result->Fetch(); - std::string const eventName = fields[0].Get(); CachedEvent e; e.value = fields[1].Get(); e.lastChangeTime = fields[2].Get(); e.validIn = fields[3].Get(); e.data = fields[4].Get(); - eventCache[bot][eventName] = std::move(e); + + cache.events.emplace(fields[0].Get(), std::move(e)); } while (result->NextRow()); } + + cache.loaded = true; } - CachedEvent& e = eventCache[bot][event]; - /*if (e.IsEmpty()) + auto it = cache.events.find(event); + if (it == cache.events.end()) + return nullptr; + + CachedEvent& e = it->second; + + // remove expired events + if (e.validIn && (NowSeconds() - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink") { - 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(); - } + cache.events.erase(it); + return nullptr; } - */ - if ((time(0) - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink") - e.value = 0; - - return e.value; + return &e; } -std::string const RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const event) +uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const& event) { - std::string data = ""; - if (GetEventValue(bot, event)) - { - CachedEvent e = eventCache[bot][event]; - data = e.data; - } + if (CachedEvent* e = FindEvent(bot, event)) + return e->value; - return data; + return 0; } -uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn, - std::string const data) +std::string RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const& event) +{ + if (CachedEvent* e = FindEvent(bot, event)) + return e->data; + + return ""; +} + +uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn, + std::string const& data) { PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction(); @@ -2787,43 +2790,55 @@ uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, ui stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RANDOM_BOTS); stmt->SetData(0, 0); stmt->SetData(1, bot); - stmt->SetData(2, static_cast(GameTime::GetGameTime().count())); + stmt->SetData(2, NowSeconds()); stmt->SetData(3, validIn); stmt->SetData(4, event.c_str()); stmt->SetData(5, value); - if (data != "") - { + + if (!data.empty()) stmt->SetData(6, data.c_str()); - } else - { - stmt->SetData(6); - } + stmt->SetData(6); // NULL + trans->Append(stmt); } PlayerbotsDatabase.CommitTransaction(trans); - CachedEvent e(value, (uint32)time(nullptr), validIn, data); - eventCache[bot][event] = std::move(e); + // Update in-memory cache + BotEventCache& cache = eventCache[bot]; + cache.loaded = true; + + if (!value) + { + cache.events.erase(event); + return 0; + } + + CachedEvent& e = cache.events[event]; // create-on-write is OK here + e.value = value; + e.lastChangeTime = NowSeconds(); + e.validIn = validIn; + e.data = data; + return value; } -uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const type) { return GetEventValue(bot, type); } +uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const& type) { return GetEventValue(bot, type); } -uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const type) +uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const& type) { return GetValue(bot->GetGUID().GetCounter(), type); } -std::string const RandomPlayerbotMgr::GetData(uint32 bot, std::string const type) { return GetEventData(bot, type); } +std::string RandomPlayerbotMgr::GetData(uint32 bot, std::string const& type) { return GetEventData(bot, type); } -void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const type, uint32 value, std::string const data) +void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data) { SetEventValue(bot, type, value, sPlayerbotAIConfig->maxRandomBotInWorldTime, data); } -void RandomPlayerbotMgr::SetValue(Player* bot, std::string const type, uint32 value, std::string const data) +void RandomPlayerbotMgr::SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data) { SetValue(bot->GetGUID().GetCounter(), type, value, data); } @@ -3112,7 +3127,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) void RandomPlayerbotMgr::OnPlayerLoginError(uint32 bot) { SetEventValue(bot, "add", 0, 0); - currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end()); + currentBots.remove(bot); } Player* RandomPlayerbotMgr::GetRandomPlayer() @@ -3494,7 +3509,8 @@ void RandomPlayerbotMgr::Remove(Player* bot) stmt->SetData(1, owner.GetCounter()); PlayerbotsDatabase.Execute(stmt); - eventCache[owner.GetCounter()].clear(); + uint32 botId = owner.GetCounter(); + eventCache.erase(botId); LogoutPlayerBot(owner); } @@ -3511,7 +3527,7 @@ CreatureData const* RandomPlayerbotMgr::GetCreatureDataByEntry(uint32 entry) return nullptr; } -ObjectGuid const RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId) +ObjectGuid RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId) { ObjectGuid battleMasterGUID = ObjectGuid::Empty; diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index be624328..d65a59c9 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -9,6 +9,7 @@ #include "NewRpgInfo.h" #include "ObjectGuid.h" #include "PlayerbotMgr.h" +#include "GameTime.h" struct BattlegroundInfo { @@ -45,25 +46,20 @@ class ChatHandler; class PerformanceMonitorOperation; class WorldLocation; -class CachedEvent +struct CachedEvent { -public: - CachedEvent() : value(0), lastChangeTime(0), validIn(0), data("") {} - CachedEvent(const CachedEvent& other) - : value(other.value), lastChangeTime(other.lastChangeTime), validIn(other.validIn), data(other.data) - { - } - CachedEvent(uint32 value, uint32 lastChangeTime, uint32 validIn, std::string const data = "") - : value(value), lastChangeTime(lastChangeTime), validIn(validIn), data(data) - { - } - - bool IsEmpty() { return !lastChangeTime; } - - uint32 value; - uint32 lastChangeTime; - uint32 validIn; + uint32 value = 0; + uint32 lastChangeTime = 0; + uint32 validIn = 0; std::string data; + + bool IsEmpty() const { return !lastChangeTime; } +}; + +struct BotEventCache +{ + bool loaded = false; + std::unordered_map events; }; // https://gist.github.com/bradley219/5373998 @@ -139,13 +135,13 @@ public: void Revive(Player* player); void ChangeStrategy(Player* player); void ChangeStrategyOnce(Player* player); - uint32 GetValue(Player* bot, std::string const type); - uint32 GetValue(uint32 bot, std::string const type); - std::string const GetData(uint32 bot, std::string const type); - void SetValue(uint32 bot, std::string const type, uint32 value, std::string const data = ""); - void SetValue(Player* bot, std::string const type, uint32 value, std::string const data = ""); + uint32 GetValue(Player* bot, std::string const& type); + uint32 GetValue(uint32 bot, std::string const& type); + std::string GetData(uint32 bot, std::string const& type); + void SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data = ""); + void SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data = ""); void Remove(Player* bot); - ObjectGuid const GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId); + ObjectGuid GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId); CreatureData const* GetCreatureDataByEntry(uint32 entry); void LoadBattleMastersCache(); std::map> BattlegroundData; @@ -203,10 +199,11 @@ private: bool _isBotInitializing = true; bool _isBotLogging = true; NewRpgStatistic rpgStasticTotal; - uint32 GetEventValue(uint32 bot, std::string const event); - std::string const GetEventData(uint32 bot, std::string const event); - uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn, - std::string const data = ""); + CachedEvent* FindEvent(uint32 bot, std::string const& event); + uint32 GetEventValue(uint32 bot, std::string const& event); + std::string GetEventData(uint32 bot, std::string const& event); + uint32 SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn, + std::string const& data = ""); void GetBots(); std::vector GetBgBots(uint32 bracket); time_t BgCheckTimer; @@ -228,7 +225,7 @@ private: // std::map> rpgLocsCache; std::map>> rpgLocsCacheLevel; std::map>> BattleMastersCache; - std::map> eventCache; + std::unordered_map eventCache; std::list currentBots; uint32 bgBotsCount; uint32 playersLevel; @@ -238,6 +235,7 @@ private: std::vector addClassTypeAccounts; // Accounts marked as AddClass (type 2) //void ScaleBotActivity(); // Deprecated function + static inline uint32 NowSeconds() { return static_cast(GameTime::GetGameTime().count()); } }; #define sRandomPlayerbotMgr RandomPlayerbotMgr::instance() diff --git a/src/strategy/actions/BuyAction.cpp b/src/strategy/actions/BuyAction.cpp index 793ef8e0..1e8ec7e5 100644 --- a/src/strategy/actions/BuyAction.cpp +++ b/src/strategy/actions/BuyAction.cpp @@ -224,42 +224,36 @@ bool BuyAction::Execute(Event event) bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto) { - uint32 oldCount = AI_VALUE2(uint32, "item count", proto->Name1); - - if (!tItems) + if (!tItems || !proto) return false; uint32 itemId = proto->ItemId; - for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++) + uint32 oldCount = bot->GetItemCount(itemId, false); + + for (uint32 slot = 0; slot < tItems->GetItemCount(); ++slot) { - if (tItems->GetItem(slot)->item == itemId) + if (tItems->GetItem(slot)->item != itemId) + continue; + + uint32 botMoney = bot->GetMoney(); + if (botAI->HasCheat(BotCheatMask::gold)) + bot->SetMoney(10000000); + + bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT); + + if (botAI->HasCheat(BotCheatMask::gold)) + bot->SetMoney(botMoney); + + uint32 newCount = bot->GetItemCount(itemId, false); + if (newCount > oldCount) { - uint32 botMoney = bot->GetMoney(); - if (botAI->HasCheat(BotCheatMask::gold)) - { - bot->SetMoney(10000000); - } - - bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT); - - if (botAI->HasCheat(BotCheatMask::gold)) - { - bot->SetMoney(botMoney); - } - - if (oldCount < - AI_VALUE2( - uint32, "item count", - proto->Name1)) // BuyItem Always returns false (unless unique) so we have to check the item counts. - { - std::ostringstream out; - out << "Buying " << ChatHelper::FormatItem(proto); - botAI->TellMaster(out.str()); - return true; - } - - return false; + std::ostringstream out; + out << "Buying " << ChatHelper::FormatItem(proto); + botAI->TellMaster(out.str()); + return true; } + + return false; } return false; diff --git a/src/strategy/actions/ReadyCheckAction.cpp b/src/strategy/actions/ReadyCheckAction.cpp index 00b9e4c2..2fc25e6d 100644 --- a/src/strategy/actions/ReadyCheckAction.cpp +++ b/src/strategy/actions/ReadyCheckAction.cpp @@ -3,8 +3,11 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "ReadyCheckAction.h" +#include +#include +#include +#include "ReadyCheckAction.h" #include "Event.h" #include "Playerbots.h" @@ -27,14 +30,17 @@ std::string const formatPercent(std::string const name, uint8 value, float perce class ReadyChecker { public: + virtual ~ReadyChecker() = default; virtual bool Check(PlayerbotAI* botAI, AiObjectContext* context) = 0; virtual std::string const getName() = 0; virtual bool PrintAlways() { return true; } - static std::vector checkers; + static std::vector> checkers; + static std::once_flag initFlag; }; -std::vector ReadyChecker::checkers; +std::vector> ReadyChecker::checkers; +std::once_flag ReadyChecker::initFlag; class HealthChecker : public ReadyChecker { @@ -160,25 +166,30 @@ bool ReadyCheckAction::Execute(Event event) bool ReadyCheckAction::ReadyCheck() { - if (ReadyChecker::checkers.empty()) - { - ReadyChecker::checkers.push_back(new HealthChecker()); - ReadyChecker::checkers.push_back(new ManaChecker()); - ReadyChecker::checkers.push_back(new DistanceChecker()); - ReadyChecker::checkers.push_back(new HunterChecker()); + std::call_once( + ReadyChecker::initFlag, + []() + { + ReadyChecker::checkers.reserve(8); - ReadyChecker::checkers.push_back(new ItemCountChecker("food", "Food")); - ReadyChecker::checkers.push_back(new ManaPotionChecker("drink", "Water")); - ReadyChecker::checkers.push_back(new ItemCountChecker("healing potion", "Hpot")); - ReadyChecker::checkers.push_back(new ManaPotionChecker("mana potion", "Mpot")); - } + ReadyChecker::checkers.emplace_back(std::make_unique()); + ReadyChecker::checkers.emplace_back(std::make_unique()); + ReadyChecker::checkers.emplace_back(std::make_unique()); + ReadyChecker::checkers.emplace_back(std::make_unique()); + + ReadyChecker::checkers.emplace_back(std::make_unique("food", "Food")); + ReadyChecker::checkers.emplace_back(std::make_unique("drink", "Water")); + ReadyChecker::checkers.emplace_back(std::make_unique("healing potion", "Hpot")); + ReadyChecker::checkers.emplace_back(std::make_unique("mana potion", "Mpot")); + }); bool result = true; - for (std::vector::iterator i = ReadyChecker::checkers.begin(); i != ReadyChecker::checkers.end(); - ++i) + for (auto const& checkerPtr : ReadyChecker::checkers) { - ReadyChecker* checker = *i; - bool ok = checker->Check(botAI, context); + if (!checkerPtr) + continue; + + bool ok = checkerPtr->Check(botAI, context); result = result && ok; } diff --git a/src/strategy/values/Formations.cpp b/src/strategy/values/Formations.cpp index 30c73825..69869889 100644 --- a/src/strategy/values/Formations.cpp +++ b/src/strategy/values/Formations.cpp @@ -21,11 +21,37 @@ bool IsSameLocation(WorldLocation const& a, WorldLocation const& b) bool Formation::IsNullLocation(WorldLocation const& loc) { return IsSameLocation(loc, Formation::NullLocation); } +bool ValidateTargetContext(Unit* a, Unit* b, Map*& outMap) +{ + if (!a || !b || a == b) + return false; + + if (!a->IsInWorld() || !b->IsInWorld()) + return false; + + if (a->IsDuringRemoveFromWorld() || b->IsDuringRemoveFromWorld()) + return false; + + Map* map = a->GetMap(); + if (!map || map != b->GetMap()) + return false; + + outMap = map; + + return true; +} + +bool ValidateTargetContext(Unit* a, Unit* b) +{ + Map* unused = nullptr; + return ValidateTargetContext(a, b, unused); +} + WorldLocation MoveAheadFormation::GetLocation() { Player* master = GetMaster(); - if (!master || master == bot) - return WorldLocation(); + if (!ValidateTargetContext(master, bot)) + return Formation::NullLocation; WorldLocation loc = GetLocationInternal(); if (Formation::IsNullLocation(loc)) @@ -40,7 +66,7 @@ WorldLocation MoveAheadFormation::GetLocation() // float ori = master->GetOrientation(); // float x1 = x + sPlayerbotAIConfig->tooCloseDistance * cos(ori); // float y1 = y + sPlayerbotAIConfig->tooCloseDistance * sin(ori); - // float ground = master->GetMap()->GetHeight(x1, y1, z); + // float ground = map->GetHeight(x1, y1, z); // if (ground > INVALID_HEIGHT) // { // x = x1; @@ -48,7 +74,7 @@ WorldLocation MoveAheadFormation::GetLocation() // } // } - // float ground = master->GetMap()->GetHeight(x, y, z); + // float ground = map->GetHeight(x, y, z); // if (ground <= INVALID_HEIGHT) // return Formation::NullLocation; @@ -81,16 +107,17 @@ public: WorldLocation GetLocationInternal() override { Player* master = GetMaster(); - if (!master) - return WorldLocation(); + Map* map = nullptr; + if (!ValidateTargetContext(master, bot, map)) + return Formation::NullLocation; float range = sPlayerbotAIConfig->followDistance; float angle = GetFollowAngle(); float x = master->GetPositionX() + cos(angle) * range; float y = master->GetPositionY() + sin(angle) * range; float z = master->GetPositionZ() + master->GetHoverHeight(); - if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), - master->GetPositionZ(), x, y, z)) + if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), + master->GetPositionZ(), x, y, z)) { x = master->GetPositionX() + cos(angle) * range; y = master->GetPositionY() + sin(angle) * range; @@ -111,8 +138,9 @@ public: WorldLocation GetLocationInternal() override { Player* master = GetMaster(); - if (!master) - return WorldLocation(); + Map* map = nullptr; + if (!ValidateTargetContext(master, bot, map)) + return Formation::NullLocation; float range = sPlayerbotAIConfig->followDistance; float angle = GetFollowAngle(); @@ -120,49 +148,28 @@ public: time_t now = time(nullptr); if (!lastChangeTime || now - lastChangeTime >= 3) { - Player* master = GetMaster(); - if (!master) - return WorldLocation(); + lastChangeTime = now; - float range = sPlayerbotAIConfig->followDistance; - float angle = GetFollowAngle(); - - time_t now = time(nullptr); - if (!lastChangeTime || now - lastChangeTime >= 3) - { - lastChangeTime = now; - dx = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance; - dy = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance; - dr = sqrt(dx * dx + dy * dy); - } - - float x = master->GetPositionX() + cos(angle) * range + dx; - float y = master->GetPositionY() + sin(angle) * range + dy; - float z = master->GetPositionZ() + master->GetHoverHeight(); - if (!master->GetMap()->CheckCollisionAndGetValidCoords( - master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z)) - { - x = master->GetPositionX() + cos(angle) * range + dx; - y = master->GetPositionY() + sin(angle) * range + dy; - z = master->GetPositionZ() + master->GetHoverHeight(); - master->UpdateAllowedPositionZ(x, y, z); - } - // bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), - // bot->GetPositionZ(), x, y, z); - return WorldLocation(master->GetMapId(), x, y, z); + dx = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance; + dy = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance; + dr = std::sqrt(dx * dx + dy * dy); } - float x = master->GetPositionX() + cos(angle) * range + dx; - float y = master->GetPositionY() + sin(angle) * range + dy; + float x = master->GetPositionX() + std::cos(angle) * range + dx; + float y = master->GetPositionY() + std::sin(angle) * range + dy; float z = master->GetPositionZ() + master->GetHoverHeight(); - if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), - master->GetPositionZ(), x, y, z)) + + if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), + master->GetPositionZ(), x, y, z)) { - x = master->GetPositionX() + cos(angle) * range + dx; - y = master->GetPositionY() + sin(angle) * range + dy; + // Recompute a clean fallback and clamp Z + x = master->GetPositionX() + std::cos(angle) * range + dx; + y = master->GetPositionY() + std::sin(angle) * range + dy; z = master->GetPositionZ() + master->GetHoverHeight(); + master->UpdateAllowedPositionZ(x, y, z); } + return WorldLocation(master->GetMapId(), x, y, z); } @@ -182,16 +189,18 @@ public: WorldLocation GetLocation() override { - float range = 2.0f; - Unit* target = AI_VALUE(Unit*, "current target"); Player* master = GetMaster(); - if (!target && target != bot) + + // Fix: if no target OR target is the bot, fall back to master + if (!target || target == bot) target = master; - if (!target) + Map* map = nullptr; + if (!ValidateTargetContext(master, bot, map)) return Formation::NullLocation; + float range = 2.0f; switch (bot->getClass()) { case CLASS_HUNTER: @@ -214,14 +223,16 @@ public: float x = target->GetPositionX() + cos(angle) * range; float y = target->GetPositionY() + sin(angle) * range; float z = target->GetPositionZ(); - if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(), - target->GetPositionZ(), x, y, z)) + + if (!map->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(), + target->GetPositionZ(), x, y, z)) { x = target->GetPositionX() + cos(angle) * range; y = target->GetPositionY() + sin(angle) * range; z = target->GetPositionZ(); target->UpdateAllowedPositionZ(x, y, z); } + return WorldLocation(bot->GetMapId(), x, y, z); } }; @@ -249,18 +260,18 @@ public: float orientation = master->GetOrientation(); std::vector players; - GroupReference* gref = group->GetFirstMember(); - while (gref) + players.reserve(group->GetMembersCount()); + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - if (member != master) - players.push_back(member); + if (!member || member == master) + continue; - gref = gref->next(); + players.push_back(member); } - players.insert(players.begin() + group->GetMembersCount() / 2, master); - + players.insert(players.begin() + players.size() / 2, master); return MoveLine(players, 0.0f, x, y, z, orientation, range); } }; @@ -289,19 +300,17 @@ public: std::vector tanks; std::vector dps; - GroupReference* gref = group->GetFirstMember(); - while (gref) + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - if (member != master) - { - if (botAI->IsTank(member)) - tanks.push_back(member); - else - dps.push_back(member); - } + if (!member || member == master) + continue; - gref = gref->next(); + if (botAI->IsTank(member)) + tanks.push_back(member); + else + dps.push_back(member); } if (botAI->IsTank(master)) @@ -310,25 +319,21 @@ public: dps.insert(dps.begin() + (dps.size() + 1) / 2, master); if (botAI->IsTank(bot) && botAI->IsTank(master)) - { return MoveLine(tanks, 0.0f, x, y, z, orientation, range); - } if (!botAI->IsTank(bot) && !botAI->IsTank(master)) - { return MoveLine(dps, 0.0f, x, y, z, orientation, range); - } if (botAI->IsTank(bot) && !botAI->IsTank(master)) { - float diff = tanks.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f; + float diff = (tanks.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f; return MoveLine(tanks, diff, x + cos(orientation) * range, y + sin(orientation) * range, z, orientation, range); } if (!botAI->IsTank(bot) && botAI->IsTank(master)) { - float diff = dps.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f; + float diff = (dps.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f; return MoveLine(dps, diff, x - cos(orientation) * range, y - sin(orientation) * range, z, orientation, range); } @@ -344,65 +349,69 @@ public: WorldLocation GetLocation() override { + Player* master = GetMaster(); + Map* map = nullptr; + if (!ValidateTargetContext(master, bot, map)) + return Formation::NullLocation; + float range = sPlayerbotAIConfig->farDistance; float followRange = sPlayerbotAIConfig->followDistance; - Player* master = GetMaster(); - if (!master) - return Formation::NullLocation; - if (sServerFacade->GetDistance2d(bot, master) <= range) return Formation::NullLocation; - float angle = master->GetAngle(bot); + float angleToBot = master->GetAngle(bot); float followAngle = GetFollowAngle(); - float x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange; - float y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange; + float x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange; + float y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange; float z = master->GetPositionZ(); float ground = master->GetMapHeight(x, y, z + 30.0f); if (ground <= INVALID_HEIGHT) { - float minDist = 0, minX = 0, minY = 0; - for (float angle = 0.0f; angle <= 2 * M_PI; angle += M_PI / 16.0f) + float minDist = 0.f; + float minX = 0.f, minY = 0.f; + + for (float a = 0.0f; a <= 2 * M_PI; a += M_PI / 16.0f) { - x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange; - y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange; - float dist = sServerFacade->GetDistance2d(bot, x, y); - float ground = master->GetMapHeight(x, y, z + 30.0f); - if (ground > INVALID_HEIGHT && (!minDist || minDist > dist)) + float tx = master->GetPositionX() + cos(a) * range + cos(followAngle) * followRange; + float ty = master->GetPositionY() + sin(a) * range + sin(followAngle) * followRange; + + float dist = sServerFacade->GetDistance2d(bot, tx, ty); + float tg = master->GetMapHeight(tx, ty, z + 30.0f); + + if (tg > INVALID_HEIGHT && (!minDist || dist < minDist)) { minDist = dist; - minX = x; - minY = y; + minX = tx; + minY = ty; } } - if (minDist) + if (!minDist) + return Formation::NullLocation; + + float lz = z; + if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), + master->GetPositionZ(), minX, minY, lz)) { - if (!master->GetMap()->CheckCollisionAndGetValidCoords( - master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z)) - { - x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange; - y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange; - z = master->GetPositionZ() + master->GetHoverHeight(); - master->UpdateAllowedPositionZ(x, y, z); - } - return WorldLocation(bot->GetMapId(), minX, minY, z); + lz = z + master->GetHoverHeight(); + master->UpdateAllowedPositionZ(minX, minY, lz); } - return Formation::NullLocation; + return WorldLocation(bot->GetMapId(), minX, minY, lz); } - if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), - master->GetPositionZ(), x, y, z)) + if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), + master->GetPositionZ(), x, y, z)) { - x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange; - y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange; + x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange; + y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange; z = master->GetPositionZ() + master->GetHoverHeight(); master->UpdateAllowedPositionZ(x, y, z); } + return WorldLocation(bot->GetMapId(), x, y, z); } }; @@ -653,31 +662,36 @@ WorldLocation MoveFormation::MoveSingleLine(std::vector line, float dif float orientation, float range) { float count = line.size(); - float angle = orientation - M_PI / 2.0f; - float x = cx + cos(angle) * (range * floor(count / 2.0f) + diff); - float y = cy + sin(angle) * (range * floor(count / 2.0f) + diff); + float angleLeft = orientation - M_PI / 2.0f; + float x0 = cx + std::cos(angleLeft) * (range * std::floor(count / 2.0f) + diff); + float y0 = cy + std::sin(angleLeft) * (range * std::floor(count / 2.0f) + diff); uint32 index = 0; for (Player* member : line) { if (member == bot) { - float angle = orientation + M_PI / 2.0f; + float angleRight = orientation + M_PI / 2.0f; float radius = range * index; - float lx = x + cos(angle) * radius; - float ly = y + sin(angle) * radius; + float lx = x0 + std::cos(angleRight) * radius; + float ly = y0 + std::sin(angleRight) * radius; float lz = cz; Player* master = botAI->GetMaster(); - if (!master || !master->GetMap()->CheckCollisionAndGetValidCoords( - master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), lx, ly, lz)) + Map* map = master ? master->GetMap() : nullptr; + + // if not fully in world ignore collision corrections. + if (!master || !map || !bot || map != bot->GetMap() || !master->IsInWorld() || + master->IsDuringRemoveFromWorld() || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld()) { - lx = x + cos(angle) * radius; - ly = y + sin(angle) * radius; - lz = cz; + return WorldLocation(bot->GetMapId(), lx, ly, lz); } + // if fully loaded check collision and applies coordinate corrections if needed + map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), + master->GetPositionZ(), lx, ly, lz); + return WorldLocation(bot->GetMapId(), lx, ly, lz); } diff --git a/src/strategy/values/ItemUsageValue.cpp b/src/strategy/values/ItemUsageValue.cpp index 3be0db2c..25866c80 100644 --- a/src/strategy/values/ItemUsageValue.cpp +++ b/src/strategy/values/ItemUsageValue.cpp @@ -80,7 +80,7 @@ ItemUsage ItemUsageValue::Calculate() return ITEM_USAGE_USE; if (proto->Class == ITEM_CLASS_CONSUMABLE && - (proto->MaxCount == 0 || AI_VALUE2(uint32, "item count", proto->Name1) < proto->MaxCount)) + (proto->MaxCount == 0 || bot->GetItemCount(itemId, false) < proto->MaxCount)) { std::string const foodType = GetConsumableType(proto, bot->GetPower(POWER_MANA)); @@ -520,7 +520,7 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr { if (quest->RequiredItemId[i] == proto->ItemId) { - if (AI_VALUE2(uint32, "item count", proto->Name1) >= quest->RequiredItemCount[i]) + if (player->GetItemCount(proto->ItemId, false) >= quest->RequiredItemCount[i]) continue; return true; // Item is directly required for a quest @@ -549,7 +549,7 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr { if (quest->RequiredItemId[j] == createdItemId) { - if (AI_VALUE2(uint32, "item count", createdItemId) >= quest->RequiredItemCount[j]) + if (player->GetItemCount(createdItemId, false) >= quest->RequiredItemCount[j]) continue; return true; // Item is useful because it creates a required quest item