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