Compare commits

..

17 Commits

Author SHA1 Message Date
bash
706ef442c1 Fix another C0000005 ACCESS_VIOLATION
https://github.com/liyunfan1223/mod-playerbots/issues/1537
2025-08-12 00:12:51 +02:00
Alex Dcnh
c6b0424c29 [Fix issue #1527] : startup crash in tank target selection — add TOCTOU & null-safety guards (#1532)
* Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks

* Fix Issue 1528

* Fix Issue #1527
2025-08-11 17:00:31 +02:00
Alex Dcnh
2e0a161623 [Fix issue #1528] Close small window where the “in a BG/arena” state can change between the check (InBattleground() / InArena()) and grabbing the pointer (GetBattleground()), which leads to a null dereference. (#1530)
* Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks

* Fix Issue 1528
2025-08-11 16:27:38 +02:00
Alex Dcnh
e4ea8e2694 Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks (#1529) 2025-08-11 16:27:25 +02:00
bash
ddfa919154 Dont wait to travel when in combat. (#1524)
Prevents bot adding a travel delay when in combat
2025-08-11 01:11:54 +02:00
bash
380312ffd2 nullptr fix (#1523) 2025-08-10 22:59:34 +02:00
Alex Dcnh
872e417613 Playerbots/LFG: fix false not eligible & dungeon 0/type 0, add clear diagnostics (#1521)
Tested
2025-08-10 21:23:02 +02:00
bash
3d28a81508 nullptr exception (#1520) 2025-08-10 19:31:10 +02:00
bash
bcd6f5bc06 Removed bot freezing at startup and system message, not relevant anymore (#1519) 2025-08-10 19:11:39 +02:00
bash
15f138aab0 Don't apply XPRate multiplier when bot is in group with real player (#1495)
* dont apply XPRate if bot is in group with real player

https://github.com/liyunfan1223/mod-playerbots/issues/1490

* Optimize code

* Oops minor correction

* Defense check on the player itself

* Safer way to check the leader is real player.

* Added abit more defense programming, should be needed still ..why not
2025-08-10 18:28:39 +02:00
ThePenguinMan96
5759a98d5a Warlock Soul Shard Cap Increase / Firestone and Spellstone Modes (#1514)
Hello everyone,

This is a small change to warlocks that accomplishes 2 things:

1. Changes the firestone and spellstone weapon enchants so only one of them can be active - players reported to me that both strategies could be present before, resulting in a bug where the bot repeatedly applied the enchant.
2. Changes the soul shard deletion cap from 6 or more to 26 or more - players will now be able to stockpile soul shards up to 25 in a bot's inventory before the bot starts deleting them one at a time back down 25. I chose 25 because if it was higher, drain soul would get multiple shards above the 32 unique cap, and spam the player "I can't carry any more of those". It was super annoying, and with testing, I have not seen this error at 25. This aims to address issue #1502 .
2025-08-10 11:13:01 +02:00
brighton-chi
13fca4398d Remove EPL from pvp prohibited zones (#1511)
* Remove EPL from pvp prohibited zones

* fixed unrelated error in comments

* Update default value
2025-08-09 20:59:55 +02:00
Yunfan Li
a307eb2f08 VisitAllObjects to VisitObjects (sync with acore) (#1513) 2025-08-09 19:17:33 +08:00
Alex Dcnh
966bf1d6af Correct side effects of merge f5ef5bd1c2 (#1512)
* Update PlayerbotMgr.h

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.h

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.h

* Update PlayerbotMgr.cpp

* Update PlayerbotMgr.h

* Update PlayerbotMgr.h
2025-08-08 20:36:03 +02:00
Alex Dcnh
f5ef5bd1c2 Fix ACCESS_VIOLATION in mod-playerbots: purge stale AIs, add thread-safety, and harden HasRealPlayerMaster (#1507) 2025-08-07 00:31:00 +02:00
ThePenguinMan96
0afcf29490 Warlock Dismount Pet Fix (#1501)
Hello everyone,

This PR is to address #1489, where the warlock summons a pet when they dismount.

A tester found that the cause was the "wrong pet" triggering "summon (pet)". I looked into the "wrong pet" trigger, and noticed that there was not a clause if there was no active pet. It was inadvertently casting "summon (pet)" because for a brief second after dismounting, the warlock didn't technically have a pet.

I was able to recreate the issue based on tester feedback (dismounting with a warlock bot that has a pet). I tested this fix locally - it seems to work as intended. The warlock no longer attempts to summon a pet when dismounting. I tested it with nc +debug, and noticed that the "wrong pet" trigger was no longer firing. I also checked the logs - nothing.

Thank y'all for the testing and feedback!
2025-08-05 09:10:06 +02:00
brighton-chi
a6c07ca16d Improve attackaction failure message system (#1498) 2025-08-03 23:12:33 +02:00
30 changed files with 867 additions and 208 deletions

View File

@@ -502,8 +502,8 @@ AiPlayerbot.AutoGearScoreLimit = 0
# "power" (bots have infinite energy, rage, and runic power)
# "taxi" (bots may use all flight paths, though they will not actually learn them)
# "raid" (bots use cheats implemented into raid strategies)
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,taxi")
# Default: taxi is enabled
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi")
# Default: taxi and raid are enabled
AiPlayerbot.BotCheats = "taxi,raid"
#
@@ -1107,7 +1107,7 @@ AiPlayerbot.RandomBotArenaTeamMinRating = 1000
AiPlayerbot.DeleteRandomBotArenaTeams = 0
# PvP Restricted Zones (bots don't pvp)
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,139,3951"
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
# PvP Restricted Areas (bots don't pvp)
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080"

View File

@@ -947,8 +947,21 @@ bool BroadcastHelper::BroadcastSuggestThunderfury(PlayerbotAI* ai, Player* bot)
{
std::map<std::string, std::string> 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),

View File

@@ -2373,7 +2373,7 @@ std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry)
return name;
}
std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
/*std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
{
std::vector<Player*> members;
@@ -2392,6 +2392,34 @@ std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
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;
}
@@ -3970,15 +3998,37 @@ bool IsAlliance(uint8 race)
bool PlayerbotAI::HasRealPlayerMaster()
{
if (master)
// if (master)
// {
// PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
// return !masterBotAI || masterBotAI->IsRealPlayer();
// }
//
// return false;
// Removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
/* 1) The "master" pointer can be null if the bot was created
without a master player or if the master was just removed. */
if (!master)
return false;
/* 2) Is the master player still present in the world?
If FindPlayer fails, we invalidate "master" and stop here. */
if (!ObjectAccessor::FindPlayer(master->GetGUID()))
{
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
return !masterBotAI || masterBotAI->IsRealPlayer();
master = nullptr; // avoids repeating the check on the next tick
return false;
}
return false;
/* 3) If the master is a bot, we check that it is itself controlled
by a real player. Otherwise, it's already a real player → true. */
if (PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master))
return masterBotAI->IsRealPlayer(); // bot controlled by a player?
return true; // master = real player
}
bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(master); }
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
@@ -4192,19 +4242,6 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
}
}
// only keep updating till initializing time has completed,
// which prevents unneeded expensive GameTime calls.
if (_isBotInitializing)
{
_isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.11;
// no activity allowed during bot initialization
if (_isBotInitializing)
{
return false;
}
}
// General exceptions
if (activityType == PACKET_ACTIVITY)
{

View File

@@ -611,7 +611,6 @@ private:
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
void HandleCommands();
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
bool _isBotInitializing = false;
protected:
Player* bot;

View File

@@ -148,7 +148,7 @@ bool PlayerbotAIConfig::Initialize()
LoadList<std::vector<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedZoneIds",
"2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,"
"3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,139,3951"),
"3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"),
pvpProhibitedZoneIds);
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds", "976,35"),
pvpProhibitedAreaIds);

View File

@@ -36,8 +36,28 @@
#include "BroadcastHelper.h"
#include "PlayerbotDbStore.h"
#include "WorldSessionMgr.h"
#include "DatabaseEnv.h" // Added for gender choice
#include <algorithm> // Added for gender choice
#include "DatabaseEnv.h"
#include <algorithm>
#include "Log.h"
#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
{
@@ -122,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();
@@ -202,31 +222,70 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
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())
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI)
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot))
{
botAI->HandleTeleportAck();
// Dont 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;
while (session->GetPacketQueue().next(packet))
{
OpcodeClient opcode = static_cast<OpcodeClient>(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<OpcodeClient>(packet->GetOpcode());
const ClientOpcodeHandler* opHandle = opcodeTable[opcode];
if (!opHandle)
{
// Unknown handler: drop cleanly
delete packet;
continue;
}
opHandle->Call(session, *packet);
delete packet;
}
@@ -316,10 +375,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())
@@ -352,11 +414,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<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
logout = true;
@@ -373,19 +437,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
@@ -418,11 +488,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<TravelTarget*>("travel target")->Get();
if (target)
@@ -431,7 +501,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);
}
}
@@ -527,6 +599,9 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
{
botAI->ResetStrategies(!sRandomPlayerbotMgr->IsRandomBot(bot));
}
botAI->Reset(true); // Reset transient states (incl. LFG "proposal") to avoid the "one or more players are not eligible" error after reconnect.
sPlayerbotDbStore->Load(botAI);
if (master && !master->HasUnitState(UNIT_STATE_IN_FLIGHT))
@@ -546,16 +621,21 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
if (master && master->GetGroup() && !group)
{
Group* mgroup = master->GetGroup();
if (mgroup->GetMembersCount() >= 5)
// if (mgroup->GetMembersCount() >= 5)
if (mgroup->GetMembersCount() + 1 > 5) // only convert in raid if the add of THIS bot make group > 5
{
if (!mgroup->isRaidGroup() && !mgroup->isLFGGroup() && !mgroup->isBGGroup() && !mgroup->isBFGroup())
{
mgroup->ConvertToRaid();
}
if (mgroup->isRaidGroup())
{
mgroup->AddMember(bot);
}
//if (mgroup->isRaidGroup())
//{
//mgroup->AddMember(bot);
//}
mgroup->AddMember(bot);
LOG_DEBUG("mod-playerbots", "[GROUP] after add: members={}, isRaid={}, isLFG={}",
(int)mgroup->GetMembersCount(), mgroup->isRaidGroup() ? 1 : 0, mgroup->isLFGGroup() ? 1 : 0);
}
else
{
@@ -734,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 &&
cmd != "init=auto")
@@ -1591,7 +1673,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)
@@ -1726,23 +1808,72 @@ void PlayerbotsMgr::RemovePlayerBotData(ObjectGuid const& guid, bool is_AI)
PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player)
{
if (!(sPlayerbotAIConfig->enabled) || !player)
{
return nullptr;
}
// if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) {
// if (!(sPlayerbotAIConfig->enabled) || !player)
// {
// return nullptr;
// }
auto itr = _playerbotsAIMap.find(player->GetGUID());
if (itr != _playerbotsAIMap.end())
{
if (itr->second->IsBotAI())
return reinterpret_cast<PlayerbotAI*>(itr->second);
// // if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) {
// // return nullptr;
// // }
// auto itr = _playerbotsAIMap.find(player->GetGUID());
// if (itr != _playerbotsAIMap.end())
// {
// if (itr->second->IsBotAI())
// return reinterpret_cast<PlayerbotAI*>(itr->second);
// }
//
// return nullptr;
// removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
if (!player || !sPlayerbotAIConfig->enabled)
return nullptr;
// First read the GUID into a local variable, but ONLY after the check!
ObjectGuid guid = player->GetGUID(); // <-- OK here, we know that player != nullptr
{
std::shared_lock rlock(_aiMutex);
auto it = _playerbotsAIMap.find(guid);
if (it != _playerbotsAIMap.end() && it->second->IsBotAI())
return static_cast<PlayerbotAI*>(it->second);
}
// Transient state: NEVER break the master ⇄ bots relationship here.
if (!ObjectAccessor::FindPlayer(guid))
{
RemovePlayerbotAI(guid, /*removeMgrEntry=*/false);
}
return nullptr;
}
// removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
PlayerbotAI* PlayerbotsMgr::GetPlayerbotAIByGuid(ObjectGuid guid)
{
if (!sPlayerbotAIConfig->enabled)
return nullptr;
std::shared_lock rlock(_aiMutex);
auto it = _playerbotsAIMap.find(guid);
if (it != _playerbotsAIMap.end() && it->second->IsBotAI())
return static_cast<PlayerbotAI*>(it->second);
return nullptr;
}
void PlayerbotsMgr::RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntry /*= true*/)
{
std::unique_lock wlock(_aiMutex);
if (auto it = _playerbotsAIMap.find(guid); it != _playerbotsAIMap.end())
{
delete it->second;
_playerbotsAIMap.erase(it);
LOG_DEBUG("mod-playerbots", "Removed stale AI for GUID {}",
static_cast<uint64>(guid.GetRawValue()));
}
if (removeMgrEntry)
_playerbotsMgrMap.erase(guid); // we NO longer touch the relation in a "soft" purge
}
PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
{
if (!(sPlayerbotAIConfig->enabled) || !player)

View File

@@ -12,6 +12,7 @@
#include "PlayerbotAIBase.h"
#include "QueryHolder.h"
#include "QueryResult.h"
#include <shared_mutex> // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
class ChatHandler;
class PlayerbotAI;
@@ -114,13 +115,38 @@ public:
void RemovePlayerBotData(ObjectGuid const& guid, bool is_AI);
PlayerbotAI* GetPlayerbotAI(Player* player);
PlayerbotAI* GetPlayerbotAIByGuid(ObjectGuid guid); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
// void RemovePlayerbotAI(ObjectGuid const& guid); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
// removeMgrEntry = true => "hard" purge (AI + manager relation), for real logouts
// removeMgrEntry = false => "soft" purge (AI only), for detected "stale" cases
void RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntry = true);
PlayerbotMgr* GetPlayerbotMgr(Player* player);
private:
std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsAIMap;
std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsMgrMap;
mutable std::shared_mutex _aiMutex; // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
};
#define sPlayerbotsMgr PlayerbotsMgr::instance()
// Temporary addition If it keeps crashing, we will use them.
// Like
// BEFORE : PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
// AFTER (safe) : PlayerbotAI* botAI = GET_PLAYERBOT_AI_SAFE(bot);
// BEFORE : if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player)) { ... }
// AFTER (safe) : if (PlayerbotAI* botAI = GET_PLAYERBOT_AI_SAFE(player)) { ... }
// --- SAFE helpers (append to PlayerbotMgr.h) ---
inline PlayerbotAI* GET_PLAYERBOT_AI_SAFE(Player* p)
{
// Avoid any dereference during transient states (nullptr, teleport, flight, etc.)
return p ? sPlayerbotsMgr->GetPlayerbotAI(p) : nullptr;
}
inline PlayerbotMgr* GET_PLAYERBOT_MGR_SAFE(Player* p)
{
return p ? sPlayerbotsMgr->GetPlayerbotMgr(p) : nullptr;
}
// --- end SAFE helpers ---
#endif

