mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Compare commits
5 Commits
hermensbas
...
hermensbas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
706ef442c1 | ||
|
|
c6b0424c29 | ||
|
|
2e0a161623 | ||
|
|
e4ea8e2694 | ||
|
|
ddfa919154 |
@@ -947,8 +947,21 @@ bool BroadcastHelper::BroadcastSuggestThunderfury(PlayerbotAI* ai, Player* bot)
|
|||||||
{
|
{
|
||||||
std::map<std::string, std::string> placeholders;
|
std::map<std::string, std::string> placeholders;
|
||||||
ItemTemplate const* thunderfuryProto = sObjectMgr->GetItemTemplate(19019);
|
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(
|
return BroadcastToChannelWithGlobalChance(
|
||||||
ai,
|
ai,
|
||||||
BOT_TEXT2("thunderfury_spam", placeholders),
|
BOT_TEXT2("thunderfury_spam", placeholders),
|
||||||
|
|||||||
@@ -2373,7 +2373,7 @@ std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry)
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
|
/*std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
|
||||||
{
|
{
|
||||||
std::vector<Player*> members;
|
std::vector<Player*> members;
|
||||||
|
|
||||||
@@ -2392,6 +2392,34 @@ std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
|
|||||||
members.push_back(ref->GetSource());
|
members.push_back(ref->GetSource());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return members;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
|
||||||
|
{
|
||||||
|
std::vector<Player*> 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;
|
return members;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,10 +36,28 @@
|
|||||||
#include "BroadcastHelper.h"
|
#include "BroadcastHelper.h"
|
||||||
#include "PlayerbotDbStore.h"
|
#include "PlayerbotDbStore.h"
|
||||||
#include "WorldSessionMgr.h"
|
#include "WorldSessionMgr.h"
|
||||||
#include "DatabaseEnv.h" // Added for gender choice
|
#include "DatabaseEnv.h"
|
||||||
#include <algorithm> // Added for gender choice
|
#include <algorithm>
|
||||||
#include "Log.h" // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
#include "Log.h"
|
||||||
#include <shared_mutex> // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
#include <shared_mutex> // 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<TravelTarget*>("travel target"))
|
||||||
|
tt->Set(nullptr);
|
||||||
|
|
||||||
|
// TODO: add other pointer-type values here if you have any
|
||||||
|
// e.g.: ctx->GetValue<SomePtr>("some key")->Set(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class BotInitGuard
|
class BotInitGuard
|
||||||
{
|
{
|
||||||
@@ -124,7 +142,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
|||||||
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer);
|
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer);
|
||||||
if (!mgr)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
uint32 count = mgr->GetPlayerbotsCount() + botLoading.size();
|
uint32 count = mgr->GetPlayerbotsCount() + botLoading.size();
|
||||||
@@ -204,31 +222,70 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
|||||||
|
|
||||||
void PlayerbotHolder::UpdateSessions()
|
void PlayerbotHolder::UpdateSessions()
|
||||||
{
|
{
|
||||||
for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr)
|
// snapshot of keys
|
||||||
|
std::vector<ObjectGuid> guids;
|
||||||
|
guids.reserve(playerBots.size());
|
||||||
|
for (auto const& kv : playerBots)
|
||||||
|
guids.push_back(kv.first);
|
||||||
|
|
||||||
|
// safe iterate of snapshot of keys.
|
||||||
|
for (ObjectGuid const& guid : guids)
|
||||||
{
|
{
|
||||||
Player* const bot = itr->second;
|
Player* bot = GetPlayerBot(guid);
|
||||||
|
if (!bot)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (bot->IsBeingTeleported())
|
if (bot->IsBeingTeleported())
|
||||||
{
|
{
|
||||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot))
|
||||||
if (botAI)
|
|
||||||
{
|
{
|
||||||
botAI->HandleTeleportAck();
|
botAI->HandleTeleportAck();
|
||||||
|
|
||||||
|
// Don’t process packets in the same tick as a teleport ack
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (bot->IsInWorld())
|
|
||||||
|
if (!bot->IsInWorld())
|
||||||
{
|
{
|
||||||
HandleBotPackets(bot->GetSession());
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WorldSession* sess = bot->GetSession())
|
||||||
|
{
|
||||||
|
// This may log the bot out or mutate the map, hence the usage of a snapshop
|
||||||
|
HandleBotPackets(sess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerbotHolder::HandleBotPackets(WorldSession* session)
|
/*void PlayerbotHolder::HandleBotPackets(WorldSession* session)
|
||||||
{
|
{
|
||||||
WorldPacket* packet;
|
WorldPacket* packet;
|
||||||
while (session->GetPacketQueue().next(packet))
|
while (session->GetPacketQueue().next(packet))
|
||||||
{
|
{
|
||||||
OpcodeClient opcode = static_cast<OpcodeClient>(packet->GetOpcode());
|
OpcodeClient opcode = static_cast<OpcodeClient>(packet->GetOpcode());
|
||||||
ClientOpcodeHandler const* opHandle = opcodeTable[opcode];
|
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<OpcodeClient>(packet->GetOpcode());
|
||||||
|
const ClientOpcodeHandler* opHandle = opcodeTable[opcode];
|
||||||
|
|
||||||
|
if (!opHandle)
|
||||||
|
{
|
||||||
|
// Unknown handler: drop cleanly
|
||||||
|
delete packet;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
opHandle->Call(session, *packet);
|
opHandle->Call(session, *packet);
|
||||||
delete packet;
|
delete packet;
|
||||||
}
|
}
|
||||||
@@ -318,10 +375,13 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
|
|||||||
sPlayerbotDbStore->Save(botAI);
|
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);
|
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;
|
WorldSession* masterWorldSessionPtr = nullptr;
|
||||||
|
|
||||||
if (botWorldSessionPtr->isLogingOut())
|
if (botWorldSessionPtr->isLogingOut())
|
||||||
@@ -354,11 +414,13 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
|
|||||||
logout = true;
|
logout = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TravelTarget* target = nullptr;
|
/*TravelTarget* target = nullptr;
|
||||||
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.
|
||||||
{
|
{
|
||||||
target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get();
|
target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("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
|
// Peiru: Allow bots to always instant logout to see if this resolves logout crashes
|
||||||
logout = true;
|
logout = true;
|
||||||
@@ -375,19 +437,25 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
|
|||||||
botWorldSessionPtr->HandleLogoutRequestOpcode(data);
|
botWorldSessionPtr->HandleLogoutRequestOpcode(data);
|
||||||
if (!bot)
|
if (!bot)
|
||||||
{
|
{
|
||||||
RemoveFromPlayerbotsMap(guid);
|
/*RemoveFromPlayerbotsMap(guid);
|
||||||
delete botWorldSessionPtr;
|
delete botWorldSessionPtr;
|
||||||
if (target)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
else
|
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
|
delete botWorldSessionPtr; // finally delete the bot's WorldSession
|
||||||
if (target)
|
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;
|
return;
|
||||||
} // if instant logout possible, do it
|
} // if instant logout possible, do it
|
||||||
@@ -420,11 +488,11 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid)
|
|||||||
sPlayerbotDbStore->Save(botAI);
|
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);
|
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<TravelTarget*>("travel target")->Get();
|
TravelTarget* target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get();
|
||||||
if (target)
|
if (target)
|
||||||
@@ -433,7 +501,9 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid)
|
|||||||
|
|
||||||
RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
|
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 +634,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
|||||||
//}
|
//}
|
||||||
mgroup->AddMember(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);
|
(int)mgroup->GetMembersCount(), mgroup->isRaidGroup() ? 1 : 0, mgroup->isLFGGroup() ? 1 : 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -744,9 +814,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 &&
|
if (master->GetSession()->GetSecurity() <= SEC_PLAYER && sPlayerbotAIConfig->autoInitOnly &&
|
||||||
cmd != "init=auto")
|
cmd != "init=auto")
|
||||||
@@ -1601,7 +1673,7 @@ void PlayerbotMgr::OnBotLoginInternal(Player* const bot)
|
|||||||
botAI->SetMaster(master);
|
botAI->SetMaster(master);
|
||||||
botAI->ResetStrategies();
|
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)
|
void PlayerbotMgr::OnPlayerLogin(Player* player)
|
||||||
@@ -1794,7 +1866,7 @@ void PlayerbotsMgr::RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntr
|
|||||||
{
|
{
|
||||||
delete it->second;
|
delete it->second;
|
||||||
_playerbotsAIMap.erase(it);
|
_playerbotsAIMap.erase(it);
|
||||||
LOG_DEBUG("playerbots", "Removed stale AI for GUID {}",
|
LOG_DEBUG("mod-playerbots", "Removed stale AI for GUID {}",
|
||||||
static_cast<uint64>(guid.GetRawValue()));
|
static_cast<uint64>(guid.GetRawValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ public:
|
|||||||
|
|
||||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Player* receiver) override
|
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))
|
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(receiver))
|
||||||
{
|
{
|
||||||
@@ -145,14 +145,23 @@ public:
|
|||||||
|
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
|
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())
|
if (Player* member = itr->GetSource())
|
||||||
{
|
{
|
||||||
@@ -161,6 +170,18 @@ public:
|
|||||||
botAI->HandleCommand(type, msg, player);
|
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())
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -994,9 +994,18 @@ void RandomPlayerbotMgr::CheckBgQueue()
|
|||||||
isRated = ginfo.IsRated;
|
isRated = ginfo.IsRated;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) ||
|
/*if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) ||
|
||||||
(player->InArena() && player->GetBattleground()->isRated()))
|
(player->InArena() && player->GetBattleground()->isRated()))
|
||||||
|
isRated = true;*/
|
||||||
|
if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID())) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528
|
||||||
|
{
|
||||||
isRated = true;
|
isRated = true;
|
||||||
|
}
|
||||||
|
else if (Battleground const* bg = player->GetBattleground())
|
||||||
|
{
|
||||||
|
if (player->InArena() && bg->isRated())
|
||||||
|
isRated = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (isRated)
|
if (isRated)
|
||||||
BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount++;
|
BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount++;
|
||||||
@@ -1011,15 +1020,24 @@ void RandomPlayerbotMgr::CheckBgQueue()
|
|||||||
else
|
else
|
||||||
BattlegroundData[queueTypeId][bracketId].bgHordePlayerCount++;
|
BattlegroundData[queueTypeId][bracketId].bgHordePlayerCount++;
|
||||||
|
|
||||||
// If a player has joined the BG, update the instance count in BattlegroundData (for consistency)
|
/*// If a player has joined the BG, update the instance count in BattlegroundData (for consistency)
|
||||||
if (player->InBattleground())
|
if (player->InBattleground())
|
||||||
{
|
{
|
||||||
std::vector<uint32>* instanceIds = nullptr;
|
std::vector<uint32>* instanceIds = nullptr;
|
||||||
uint32 instanceId = player->GetBattleground()->GetInstanceID();
|
uint32 instanceId = player->GetBattleground()->GetInstanceID();
|
||||||
|
|
||||||
instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances;
|
instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances;*/
|
||||||
if (instanceIds &&
|
// If a player has joined the BG, update the instance count in BattlegroundData (for consistency)
|
||||||
|
if (Battleground const* bg = player->GetBattleground()) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528
|
||||||
|
{
|
||||||
|
std::vector<uint32>* instanceIds = nullptr;
|
||||||
|
uint32 instanceId = bg->GetInstanceID();
|
||||||
|
|
||||||
|
instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances;
|
||||||
|
|
||||||
|
if (instanceIds &&
|
||||||
std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end())
|
std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end())
|
||||||
|
|
||||||
instanceIds->push_back(instanceId);
|
instanceIds->push_back(instanceId);
|
||||||
|
|
||||||
BattlegroundData[queueTypeId][bracketId].bgInstanceCount = instanceIds->size();
|
BattlegroundData[queueTypeId][bracketId].bgInstanceCount = instanceIds->size();
|
||||||
@@ -1082,10 +1100,20 @@ void RandomPlayerbotMgr::CheckBgQueue()
|
|||||||
isRated = ginfo.IsRated;
|
isRated = ginfo.IsRated;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bgQueue.IsPlayerInvitedToRatedArena(guid) || (bot->InArena() && bot->GetBattleground()->isRated()))
|
/*if (bgQueue.IsPlayerInvitedToRatedArena(guid) || (bot->InArena() && bot->GetBattleground()->isRated()))
|
||||||
|
isRated = true;*/
|
||||||
|
if (bgQueue.IsPlayerInvitedToRatedArena(guid)) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528
|
||||||
|
{
|
||||||
isRated = true;
|
isRated = true;
|
||||||
|
}
|
||||||
if (isRated)
|
else if (Battleground const* bg = bot->GetBattleground())
|
||||||
|
{
|
||||||
|
if (bot->InArena() && bg->isRated())
|
||||||
|
isRated = true;
|
||||||
|
}
|
||||||
|
// END [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528
|
||||||
|
|
||||||
|
if (isRated)
|
||||||
BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount++;
|
BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount++;
|
||||||
else
|
else
|
||||||
BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount++;
|
BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount++;
|
||||||
@@ -1098,10 +1126,15 @@ void RandomPlayerbotMgr::CheckBgQueue()
|
|||||||
BattlegroundData[queueTypeId][bracketId].bgHordeBotCount++;
|
BattlegroundData[queueTypeId][bracketId].bgHordeBotCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot->InBattleground())
|
/*if (bot->InBattleground())
|
||||||
{
|
{
|
||||||
std::vector<uint32>* instanceIds = nullptr;
|
std::vector<uint32>* instanceIds = nullptr;
|
||||||
uint32 instanceId = bot->GetBattleground()->GetInstanceID();
|
uint32 instanceId = bot->GetBattleground()->GetInstanceID();*/
|
||||||
|
if (Battleground const* bg = bot->GetBattleground()) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528
|
||||||
|
{
|
||||||
|
std::vector<uint32>* instanceIds = nullptr;
|
||||||
|
uint32 instanceId = bg->GetInstanceID();
|
||||||
|
//END [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528
|
||||||
bool isArena = false;
|
bool isArena = false;
|
||||||
bool isRated = false;
|
bool isRated = false;
|
||||||
|
|
||||||
@@ -1109,7 +1142,8 @@ void RandomPlayerbotMgr::CheckBgQueue()
|
|||||||
if (bot->InArena())
|
if (bot->InArena())
|
||||||
{
|
{
|
||||||
isArena = true;
|
isArena = true;
|
||||||
if (bot->GetBattleground()->isRated())
|
// if (bot->GetBattleground()->isRated())
|
||||||
|
if (bg->isRated()) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528
|
||||||
{
|
{
|
||||||
isRated = true;
|
isRated = true;
|
||||||
instanceIds = &BattlegroundData[queueTypeId][bracketId].ratedArenaInstances;
|
instanceIds = &BattlegroundData[queueTypeId][bracketId].ratedArenaInstances;
|
||||||
@@ -1725,7 +1759,11 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prevent blink to be detected by visible real players
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2333,8 +2371,10 @@ void RandomPlayerbotMgr::RandomizeFirst(Player* bot)
|
|||||||
PlayerbotsDatabase.Execute(stmt);
|
PlayerbotsDatabase.Execute(stmt);
|
||||||
|
|
||||||
// teleport to a random inn for bot level
|
// teleport to a random inn for bot level
|
||||||
if (GET_PLAYERBOT_AI(bot))
|
/*if (GET_PLAYERBOT_AI(bot))
|
||||||
GET_PLAYERBOT_AI(bot)->Reset(true);
|
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())
|
if (bot->GetGroup())
|
||||||
bot->RemoveFromGroup();
|
bot->RemoveFromGroup();
|
||||||
@@ -2374,8 +2414,10 @@ void RandomPlayerbotMgr::RandomizeMin(Player* bot)
|
|||||||
PlayerbotsDatabase.Execute(stmt);
|
PlayerbotsDatabase.Execute(stmt);
|
||||||
|
|
||||||
// teleport to a random inn for bot level
|
// teleport to a random inn for bot level
|
||||||
if (GET_PLAYERBOT_AI(bot))
|
/*if (GET_PLAYERBOT_AI(bot))
|
||||||
GET_PLAYERBOT_AI(bot)->Reset(true);
|
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())
|
if (bot->GetGroup())
|
||||||
bot->RemoveFromGroup();
|
bot->RemoveFromGroup();
|
||||||
@@ -2468,7 +2510,7 @@ void RandomPlayerbotMgr::Refresh(Player* bot)
|
|||||||
|
|
||||||
bool RandomPlayerbotMgr::IsRandomBot(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())
|
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
|
||||||
return false;
|
return false;
|
||||||
@@ -2478,6 +2520,17 @@ bool RandomPlayerbotMgr::IsRandomBot(Player* bot)
|
|||||||
return IsRandomBot(bot->GetGUID().GetCounter());
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2495,7 +2548,7 @@ bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot)
|
|||||||
|
|
||||||
bool RandomPlayerbotMgr::IsAddclassBot(Player* bot)
|
bool RandomPlayerbotMgr::IsAddclassBot(Player* bot)
|
||||||
{
|
{
|
||||||
if (bot && GET_PLAYERBOT_AI(bot))
|
/*if (bot && GET_PLAYERBOT_AI(bot))
|
||||||
{
|
{
|
||||||
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
|
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
|
||||||
return false;
|
return false;
|
||||||
@@ -2505,6 +2558,17 @@ bool RandomPlayerbotMgr::IsAddclassBot(Player* bot)
|
|||||||
return IsAddclassBot(bot->GetGUID().GetCounter());
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2844,8 +2908,9 @@ void RandomPlayerbotMgr::HandleCommand(uint32 type, std::string const text, Play
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer); // Possible crash source because we don't check if the returned pointer is not null
|
||||||
GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer);
|
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 +2983,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
|
|||||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||||
{
|
{
|
||||||
Player* member = gref->GetSource();
|
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 (botAI && member == player && (!botAI->GetMaster() || GET_PLAYERBOT_AI(botAI->GetMaster())))
|
||||||
{
|
{
|
||||||
if (!bot->InBattleground())
|
if (!bot->InBattleground())
|
||||||
@@ -2929,6 +2994,20 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
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 +3146,29 @@ void RandomPlayerbotMgr::PrintStats()
|
|||||||
lvlPerClass[bot->getClass()] += bot->GetLevel();
|
lvlPerClass[bot->getClass()] += bot->GetLevel();
|
||||||
lvlPerRace[bot->getRace()] += bot->GetLevel();
|
lvlPerRace[bot->getRace()] += bot->GetLevel();
|
||||||
|
|
||||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
/*PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||||
if (botAI->AllowActivity())
|
if (botAI->AllowActivity())
|
||||||
++active;
|
++active;
|
||||||
|
|
||||||
if (botAI->GetAiObjectContext()->GetValue<bool>("random bot update")->Get())
|
if (botAI->GetAiObjectContext()->GetValue<bool>("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<bool>("random bot update"))
|
||||||
|
if (v->Get())
|
||||||
|
++update;
|
||||||
|
}
|
||||||
|
// End CrashFix
|
||||||
|
|
||||||
uint32 botId = bot->GetGUID().GetCounter();
|
uint32 botId = bot->GetGUID().GetCounter();
|
||||||
if (!GetEventValue(botId, "randomize"))
|
if (!GetEventValue(botId, "randomize"))
|
||||||
++randomize;
|
++randomize;
|
||||||
|
|||||||
@@ -1227,7 +1227,7 @@ std::string const QuestObjectiveTravelDestination::getTitle()
|
|||||||
return out.str();
|
return out.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RpgTravelDestination::isActive(Player* bot)
|
/*bool RpgTravelDestination::isActive(Player* bot) // Old Code
|
||||||
{
|
{
|
||||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||||
AiObjectContext* context = botAI->GetAiObjectContext();
|
AiObjectContext* context = botAI->GetAiObjectContext();
|
||||||
@@ -1264,6 +1264,62 @@ bool RpgTravelDestination::isActive(Player* bot)
|
|||||||
ReputationRank reaction = bot->GetReputationRank(factionEntry->faction);
|
ReputationRank reaction = bot->GetReputationRank(factionEntry->faction);
|
||||||
|
|
||||||
return reaction > REP_NEUTRAL;
|
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<GuidSet&>("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); }
|
CreatureTemplate const* RpgTravelDestination::GetCreatureTemplate() { return sObjectMgr->GetCreatureTemplate(entry); }
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
|
|||||||
WorldLocation location = *target->getPosition();
|
WorldLocation location = *target->getPosition();
|
||||||
|
|
||||||
Group* group = bot->GetGroup();
|
Group* group = bot->GetGroup();
|
||||||
if (group && !urand(0, 1) && bot == botAI->GetGroupMaster())
|
if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat())
|
||||||
{
|
{
|
||||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class FindTargetForTankStrategy : public FindNonCcTargetStrategy
|
|||||||
public:
|
public:
|
||||||
FindTargetForTankStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minThreat(0) {}
|
FindTargetForTankStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minThreat(0) {}
|
||||||
|
|
||||||
void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override
|
/*void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override
|
||||||
{
|
{
|
||||||
if (!creature || !creature->IsAlive())
|
if (!creature || !creature->IsAlive())
|
||||||
{
|
{
|
||||||
@@ -37,6 +37,43 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (minThreat >= threat)
|
||||||
|
{
|
||||||
|
minThreat = threat;
|
||||||
|
result = creature;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override
|
||||||
|
{
|
||||||
|
// [Crash fix] Filter out anything that is not ready/valid
|
||||||
|
if (!creature || !creature->IsAlive() || !creature->IsInWorld() || creature->IsDuringRemoveFromWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!threatMgr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
if (!bot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float threat = threatMgr->GetThreat(bot);
|
||||||
|
|
||||||
|
if (!result || !result->IsAlive() || !result->IsInWorld() || result->IsDuringRemoveFromWorld())
|
||||||
|
{
|
||||||
|
// [Crash fix] If the previous target has become invalid, restart cleanly
|
||||||
|
minThreat = threat;
|
||||||
|
result = creature;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neglect si la victime actuelle est le MT (ou s'il n'y a pas de victime)
|
||||||
|
if (HostileReference* cv = threatMgr->getCurrentVictim())
|
||||||
|
{
|
||||||
|
Unit* victim = cv->getTarget();
|
||||||
|
if (victim && victim->ToPlayer() && botAI->IsMainTank(victim->ToPlayer()))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (minThreat >= threat)
|
if (minThreat >= threat)
|
||||||
{
|
{
|
||||||
minThreat = threat;
|
minThreat = threat;
|
||||||
@@ -53,7 +90,7 @@ class FindTankTargetSmartStrategy : public FindTargetStrategy
|
|||||||
public:
|
public:
|
||||||
FindTankTargetSmartStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI) {}
|
FindTankTargetSmartStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI) {}
|
||||||
|
|
||||||
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
|
/*void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
|
||||||
{
|
{
|
||||||
if (Group* group = botAI->GetBot()->GetGroup())
|
if (Group* group = botAI->GetBot()->GetGroup())
|
||||||
{
|
{
|
||||||
@@ -69,8 +106,32 @@ public:
|
|||||||
{
|
{
|
||||||
result = attacker;
|
result = attacker;
|
||||||
}
|
}
|
||||||
|
}*/
|
||||||
|
void CheckAttacker(Unit* attacker, ThreatMgr* /*threatMgr*/) override
|
||||||
|
{
|
||||||
|
// [Crash fix] Protect against null/out-of-world/being-removed units
|
||||||
|
if (!attacker || !attacker->IsAlive() || !attacker->IsInWorld() || attacker->IsDuringRemoveFromWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Player* me = botAI->GetBot())
|
||||||
|
{
|
||||||
|
if (Group* group = me->GetGroup())
|
||||||
|
{
|
||||||
|
ObjectGuid guid = group->GetTargetIcon(4);
|
||||||
|
if (guid && attacker->GetGUID() == guid)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Crash fix] If 'result' has become invalid, forget it
|
||||||
|
if (result && (!result->IsAlive() || !result->IsInWorld() || result->IsDuringRemoveFromWorld()))
|
||||||
|
result = nullptr;
|
||||||
|
|
||||||
|
if (!result || IsBetter(attacker, result))
|
||||||
|
result = attacker;
|
||||||
}
|
}
|
||||||
bool IsBetter(Unit* new_unit, Unit* old_unit)
|
|
||||||
|
/*bool IsBetter(Unit* new_unit, Unit* old_unit)
|
||||||
{
|
{
|
||||||
Player* bot = botAI->GetBot();
|
Player* bot = botAI->GetBot();
|
||||||
// if group has multiple tanks, main tank just focus on the current target
|
// if group has multiple tanks, main tank just focus on the current target
|
||||||
@@ -97,8 +158,47 @@ public:
|
|||||||
return new_dis < old_dis;
|
return new_dis < old_dis;
|
||||||
}
|
}
|
||||||
return new_threat < old_threat;
|
return new_threat < old_threat;
|
||||||
|
}*/
|
||||||
|
bool IsBetter(Unit* new_unit, Unit* old_unit)
|
||||||
|
{
|
||||||
|
// [Crash fix] If either one is invalid, decide straight away
|
||||||
|
if (!new_unit || !new_unit->IsAlive() || !new_unit->IsInWorld() || new_unit->IsDuringRemoveFromWorld())
|
||||||
|
return false;
|
||||||
|
if (!old_unit || !old_unit->IsAlive() || !old_unit->IsInWorld() || old_unit->IsDuringRemoveFromWorld())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
if (!bot)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// if multiple tanks, logically focus on the current target
|
||||||
|
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("current target")->Get();
|
||||||
|
if (currentTarget && botAI->IsMainTank(bot) && botAI->GetGroupTankNum(bot) > 1)
|
||||||
|
{
|
||||||
|
if (old_unit == currentTarget)
|
||||||
|
return false;
|
||||||
|
if (new_unit == currentTarget)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float new_threat = new_unit->GetThreatMgr().GetThreat(bot);
|
||||||
|
float old_threat = old_unit->GetThreatMgr().GetThreat(bot);
|
||||||
|
float new_dis = bot->GetDistance(new_unit);
|
||||||
|
float old_dis = bot->GetDistance(old_unit);
|
||||||
|
|
||||||
|
// hasAggro? -> withinMelee? -> threat
|
||||||
|
int nl = GetIntervalLevel(new_unit);
|
||||||
|
int ol = GetIntervalLevel(old_unit);
|
||||||
|
if (nl != ol)
|
||||||
|
return nl > ol;
|
||||||
|
|
||||||
|
if (nl == 2)
|
||||||
|
return new_dis < old_dis;
|
||||||
|
|
||||||
|
return new_threat < old_threat;
|
||||||
}
|
}
|
||||||
int32_t GetIntervalLevel(Unit* unit)
|
|
||||||
|
/*int32_t GetIntervalLevel(Unit* unit)
|
||||||
{
|
{
|
||||||
if (!botAI->HasAggro(unit))
|
if (!botAI->HasAggro(unit))
|
||||||
{
|
{
|
||||||
@@ -109,12 +209,28 @@ public:
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
}*/
|
||||||
|
int32_t GetIntervalLevel(Unit* unit)
|
||||||
|
{
|
||||||
|
// [Crash fix] Basic guards
|
||||||
|
if (!unit || !unit->IsAlive() || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!botAI->HasAggro(unit))
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
if (Player* bot = botAI->GetBot())
|
||||||
|
{
|
||||||
|
if (bot->IsWithinMeleeRange(unit))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Unit* TankTargetValue::Calculate()
|
Unit* TankTargetValue::Calculate()
|
||||||
{
|
{
|
||||||
// FindTargetForTankStrategy strategy(botAI);
|
// [Note] Using the "smart" strategy below. Guards have been added in CheckAttacker/IsBetter.
|
||||||
FindTankTargetSmartStrategy strategy(botAI);
|
FindTankTargetSmartStrategy strategy(botAI);
|
||||||
return FindTarget(&strategy);
|
return FindTarget(&strategy);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
Unit* FindTargetStrategy::GetResult() { return result; }
|
Unit* FindTargetStrategy::GetResult() { return result; }
|
||||||
|
|
||||||
Unit* TargetValue::FindTarget(FindTargetStrategy* strategy)
|
/*Unit* TargetValue::FindTarget(FindTargetStrategy* strategy)
|
||||||
{
|
{
|
||||||
GuidVector attackers = botAI->GetAiObjectContext()->GetValue<GuidVector>("attackers")->Get();
|
GuidVector attackers = botAI->GetAiObjectContext()->GetValue<GuidVector>("attackers")->Get();
|
||||||
for (ObjectGuid const guid : attackers)
|
for (ObjectGuid const guid : attackers)
|
||||||
@@ -27,6 +27,28 @@ Unit* TargetValue::FindTarget(FindTargetStrategy* strategy)
|
|||||||
strategy->CheckAttacker(unit, &ThreatMgr);
|
strategy->CheckAttacker(unit, &ThreatMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return strategy->GetResult();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
Unit* TargetValue::FindTarget(FindTargetStrategy* strategy)
|
||||||
|
{
|
||||||
|
// [Crash fix] The very first AI tick can occur before everything is "in world".
|
||||||
|
// Filter out units that are non-living / being removed / out of world.
|
||||||
|
AiObjectContext* ctx = botAI->GetAiObjectContext();
|
||||||
|
if (!ctx)
|
||||||
|
return strategy->GetResult();
|
||||||
|
|
||||||
|
GuidVector attackers = ctx->GetValue<GuidVector>("attackers")->Get();
|
||||||
|
for (ObjectGuid const& guid : attackers)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(guid);
|
||||||
|
if (!unit || !unit->IsAlive() || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ThreatMgr& threatMgrRef = unit->GetThreatMgr();
|
||||||
|
strategy->CheckAttacker(unit, &threatMgrRef);
|
||||||
|
}
|
||||||
|
|
||||||
return strategy->GetResult();
|
return strategy->GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user