diff --git a/src/BroadcastHelper.cpp b/src/BroadcastHelper.cpp index 85bc5221..2de066f6 100644 --- a/src/BroadcastHelper.cpp +++ b/src/BroadcastHelper.cpp @@ -947,8 +947,21 @@ bool BroadcastHelper::BroadcastSuggestThunderfury(PlayerbotAI* ai, Player* bot) { std::map placeholders; ItemTemplate const* thunderfuryProto = sObjectMgr->GetItemTemplate(19019); - placeholders["%thunderfury_link"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(thunderfuryProto); - + // placeholders["%thunderfury_link"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(thunderfuryProto); // Old code + // [Crash fix] Protect from nil AI : a real player doesn't have PlayerbotAI. + // Before: direct deref GET_PLAYERBOT_AI(bot)->... could crash World/General. + if (auto* ai = GET_PLAYERBOT_AI(bot)) + { + if (auto* chat = ai->GetChatHelper()) + placeholders["%thunderfury_link"] = chat->FormatItem(thunderfuryProto); + else + placeholders["%thunderfury_link"] = ""; // fallback: no chat helper + } + else + { + placeholders["%thunderfury_link"] = ""; // fallback: no d'AI (real player) + } + // End crash fix return BroadcastToChannelWithGlobalChance( ai, BOT_TEXT2("thunderfury_spam", placeholders), diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 998310f9..3f67b505 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -2373,7 +2373,7 @@ std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry) return name; } -std::vector PlayerbotAI::GetPlayersInGroup() +/*std::vector PlayerbotAI::GetPlayersInGroup() { std::vector members; @@ -2392,6 +2392,34 @@ std::vector PlayerbotAI::GetPlayersInGroup() members.push_back(ref->GetSource()); } + return members; +}*/ + +std::vector PlayerbotAI::GetPlayersInGroup() +{ + std::vector members; + + Group* group = bot->GetGroup(); + if (!group) + return members; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member) + continue; + + // Celaning, we don't call 2 times GET_PLAYERBOT_AI and never reference it if nil + if (auto* ai = GET_PLAYERBOT_AI(member)) + { + // If it's a bot (not real player) => we ignor it + if (!ai->IsRealPlayer()) + continue; + } + + members.push_back(member); + } + return members; } diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 182177c2..dee1437c 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -36,10 +36,28 @@ #include "BroadcastHelper.h" #include "PlayerbotDbStore.h" #include "WorldSessionMgr.h" -#include "DatabaseEnv.h" // Added for gender choice -#include // Added for gender choice -#include "Log.h" // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) +#include "DatabaseEnv.h" +#include +#include "Log.h" #include // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) +#include "TravelMgr.h" + +namespace { + // [Crash fix] Centralize clearing of pointer values in the AI context + static void ClearAIContextPointerValues(PlayerbotAI* ai) + { + if (!ai) return; + if (AiObjectContext* ctx = ai->GetAiObjectContext()) + { + // Known today + if (auto* tt = ctx->GetValue("travel target")) + tt->Set(nullptr); + + // TODO: add other pointer-type values here if you have any + // e.g.: ctx->GetValue("some key")->Set(nullptr); + } + } +} class BotInitGuard { @@ -124,7 +142,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer); if (!mgr) { - LOG_DEBUG("playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue()); + LOG_DEBUG("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue()); return; } uint32 count = mgr->GetPlayerbotsCount() + botLoading.size(); @@ -222,13 +240,33 @@ void PlayerbotHolder::UpdateSessions() } } -void PlayerbotHolder::HandleBotPackets(WorldSession* session) +/*void PlayerbotHolder::HandleBotPackets(WorldSession* session) { WorldPacket* packet; while (session->GetPacketQueue().next(packet)) { OpcodeClient opcode = static_cast(packet->GetOpcode()); ClientOpcodeHandler const* opHandle = opcodeTable[opcode]; + opHandle->Call(session, *packet); + delete packet; + } +}*/ + +void PlayerbotHolder::HandleBotPackets(WorldSession* session) // [Crash Fix] Secure packet dispatch (avoid calling on a null handler) +{ + WorldPacket* packet; + while (session->GetPacketQueue().next(packet)) + { + const OpcodeClient opcode = static_cast(packet->GetOpcode()); + const ClientOpcodeHandler* opHandle = opcodeTable[opcode]; + + if (!opHandle) + { + // Unknown handler: drop cleanly + delete packet; + continue; + } + opHandle->Call(session, *packet); delete packet; } @@ -318,10 +356,13 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) sPlayerbotDbStore->Save(botAI); } - LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str()); + LOG_DEBUG("mod-playerbots", "Bot {} logging out", bot->GetName().c_str()); bot->SaveToDB(false, false); - WorldSession* botWorldSessionPtr = bot->GetSession(); + // WorldSession* botWorldSessionPtr = bot->GetSession(); + WorldSession* botWorldSessionPtr = bot->GetSession(); // Small safeguard on the session (as a precaution) + if (!botWorldSessionPtr) + return; WorldSession* masterWorldSessionPtr = nullptr; if (botWorldSessionPtr->isLogingOut()) @@ -354,11 +395,13 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) logout = true; } - TravelTarget* target = nullptr; + /*TravelTarget* target = nullptr; if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values. { target = botAI->GetAiObjectContext()->GetValue("travel target")->Get(); - } + }*/ + // [Crash fix] Centralized cleanup of pointer values in the context + ClearAIContextPointerValues(botAI); // Peiru: Allow bots to always instant logout to see if this resolves logout crashes logout = true; @@ -375,19 +418,25 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) botWorldSessionPtr->HandleLogoutRequestOpcode(data); if (!bot) { - RemoveFromPlayerbotsMap(guid); + /*RemoveFromPlayerbotsMap(guid); delete botWorldSessionPtr; if (target) - delete target; + delete target;*/ + // [Crash fix] bot can be destroyed by the logout request: clean up without touching old pointers + RemoveFromPlayerbotsMap(guid); + delete botWorldSessionPtr; } return; } else { - RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap + /*RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap delete botWorldSessionPtr; // finally delete the bot's WorldSession if (target) - delete target; + delete target;*/ + // [Crash fix] no more deleting 'target' here: ownership handled by the AI/Context + RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap + delete botWorldSessionPtr; // finally delete the bot's WorldSession } return; } // if instant logout possible, do it @@ -420,11 +469,11 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid) sPlayerbotDbStore->Save(botAI); } - LOG_DEBUG("playerbots", "Bot {} logged out", bot->GetName().c_str()); + LOG_DEBUG("mod-playerbots", "Bot {} logged out", bot->GetName().c_str()); bot->SaveToDB(false, false); - if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values. + /*if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values. { TravelTarget* target = botAI->GetAiObjectContext()->GetValue("travel target")->Get(); if (target) @@ -433,7 +482,9 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid) RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap - delete botAI; + delete botAI;*/ + // [Crash fix] Centralized cleanup of pointer values in the context + ClearAIContextPointerValues(botAI); } } @@ -564,7 +615,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) //} mgroup->AddMember(bot); - LOG_DEBUG("playerbots", "[GROUP] after add: members={}, isRaid={}, isLFG={}", + LOG_DEBUG("mod-playerbots", "[GROUP] after add: members={}, isRaid={}, isLFG={}", (int)mgroup->GetMembersCount(), mgroup->isRaidGroup() ? 1 : 0, mgroup->isLFGGroup() ? 1 : 0); } else @@ -744,9 +795,11 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje } } - if (GET_PLAYERBOT_AI(bot)) + // if (GET_PLAYERBOT_AI(bot)) + if (PlayerbotAI* ai = GET_PLAYERBOT_AI(bot)) // [Tidy/Crash fix] Acquire AI once and reuse; avoid multiple GET_PLAYERBOT_AI calls. { - if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster()) + // if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster()) + if (Player* master = ai->GetMaster()) { if (master->GetSession()->GetSecurity() <= SEC_PLAYER && sPlayerbotAIConfig->autoInitOnly && cmd != "init=auto") @@ -1601,7 +1654,7 @@ void PlayerbotMgr::OnBotLoginInternal(Player* const bot) botAI->SetMaster(master); botAI->ResetStrategies(); - LOG_INFO("playerbots", "Bot {} logged in", bot->GetName().c_str()); + LOG_INFO("mod-playerbots", "Bot {} logged in", bot->GetName().c_str()); } void PlayerbotMgr::OnPlayerLogin(Player* player) @@ -1794,7 +1847,7 @@ void PlayerbotsMgr::RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntr { delete it->second; _playerbotsAIMap.erase(it); - LOG_DEBUG("playerbots", "Removed stale AI for GUID {}", + LOG_DEBUG("mod-playerbots", "Removed stale AI for GUID {}", static_cast(guid.GetRawValue())); } diff --git a/src/Playerbots.cpp b/src/Playerbots.cpp index e39171b1..d7fb2ea5 100644 --- a/src/Playerbots.cpp +++ b/src/Playerbots.cpp @@ -137,7 +137,7 @@ public: bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Player* receiver) override { - if (type == CHAT_MSG_WHISPER) + /*if (type == CHAT_MSG_WHISPER) { if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(receiver)) { @@ -145,14 +145,23 @@ public: return false; } - } + }*/ + + if (type == CHAT_MSG_WHISPER && receiver) // [Crash Fix] Add non-null receiver check to avoid calling on a null pointer in edge cases. + { + if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(receiver)) + { + botAI->HandleCommand(type, msg, player); + return false; + } + } return true; } void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + /*for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { if (Player* member = itr->GetSource()) { @@ -161,6 +170,18 @@ public: botAI->HandleCommand(type, msg, player); } } + }*/ + if (!group) return; // [Crash Fix] 'group' should not be null in this hook, but this safeguard prevents a crash if the caller changes or in case of an unexpected call. + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member) continue; + + if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(member)) + { + botAI->HandleCommand(type, msg, player); + } } } @@ -177,7 +198,9 @@ public: { if (bot->GetGuildId() == player->GetGuildId()) { - GET_PLAYERBOT_AI(bot)->HandleCommand(type, msg, player); + // GET_PLAYERBOT_AI(bot)->HandleCommand(type, msg, player); + if (PlayerbotAI* ai = GET_PLAYERBOT_AI(bot)) // [Crash Fix] Possible crash source because we don't check if the returned pointer is not null + ai->HandleCommand(type, msg, player); } } } diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 1b69d7f3..658e43d3 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -1725,7 +1725,11 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& } // Prevent blink to be detected by visible real players - if (botAI->HasPlayerNearby(150.0f)) + /*if (botAI->HasPlayerNearby(150.0f)) + { + break; + }*/ + if (botAI && botAI->HasPlayerNearby(150.0f)) // [Crash fix] 'botAI' can be null earlier in the function. { break; } @@ -2333,8 +2337,10 @@ void RandomPlayerbotMgr::RandomizeFirst(Player* bot) PlayerbotsDatabase.Execute(stmt); // teleport to a random inn for bot level - if (GET_PLAYERBOT_AI(bot)) - GET_PLAYERBOT_AI(bot)->Reset(true); + /*if (GET_PLAYERBOT_AI(bot)) + GET_PLAYERBOT_AI(bot)->Reset(true);*/ + if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Avoid 2 calls to GET_PLAYERBOT_AI and protect the dereference. + ai->Reset(true); if (bot->GetGroup()) bot->RemoveFromGroup(); @@ -2374,8 +2380,10 @@ void RandomPlayerbotMgr::RandomizeMin(Player* bot) PlayerbotsDatabase.Execute(stmt); // teleport to a random inn for bot level - if (GET_PLAYERBOT_AI(bot)) - GET_PLAYERBOT_AI(bot)->Reset(true); + /*if (GET_PLAYERBOT_AI(bot)) + GET_PLAYERBOT_AI(bot)->Reset(true);*/ + if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Avoid 2 calls to GET_PLAYERBOT_AI and protect the dereference. + ai->Reset(true); if (bot->GetGroup()) bot->RemoveFromGroup(); @@ -2468,7 +2476,7 @@ void RandomPlayerbotMgr::Refresh(Player* bot) bool RandomPlayerbotMgr::IsRandomBot(Player* bot) { - if (bot && GET_PLAYERBOT_AI(bot)) + /*if (bot && GET_PLAYERBOT_AI(bot)) { if (GET_PLAYERBOT_AI(bot)->IsRealPlayer()) return false; @@ -2478,6 +2486,17 @@ bool RandomPlayerbotMgr::IsRandomBot(Player* bot) return IsRandomBot(bot->GetGUID().GetCounter()); } + return false;*/ + + if (bot) // [Tidy] Single AI acquisition + same logic. + { + if (auto* ai = GET_PLAYERBOT_AI(bot)) + { + if (ai->IsRealPlayer()) + return false; + } + return IsRandomBot(bot->GetGUID().GetCounter()); + } return false; } @@ -2495,7 +2514,7 @@ bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot) bool RandomPlayerbotMgr::IsAddclassBot(Player* bot) { - if (bot && GET_PLAYERBOT_AI(bot)) + /*if (bot && GET_PLAYERBOT_AI(bot)) { if (GET_PLAYERBOT_AI(bot)->IsRealPlayer()) return false; @@ -2505,6 +2524,17 @@ bool RandomPlayerbotMgr::IsAddclassBot(Player* bot) return IsAddclassBot(bot->GetGUID().GetCounter()); } + return false;*/ + + if (bot) // [Tidy] Single AI acquisition + same logic. + { + if (auto* ai = GET_PLAYERBOT_AI(bot)) + { + if (ai->IsRealPlayer()) + return false; + } + return IsAddclassBot(bot->GetGUID().GetCounter()); + } return false; } @@ -2844,8 +2874,9 @@ void RandomPlayerbotMgr::HandleCommand(uint32 type, std::string const text, Play continue; } } - - GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer); + // GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer); // Possible crash source because we don't check if the returned pointer is not null + if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Protect the call on a null AI (World/General chat path). + ai->HandleCommand(type, text, fromPlayer); } } @@ -2918,7 +2949,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + /*PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI && member == player && (!botAI->GetMaster() || GET_PLAYERBOT_AI(botAI->GetMaster()))) { if (!bot->InBattleground()) @@ -2929,6 +2960,20 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) } break; + }*/ + if (auto* botAI = GET_PLAYERBOT_AI(bot)) // [Tidy] Avoid GET_PLAYERBOT_AI(...) on a potentially null master. + { + Player* master = botAI->GetMaster(); + if (member == player && (!master || GET_PLAYERBOT_AI(master))) + { + if (!bot->InBattleground()) + { + botAI->SetMaster(player); + botAI->ResetStrategies(); + botAI->TellMaster("Hello"); + } + break; + } } } } @@ -3067,13 +3112,29 @@ void RandomPlayerbotMgr::PrintStats() lvlPerClass[bot->getClass()] += bot->GetLevel(); lvlPerRace[bot->getRace()] += bot->GetLevel(); - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + /*PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI->AllowActivity()) ++active; if (botAI->GetAiObjectContext()->GetValue("random bot update")->Get()) - ++update; - + ++update;*/ + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); // [Crash fix] Declare botAI in the loop scope and exit early if null, + if (!botAI) + continue; // real player / no AI → ignore this bot for stats + + if (botAI->AllowActivity()) + ++active; + + // Secure access to the context and the value + if (AiObjectContext* ctx = botAI->GetAiObjectContext()) + { + if (auto* v = ctx->GetValue("random bot update")) + if (v->Get()) + ++update; + } + // End CrashFix + uint32 botId = bot->GetGUID().GetCounter(); if (!GetEventValue(botId, "randomize")) ++randomize; diff --git a/src/TravelMgr.cpp b/src/TravelMgr.cpp index 69d510b9..6b46fb5a 100644 --- a/src/TravelMgr.cpp +++ b/src/TravelMgr.cpp @@ -1227,7 +1227,7 @@ std::string const QuestObjectiveTravelDestination::getTitle() return out.str(); } -bool RpgTravelDestination::isActive(Player* bot) +/*bool RpgTravelDestination::isActive(Player* bot) // Old Code { PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); AiObjectContext* context = botAI->GetAiObjectContext(); @@ -1264,6 +1264,62 @@ bool RpgTravelDestination::isActive(Player* bot) ReputationRank reaction = bot->GetReputationRank(factionEntry->faction); return reaction > REP_NEUTRAL; +}*/ + +bool RpgTravelDestination::isActive(Player* bot) +{ + // [Crash fix] Never dereference the AI if the player is real (null AI). + if (!bot) + return false; + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (!botAI) + return false; // real player (no AI) => inactive destination + + AiObjectContext* context = botAI->GetAiObjectContext(); + if (!context) + return false; + + CreatureTemplate const* cInfo = GetCreatureTemplate(); + if (!cInfo) + return false; + + bool isUsefull = false; + + if (cInfo->npcflag & UNIT_NPC_FLAG_VENDOR) + if (AI_VALUE2_LAZY(bool, "group or", "should sell,can sell,following party,near leader")) + isUsefull = true; + + if (cInfo->npcflag & UNIT_NPC_FLAG_REPAIR) + if (AI_VALUE2_LAZY(bool, "group or", "should repair,can repair,following party,near leader")) + isUsefull = true; + + if (!isUsefull) + return false; + + // [Crash fix] Read the ignore list via 'context' and check that the Value exists + GuidSet const* ignoreList = nullptr; + if (auto* value = context->GetValue("ignore rpg target")) + ignoreList = &value->Get(); + + if (ignoreList) + { + for (ObjectGuid const& guid : *ignoreList) + { + if (guid.GetEntry() == getEntry()) + return false; + } + } + + // Secure access to the faction template + if (FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(cInfo->faction)) + { + ReputationRank reaction = bot->GetReputationRank(factionEntry->faction); + return reaction > REP_NEUTRAL; + } + + // As a precaution, if the faction is not found, consider inactive + return false; } CreatureTemplate const* RpgTravelDestination::GetCreatureTemplate() { return sObjectMgr->GetCreatureTemplate(entry); }