View File

@@ -30,6 +30,7 @@
#include "cs_playerbots.h"
#include "cmath"
#include "BattleGroundTactics.h"
#include "ObjectAccessor.h"
class PlayerbotsDatabaseScript : public DatabaseScript
{
@@ -108,7 +109,7 @@ public:
"|cffcccccchttps://github.com/liyunfan1223/mod-playerbots|r");
}
if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin)
/*if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin)
{
std::string roundedTime =
std::to_string(std::ceil((sPlayerbotAIConfig->maxRandomBots * 0.11 / 60) * 10) / 10.0);
@@ -117,7 +118,7 @@ public:
ChatHandler(player->GetSession()).SendSysMessage(
"|cff00ff00Playerbots:|r bot initialization at server startup takes about '"
+ roundedTime + "' minutes.");
}
}*/
}
}
@@ -136,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))
{
@@ -144,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())
{
@@ -160,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);
}
}
}
@@ -176,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);
}
}
}
@@ -311,7 +335,7 @@ class PlayerbotsScript : public PlayerbotScript
public:
PlayerbotsScript() : PlayerbotScript("PlayerbotsScript") {}
bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList) override
/*bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList) override
{
bool nonBotFound = false;
for (ObjectGuid const& guid : guidsList.guids)
@@ -325,7 +349,137 @@ public:
}
return nonBotFound;
}*/
// New LFG Function
bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList)
{
const size_t totalSlots = guidsList.guids.size();
size_t ignoredEmpty = 0, ignoredNonPlayer = 0;
size_t offlinePlayers = 0, botPlayers = 0, realPlayers = 0;
bool groupGuidSeen = false;
LOG_DEBUG("playerbots", "[LFG] check start: slots={}", totalSlots);
for (size_t i = 0; i < totalSlots; ++i)
{
ObjectGuid const& guid = guidsList.guids[i];
// 1) Placeholders to ignore
if (guid.IsEmpty())
{
++ignoredEmpty;
LOG_DEBUG("playerbots", "[LFG] slot {}: <empty> -> ignored", i);
continue;
}
// Group GUID: in the original implementation this counted as "non-bot found"
if (guid.IsGroup())
{
groupGuidSeen = true;
LOG_DEBUG("playerbots", "[LFG] slot {}: <GROUP GUID> -> counts as having a real player (compat)", i);
continue;
}
// Other non-Player GUIDs: various placeholders, ignore them
if (!guid.IsPlayer())
{
++ignoredNonPlayer;
LOG_DEBUG("playerbots", "[LFG] slot {}: guid={} (non-player/high={}) -> ignored", i,
static_cast<uint64>(guid.GetRawValue()), (unsigned)guid.GetHigh());
continue;
}
// 2) Player present?
Player* player = ObjectAccessor::FindPlayer(guid);
if (!player)
{
++offlinePlayers;
LOG_DEBUG("playerbots", "[LFG] slot {}: player guid={} is offline/not in world", i,
static_cast<uint64>(guid.GetRawValue()));
continue;
}
// 3) Bot or real player?
if (GET_PLAYERBOT_AI(player) != nullptr)
{
++botPlayers;
LOG_DEBUG("playerbots", "[LFG] slot {}: BOT {} (lvl {}, class {})", i, player->GetName().c_str(),
player->GetLevel(), player->getClass());
}
else
{
++realPlayers;
LOG_DEBUG("playerbots", "[LFG] slot {}: REAL {} (lvl {}, class {})", i, player->GetName().c_str(),
player->GetLevel(), player->getClass());
}
}
// "Ultra-early phase" detection: only placeholders => DO NOT VETO
const bool onlyPlaceholders = (realPlayers + botPlayers + (groupGuidSeen ? 1 : 0)) == 0 &&
(ignoredEmpty + ignoredNonPlayer) == totalSlots;
// "Soft" LFG preflight if we actually see players AND at least one offline
if (!onlyPlaceholders && offlinePlayers > 0)
{
// Find a plausible leader: prefer a real online player, otherwise any online player
Player* leader = nullptr;
for (ObjectGuid const& guid : guidsList.guids)
if (guid.IsPlayer())
if (Player* p = ObjectAccessor::FindPlayer(guid))
if (GET_PLAYERBOT_AI(p) == nullptr)
{
leader = p;
break;
}
if (!leader)
for (ObjectGuid const& guid : guidsList.guids)
if (guid.IsPlayer())
if (Player* p = ObjectAccessor::FindPlayer(guid))
{
leader = p;
break;
}
if (leader)
{
Group* g = leader->GetGroup();
if (g)
{
LOG_DEBUG("playerbots", "[LFG-RESET] group members={}, isRaid={}, isLFGGroup={}",
(int)g->GetMembersCount(), g->isRaidGroup() ? 1 : 0, g->isLFGGroup() ? 1 : 0);
// "Soft" reset of LFG states on the bots' AI side (proposal/role-check, etc.)
for (GroupReference* ref = g->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member)
continue;
if (PlayerbotAI* ai = GET_PLAYERBOT_AI(member))
ai->Reset(true);
}
}
}
LOG_DEBUG("playerbots", "[LFG] preflight soft-reset triggered (offline detected) -> allowQueue=no (retry)");
return false; // ask the client to retry right after the reset
}
// "Hybrid" policy: permissive if only placeholders; otherwise original logic
bool allowQueue = onlyPlaceholders ? true : ((offlinePlayers == 0) && (realPlayers >= 1 || groupGuidSeen));
LOG_DEBUG("playerbots",
"[LFG] summary: slots={}, real={}, bots={}, offline={}, ignored(empty+nonPlayer)={}, "
"groupGuidSeen={} -> allowQueue={}",
totalSlots, realPlayers, botPlayers, offlinePlayers, (ignoredEmpty + ignoredNonPlayer),
(groupGuidSeen ? "yes" : "no"), (allowQueue ? "yes" : "no"));
return allowQueue;
}
// End LFG
void OnPlayerbotCheckKillTask(Player* player, Unit* victim) override
{
@@ -377,6 +531,10 @@ public:
void OnPlayerbotLogout(Player* player) override
{
// immediate purge of the bot's AI upon disconnection
if (player && player->GetSession()->IsBot())
sPlayerbotsMgr->RemovePlayerbotAI(player->GetGUID()); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);

View File

@@ -994,9 +994,18 @@ void RandomPlayerbotMgr::CheckBgQueue()
isRated = ginfo.IsRated;
}
if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) ||
/*if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) ||
(player->InArena() && player->GetBattleground()->isRated()))
isRated = true;*/
if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID())) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528
{
isRated = true;
}
else if (Battleground const* bg = player->GetBattleground())
{
if (player->InArena() && bg->isRated())
isRated = true;
}
if (isRated)
BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount++;
@@ -1011,15 +1020,24 @@ void RandomPlayerbotMgr::CheckBgQueue()
else
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())
{
std::vector<uint32>* instanceIds = nullptr;
uint32 instanceId = player->GetBattleground()->GetInstanceID();
instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances;
if (instanceIds &&
instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances;*/
// 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())
instanceIds->push_back(instanceId);
BattlegroundData[queueTypeId][bracketId].bgInstanceCount = instanceIds->size();
@@ -1082,10 +1100,20 @@ void RandomPlayerbotMgr::CheckBgQueue()
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;
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++;
else
BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount++;
@@ -1098,10 +1126,15 @@ void RandomPlayerbotMgr::CheckBgQueue()
BattlegroundData[queueTypeId][bracketId].bgHordeBotCount++;
}
if (bot->InBattleground())
/*if (bot->InBattleground())
{
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 isRated = false;
@@ -1109,7 +1142,8 @@ void RandomPlayerbotMgr::CheckBgQueue()
if (bot->InArena())
{
isArena = true;
if (bot->GetBattleground()->isRated())
// if (bot->GetBattleground()->isRated())
if (bg->isRated()) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528
{
isRated = true;
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
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;
}
@@ -2180,7 +2218,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot)
float range = sPlayerbotAIConfig->randomBotTeleportDistance;
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
if (!targets.empty())
{
@@ -2333,8 +2371,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 +2414,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 +2510,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 +2520,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 +2548,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 +2558,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 +2908,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 +2983,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 +2994,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 +3146,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<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();
if (!GetEventValue(botId, "randomize"))
++randomize;

View File

@@ -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<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); }

View File

@@ -164,15 +164,16 @@ void PlayerbotFactory::Init()
{
continue;
}
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(gemId);
if (proto) {
if (proto->ItemLevel < 60)
continue;
if (proto->ItemLevel < 60)
continue;
if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE)
{
continue;
if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE)
continue;
}
if (sRandomItemMgr->IsTestItem(gemId))
continue;
@@ -180,9 +181,11 @@ void PlayerbotFactory::Init()
{
continue;
}
// LOG_INFO("playerbots", "Add {} to enchantment gems", gemId);
enchantGemIdCache.push_back(gemId);
}
LOG_INFO("playerbots", "Loading {} enchantment gems", enchantGemIdCache.size());
}
@@ -1017,14 +1020,16 @@ void PlayerbotFactory::ClearSkills()
}
bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0);
bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0);
// unlearn default race/class skills
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass());
for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
{
uint32 skillId = itr->SkillId;
if (!bot->HasSkill(skillId))
continue;
bot->SetSkill(skillId, 0, 0, 0);
if (PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass())) {
for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
{
uint32 skillId = itr->SkillId;
if (!bot->HasSkill(skillId))
continue;
bot->SetSkill(skillId, 0, 0, 0);
}
}
}

View File

@@ -59,10 +59,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
bool sameTarget = oldTarget == target && bot->GetVictim() == target;
bool inCombat = botAI->GetState() == BOT_STATE_COMBAT;
bool sameAttackMode = bot->HasUnitState(UNIT_STATE_MELEE_ATTACKING) == shouldMelee;
// there's no reason to do attack again
if (sameTarget && inCombat && sameAttackMode)
return false;
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
@@ -82,52 +79,53 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
if (!target->IsInWorld())
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
return false;
}
if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) ||
sPlayerbotAIConfig->IsInPvpProhibitedArea(bot->GetAreaId()))
&& (target->IsPlayer() || target->IsPet()))
{
if (verbose)
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
return false;
}
if (bot->IsFriendlyTo(target))
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
return false;
}
if (target->isDead())
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is dead.");
return false;
}
if (!bot->IsWithinLOSInMap(target))
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
return false;
}
if (sameTarget && inCombat && sameAttackMode)
{
if (verbose)
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
return false;
}
if (!bot->IsValidAttackTarget(target))
{
if (verbose)
botAI->TellError("I cannot attack an invalid target");
return false;
}
std::ostringstream msg;
msg << target->GetName();
if (bot->IsFriendlyTo(target))
{
msg << " is friendly to me";
if (verbose)
botAI->TellError(msg.str());
return false;
}
if (!bot->IsWithinLOSInMap(target))
{
msg << " is not in my sight";
if (verbose)
botAI->TellError(msg.str());
return false;
}
if (target->isDead())
{
msg << " is dead";
if (verbose)
botAI->TellError(msg.str());
return false;
}
if (sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId())
&& (target->IsPlayer() || target->IsPet()))
{
if (verbose)
botAI->TellError("I cannot attack others in PvP prohibited zones");
botAI->TellError("I cannot attack an invalid target.");
return false;
}
@@ -141,9 +139,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
ObjectGuid guid = target->GetGUID();
bot->SetSelection(target->GetGUID());
context->GetValue<Unit*>("old target")->Set(oldTarget);
context->GetValue<Unit*>("old target")->Set(oldTarget);
context->GetValue<Unit*>("current target")->Set(target);
context->GetValue<LootObjectStack*>("available loot")->Get()->Add(guid);
@@ -157,7 +153,6 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
bot->StopMoving();
}
if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
{
sServerFacade->SetFacingTo(bot, target);

View File

@@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
WorldLocation location = *target->getPosition();
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())
{

View File

@@ -39,7 +39,7 @@ bool RevealGatheringItemAction::Execute(Event event)
std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(bot, sPlayerbotAIConfig->grindDistance);
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, sPlayerbotAIConfig->reactDistance);
Cell::VisitObjects(bot, searcher, sPlayerbotAIConfig->reactDistance);
std::vector<GameObject*> result;
for (GameObject* go : targets)

View File

@@ -23,7 +23,7 @@ bool TravelAction::Execute(Event event)
std::list<Unit*> targets;
Acore::AnyUnitInObjectRangeCheck u_check(bot, sPlayerbotAIConfig->sightDistance * 2);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, sPlayerbotAIConfig->sightDistance);
Cell::VisitObjects(bot, searcher, sPlayerbotAIConfig->sightDistance);
for (Unit* unit : targets)
{

View File

@@ -110,7 +110,7 @@ bool SummonAction::SummonUsingGos(Player* summoner, Player* player)
std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(summoner, sPlayerbotAIConfig->sightDistance);
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(summoner, targets, u_check);
Cell::VisitAllObjects(summoner, searcher, sPlayerbotAIConfig->sightDistance);
Cell::VisitObjects(summoner, searcher, sPlayerbotAIConfig->sightDistance);
for (GameObject* go : targets)
{
@@ -130,7 +130,7 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
std::list<Unit*> targets;
Acore::AnyUnitInObjectRangeCheck u_check(summoner, sPlayerbotAIConfig->sightDistance);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(summoner, targets, u_check);
Cell::VisitAllObjects(summoner, searcher, sPlayerbotAIConfig->sightDistance);
Cell::VisitObjects(summoner, searcher, sPlayerbotAIConfig->sightDistance);
for (Unit* unit : targets)
{

View File

@@ -6549,7 +6549,7 @@ bool IccSindragosaFrostBombAction::Execute(Event event)
float range = 200.0f;
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, units, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
for (Unit* unit : units)
{

View File

@@ -21,7 +21,7 @@ bool CollisionValue::Calculate()
float range = sPlayerbotAIConfig->contactDistance;
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
for (Unit* target : targets)
{

View File

@@ -26,7 +26,7 @@ void NearestCorpsesValue::FindUnits(std::list<Unit*>& targets)
{
AnyDeadUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<AnyDeadUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestCorpsesValue::AcceptUnit(Unit* unit) { return true; }

View File

@@ -14,7 +14,7 @@ void NearestFriendlyPlayersValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyFriendlyUnitInObjectRangeCheck u_check(bot, bot, range);
Acore::UnitListSearcher<Acore::AnyFriendlyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestFriendlyPlayersValue::AcceptUnit(Unit* unit)

View File

@@ -17,7 +17,7 @@ GuidVector NearestGameObjects::Calculate()
std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(bot, range);
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
GuidVector result;
for (GameObject* go : targets)
@@ -34,7 +34,7 @@ GuidVector NearestTrapWithDamageValue::Calculate()
std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(bot, range);
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
GuidVector result;
for (GameObject* go : targets)

View File

@@ -14,7 +14,7 @@ void NearestNonBotPlayersValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestNonBotPlayersValue::AcceptUnit(Unit* unit)

View File

@@ -15,7 +15,7 @@ void NearestNpcsValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestNpcsValue::AcceptUnit(Unit* unit) { return !unit->IsPlayer(); }
@@ -24,7 +24,7 @@ void NearestHostileNpcsValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestHostileNpcsValue::AcceptUnit(Unit* unit) { return unit->IsHostileTo(bot) && !unit->IsPlayer(); }
@@ -33,7 +33,7 @@ void NearestVehiclesValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestVehiclesValue::AcceptUnit(Unit* unit)
@@ -52,7 +52,7 @@ void NearestTriggersValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnfriendlyUnitInObjectRangeCheck u_check(bot, bot, range);
Acore::UnitListSearcher<Acore::AnyUnfriendlyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestTriggersValue::AcceptUnit(Unit* unit) { return !unit->IsPlayer(); }
@@ -61,7 +61,7 @@ void NearestTotemsValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestTotemsValue::AcceptUnit(Unit* unit) { return unit->IsTotem(); }

View File

@@ -49,7 +49,7 @@ void PossibleRpgTargetsValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool PossibleRpgTargetsValue::AcceptUnit(Unit* unit)
@@ -141,7 +141,7 @@ void PossibleNewRpgTargetsValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool PossibleNewRpgTargetsValue::AcceptUnit(Unit* unit)
@@ -168,7 +168,7 @@ GuidVector PossibleNewRpgGameObjectsValue::Calculate()
std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(bot, range);
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
std::vector<std::pair<ObjectGuid, float>> guidDistancePairs;

View File

@@ -21,7 +21,7 @@ void PossibleTargetsValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnfriendlyUnitInObjectRangeCheck u_check(bot, bot, range);
Acore::UnitListSearcher<Acore::AnyUnfriendlyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool PossibleTargetsValue::AcceptUnit(Unit* unit) { return AttackersValue::IsPossibleTarget(unit, bot, range); }
@@ -30,7 +30,7 @@ void PossibleTriggersValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnfriendlyUnitInObjectRangeCheck u_check(bot, bot, range);
Acore::UnitListSearcher<Acore::AnyUnfriendlyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
Cell::VisitObjects(bot, searcher, range);
}
bool PossibleTriggersValue::AcceptUnit(Unit* unit)

View File

@@ -14,7 +14,7 @@ class FindTargetForTankStrategy : public FindNonCcTargetStrategy
public:
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())
{
@@ -37,6 +37,43 @@ public:
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)
{
minThreat = threat;
@@ -53,7 +90,7 @@ class FindTankTargetSmartStrategy : public FindTargetStrategy
public:
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())
{
@@ -69,8 +106,32 @@ public:
{
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();
// 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_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))
{
@@ -109,12 +209,28 @@ public:
return 1;
}
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()
{
// FindTargetForTankStrategy strategy(botAI);
// [Note] Using the "smart" strategy below. Guards have been added in CheckAttacker/IsBetter.
FindTankTargetSmartStrategy strategy(botAI);
return FindTarget(&strategy);
}

View File

@@ -14,7 +14,7 @@
Unit* FindTargetStrategy::GetResult() { return result; }
Unit* TargetValue::FindTarget(FindTargetStrategy* strategy)
/*Unit* TargetValue::FindTarget(FindTargetStrategy* strategy)
{
GuidVector attackers = botAI->GetAiObjectContext()->GetValue<GuidVector>("attackers")->Get();
for (ObjectGuid const guid : attackers)
@@ -27,6 +27,28 @@ Unit* TargetValue::FindTarget(FindTargetStrategy* strategy)
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();
}

View File

@@ -21,8 +21,8 @@
const int ITEM_SOUL_SHARD = 6265;
// Checks if the bot has less than 20 soul shards, and if so, allows casting Drain Soul
bool CastDrainSoulAction::isUseful() { return AI_VALUE2(uint32, "item count", "soul shard") < 20; }
// Checks if the bot has less than 26 soul shards, and if so, allows casting Drain Soul
bool CastDrainSoulAction::isUseful() { return AI_VALUE2(uint32, "item count", "soul shard") < 26; }
// Checks if the bot's health is above a certain threshold, and if so, allows casting Life Tap
bool CastLifeTapAction::isUseful() { return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig->lowHealth; }

View File

@@ -29,8 +29,6 @@ public:
creators["boost"] = &WarlockStrategyFactoryInternal::boost;
creators["cc"] = &WarlockStrategyFactoryInternal::cc;
creators["pet"] = &WarlockStrategyFactoryInternal::pet;
creators["spellstone"] = &WarlockStrategyFactoryInternal::spellstone;
creators["firestone"] = &WarlockStrategyFactoryInternal::firestone;
creators["meta melee"] = &WarlockStrategyFactoryInternal::meta_melee_aoe;
creators["tank"] = &WarlockStrategyFactoryInternal::tank;
creators["aoe"] = &WarlockStrategyFactoryInternal::aoe;
@@ -42,8 +40,6 @@ private:
static Strategy* boost(PlayerbotAI* botAI) { return new WarlockBoostStrategy(botAI); }
static Strategy* cc(PlayerbotAI* botAI) { return new WarlockCcStrategy(botAI); }
static Strategy* pet(PlayerbotAI* botAI) { return new WarlockPetStrategy(botAI); }
static Strategy* spellstone(PlayerbotAI* botAI) { return new UseSpellstoneStrategy(botAI); }
static Strategy* firestone(PlayerbotAI* botAI) { return new UseFirestoneStrategy(botAI); }
static Strategy* meta_melee_aoe(PlayerbotAI* botAI) { return new MetaMeleeAoeStrategy(botAI); }
static Strategy* tank(PlayerbotAI* botAI) { return new TankWarlockStrategy(botAI); }
static Strategy* aoe(PlayerbotAI* botAI) { return new AoEWarlockStrategy(botAI); }
@@ -125,6 +121,20 @@ private:
static Strategy* curse_of_weakness(PlayerbotAI* botAI) { return new WarlockCurseOfWeaknessStrategy(botAI); }
};
class WarlockWeaponStoneStrategyFactoryInternal : public NamedObjectContext<Strategy>
{
public:
WarlockWeaponStoneStrategyFactoryInternal() : NamedObjectContext<Strategy>(false, true)
{
creators["firestone"] = &WarlockWeaponStoneStrategyFactoryInternal::firestone;
creators["spellstone"] = &WarlockWeaponStoneStrategyFactoryInternal::spellstone;
}
private:
static Strategy* firestone(PlayerbotAI* ai) { return new UseFirestoneStrategy(ai); }
static Strategy* spellstone(PlayerbotAI* ai) { return new UseSpellstoneStrategy(ai); }
};
class WarlockTriggerFactoryInternal : public NamedObjectContext<Trigger>
{
public:
@@ -333,19 +343,13 @@ private:
static Action* devour_magic_purge(PlayerbotAI* botAI) { return new CastDevourMagicPurgeAction(botAI); }
static Action* devour_magic_cleanse(PlayerbotAI* botAI) { return new CastDevourMagicCleanseAction(botAI); }
static Action* seed_of_corruption(PlayerbotAI* botAI) { return new CastSeedOfCorruptionAction(botAI); }
static Action* seed_of_corruption_on_attacker(PlayerbotAI* botAI)
{
return new CastSeedOfCorruptionOnAttackerAction(botAI);
}
static Action* seed_of_corruption_on_attacker(PlayerbotAI* botAI) { return new CastSeedOfCorruptionOnAttackerAction(botAI); }
static Action* rain_of_fire(PlayerbotAI* botAI) { return new CastRainOfFireAction(botAI); }
static Action* hellfire(PlayerbotAI* botAI) { return new CastHellfireAction(botAI); }
static Action* shadowfury(PlayerbotAI* botAI) { return new CastShadowfuryAction(botAI); }
static Action* life_tap(PlayerbotAI* botAI) { return new CastLifeTapAction(botAI); }
static Action* unstable_affliction(PlayerbotAI* ai) { return new CastUnstableAfflictionAction(ai); }
static Action* unstable_affliction_on_attacker(PlayerbotAI* ai)
{
return new CastUnstableAfflictionOnAttackerAction(ai);
}
static Action* unstable_affliction_on_attacker(PlayerbotAI* ai) { return new CastUnstableAfflictionOnAttackerAction(ai); }
static Action* haunt(PlayerbotAI* ai) { return new CastHauntAction(ai); }
static Action* demonic_empowerment(PlayerbotAI* ai) { return new CastDemonicEmpowermentAction(ai); }
static Action* metamorphosis(PlayerbotAI* ai) { return new CastMetamorphosisAction(ai); }
@@ -360,10 +364,7 @@ private:
static Action* searing_pain(PlayerbotAI* botAI) { return new CastSearingPainAction(botAI); }
static Action* shadow_ward(PlayerbotAI* botAI) { return new CastShadowWardAction(botAI); }
static Action* curse_of_agony(PlayerbotAI* botAI) { return new CastCurseOfAgonyAction(botAI); }
static Action* curse_of_agony_on_attacker(PlayerbotAI* botAI)
{
return new CastCurseOfAgonyOnAttackerAction(botAI);
}
static Action* curse_of_agony_on_attacker(PlayerbotAI* botAI) { return new CastCurseOfAgonyOnAttackerAction(botAI); }
static Action* curse_of_the_elements(PlayerbotAI* ai) { return new CastCurseOfTheElementsAction(ai); }
static Action* curse_of_doom(PlayerbotAI* ai) { return new CastCurseOfDoomAction(ai); }
static Action* curse_of_exhaustion(PlayerbotAI* ai) { return new CastCurseOfExhaustionAction(ai); }
@@ -397,6 +398,7 @@ void WarlockAiObjectContext::BuildSharedStrategyContexts(SharedNamedObjectContex
strategyContexts.Add(new WarlockPetStrategyFactoryInternal());
strategyContexts.Add(new WarlockSoulstoneStrategyFactoryInternal());
strategyContexts.Add(new WarlockCurseStrategyFactoryInternal());
strategyContexts.Add(new WarlockWeaponStoneStrategyFactoryInternal());
}
void WarlockAiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts)

View File

@@ -46,7 +46,7 @@ bool WarlockConjuredItemTrigger::IsActive()
bool OutOfSoulShardsTrigger::IsActive() { return GetSoulShardCount(botAI->GetBot()) == 0; }
bool TooManySoulShardsTrigger::IsActive() { return GetSoulShardCount(botAI->GetBot()) >= 6; }
bool TooManySoulShardsTrigger::IsActive() { return GetSoulShardCount(botAI->GetBot()) >= 26; }
bool OutOfSoulstoneTrigger::IsActive() { return GetSoulstoneCount(botAI->GetBot()) == 0; }
@@ -207,7 +207,11 @@ bool WrongPetTrigger::IsActive()
if (enabledCount != 1)
return false;
// Step 3: At this point, we know only one pet strategy is enabled.
// Step 3: If there is no pet, do not trigger.
if (!pet)
return false;
// Step 4: At this point, we know only one pet strategy is enabled.
// We check if the currently summoned pet matches the enabled strategy.
bool correctPet = false;
if (pet)
@@ -218,16 +222,16 @@ bool WrongPetTrigger::IsActive()
correctPet = true;
}
// Step 4: If the correct pet is already summoned, the trigger should not activate.
// Step 5: If the correct pet is already summoned, the trigger should not activate.
if (correctPet)
return false;
// Step 5: Finally, check if the bot actually knows the spell to summon the desired pet.
// Step 6: Finally, check if the bot actually knows the spell to summon the desired pet.
// If so, the trigger is active (bot should summon the correct pet).
if (bot->HasSpell(enabledPet->spellId))
return true;
// Step 6: If we get here, the bot doesn't know the spell required to support the active pet strategy
// Step 7: If we get here, the bot doesn't know the spell required to support the active pet strategy
return false;
}