Files
mod-playerbots/src/PlayerbotAI.cpp
SaW 89590860fb Performance: Refactored a bunch of functions in Playerbot ai (#865)
* Update PlayerbotAI.h

* Refactored a number of functions in PlayerbotAI.cpp

* Update PlayerbotAI.cpp

* Update PlayerbotAI.cpp - update for commit done

Take
568592f188
into account.

* Missing check for aurEff

* Update PlayerbotAI.cpp

nvm...

* Update PlayerbotAI.cpp

GetAura

* Update PlayerbotAI.cpp

Simplified/Optimized sPlayerbotAIConfig->dynamicReactDelay logic for in-combat.

* Update PlayerbotAI.cpp

Dubass fix

* Update PlayerbotAI.cpp

Fix bots leaving dungeon group,. again.

* Update PlayerbotAI.cpp

* Update PlayerbotAI.cpp - order correction

...Required for proper pet behavior.

* Update PlayerbotAI.cpp - UpdateAIGroupMembership()

Final refactor of helper function as all now works as required.

* Update PlayerbotAI.cpp

FindItemInInventory

* Update PlayerbotAI.h

Added helper functions, correct public -> private
2025-01-15 22:49:41 +01:00

5989 lines
186 KiB
C++
Raw Blame History

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "PlayerbotAI.h"
#include <cmath>
#include <sstream>
#include <string>
#include <mutex>
#include "AiFactory.h"
#include "BudgetValues.h"
#include "ChannelMgr.h"
#include "CharacterPackets.h"
#include "CreatureAIImpl.h"
#include "EmoteAction.h"
#include "Engine.h"
#include "ExternalEventHelper.h"
#include "GuildMgr.h"
#include "GuildTaskMgr.h"
#include "LFGMgr.h"
#include "LastMovementValue.h"
#include "LastSpellCastValue.h"
#include "LogLevelAction.h"
#include "LootObjectStack.h"
#include "MapMgr.h"
#include "MotionMaster.h"
#include "MoveSpline.h"
#include "MoveSplineInit.h"
#include "NewRpgStrategy.h"
#include "ObjectGuid.h"
#include "PerformanceMonitor.h"
#include "Player.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotDbStore.h"
#include "PlayerbotMgr.h"
#include "Playerbots.h"
#include "PointMovementGenerator.h"
#include "PositionValue.h"
#include "RandomPlayerbotMgr.h"
#include "SayAction.h"
#include "ScriptMgr.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
#include "SocialMgr.h"
#include "SpellAuraEffects.h"
#include "SpellInfo.h"
#include "Transport.h"
#include "Unit.h"
#include "UpdateTime.h"
#include "Vehicle.h"
#include "GameTime.h"
std::vector<std::string> PlayerbotAI::dispel_whitelist = {
"mutating injection",
"frostbolt",
};
std::vector<std::string>& split(std::string const s, char delim, std::vector<std::string>& elems);
std::vector<std::string> split(std::string const s, char delim);
char* strstri(char const* str1, char const* str2);
std::string& trim(std::string& s);
std::set<std::string> PlayerbotAI::unsecuredCommands;
PlayerbotChatHandler::PlayerbotChatHandler(Player* pMasterPlayer) : ChatHandler(pMasterPlayer->GetSession()) {}
uint32 PlayerbotChatHandler::extractQuestId(std::string const str)
{
char* source = (char*)str.c_str();
char* cId = extractKeyFromLink(source, "Hquest");
return cId ? atol(cId) : 0;
}
void PacketHandlingHelper::AddHandler(uint16 opcode, std::string const handler) { handlers[opcode] = handler; }
void PacketHandlingHelper::Handle(ExternalEventHelper& helper)
{
while (!queue.empty())
{
helper.HandlePacket(handlers, queue.top());
queue.pop();
}
}
void PacketHandlingHelper::AddPacket(WorldPacket const& packet)
{
if (packet.empty())
return;
// assert(handlers);
// assert(packet);
// assert(packet.GetOpcode());
if (handlers.find(packet.GetOpcode()) != handlers.end())
queue.push(WorldPacket(packet));
}
PlayerbotAI::PlayerbotAI()
: PlayerbotAIBase(true),
bot(nullptr),
aiObjectContext(nullptr),
currentEngine(nullptr),
chatHelper(this),
chatFilter(this),
accountId(0),
security(nullptr),
master(nullptr),
currentState(BOT_STATE_NON_COMBAT)
{
for (uint8 i = 0; i < BOT_STATE_MAX; i++)
engines[i] = nullptr;
for (uint8 i = 0; i < MAX_ACTIVITY_TYPE; i++)
{
allowActiveCheckTimer[i] = time(nullptr);
allowActive[i] = false;
}
}
PlayerbotAI::PlayerbotAI(Player* bot)
: PlayerbotAIBase(true),
bot(bot),
chatHelper(this),
chatFilter(this),
master(nullptr),
security(bot) // reorder args - whipowill
{
if (!bot->isTaxiCheater() && HasCheat((BotCheatMask::taxi)))
bot->SetTaxiCheater(true);
for (uint8 i = 0; i < MAX_ACTIVITY_TYPE; i++)
{
allowActiveCheckTimer[i] = time(nullptr);
allowActive[i] = false;
}
accountId = bot->GetSession()->GetAccountId();
aiObjectContext = AiFactory::createAiObjectContext(bot, this);
engines[BOT_STATE_COMBAT] = AiFactory::createCombatEngine(bot, this, aiObjectContext);
engines[BOT_STATE_NON_COMBAT] = AiFactory::createNonCombatEngine(bot, this, aiObjectContext);
engines[BOT_STATE_DEAD] = AiFactory::createDeadEngine(bot, this, aiObjectContext);
if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId());
currentEngine = engines[BOT_STATE_NON_COMBAT];
currentState = BOT_STATE_NON_COMBAT;
masterIncomingPacketHandlers.AddHandler(CMSG_GAMEOBJ_USE, "use game object");
masterIncomingPacketHandlers.AddHandler(CMSG_AREATRIGGER, "area trigger");
// masterIncomingPacketHandlers.AddHandler(CMSG_GAMEOBJ_USE, "use game object");
masterIncomingPacketHandlers.AddHandler(CMSG_LOOT_ROLL, "loot roll");
masterIncomingPacketHandlers.AddHandler(CMSG_GOSSIP_HELLO, "gossip hello");
masterIncomingPacketHandlers.AddHandler(CMSG_QUESTGIVER_HELLO, "gossip hello");
masterIncomingPacketHandlers.AddHandler(CMSG_ACTIVATETAXI, "activate taxi");
masterIncomingPacketHandlers.AddHandler(CMSG_ACTIVATETAXIEXPRESS, "activate taxi");
masterIncomingPacketHandlers.AddHandler(CMSG_TAXICLEARALLNODES, "taxi done");
masterIncomingPacketHandlers.AddHandler(CMSG_TAXICLEARNODE, "taxi done");
masterIncomingPacketHandlers.AddHandler(CMSG_GROUP_UNINVITE, "uninvite");
masterIncomingPacketHandlers.AddHandler(CMSG_GROUP_UNINVITE_GUID, "uninvite guid");
masterIncomingPacketHandlers.AddHandler(CMSG_LFG_TELEPORT, "lfg teleport");
masterIncomingPacketHandlers.AddHandler(CMSG_CAST_SPELL, "see spell");
masterIncomingPacketHandlers.AddHandler(CMSG_REPOP_REQUEST, "release spirit");
masterIncomingPacketHandlers.AddHandler(CMSG_RECLAIM_CORPSE, "revive from corpse");
botOutgoingPacketHandlers.AddHandler(SMSG_PETITION_SHOW_SIGNATURES, "petition offer");
botOutgoingPacketHandlers.AddHandler(SMSG_GROUP_INVITE, "group invite");
botOutgoingPacketHandlers.AddHandler(SMSG_GUILD_INVITE, "guild invite");
botOutgoingPacketHandlers.AddHandler(BUY_ERR_NOT_ENOUGHT_MONEY, "not enough money");
botOutgoingPacketHandlers.AddHandler(BUY_ERR_REPUTATION_REQUIRE, "not enough reputation");
botOutgoingPacketHandlers.AddHandler(SMSG_GROUP_SET_LEADER, "group set leader");
botOutgoingPacketHandlers.AddHandler(SMSG_FORCE_RUN_SPEED_CHANGE, "check mount state");
botOutgoingPacketHandlers.AddHandler(SMSG_RESURRECT_REQUEST, "resurrect request");
botOutgoingPacketHandlers.AddHandler(SMSG_INVENTORY_CHANGE_FAILURE, "cannot equip");
botOutgoingPacketHandlers.AddHandler(SMSG_TRADE_STATUS, "trade status");
botOutgoingPacketHandlers.AddHandler(SMSG_LOOT_RESPONSE, "loot response");
botOutgoingPacketHandlers.AddHandler(SMSG_ITEM_PUSH_RESULT, "item push result");
botOutgoingPacketHandlers.AddHandler(SMSG_PARTY_COMMAND_RESULT, "party command");
botOutgoingPacketHandlers.AddHandler(SMSG_LEVELUP_INFO, "levelup");
botOutgoingPacketHandlers.AddHandler(SMSG_LOG_XPGAIN, "xpgain");
botOutgoingPacketHandlers.AddHandler(SMSG_CAST_FAILED, "cast failed");
botOutgoingPacketHandlers.AddHandler(SMSG_DUEL_REQUESTED, "duel requested");
botOutgoingPacketHandlers.AddHandler(SMSG_INVENTORY_CHANGE_FAILURE, "inventory change failure");
botOutgoingPacketHandlers.AddHandler(SMSG_BATTLEFIELD_STATUS, "bg status");
botOutgoingPacketHandlers.AddHandler(SMSG_LFG_ROLE_CHECK_UPDATE, "lfg role check");
botOutgoingPacketHandlers.AddHandler(SMSG_LFG_PROPOSAL_UPDATE, "lfg proposal");
botOutgoingPacketHandlers.AddHandler(SMSG_TEXT_EMOTE, "receive text emote");
botOutgoingPacketHandlers.AddHandler(SMSG_EMOTE, "receive emote");
botOutgoingPacketHandlers.AddHandler(SMSG_LOOT_START_ROLL, "master loot roll");
botOutgoingPacketHandlers.AddHandler(SMSG_ARENA_TEAM_INVITE, "arena team invite");
botOutgoingPacketHandlers.AddHandler(SMSG_GROUP_DESTROYED, "group destroyed");
botOutgoingPacketHandlers.AddHandler(SMSG_GROUP_LIST, "group list");
masterOutgoingPacketHandlers.AddHandler(SMSG_PARTY_COMMAND_RESULT, "party command");
masterOutgoingPacketHandlers.AddHandler(MSG_RAID_READY_CHECK, "ready check");
masterOutgoingPacketHandlers.AddHandler(MSG_RAID_READY_CHECK_FINISHED, "ready check finished");
masterOutgoingPacketHandlers.AddHandler(SMSG_QUESTGIVER_OFFER_REWARD, "questgiver quest details");
// quest packet
masterIncomingPacketHandlers.AddHandler(CMSG_QUESTGIVER_COMPLETE_QUEST, "complete quest");
masterIncomingPacketHandlers.AddHandler(CMSG_QUESTGIVER_ACCEPT_QUEST, "accept quest");
masterIncomingPacketHandlers.AddHandler(CMSG_QUEST_CONFIRM_ACCEPT, "confirm quest");
masterIncomingPacketHandlers.AddHandler(CMSG_PUSHQUESTTOPARTY, "quest share");
botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_COMPLETE, "quest update complete");
botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_KILL, "quest update add kill");
botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item");
botOutgoingPacketHandlers.AddHandler(SMSG_QUEST_CONFIRM_ACCEPT, "confirm quest");
}
PlayerbotAI::~PlayerbotAI()
{
for (uint8 i = 0; i < BOT_STATE_MAX; i++)
{
if (engines[i])
delete engines[i];
}
if (aiObjectContext)
delete aiObjectContext;
if (bot)
sPlayerbotsMgr->RemovePlayerBotData(bot->GetGUID(), true);
}
void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
{
// Handle the AI check delay
if (nextAICheckDelay > elapsed)
nextAICheckDelay -= elapsed;
else
nextAICheckDelay = 0;
// Early return if bot is in invalid state
if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
return;
// Handle cheat options (set bot health and power if cheats are enabled)
if (bot->IsAlive() && (static_cast<uint32>(GetCheat()) > 0 || static_cast<uint32>(sPlayerbotAIConfig->botCheatMask) > 0))
{
if (HasCheat(BotCheatMask::health))
bot->SetFullHealth();
if (HasCheat(BotCheatMask::mana) && bot->getPowerType() == POWER_MANA)
bot->SetPower(POWER_MANA, bot->GetMaxPower(POWER_MANA));
if (HasCheat(BotCheatMask::power) && bot->getPowerType() != POWER_MANA)
bot->SetPower(bot->getPowerType(), bot->GetMaxPower(bot->getPowerType()));
}
AllowActivity();
if (!CanUpdateAI())
return;
// Handle the current spell
Spell* currentSpell = bot->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!currentSpell)
currentSpell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (currentSpell)
{
const SpellInfo* spellInfo = currentSpell->GetSpellInfo();
if (spellInfo && currentSpell->getState() == SPELL_STATE_PREPARING)
{
Unit* spellTarget = currentSpell->m_targets.GetUnitTarget();
// Interrupt if target is dead or spell can't target dead units
if (spellTarget && !spellTarget->IsAlive() && !spellInfo->IsAllowingDeadTarget())
{
InterruptSpell();
YieldThread(GetReactDelay());
return;
}
bool isHeal = false;
bool isSingleTarget = true;
for (uint8 i = 0; i < 3; ++i)
{
if (!spellInfo->Effects[i].Effect)
continue;
// Check if spell is a heal
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL ||
spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL_MAX_HEALTH ||
spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL_MECHANICAL)
isHeal = true;
// Check if spell is single-target
if ((spellInfo->Effects[i].TargetA.GetTarget() && spellInfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY) ||
(spellInfo->Effects[i].TargetB.GetTarget() && spellInfo->Effects[i].TargetB.GetTarget() != TARGET_UNIT_TARGET_ALLY))
{
isSingleTarget = false;
}
}
// Interrupt if target ally has full health (heal by other member)
if (isHeal && isSingleTarget && spellTarget && spellTarget->IsFullHealth())
{
InterruptSpell();
YieldThread(GetReactDelay());
return;
}
// Ensure bot is facing target if necessary
if (spellTarget && !bot->HasInArc(CAST_ANGLE_IN_FRONT, spellTarget) && (spellInfo->FacingCasterFlags & SPELL_FACING_FLAG_INFRONT))
{
sServerFacade->SetFacingTo(bot, spellTarget);
}
// Wait for spell cast
YieldThread(GetReactDelay());
return;
}
}
// Handle transport check delay
if (nextTransportCheck > elapsed)
nextTransportCheck -= elapsed;
else
nextTransportCheck = 0;
if (!nextTransportCheck)
{
nextTransportCheck = 1000;
Transport* newTransport = bot->GetMap()->GetTransportForPos(bot->GetPhaseMask(), bot->GetPositionX(),
bot->GetPositionY(), bot->GetPositionZ(), bot);
if (newTransport != bot->GetTransport())
{
LOG_DEBUG("playerbots", "Bot {} is on a transport", bot->GetName());
if (bot->GetTransport())
bot->GetTransport()->RemovePassenger(bot, true);
if (newTransport)
newTransport->AddPassenger(bot, true);
bot->StopMovingOnCurrentPos();
}
}
// Update the bot's group status (moved to helper function)
UpdateAIGroupMembership();
// Update internal AI
UpdateAIInternal(elapsed, minimal);
YieldThread(GetReactDelay());
}
// Helper function for UpdateAI to check group membership and handle removal if necessary
void PlayerbotAI::UpdateAIGroupMembership()
{
if (!bot || !bot->GetGroup())
return;
Group* group = bot->GetGroup();
if (!bot->InBattleground() && !bot->inRandomLfgDungeon() && !group->isLFGGroup())
{
Player* leader = group->GetLeader();
if (leader && leader != bot) // Ensure the leader is valid and not the bot itself
{
PlayerbotAI* leaderAI = GET_PLAYERBOT_AI(leader);
if (leaderAI && !leaderAI->IsRealPlayer())
{
bot->RemoveFromGroup();
ResetStrategies();
}
}
}
else if (group->isLFGGroup())
{
bool hasRealPlayer = false;
// Iterate over all group members to check if at least one is a real player
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (memberAI && !memberAI->IsRealPlayer())
continue;
hasRealPlayer = true;
break;
}
if (!hasRealPlayer)
{
bot->RemoveFromGroup();
ResetStrategies();
}
}
}
void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal)
{
if (bot->IsBeingTeleported() || !bot->IsInWorld())
return;
std::string const mapString = WorldPosition(bot).isOverworld() ? std::to_string(bot->GetMapId()) : "I";
PerformanceMonitorOperation* pmo =
sPerformanceMonitor->start(PERF_MON_TOTAL, "PlayerbotAI::UpdateAIInternal " + mapString);
ExternalEventHelper helper(aiObjectContext);
// chat replies
for (auto it = chatReplies.begin(); it != chatReplies.end();)
{
time_t checkTime = it->m_time;
if (checkTime && time(0) < checkTime)
{
++it;
continue;
}
ChatReplyAction::ChatReplyDo(bot, it->m_type, it->m_guid1, it->m_guid2, it->m_msg, it->m_chanName, it->m_name);
it = chatReplies.erase(it);
}
HandleCommands();
// logout if logout timer is ready or if instant logout is possible
if (bot->GetSession()->isLogingOut())
{
WorldSession* botWorldSessionPtr = bot->GetSession();
bool logout = botWorldSessionPtr->ShouldLogOut(time(nullptr));
if (!master || !master->GetSession()->GetPlayer())
logout = true;
if (bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
botWorldSessionPtr->GetSecurity() >= (AccountTypes)sWorld->getIntConfig(CONFIG_INSTANT_LOGOUT))
{
logout = true;
}
if (master &&
(master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || master->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
(master->GetSession() &&
master->GetSession()->GetSecurity() >= (AccountTypes)sWorld->getIntConfig(CONFIG_INSTANT_LOGOUT))))
{
logout = true;
}
if (logout)
{
PlayerbotMgr* masterBotMgr = nullptr;
if (master)
masterBotMgr = GET_PLAYERBOT_MGR(master);
if (masterBotMgr)
{
masterBotMgr->LogoutPlayerBot(bot->GetGUID());
}
else
{
sRandomPlayerbotMgr->LogoutPlayerBot(bot->GetGUID());
}
return;
}
SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
return;
}
botOutgoingPacketHandlers.Handle(helper);
masterIncomingPacketHandlers.Handle(helper);
masterOutgoingPacketHandlers.Handle(helper);
DoNextAction(minimal);
if (pmo)
pmo->finish();
}
void PlayerbotAI::HandleCommands()
{
ExternalEventHelper helper(aiObjectContext);
for (auto it = chatCommands.begin(); it != chatCommands.end();)
{
time_t& checkTime = it->GetTime();
if (checkTime && time(0) < checkTime)
{
++it;
continue;
}
const std::string& command = it->GetCommand();
Player* owner = it->GetOwner();
if (!helper.ParseChatCommand(command, owner) && it->GetType() == CHAT_MSG_WHISPER)
{
// ostringstream out; out << "Unknown command " << command;
// TellPlayer(out);
// helper.ParseChatCommand("help");
}
it = chatCommands.erase(it);
}
}
std::map<std::string, ChatMsg> chatMap;
void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang)
{
std::string filtered = text;
if (!IsAllowedCommand(filtered) && !GetSecurity()->CheckLevelFor(PlayerbotSecurityLevel::PLAYERBOT_SECURITY_INVITE,
type != CHAT_MSG_WHISPER, &fromPlayer))
return;
if (type == CHAT_MSG_ADDON)
return;
if (filtered.find("BOT\t") == 0) // Mangosbot has BOT prefix so we remove that.
filtered = filtered.substr(4);
else if (lang == LANG_ADDON) // Other addon messages should not command bots.
return;
if (type == CHAT_MSG_SYSTEM)
return;
if (filtered.find(sPlayerbotAIConfig->commandSeparator) != std::string::npos)
{
std::vector<std::string> commands;
split(commands, filtered, sPlayerbotAIConfig->commandSeparator.c_str());
for (std::vector<std::string>::iterator i = commands.begin(); i != commands.end(); ++i)
{
HandleCommand(type, *i, fromPlayer);
}
return;
}
if (!sPlayerbotAIConfig->commandPrefix.empty())
{
if (filtered.find(sPlayerbotAIConfig->commandPrefix) != 0)
return;
filtered = filtered.substr(sPlayerbotAIConfig->commandPrefix.size());
}
if (chatMap.empty())
{
chatMap["#w "] = CHAT_MSG_WHISPER;
chatMap["#p "] = CHAT_MSG_PARTY;
chatMap["#r "] = CHAT_MSG_RAID;
chatMap["#a "] = CHAT_MSG_ADDON;
chatMap["#g "] = CHAT_MSG_GUILD;
}
currentChat = std::pair<ChatMsg, time_t>(CHAT_MSG_WHISPER, 0);
for (std::map<std::string, ChatMsg>::iterator i = chatMap.begin(); i != chatMap.end(); ++i)
{
if (filtered.find(i->first) == 0)
{
filtered = filtered.substr(3);
currentChat = std::pair<ChatMsg, time_t>(i->second, time(0) + 2);
break;
}
}
filtered = chatFilter.Filter(trim((std::string&)filtered));
if (filtered.empty())
return;
if (filtered.substr(0, 6) == "debug ")
{
std::string response = HandleRemoteCommand(filtered.substr(6));
WorldPacket data;
ChatHandler::BuildChatPacket(data, CHAT_MSG_ADDON, response.c_str(), LANG_ADDON, CHAT_TAG_NONE, bot->GetGUID(),
bot->GetName());
sServerFacade->SendPacket(&fromPlayer, &data);
return;
}
if (!IsAllowedCommand(filtered) &&
!GetSecurity()->CheckLevelFor(PlayerbotSecurityLevel::PLAYERBOT_SECURITY_ALLOW_ALL, type != CHAT_MSG_WHISPER,
&fromPlayer))
return;
if (type == CHAT_MSG_RAID_WARNING && filtered.find(bot->GetName()) != std::string::npos &&
filtered.find("award") == std::string::npos)
{
chatCommands.push_back(ChatCommandHolder("warning", &fromPlayer, type));
return;
}
if ((filtered.size() > 2 && filtered.substr(0, 2) == "d ") ||
(filtered.size() > 3 && filtered.substr(0, 3) == "do "))
{
Event event("do", "", &fromPlayer);
std::string action = filtered.substr(filtered.find(" ") + 1);
DoSpecificAction(action, event);
}
if (ChatHelper::parseValue("command", filtered).substr(0, 3) == "do ")
{
Event event("do", "", &fromPlayer);
std::string action = ChatHelper::parseValue("command", filtered);
action = action.substr(3);
DoSpecificAction(action, event);
}
else if (type != CHAT_MSG_WHISPER && filtered.size() > 6 && filtered.substr(0, 6) == "queue ")
{
std::string remaining = filtered.substr(filtered.find(" ") + 1);
int index = 1;
Group* group = bot->GetGroup();
if (group)
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
if (ref->GetSource() == master)
continue;
if (ref->GetSource() == bot)
break;
index++;
}
}
chatCommands.push_back(ChatCommandHolder(remaining, &fromPlayer, type, time(0) + index));
}
else if (filtered == "reset")
{
Reset(true);
}
// TODO: missing implementation to port
/*else if (filtered == "logout")
{
if (!(bot->IsStunnedByLogout() || bot->GetSession()->isLogingOut()))
{
if (type == CHAT_MSG_WHISPER)
TellPlayer(&fromPlayer, BOT_TEXT("logout_start"));
if (master && master->GetPlayerbotMgr())
SetShouldLogOut(true);
}
}
else if (filtered == "logout cancel")
{
if (bot->IsStunnedByLogout() || bot->GetSession()->isLogingOut())
{
if (type == CHAT_MSG_WHISPER)
TellPlayer(&fromPlayer, BOT_TEXT("logout_cancel"));
WorldPacket p;
bot->GetSession()->HandleLogoutCancelOpcode(p);
SetShouldLogOut(false);
}
}
else if ((filtered.size() > 5) && (filtered.substr(0, 5) == "wait ") && (filtered.find("wait for attack") ==
std::string::npos))
{
std::string remaining = filtered.substr(filtered.find(" ") + 1);
uint32 delay = atof(remaining.c_str()) * IN_MILLISECONDS;
if (delay > 20000)
{
bot->TellMaster(&fromPlayer, "Max wait time is 20 seconds!");
return;
}
IncreaseAIInternalUpdateDelay(delay);
isWaiting = true;
TellPlayer(&fromPlayer, "Waiting for " + remaining + " seconds!");
return;
}*/
else
{
chatCommands.push_back(ChatCommandHolder(filtered, &fromPlayer, type));
}
}
void PlayerbotAI::HandleTeleportAck()
{
if (IsRealPlayer())
return;
bot->GetMotionMaster()->Clear(true);
bot->StopMoving();
if (bot->IsBeingTeleportedNear())
{
// Temporary fix for instance can not enter
if (!bot->IsInWorld())
{
bot->GetMap()->AddPlayerToMap(bot);
}
while (bot->IsInWorld() && bot->IsBeingTeleportedNear())
{
Player* plMover = bot->m_mover->ToPlayer();
if (!plMover)
return;
WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 20);
p << plMover->GetPackGUID();
p << (uint32)0; // supposed to be flags? not used currently
p << (uint32)0; // time - not currently used
bot->GetSession()->HandleMoveTeleportAck(p);
};
}
if (bot->IsBeingTeleportedFar())
{
while (bot->IsBeingTeleportedFar())
{
bot->GetSession()->HandleMoveWorldportAck();
}
// SetNextCheckDelay(urand(2000, 5000));
if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId(), true);
Reset(true);
}
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
}
void PlayerbotAI::Reset(bool full)
{
if (bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
return;
WorldSession* botWorldSessionPtr = bot->GetSession();
bool logout = botWorldSessionPtr->ShouldLogOut(time(nullptr));
// cancel logout
if (!logout && bot->GetSession()->isLogingOut())
{
WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL);
bot->GetSession()->HandleLogoutCancelOpcode(data);
TellMaster("Logout cancelled!");
}
currentEngine = engines[BOT_STATE_NON_COMBAT];
currentState = BOT_STATE_NON_COMBAT;
nextAICheckDelay = 0;
whispers.clear();
aiObjectContext->GetValue<Unit*>("old target")->Set(nullptr);
aiObjectContext->GetValue<Unit*>("current target")->Set(nullptr);
aiObjectContext->GetValue<GuidVector>("prioritized targets")->Reset();
aiObjectContext->GetValue<ObjectGuid>("pull target")->Set(ObjectGuid::Empty);
aiObjectContext->GetValue<GuidPosition>("rpg target")->Set(GuidPosition());
aiObjectContext->GetValue<LootObject>("loot target")->Set(LootObject());
aiObjectContext->GetValue<uint32>("lfg proposal")->Set(0);
bot->SetTarget();
LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get();
lastSpell.Reset();
if (full)
{
aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
aiObjectContext->GetValue<LastMovement&>("last area trigger")->Get().Set(nullptr);
aiObjectContext->GetValue<LastMovement&>("last taxi")->Get().Set(nullptr);
aiObjectContext->GetValue<TravelTarget*>("travel target")
->Get()
->setTarget(sTravelMgr->nullTravelDestination, sTravelMgr->nullWorldPosition, true);
aiObjectContext->GetValue<TravelTarget*>("travel target")->Get()->setStatus(TRAVEL_STATUS_EXPIRED);
aiObjectContext->GetValue<TravelTarget*>("travel target")->Get()->setExpireIn(1000);
rpgInfo = NewRpgInfo();
}
aiObjectContext->GetValue<GuidSet&>("ignore rpg target")->Get().clear();
bot->GetMotionMaster()->Clear();
InterruptSpell();
if (full)
{
for (uint8 i = 0; i < BOT_STATE_MAX; i++)
{
engines[i]->Init();
}
}
}
bool PlayerbotAI::IsAllowedCommand(std::string const text)
{
if (unsecuredCommands.empty())
{
unsecuredCommands.insert("who");
unsecuredCommands.insert("wts");
unsecuredCommands.insert("sendmail");
unsecuredCommands.insert("invite");
unsecuredCommands.insert("leave");
unsecuredCommands.insert("rpg status");
}
for (std::set<std::string>::iterator i = unsecuredCommands.begin(); i != unsecuredCommands.end(); ++i)
{
if (text.find(*i) == 0)
{
return true;
}
}
return false;
}
void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fromPlayer)
{
if (!GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, type != CHAT_MSG_WHISPER, fromPlayer))
return;
if (type == CHAT_MSG_ADDON)
return;
if (type == CHAT_MSG_SYSTEM)
return;
if (text.find(sPlayerbotAIConfig->commandSeparator) != std::string::npos)
{
std::vector<std::string> commands;
split(commands, text, sPlayerbotAIConfig->commandSeparator.c_str());
for (std::vector<std::string>::iterator i = commands.begin(); i != commands.end(); ++i)
{
HandleCommand(type, *i, fromPlayer);
}
return;
}
std::string filtered = text;
if (!sPlayerbotAIConfig->commandPrefix.empty())
{
if (filtered.find(sPlayerbotAIConfig->commandPrefix) != 0)
return;
filtered = filtered.substr(sPlayerbotAIConfig->commandPrefix.size());
}
if (chatMap.empty())
{
chatMap["#w "] = CHAT_MSG_WHISPER;
chatMap["#p "] = CHAT_MSG_PARTY;
chatMap["#r "] = CHAT_MSG_RAID;
chatMap["#a "] = CHAT_MSG_ADDON;
chatMap["#g "] = CHAT_MSG_GUILD;
}
currentChat = std::pair<ChatMsg, time_t>(CHAT_MSG_WHISPER, 0);
for (std::map<std::string, ChatMsg>::iterator i = chatMap.begin(); i != chatMap.end(); ++i)
{
if (filtered.find(i->first) == 0)
{
filtered = filtered.substr(3);
currentChat = std::pair<ChatMsg, time_t>(i->second, time(nullptr) + 2);
break;
}
}
filtered = chatFilter.Filter(trim(filtered));
if (filtered.empty())
return;
if (filtered.substr(0, 6) == "debug ")
{
std::string const response = HandleRemoteCommand(filtered.substr(6));
WorldPacket data;
ChatHandler::BuildChatPacket(data, (ChatMsg)type, type == CHAT_MSG_ADDON ? LANG_ADDON : LANG_UNIVERSAL, bot,
nullptr, response.c_str());
fromPlayer->SendDirectMessage(&data);
return;
}
if (!IsAllowedCommand(filtered) &&
(!GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, type != CHAT_MSG_WHISPER, fromPlayer)))
return;
if (type == CHAT_MSG_RAID_WARNING && filtered.find(bot->GetName()) != std::string::npos &&
filtered.find("award") == std::string::npos)
{
chatCommands.push_back(ChatCommandHolder("warning", fromPlayer, type));
return;
}
if ((filtered.size() > 2 && filtered.substr(0, 2) == "d ") ||
(filtered.size() > 3 && filtered.substr(0, 3) == "do "))
{
std::string const action = filtered.substr(filtered.find(" ") + 1);
DoSpecificAction(action);
}
else if (type != CHAT_MSG_WHISPER && filtered.size() > 6 && filtered.substr(0, 6) == "queue ")
{
std::string const remaining = filtered.substr(filtered.find(" ") + 1);
uint32 index = 1;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
if (ref->GetSource() == master)
continue;
if (ref->GetSource() == bot)
break;
++index;
}
}
chatCommands.push_back(ChatCommandHolder(remaining, fromPlayer, type, time(nullptr) + index));
}
else if (filtered == "reset")
{
Reset(true);
}
else if (filtered == "logout")
{
if (!bot->GetSession()->isLogingOut())
{
if (type == CHAT_MSG_WHISPER)
TellMaster("I'm logging out!");
PlayerbotMgr* masterBotMgr = nullptr;
if (master)
masterBotMgr = GET_PLAYERBOT_MGR(master);
if (masterBotMgr)
masterBotMgr->LogoutPlayerBot(bot->GetGUID());
}
}
else if (filtered == "logout cancel")
{
if (bot->GetSession()->isLogingOut())
{
if (type == CHAT_MSG_WHISPER)
TellMaster("Logout cancelled!");
WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL);
bot->GetSession()->HandleLogoutCancelOpcode(data);
}
}
else
{
chatCommands.push_back(ChatCommandHolder(filtered, fromPlayer, type));
}
}
void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
{
if (packet.empty())
return;
if (!bot || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
{
return;
}
switch (packet.GetOpcode())
{
case SMSG_SPELL_FAILURE:
{
WorldPacket p(packet);
p.rpos(0);
ObjectGuid casterGuid;
p >> casterGuid.ReadAsPacked();
if (casterGuid != bot->GetGUID())
return;
uint8 count, result;
uint32 spellId;
p >> count >> spellId >> result;
SpellInterrupted(spellId);
return;
}
case SMSG_SPELL_DELAYED:
{
WorldPacket p(packet);
p.rpos(0);
ObjectGuid casterGuid;
p >> casterGuid.ReadAsPacked();
if (casterGuid != bot->GetGUID())
return;
uint32 delaytime;
p >> delaytime;
if (delaytime <= 1000)
IncreaseNextCheckDelay(delaytime);
return;
}
case SMSG_EMOTE: // do not react to NPC emotes
{
WorldPacket p(packet);
ObjectGuid source;
uint32 emoteId;
p.rpos(0);
p >> emoteId >> source;
if (source.IsPlayer())
botOutgoingPacketHandlers.AddPacket(packet);
return;
}
case SMSG_MESSAGECHAT: // do not react to self or if not ready to reply
{
if (!sPlayerbotAIConfig->randomBotTalk)
return;
if (!AllowActivity())
return;
WorldPacket p(packet);
if (!p.empty() && (p.GetOpcode() == SMSG_MESSAGECHAT || p.GetOpcode() == SMSG_GM_MESSAGECHAT))
{
p.rpos(0);
uint8 msgtype, chatTag;
uint32 lang, textLen, nameLen, unused;
ObjectGuid guid1, guid2;
std::string name = "";
std::string chanName = "";
std::string message = "";
p >> msgtype >> lang;
p >> guid1 >> unused;
if (guid1.IsEmpty() || p.size() > p.DEFAULT_SIZE)
return;
if (p.GetOpcode() == SMSG_GM_MESSAGECHAT)
{
p >> textLen;
p >> name;
}
switch (msgtype)
{
case CHAT_MSG_CHANNEL:
p >> chanName;
[[fallthrough]];
case CHAT_MSG_SAY:
case CHAT_MSG_PARTY:
case CHAT_MSG_YELL:
case CHAT_MSG_WHISPER:
case CHAT_MSG_GUILD:
p >> guid2;
p >> textLen >> message >> chatTag;
break;
default:
return;
}
// do not reply to self but always try to reply to real player
if (guid1 != bot->GetGUID())
{
time_t lastChat = GetAiObjectContext()->GetValue<time_t>("last said", "chat")->Get();
bool isPaused = time(0) < lastChat;
bool shouldReply = false;
bool isFromFreeBot = false;
sCharacterCache->GetCharacterNameByGuid(guid1, name);
uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guid1);
isFromFreeBot = sPlayerbotAIConfig->IsInRandomAccountList(accountId);
bool isMentioned = message.find(bot->GetName()) != std::string::npos;
ChatChannelSource chatChannelSource = GetChatChannelSource(bot, msgtype, chanName);
// random bot speaks, chat CD
if (isFromFreeBot && isPaused)
return;
// BG: react only if mentioned or if not channel and real player spoke
if (bot->InBattleground() && !(isMentioned || (msgtype != CHAT_MSG_CHANNEL && !isFromFreeBot)))
return;
if (HasRealPlayerMaster() && guid1 != GetMaster()->GetGUID())
return;
if (lang == LANG_ADDON)
return;
if (message.starts_with(sPlayerbotAIConfig->toxicLinksPrefix) &&
(GetChatHelper()->ExtractAllItemIds(message).size() > 0 ||
GetChatHelper()->ExtractAllQuestIds(message).size() > 0) &&
sPlayerbotAIConfig->toxicLinksRepliesChance)
{
if (urand(0, 50) > 0 || urand(1, 100) > sPlayerbotAIConfig->toxicLinksRepliesChance)
{
return;
}
}
else if ((GetChatHelper()->ExtractAllItemIds(message).count(19019) &&
sPlayerbotAIConfig->thunderfuryRepliesChance))
{
if (urand(0, 60) > 0 || urand(1, 100) > sPlayerbotAIConfig->thunderfuryRepliesChance)
{
return;
}
}
else
{
if (isFromFreeBot && urand(0, 20))
return;
// if (msgtype == CHAT_MSG_GUILD && (!sPlayerbotAIConfig->guildRepliesRate || urand(1, 100) >=
// sPlayerbotAIConfig->guildRepliesRate)) return;
if (!isFromFreeBot)
{
if (!isMentioned && urand(0, 4))
return;
}
else
{
if (urand(0, 20 + 10 * isMentioned))
return;
}
}
QueueChatResponse(
std::move(ChatQueuedReply{msgtype, guid1.GetCounter(), guid2.GetCounter(), message, chanName,
name, time(nullptr) + urand(inCombat ? 10 : 5, inCombat ? 25 : 15)}));
GetAiObjectContext()->GetValue<time_t>("last said", "chat")->Set(time(0) + urand(5, 25));
return;
}
}
return;
}
case SMSG_MOVE_KNOCK_BACK: // handle knockbacks
{
WorldPacket p(packet);
p.rpos(0);
ObjectGuid guid;
uint32 counter;
float vcos, vsin, horizontalSpeed, verticalSpeed = 0.f;
p >> guid.ReadAsPacked() >> counter >> vcos >> vsin >> horizontalSpeed >> verticalSpeed;
if (horizontalSpeed <= 0.1f)
{
horizontalSpeed = 0.11f;
}
verticalSpeed = -verticalSpeed;
// high vertical may result in stuck as bot can not handle gravity
if (verticalSpeed > 35.0f)
break;
// stop casting
InterruptSpell();
// stop movement
bot->StopMoving();
bot->GetMotionMaster()->Clear();
Unit* currentTarget = GetAiObjectContext()->GetValue<Unit*>("current target")->Get();
bot->GetMotionMaster()->MoveKnockbackFromForPlayer(bot->GetPositionX() - vcos, bot->GetPositionY() - vsin,
horizontalSpeed, verticalSpeed);
// bot->AddUnitMovementFlag(MOVEMENTFLAG_FALLING);
// bot->AddUnitMovementFlag(MOVEMENTFLAG_FORWARD);
// bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_PENDING_STOP);
// if (bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_SPLINE_ELEVATION))
// bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_SPLINE_ELEVATION);
// bot->GetMotionMaster()->MoveIdle();
// Position dest = bot->GetPosition();
// float moveTimeHalf = verticalSpeed / Movement::gravity;
// float dist = 2 * moveTimeHalf * horizontalSpeed;
// float max_height = -Movement::computeFallElevation(moveTimeHalf, false, -verticalSpeed);
// Use a mmap raycast to get a valid destination.
// bot->GetMotionMaster()->MoveKnockbackFrom(fx, fy, horizontalSpeed, verticalSpeed);
// // set delay based on actual distance
// float newdis = sqrt(sServerFacade->GetDistance2d(bot, fx, fy));
// SetNextCheckDelay((uint32)((newdis / dis) * moveTimeHalf * 4 * IN_MILLISECONDS));
// // add moveflags
// // copy MovementInfo
// MovementInfo movementInfo = bot->m_movementInfo;
// // send ack
// WorldPacket ack(CMSG_MOVE_KNOCK_BACK_ACK);
// // movementInfo.jump.cosAngle = vcos;
// // movementInfo.jump.sinAngle = vsin;
// // movementInfo.jump.zspeed = -verticalSpeed;
// // movementInfo.jump.xyspeed = horizontalSpeed;
// ack << bot->GetGUID().WriteAsPacked();
// // bot->m_mover->BuildMovementPacket(&ack);
// ack << (uint32)0;
// bot->BuildMovementPacket(&ack);
// // ack << movementInfo.jump.sinAngle;
// // ack << movementInfo.jump.cosAngle;
// // ack << movementInfo.jump.xyspeed;
// // ack << movementInfo.jump.zspeed;
// bot->GetSession()->HandleMoveKnockBackAck(ack);
// // // set jump destination for MSG_LAND packet
// SetJumpDestination(Position(x, y, z, bot->GetOrientation()));
// bot->Heart();
// */
return;
}
default:
botOutgoingPacketHandlers.AddPacket(packet);
}
}
void PlayerbotAI::SpellInterrupted(uint32 spellid)
{
for (uint8 type = CURRENT_MELEE_SPELL; type <= CURRENT_CHANNELED_SPELL; type++)
{
Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type);
if (!spell)
continue;
if (spell->GetSpellInfo()->Id == spellid)
bot->InterruptSpell((CurrentSpellTypes)type);
}
// LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get();
// lastSpell.id = 0;
}
int32 PlayerbotAI::CalculateGlobalCooldown(uint32 spellid)
{
if (!spellid)
return 0;
if (bot->HasSpellCooldown(spellid))
return sPlayerbotAIConfig->globalCoolDown;
return sPlayerbotAIConfig->reactDelay;
}
void PlayerbotAI::HandleMasterIncomingPacket(WorldPacket const& packet)
{
masterIncomingPacketHandlers.AddPacket(packet);
}
void PlayerbotAI::HandleMasterOutgoingPacket(WorldPacket const& packet)
{
masterOutgoingPacketHandlers.AddPacket(packet);
}
void PlayerbotAI::ChangeEngine(BotState type)
{
Engine* engine = engines[type];
if (currentEngine != engine)
{
currentEngine = engine;
currentState = type;
ReInitCurrentEngine();
switch (type)
{
case BOT_STATE_COMBAT:
// LOG_DEBUG("playerbots", "=== {} COMBAT ===", bot->GetName().c_str());
break;
case BOT_STATE_NON_COMBAT:
// LOG_DEBUG("playerbots", "=== {} NON-COMBAT ===", bot->GetName().c_str());
break;
case BOT_STATE_DEAD:
// LOG_DEBUG("playerbots", "=== {} DEAD ===", bot->GetName().c_str());
break;
default:
break;
}
}
}
void PlayerbotAI::DoNextAction(bool min)
{
if (!bot->IsInWorld() || bot->IsBeingTeleported() || (GetMaster() && GetMaster()->IsBeingTeleported()))
{
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
return;
}
if (bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
SetNextCheckDelay(sPlayerbotAIConfig->passiveDelay);
return;
}
// Change engine if just died
bool isBotAlive = bot->IsAlive();
if (currentEngine != engines[BOT_STATE_DEAD] && !isBotAlive)
{
bot->StopMoving();
bot->GetMotionMaster()->Clear();
bot->GetMotionMaster()->MoveIdle();
// Death Count to prevent skeleton piles
Player* master = GetMaster(); // warning here - whipowill
if (!HasActivePlayerMaster() && !bot->InBattleground())
{
uint32 dCount = aiObjectContext->GetValue<uint32>("death count")->Get();
aiObjectContext->GetValue<uint32>("death count")->Set(++dCount);
}
aiObjectContext->GetValue<Unit*>("current target")->Set(nullptr);
aiObjectContext->GetValue<Unit*>("enemy player target")->Set(nullptr);
aiObjectContext->GetValue<ObjectGuid>("pull target")->Set(ObjectGuid::Empty);
aiObjectContext->GetValue<LootObject>("loot target")->Set(LootObject());
ChangeEngine(BOT_STATE_DEAD);
return;
}
// Change engine if just ressed
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive)
{
ChangeEngine(BOT_STATE_NON_COMBAT);
return;
}
// Clear targets if in combat but sticking with old data
if (currentEngine == engines[BOT_STATE_NON_COMBAT] && bot->IsInCombat())
{
Unit* currentTarget = aiObjectContext->GetValue<Unit*>("current target")->Get();
if (currentTarget != nullptr)
{
aiObjectContext->GetValue<Unit*>("current target")->Set(nullptr);
}
}
bool minimal = !AllowActivity();
currentEngine->DoNextAction(nullptr, 0, (minimal || min));
if (minimal)
{
if (!bot->isAFK() && !bot->InBattleground() && !HasRealPlayerMaster())
bot->ToggleAFK();
SetNextCheckDelay(sPlayerbotAIConfig->passiveDelay);
return;
}
else if (bot->isAFK())
bot->ToggleAFK();
Group* group = bot->GetGroup();
PlayerbotAI* masterBotAI = nullptr;
if (master)
masterBotAI = GET_PLAYERBOT_AI(master);
// Test BG master set
if ((!master || (masterBotAI && !masterBotAI->IsRealPlayer())) && group)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return;
}
// Ideally we want to have the leader as master.
Player* newMaster = botAI->GetGroupMaster();
Player* playerMaster = nullptr;
// Are there any non-bot players in the group?
if (!newMaster || GET_PLAYERBOT_AI(newMaster))
{
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || member == bot || member == newMaster || !member->IsInWorld() || !member->IsInSameRaidWith(bot))
continue;
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
if (memberBotAI)
{
if (memberBotAI->IsRealPlayer() && !bot->InBattleground())
playerMaster = member;
continue;
}
// Same BG checks (optimize checking conditions here)
if (bot->InBattleground() && bot->GetBattleground() &&
bot->GetBattleground()->GetBgTypeID() == BATTLEGROUND_AV && !GET_PLAYERBOT_AI(member) &&
member->InBattleground() && bot->GetMapId() == member->GetMapId())
{
// Skip if same BG but same subgroup or lower level
if (!group->SameSubGroup(bot, member) || member->GetLevel() < bot->GetLevel())
continue;
// Follow real player only if higher honor points
uint32 honorpts = member->GetHonorPoints();
if (bot->GetHonorPoints() && honorpts < bot->GetHonorPoints())
continue;
playerMaster = member;
continue;
}
if (bot->InBattleground())
continue;
newMaster = member;
break;
}
}
if (!newMaster && playerMaster)
newMaster = playerMaster;
if (newMaster && (!master || master != newMaster) && bot != newMaster)
{
master = newMaster;
botAI->SetMaster(newMaster);
botAI->ResetStrategies();
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
if (botAI->GetMaster() == botAI->GetGroupMaster())
botAI->TellMaster("Hello, I follow you!");
else
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
}
}
if (master && master->IsInWorld())
{
float distance = sServerFacade->GetDistance2d(bot, master);
if (master->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_WALKING) && distance < 20.0f)
bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_WALKING);
else
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_WALKING);
if (master->IsSitState() && nextAICheckDelay < 1000)
{
if (!bot->isMoving() && distance < 10.0f)
bot->SetStandState(UNIT_STAND_STATE_SIT);
}
else if (nextAICheckDelay < 1000)
bot->SetStandState(UNIT_STAND_STATE_STAND);
}
else if (bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_WALKING))
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_WALKING);
else if ((nextAICheckDelay < 1000) && bot->IsSitState())
bot->SetStandState(UNIT_STAND_STATE_STAND);
bool hasMountAura = bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) ||
bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED);
if (hasMountAura && !bot->IsMounted())
{
bot->RemoveAurasByType(SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED);
bot->RemoveAurasByType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED);
}
}
void PlayerbotAI::ReInitCurrentEngine()
{
// InterruptSpell();
currentEngine->Init();
}
void PlayerbotAI::ChangeStrategy(std::string const names, BotState type)
{
Engine* e = engines[type];
if (!e)
return;
e->ChangeStrategy(names);
}
void PlayerbotAI::ClearStrategies(BotState type)
{
Engine* e = engines[type];
if (!e)
return;
e->removeAllStrategies();
}
std::vector<std::string> PlayerbotAI::GetStrategies(BotState type)
{
Engine* e = engines[type];
if (!e)
return std::vector<std::string>();
return e->GetStrategies();
}
void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
{
std::string strategyName;
switch (mapId)
{
case 409:
strategyName = "mc";
break;
case 469:
strategyName = "bwl";
break;
case 509:
strategyName = "aq20";
break;
case 533:
strategyName = "naxx";
break;
case 574:
strategyName = "wotlk-uk"; // Utgarde Keep
break;
case 575:
strategyName = "wotlk-up"; // Utgarde Pinnacle
break;
case 576:
strategyName = "wotlk-nex"; // The Nexus
break;
case 578:
strategyName = "wotlk-occ"; // The Oculus
break;
case 595:
strategyName = "wotlk-cos"; // The Culling of Stratholme
break;
case 599:
strategyName = "wotlk-hos"; // Halls of Stone
break;
case 600:
strategyName = "wotlk-dtk"; // Drak'Tharon Keep
break;
case 601:
strategyName = "wotlk-an"; // Azjol-Nerub
break;
case 602:
strategyName = "wotlk-hol"; // Halls of Lightning
break;
case 603:
strategyName = "uld";
break;
case 604:
strategyName = "wotlk-gd"; // Gundrak
break;
case 608:
strategyName = "wotlk-vh"; // Violet Hold
break;
case 615:
strategyName = "wotlk-os"; // Obsidian Sanctum
break;
case 616:
strategyName = "wotlk-eoe"; // Eye Of Eternity
break;
case 619:
strategyName = "wotlk-ok"; // Ahn'kahet: The Old Kingdom
break;
case 631:
strategyName = "icc";
break;
case 632:
strategyName = "wotlk-fos"; // The Forge of Souls
break;
case 650:
strategyName = "wotlk-toc"; // Trial of the Champion
break;
case 658:
strategyName = "wotlk-pos"; // Pit of Saron
break;
case 668:
strategyName = "wotlk-hor"; // Halls of Reflection
break;
default:
break;
}
if (strategyName.empty())
return;
engines[BOT_STATE_COMBAT]->addStrategy(strategyName);
engines[BOT_STATE_NON_COMBAT]->addStrategy(strategyName);
if (tellMaster && !strategyName.empty())
{
std::ostringstream out;
out << "Add " << strategyName << " instance strategy";
TellMasterNoFacing(out.str());
}
}
bool PlayerbotAI::DoSpecificAction(std::string const name, Event event, bool silent, std::string const qualifier)
{
std::ostringstream out;
for (uint8 i = 0; i < BOT_STATE_MAX; i++)
{
ActionResult res = engines[i]->ExecuteAction(name, event, qualifier);
switch (res)
{
case ACTION_RESULT_UNKNOWN:
continue;
case ACTION_RESULT_OK:
if (!silent)
{
PlaySound(TEXT_EMOTE_NOD);
}
return true;
case ACTION_RESULT_IMPOSSIBLE:
out << name << ": impossible";
if (!silent)
{
TellError(out.str());
PlaySound(TEXT_EMOTE_NO);
}
return false;
case ACTION_RESULT_USELESS:
out << name << ": useless";
if (!silent)
{
TellError(out.str());
PlaySound(TEXT_EMOTE_NO);
}
return false;
case ACTION_RESULT_FAILED:
if (!silent)
{
out << name << ": failed";
TellError(out.str());
}
return false;
}
}
if (!silent)
{
out << name << ": unknown action";
TellError(out.str());
}
return false;
}
bool PlayerbotAI::PlaySound(uint32 emote)
{
if (EmotesTextSoundEntry const* soundEntry = FindTextSoundEmoteFor(emote, bot->getRace(), bot->getGender()))
{
bot->PlayDistanceSound(soundEntry->SoundId);
return true;
}
return false;
}
bool PlayerbotAI::PlayEmote(uint32 emote)
{
WorldPacket data(SMSG_TEXT_EMOTE);
data << (TextEmotes)emote;
data << EmoteAction::GetNumberOfEmoteVariants((TextEmotes)emote, bot->getRace(), bot->getGender());
data << ((master && (sServerFacade->GetDistance2d(bot, master) < 30.0f) && urand(0, 1)) ? master->GetGUID()
: (bot->GetTarget() && urand(0, 1)) ? bot->GetTarget()
: ObjectGuid::Empty);
bot->GetSession()->HandleTextEmoteOpcode(data);
return false;
}
bool PlayerbotAI::ContainsStrategy(StrategyType type)
{
for (uint8 i = 0; i < BOT_STATE_MAX; i++)
{
if (engines[i]->HasStrategyType(type))
return true;
}
return false;
}
bool PlayerbotAI::HasStrategy(std::string const name, BotState type) { return engines[type]->HasStrategy(name); }
void PlayerbotAI::ResetStrategies(bool load)
{
for (uint8 i = 0; i < BOT_STATE_MAX; i++)
engines[i]->removeAllStrategies();
AiFactory::AddDefaultCombatStrategies(bot, this, engines[BOT_STATE_COMBAT]);
AiFactory::AddDefaultNonCombatStrategies(bot, this, engines[BOT_STATE_NON_COMBAT]);
AiFactory::AddDefaultDeadStrategies(bot, this, engines[BOT_STATE_DEAD]);
if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId());
for (uint8 i = 0; i < BOT_STATE_MAX; i++)
engines[i]->Init();
// if (load)
// sPlayerbotDbStore->Load(this);
}
bool PlayerbotAI::IsRanged(Player* player, bool bySpec)
{
PlayerbotAI* botAi = GET_PLAYERBOT_AI(player);
if (!bySpec && botAi)
return botAi->ContainsStrategy(STRATEGY_TYPE_RANGED);
int tab = AiFactory::GetPlayerSpecTab(player);
switch (player->getClass())
{
case CLASS_DEATH_KNIGHT:
case CLASS_WARRIOR:
case CLASS_ROGUE:
return false;
break;
case CLASS_DRUID:
if (tab == 1)
{
return false;
}
break;
case CLASS_PALADIN:
if (tab != 0)
{
return false;
}
break;
case CLASS_SHAMAN:
if (tab == 1)
{
return false;
}
break;
}
return true;
}
bool PlayerbotAI::IsMelee(Player* player, bool bySpec) { return !IsRanged(player, bySpec); }
bool PlayerbotAI::IsCaster(Player* player, bool bySpec) { return IsRanged(player, bySpec) && player->getClass() != CLASS_HUNTER; }
bool PlayerbotAI::IsCombo(Player* player, bool bySpec)
{
// int tab = AiFactory::GetPlayerSpecTab(player);
return player->getClass() == CLASS_ROGUE ||
(player->getClass() == CLASS_DRUID && player->HasAura(768)); // cat druid
}
bool PlayerbotAI::IsRangedDps(Player* player, bool bySpec) { return IsRanged(player, bySpec) && IsDps(player, bySpec); }
bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index)
{
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
Group::MemberSlotList const& slots = group->GetMemberSlots();
int counter = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (IsHeal(member)) // Check if the member is a healer
{
bool isAssistant = group->IsAssistant(member->GetGUID());
// Check if the index matches for both assistant and non-assistant healers
if ((isAssistant && index == counter) || (!isAssistant && index == counter))
{
return player == member;
}
counter++;
}
}
return false;
}
bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
{
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
Group::MemberSlotList const& slots = group->GetMemberSlots();
int counter = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (IsRangedDps(member)) // Check if the member is a ranged DPS
{
bool isAssistant = group->IsAssistant(member->GetGUID());
// Check the index for both assistant and non-assistant ranges
if ((isAssistant && index == counter) || (!isAssistant && index == counter))
{
return player == member;
}
counter++;
}
}
return false;
}
bool PlayerbotAI::HasAggro(Unit* unit)
{
if (!unit)
{
return false;
}
bool isMT = IsMainTank(bot);
Unit* victim = unit->GetVictim();
if (victim && (victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && IsTank(victim->ToPlayer()))))
{
return true;
}
return false;
}
int32 PlayerbotAI::GetGroupSlotIndex(Player* player)
{
Group* group = bot->GetGroup();
if (!group)
{
return -1;
}
Group::MemberSlotList const& slots = group->GetMemberSlots();
int counter = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (player == member)
{
return counter;
}
counter++;
}
return 0;
}
int32 PlayerbotAI::GetRangedIndex(Player* player)
{
if (!IsRanged(player))
{
return -1;
}
Group* group = bot->GetGroup();
if (!group)
{
return -1;
}
Group::MemberSlotList const& slots = group->GetMemberSlots();
int counter = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (player == member)
{
return counter;
}
if (IsRanged(member))
{
counter++;
}
}
return 0;
}
int32 PlayerbotAI::GetClassIndex(Player* player, uint8_t cls)
{
if (player->getClass() != cls)
{
return -1;
}
Group* group = bot->GetGroup();
if (!group)
{
return -1;
}
Group::MemberSlotList const& slots = group->GetMemberSlots();
int counter = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (player == member)
{
return counter;
}
if (member->getClass() == cls)
{
counter++;
}
}
return 0;
}
int32 PlayerbotAI::GetRangedDpsIndex(Player* player)
{
if (!IsRangedDps(player))
{
return -1;
}
Group* group = bot->GetGroup();
if (!group)
{
return -1;
}
Group::MemberSlotList const& slots = group->GetMemberSlots();
int counter = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (player == member)
{
return counter;
}
if (IsRangedDps(member))
{
counter++;
}
}
return 0;
}
int32 PlayerbotAI::GetMeleeIndex(Player* player)
{
if (IsRanged(player))
{
return -1;
}
Group* group = bot->GetGroup();
if (!group)
{
return -1;
}
Group::MemberSlotList const& slots = group->GetMemberSlots();
int counter = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (player == member)
{
return counter;
}
if (!IsRanged(member))
{
counter++;
}
}
return 0;
}
bool PlayerbotAI::IsTank(Player* player, bool bySpec)
{
PlayerbotAI* botAi = GET_PLAYERBOT_AI(player);
if (!bySpec && botAi)
return botAi->ContainsStrategy(STRATEGY_TYPE_TANK);
int tab = AiFactory::GetPlayerSpecTab(player);
switch (player->getClass())
{
case CLASS_DEATH_KNIGHT:
if (tab == DEATHKNIGHT_TAB_BLOOD)
{
return true;
}
break;
case CLASS_PALADIN:
if (tab == PALADIN_TAB_PROTECTION)
{
return true;
}
break;
case CLASS_WARRIOR:
if (tab == WARRIOR_TAB_PROTECTION)
{
return true;
}
break;
case CLASS_DRUID:
if (tab == DRUID_TAB_FERAL && (player->GetShapeshiftForm() == FORM_BEAR ||
player->GetShapeshiftForm() == FORM_DIREBEAR || player->HasAura(16931)))
{
return true;
}
break;
}
return false;
}
bool PlayerbotAI::IsHeal(Player* player, bool bySpec)
{
PlayerbotAI* botAi = GET_PLAYERBOT_AI(player);
if (!bySpec && botAi)
return botAi->ContainsStrategy(STRATEGY_TYPE_HEAL);
int tab = AiFactory::GetPlayerSpecTab(player);
switch (player->getClass())
{
case CLASS_PRIEST:
if (tab == PRIEST_TAB_DISIPLINE || tab == PRIEST_TAB_HOLY)
{
return true;
}
break;
case CLASS_DRUID:
if (tab == DRUID_TAB_RESTORATION)
{
return true;
}
break;
case CLASS_SHAMAN:
if (tab == SHAMAN_TAB_RESTORATION)
{
return true;
}
break;
case CLASS_PALADIN:
if (tab == PALADIN_TAB_HOLY)
{
return true;
}
break;
}
return false;
}
bool PlayerbotAI::IsDps(Player* player, bool bySpec)
{
PlayerbotAI* botAi = GET_PLAYERBOT_AI(player);
if (!bySpec && botAi)
return botAi->ContainsStrategy(STRATEGY_TYPE_DPS);
int tab = AiFactory::GetPlayerSpecTab(player);
switch (player->getClass())
{
case CLASS_MAGE:
case CLASS_WARLOCK:
case CLASS_HUNTER:
case CLASS_ROGUE:
return true;
case CLASS_PRIEST:
if (tab == PRIEST_TAB_SHADOW)
{
return true;
}
break;
case CLASS_DRUID:
if (tab == DRUID_TAB_BALANCE)
{
return true;
}
if (tab == DRUID_TAB_FERAL && !IsTank(player, bySpec))
{
return true;
}
break;
case CLASS_SHAMAN:
if (tab != SHAMAN_TAB_RESTORATION)
{
return true;
}
break;
case CLASS_PALADIN:
if (tab == PALADIN_TAB_RETRIBUTION)
{
return true;
}
break;
case CLASS_DEATH_KNIGHT:
if (tab != DEATHKNIGHT_TAB_BLOOD)
{
return true;
}
break;
case CLASS_WARRIOR:
if (tab != WARRIOR_TAB_PROTECTION)
{
return true;
}
break;
}
return false;
}
bool PlayerbotAI::IsMainTank(Player* player)
{
Group* group = player->GetGroup();
if (!group)
{
return false;
}
ObjectGuid mainTank = ObjectGuid();
Group::MemberSlotList const& slots = group->GetMemberSlots();
for (Group::member_citerator itr = slots.begin(); itr != slots.end(); ++itr)
{
if (itr->flags & MEMBER_FLAG_MAINTANK)
{
mainTank = itr->guid;
break;
}
}
if (mainTank != ObjectGuid::Empty)
{
return player->GetGUID() == mainTank;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (IsTank(member) && member->IsAlive())
{
return player->GetGUID() == member->GetGUID();
}
}
return false;
}
uint32 PlayerbotAI::GetGroupTankNum(Player* player)
{
Group* group = player->GetGroup();
if (!group)
{
return 0;
}
uint32 result = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (IsTank(member) && member->IsAlive())
{
result++;
}
}
return result;
}
bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMainTank(player); }
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
{
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
Group::MemberSlotList const& slots = group->GetMemberSlots();
int counter = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
{
if (index == counter)
{
return player == member;
}
counter++;
}
}
// not enough
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
{
if (index == counter)
{
return player == member;
}
counter++;
}
}
return false;
}
namespace acore
{
class UnitByGuidInRangeCheck
{
public:
UnitByGuidInRangeCheck(WorldObject const* obj, ObjectGuid guid, float range)
: i_obj(obj), i_range(range), i_guid(guid)
{
}
WorldObject const& GetFocusObject() const { return *i_obj; }
bool operator()(Unit* u) { return u->GetGUID() == i_guid && i_obj->IsWithinDistInMap(u, i_range); }
private:
WorldObject const* i_obj;
float i_range;
ObjectGuid i_guid;
};
class GameObjectByGuidInRangeCheck
{
public:
GameObjectByGuidInRangeCheck(WorldObject const* obj, ObjectGuid guid, float range)
: i_obj(obj), i_range(range), i_guid(guid)
{
}
WorldObject const& GetFocusObject() const { return *i_obj; }
bool operator()(GameObject* u)
{
if (u && i_obj->IsWithinDistInMap(u, i_range) && u->isSpawned() && u->GetGOInfo() && u->GetGUID() == i_guid)
return true;
return false;
}
private:
WorldObject const* i_obj;
float i_range;
ObjectGuid i_guid;
};
}; // namespace acore
Unit* PlayerbotAI::GetUnit(ObjectGuid guid)
{
if (!guid)
return nullptr;
return ObjectAccessor::GetUnit(*bot, guid);
}
Player* PlayerbotAI::GetPlayer(ObjectGuid guid)
{
Unit* unit = GetUnit(guid);
return unit ? unit->ToPlayer() : nullptr;
}
uint32 GetCreatureIdForCreatureTemplateId(uint32 creatureTemplateId)
{
QueryResult results =
WorldDatabase.Query("SELECT guid FROM `creature` WHERE id1 = {} LIMIT 1;", creatureTemplateId);
if (results)
{
Field* fields = results->Fetch();
return fields[0].Get<uint32>();
}
return 0;
}
Unit* PlayerbotAI::GetUnit(CreatureData const* creatureData)
{
if (!creatureData)
return nullptr;
Map* map = sMapMgr->FindMap(creatureData->mapid, 0);
if (!map)
return nullptr;
uint32 spawnId = creatureData->spawnId;
if (!spawnId) // workaround for CreatureData with missing spawnId (this just uses first matching creatureId in DB,
// but thats ok this method is only used for battlemasters and theres only 1 of each type)
spawnId = GetCreatureIdForCreatureTemplateId(creatureData->id1);
auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(spawnId);
if (creatureBounds.first == creatureBounds.second)
return nullptr;
return creatureBounds.first->second;
}
Creature* PlayerbotAI::GetCreature(ObjectGuid guid)
{
if (!guid)
return nullptr;
return ObjectAccessor::GetCreature(*bot, guid);
}
GameObject* PlayerbotAI::GetGameObject(ObjectGuid guid)
{
if (!guid)
return nullptr;
return ObjectAccessor::GetGameObject(*bot, guid);
}
// GameObject* PlayerbotAI::GetGameObject(GameObjectData const* gameObjectData)
// {
// if (!gameObjectData)
// return nullptr;
// Map* map = sMapMgr->FindMap(gameObjectData->mapid, 0);
// if (!map)
// return nullptr;
// auto gameobjectBounds = map->GetGameObjectBySpawnIdStore().equal_range(gameObjectData->spawnId);
// if (gameobjectBounds.first == gameobjectBounds.second)
// return nullptr;
// return gameobjectBounds.first->second;
// }
WorldObject* PlayerbotAI::GetWorldObject(ObjectGuid guid)
{
if (!guid)
return nullptr;
return ObjectAccessor::GetWorldObject(*bot, guid);
}
const AreaTableEntry* PlayerbotAI::GetCurrentArea()
{
return sAreaTableStore.LookupEntry(
bot->GetMap()->GetAreaId(bot->GetPhaseMask(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()));
}
const AreaTableEntry* PlayerbotAI::GetCurrentZone()
{
return sAreaTableStore.LookupEntry(
bot->GetMap()->GetZoneId(bot->GetPhaseMask(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()));
}
std::string PlayerbotAI::GetLocalizedAreaName(const AreaTableEntry* entry)
{
if (entry)
return entry->area_name[sWorld->GetDefaultDbcLocale()];
return "";
}
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 (GET_PLAYERBOT_AI(member) && !GET_PLAYERBOT_AI(member)->IsRealPlayer())
continue;
members.push_back(ref->GetSource());
}
return members;
}
bool PlayerbotAI::SayToGuild(const std::string& msg)
{
if (msg.empty())
{
return false;
}
if (bot->GetGuildId())
{
if (Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId()))
{
if (!guild->HasRankRight(bot, GR_RIGHT_GCHATSPEAK))
{
return false;
}
guild->BroadcastToGuild(bot->GetSession(), false, msg.c_str(), LANG_UNIVERSAL);
return true;
}
}
return false;
}
bool PlayerbotAI::SayToWorld(const std::string& msg)
{
if (msg.empty())
{
return false;
}
ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId());
if (!cMgr)
return false;
// no zone
if (Channel* worldChannel = cMgr->GetChannel("World", bot))
{
worldChannel->Say(bot->GetGUID(), msg.c_str(), LANG_UNIVERSAL);
return true;
}
return false;
}
bool PlayerbotAI::SayToChannel(const std::string& msg, const ChatChannelId& chanId)
{
// Checks whether the message or ChannelMgr is valid
if (msg.empty())
return false;
ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId());
if (!cMgr)
return false;
AreaTableEntry const* current_zone = GetCurrentZone();
if (!current_zone)
return false;
const auto current_str_zone = GetLocalizedAreaName(current_zone);
std::mutex socialMutex;
std::lock_guard<std::mutex> lock(socialMutex); // Blocking for thread safety when accessing SocialMgr
for (auto const& [key, channel] : cMgr->GetChannels())
{
// Checks if the channel pointer is valid
if (!channel)
continue;
// Checks if the channel matches the specified ChatChannelId
if (channel->GetChannelId() == chanId)
{
// If the channel name is empty, skip it to avoid access problems
if (channel->GetName().empty())
continue;
// Checks if the channel name contains the current zone
const auto does_contains = channel->GetName().find(current_str_zone) != std::string::npos;
if (chanId != ChatChannelId::LOOKING_FOR_GROUP && chanId != ChatChannelId::WORLD_DEFENSE && !does_contains)
{
continue;
}
else if (chanId == ChatChannelId::LOOKING_FOR_GROUP || chanId == ChatChannelId::WORLD_DEFENSE)
{
// Here you can add the capital check if necessary
}
// Final check to ensure the channel is correct before trying to say something
if (channel)
{
channel->Say(bot->GetGUID(), msg.c_str(), LANG_UNIVERSAL);
return true;
}
}
}
return false;
}
bool PlayerbotAI::SayToParty(const std::string& msg)
{
if (!bot->GetGroup())
return false;
WorldPacket data;
ChatHandler::BuildChatPacket(data, CHAT_MSG_PARTY, msg.c_str(), LANG_UNIVERSAL, CHAT_TAG_NONE, bot->GetGUID(),
bot->GetName());
for (auto reciever : GetPlayersInGroup())
{
sServerFacade->SendPacket(reciever, &data);
}
return true;
}
bool PlayerbotAI::SayToRaid(const std::string& msg)
{
if (!bot->GetGroup() || bot->GetGroup()->isRaidGroup())
return false;
WorldPacket data;
ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID, msg.c_str(), LANG_UNIVERSAL, CHAT_TAG_NONE, bot->GetGUID(),
bot->GetName());
for (auto reciever : GetPlayersInGroup())
{
sServerFacade->SendPacket(reciever, &data);
}
return true;
}
bool PlayerbotAI::Yell(const std::string& msg)
{
if (bot->GetTeamId() == TeamId::TEAM_ALLIANCE)
{
bot->Yell(msg, LANG_COMMON);
}
else
{
bot->Yell(msg, LANG_ORCISH);
}
return true;
}
bool PlayerbotAI::Say(const std::string& msg)
{
if (bot->GetTeamId() == TeamId::TEAM_ALLIANCE)
{
bot->Say(msg, LANG_COMMON);
}
else
{
bot->Say(msg, LANG_ORCISH);
}
return true;
}
bool PlayerbotAI::Whisper(const std::string& msg, const std::string& receiverName)
{
const auto receiver = ObjectAccessor::FindPlayerByName(receiverName);
if (!receiver)
{
return false;
}
if (bot->GetTeamId() == TeamId::TEAM_ALLIANCE)
{
bot->Whisper(msg, LANG_COMMON, receiver);
}
else
{
bot->Whisper(msg, LANG_ORCISH, receiver);
}
return true;
}
bool PlayerbotAI::TellMasterNoFacing(std::ostringstream& stream, PlayerbotSecurityLevel securityLevel)
{
return TellMasterNoFacing(stream.str(), securityLevel);
}
bool PlayerbotAI::TellMasterNoFacing(std::string const text, PlayerbotSecurityLevel securityLevel)
{
Player* master = GetMaster();
PlayerbotAI* masterBotAI = nullptr;
if (master)
masterBotAI = GET_PLAYERBOT_AI(master);
if ((!master || (masterBotAI && !masterBotAI->IsRealPlayer())) &&
(sPlayerbotAIConfig->randomBotSayWithoutMaster || HasStrategy("debug", BOT_STATE_NON_COMBAT)))
{
bot->Say(text, (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
return true;
}
if (!IsTellAllowed(securityLevel))
return false;
time_t lastSaid = whispers[text];
if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
{
whispers[text] = time(nullptr);
ChatMsg type = CHAT_MSG_WHISPER;
if (currentChat.second - time(nullptr) >= 1)
type = currentChat.first;
WorldPacket data;
ChatHandler::BuildChatPacket(data, type == CHAT_MSG_ADDON ? CHAT_MSG_PARTY : type,
type == CHAT_MSG_ADDON ? LANG_ADDON : LANG_UNIVERSAL, bot, nullptr, text.c_str());
master->SendDirectMessage(&data);
}
return true;
}
bool PlayerbotAI::TellError(std::string const text, PlayerbotSecurityLevel securityLevel)
{
Player* master = GetMaster();
if (!IsTellAllowed(securityLevel) || !master || GET_PLAYERBOT_AI(master))
return false;
if (PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(master))
mgr->TellError(bot->GetName(), text);
return false;
}
bool PlayerbotAI::IsTellAllowed(PlayerbotSecurityLevel securityLevel)
{
Player* master = GetMaster();
if (!master || master->IsBeingTeleported())
return false;
if (!GetSecurity()->CheckLevelFor(securityLevel, true, master))
return false;
if (sPlayerbotAIConfig->whisperDistance && !bot->GetGroup() && sRandomPlayerbotMgr->IsRandomBot(bot) &&
master->GetSession()->GetSecurity() < SEC_GAMEMASTER &&
(bot->GetMapId() != master->GetMapId() ||
sServerFacade->GetDistance2d(bot, master) > sPlayerbotAIConfig->whisperDistance))
return false;
return true;
}
bool PlayerbotAI::TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel securityLevel)
{
return TellMaster(stream.str(), securityLevel);
}
bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel)
{
if (!master || !TellMasterNoFacing(text, securityLevel))
return false;
if (!bot->isMoving() && !bot->IsInCombat() && bot->GetMapId() == master->GetMapId() &&
!bot->HasUnitState(UNIT_STATE_IN_FLIGHT) && !bot->IsFlying())
{
if (!bot->HasInArc(EMOTE_ANGLE_IN_FRONT, master, sPlayerbotAIConfig->sightDistance))
bot->SetFacingToObject(master);
bot->HandleEmoteCommand(EMOTE_ONESHOT_TALK);
}
return true;
}
bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
{
if (!aurEff)
return false;
if (!unit->IsHostileTo(bot))
return true;
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
uint32 stacks = aurEff->GetBase()->GetStackAmount();
if (stacks >= spellInfo->StackAmount)
return true;
if (aurEff->GetCaster() == bot || spellInfo->IsPositive() ||
spellInfo->Effects[aurEff->GetEffIndex()].IsAreaAuraEffect())
return true;
return false;
}
bool PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, bool checkIsOwner, int maxAuraAmount,
bool checkDuration)
{
if (!unit)
return false;
std::wstring wnamepart;
if (!Utf8toWStr(name, wnamepart))
return false;
wstrToLower(wnamepart);
int auraAmount = 0;
// Iterate through all aura types
for (uint32 auraType = SPELL_AURA_BIND_SIGHT; auraType < TOTAL_AURAS; auraType++)
{
Unit::AuraEffectList const& auras = unit->GetAuraEffectsByType((AuraType)auraType);
if (auras.empty())
continue;
// Iterate through each aura effect
for (AuraEffect const* aurEff : auras)
{
if (!aurEff)
continue;
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
if (!spellInfo)
continue;
// Check if the aura name matches
std::string_view const auraName = spellInfo->SpellName[0];
if (auraName.empty() || auraName.length() != wnamepart.length() || !Utf8FitTo(auraName, wnamepart))
continue;
// Check if this is a valid aura for the bot
if (IsRealAura(bot, aurEff, unit))
{
// Check caster if necessary
if (checkIsOwner && aurEff->GetCasterGUID() != bot->GetGUID())
continue;
// Check aura duration if necessary
if (checkDuration && aurEff->GetBase()->GetDuration() == -1)
continue;
// Count stacks and charges
uint32 maxStackAmount = spellInfo->StackAmount;
uint32 maxProcCharges = spellInfo->ProcCharges;
// Count the aura based on max stack and proc charges
if (maxStack)
{
if (maxStackAmount && aurEff->GetBase()->GetStackAmount() >= maxStackAmount)
auraAmount++;
if (maxProcCharges && aurEff->GetBase()->GetCharges() >= maxProcCharges)
auraAmount++;
}
else
{
auraAmount++;
}
// Early exit if maxAuraAmount is reached
if (maxAuraAmount < 0 && auraAmount > 0)
return true;
}
}
}
// Return based on the maximum aura amount conditions
if (maxAuraAmount >= 0)
{
return auraAmount == maxAuraAmount || (auraAmount > 0 && auraAmount <= maxAuraAmount);
}
return false;
}
bool PlayerbotAI::HasAura(uint32 spellId, Unit const* unit)
{
if (!spellId || !unit)
return false;
return unit->HasAura(spellId);
// for (uint8 effect = EFFECT_0; effect <= EFFECT_2; effect++)
// {
// AuraEffect const* aurEff = unit->GetAuraEffect(spellId, effect);
// if (IsRealAura(bot, aurEff, unit))
// return true;
// }
// return false;
}
// SAW
Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack)
{
if (!unit)
return nullptr;
std::wstring wnamepart;
if (!Utf8toWStr(name, wnamepart))
return nullptr;
wstrToLower(wnamepart);
for (uint32 auraType = SPELL_AURA_BIND_SIGHT; auraType < TOTAL_AURAS; ++auraType)
{
Unit::AuraEffectList const& auras = unit->GetAuraEffectsByType((AuraType)auraType);
if (auras.empty())
continue;
for (AuraEffect const* aurEff : auras)
{
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
std::string const& auraName = spellInfo->SpellName[0];
// Directly skip if name mismatch (both length and content)
if (auraName.empty() || auraName.length() != wnamepart.length() || !Utf8FitTo(auraName, wnamepart))
continue;
if (!IsRealAura(bot, aurEff, unit))
continue;
// Check owner if necessary
if (checkIsOwner && aurEff->GetCasterGUID() != bot->GetGUID())
continue;
// Check duration if necessary
if (checkDuration && aurEff->GetBase()->GetDuration() == -1)
continue;
// Check stack if necessary
if (checkStack != -1 && aurEff->GetBase()->GetStackAmount() < checkStack)
continue;
return aurEff->GetBase();
}
}
return nullptr;
}
bool PlayerbotAI::HasAnyAuraOf(Unit* player, ...)
{
if (!player)
return false;
va_list vl;
va_start(vl, player);
const char* cur;
while ((cur = va_arg(vl, const char*)) != nullptr)
{
if (HasAura(cur, player))
{
va_end(vl);
return true;
}
}
va_end(vl);
return false;
}
bool PlayerbotAI::CanCastSpell(std::string const name, Unit* target, Item* itemTarget)
{
return CanCastSpell(aiObjectContext->GetValue<uint32>("spell id", name)->Get(), target, true, itemTarget);
}
bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell, Item* itemTarget, Item* castItem)
{
if (!spellid)
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "Can cast spell failed. No spellid. - spellid: {}, bot name: {}", spellid,
bot->GetName());
}
return false;
}
if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "Can cast spell failed. Unit state lost control. - spellid: {}, bot name: {}",
spellid, bot->GetName());
}
return false;
}
if (!target)
target = bot;
// if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
// LOG_DEBUG("playerbots", "Can cast spell? - target name: {}, spellid: {}, bot name: {}",
// target->GetName(), spellid, bot->GetName());
if (Pet* pet = bot->GetPet())
if (pet->HasSpell(spellid))
return true;
if (checkHasSpell && !bot->HasSpell(spellid))
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots",
"Can cast spell failed. Bot not has spell. - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
}
return false;
}
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != nullptr)
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG(
"playerbots",
"CanCastSpell() target name: {}, spellid: {}, bot name: {}, failed because has current channeled spell",
target->GetName(), spellid, bot->GetName());
}
return false;
}
if (bot->HasSpellCooldown(spellid))
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots",
"Can cast spell failed. Spell not has cooldown. - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
}
return false;
}
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid);
if (!spellInfo)
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "Can cast spell failed. No spellInfo. - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
}
return false;
}
uint32 CastingTime = !spellInfo->IsChanneled() ? spellInfo->CalcCastTime(bot) : spellInfo->GetDuration();
// bool interruptOnMove = spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT;
if ((CastingTime || spellInfo->IsAutoRepeatRangedSpell()) && bot->isMoving())
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "Casting time and bot is moving - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
}
return false;
}
if (!itemTarget)
{
bool positiveSpell = spellInfo->IsPositive();
// if (positiveSpell && bot->IsHostileTo(target))
// return false;
// if (!positiveSpell && bot->IsFriendlyTo(target))
// return false;
// bool damage = false;
// for (uint8 i = EFFECT_0; i <= EFFECT_2; i++)
// {
// if (spellInfo->Effects[i].Effect == SPELL_EFFECT_SCHOOL_DAMAGE)
// {
// damage = true;
// break;
// }
// }
if (target->IsImmunedToSpell(spellInfo))
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "target is immuned to spell - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
}
return false;
}
// if (!damage)
// {
// for (uint8 i = EFFECT_0; i <= EFFECT_2; i++)
// {
// if (target->IsImmunedToSpellEffect(spellInfo, i)) {
// if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster())) {
// LOG_DEBUG("playerbots", "target is immuned to spell effect - target name: {}, spellid: {},
// bot name: {}",
// target->GetName(), spellid, bot->GetName());
// }
// return false;
// }
// }
// }
if (bot != target && sServerFacade->GetDistance2d(bot, target) > sPlayerbotAIConfig->sightDistance)
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "target is out of sight distance - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
}
return false;
}
}
Unit* oldSel = bot->GetSelectedUnit();
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
spell->m_targets.SetUnitTarget(target);
spell->m_CastItem = castItem;
if (itemTarget == nullptr)
{
itemTarget = aiObjectContext->GetValue<Item*>("item for spell", spellid)->Get();
;
}
spell->m_targets.SetItemTarget(itemTarget);
SpellCastResult result = spell->CheckCast(true);
delete spell;
// if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster())) {
// if (result != SPELL_FAILED_NOT_READY && result != SPELL_CAST_OK) {
// LOG_DEBUG("playerbots", "CanCastSpell - target name: {}, spellid: {}, bot name: {}, result: {}",
// target->GetName(), spellid, bot->GetName(), result);
// }
// }
if (oldSel)
bot->SetSelection(oldSel->GetGUID());
switch (result)
{
case SPELL_FAILED_NOT_INFRONT:
case SPELL_FAILED_NOT_STANDING:
case SPELL_FAILED_UNIT_NOT_INFRONT:
case SPELL_FAILED_MOVING:
case SPELL_FAILED_TRY_AGAIN:
case SPELL_CAST_OK:
case SPELL_FAILED_NOT_SHAPESHIFT:
case SPELL_FAILED_OUT_OF_RANGE:
return true;
default:
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
// if (result != SPELL_FAILED_NOT_READY && result != SPELL_CAST_OK) {
LOG_DEBUG("playerbots",
"CanCastSpell Check Failed. - target name: {}, spellid: {}, bot name: {}, result: {}",
target->GetName(), spellid, bot->GetName(), result);
}
return false;
}
}
bool PlayerbotAI::CanCastSpell(uint32 spellid, GameObject* goTarget, uint8 effectMask, bool checkHasSpell)
{
if (!spellid)
return false;
if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false;
Pet* pet = bot->GetPet();
if (pet && pet->HasSpell(spellid))
return true;
if (checkHasSpell && !bot->HasSpell(spellid))
return false;
if (bot->HasSpellCooldown(spellid))
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid);
if (!spellInfo)
return false;
int32 CastingTime = !spellInfo->IsChanneled() ? spellInfo->CalcCastTime(bot) : spellInfo->GetDuration();
if (CastingTime > 0 && bot->isMoving())
return false;
bool damage = false;
for (int32 i = EFFECT_0; i <= EFFECT_2; i++)
{
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_SCHOOL_DAMAGE)
{
damage = true;
break;
}
}
if (sServerFacade->GetDistance2d(bot, goTarget) > sPlayerbotAIConfig->sightDistance)
return false;
// ObjectGuid oldSel = bot->GetTarget();
// bot->SetTarget(goTarget->GetGUID());
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
spell->m_targets.SetGOTarget(goTarget);
spell->m_CastItem = aiObjectContext->GetValue<Item*>("item for spell", spellid)->Get();
spell->m_targets.SetItemTarget(spell->m_CastItem);
SpellCastResult result = spell->CheckCast(true);
delete spell;
// if (oldSel)
// bot->SetTarget(oldSel);
switch (result)
{
case SPELL_FAILED_NOT_INFRONT:
case SPELL_FAILED_NOT_STANDING:
case SPELL_FAILED_UNIT_NOT_INFRONT:
case SPELL_FAILED_MOVING:
case SPELL_FAILED_TRY_AGAIN:
case SPELL_CAST_OK:
return true;
default:
break;
}
return false;
}
bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, uint8 effectMask, bool checkHasSpell,
Item* itemTarget)
{
if (!spellid)
return false;
Pet* pet = bot->GetPet();
if (pet && pet->HasSpell(spellid))
return true;
if (checkHasSpell && !bot->HasSpell(spellid))
return false;
if (bot->HasSpellCooldown(spellid))
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid);
if (!spellInfo)
return false;
if (!itemTarget)
{
if (bot->GetDistance(x, y, z) > sPlayerbotAIConfig->sightDistance)
return false;
}
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
spell->m_targets.SetDst(x, y, z, 0.f);
spell->m_CastItem = itemTarget ? itemTarget : aiObjectContext->GetValue<Item*>("item for spell", spellid)->Get();
spell->m_targets.SetItemTarget(spell->m_CastItem);
SpellCastResult result = spell->CheckCast(true);
delete spell;
switch (result)
{
case SPELL_FAILED_NOT_INFRONT:
case SPELL_FAILED_NOT_STANDING:
case SPELL_FAILED_UNIT_NOT_INFRONT:
case SPELL_FAILED_MOVING:
case SPELL_FAILED_TRY_AGAIN:
case SPELL_CAST_OK:
return true;
default:
return false;
}
}
bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarget)
{
bool result = CastSpell(aiObjectContext->GetValue<uint32>("spell id", name)->Get(), target, itemTarget);
if (result)
{
aiObjectContext->GetValue<time_t>("last spell cast time", name)->Set(time(nullptr));
}
return result;
}
bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
{
if (!spellId)
{
return false;
}
if (!target)
target = bot;
Pet* pet = bot->GetPet();
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (pet && pet->HasSpell(spellId))
{
bool autocast = false;
for (unsigned int& m_autospell : pet->m_autospells)
{
if (m_autospell == spellId)
{
autocast = true;
break;
}
}
pet->ToggleAutocast(spellInfo, !autocast);
std::ostringstream out;
out << (autocast ? "|cffff0000|Disabling" : "|cFF00ff00|Enabling") << " pet auto-cast for ";
out << chatHelper.FormatSpell(spellInfo);
TellMaster(out);
return true;
}
// aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
// aiObjectContext->GetValue<time_t>("stay time")->Set(0);
if (bot->IsFlying() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
// if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster())) {
// LOG_DEBUG("playerbots", "Spell cast is flying - target name: {}, spellid: {}, bot name: {}}",
// target->GetName(), spellId, bot->GetName());
// }
return false;
}
// bot->ClearUnitState(UNIT_STATE_CHASE);
// bot->ClearUnitState(UNIT_STATE_FOLLOW);
bool failWithDelay = false;
if (!bot->IsStandState())
{
bot->SetStandState(UNIT_STAND_STATE_STAND);
failWithDelay = true;
}
ObjectGuid oldSel = bot->GetSelectedUnit() ? bot->GetSelectedUnit()->GetGUID() : ObjectGuid();
bot->SetSelection(target->GetGUID());
WorldObject* faceTo = target;
if (!bot->HasInArc(CAST_ANGLE_IN_FRONT, faceTo) && (spellInfo->FacingCasterFlags & SPELL_FACING_FLAG_INFRONT))
{
sServerFacade->SetFacingTo(bot, faceTo);
// failWithDelay = true;
}
if (failWithDelay)
{
SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
return false;
}
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
SpellCastTargets targets;
if (spellInfo->Targets & TARGET_FLAG_ITEM)
{
spell->m_CastItem =
itemTarget ? itemTarget : aiObjectContext->GetValue<Item*>("item for spell", spellId)->Get();
targets.SetItemTarget(spell->m_CastItem);
if (bot->GetTradeData())
{
bot->GetTradeData()->SetSpell(spellId);
delete spell;
// if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster())) {
// LOG_DEBUG("playerbots", "Spell cast no item - target name: {}, spellid: {}, bot name: {}",
// target->GetName(), spellId, bot->GetName());
// }
return true;
}
}
else if (spellInfo->Targets & TARGET_FLAG_DEST_LOCATION)
{
// WorldLocation aoe = aiObjectContext->GetValue<WorldLocation>("aoe position")->Get();
// targets.SetDst(aoe);
targets.SetDst(*target);
}
else if (spellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION)
{
targets.SetDst(*bot);
}
else
{
targets.SetUnitTarget(target);
}
if (spellInfo->Effects[0].Effect == SPELL_EFFECT_OPEN_LOCK || spellInfo->Effects[0].Effect == SPELL_EFFECT_SKINNING)
{
LootObject loot = *aiObjectContext->GetValue<LootObject>("loot target");
GameObject* go = GetGameObject(loot.guid);
if (go && go->isSpawned())
{
WorldPacket packetgouse(CMSG_GAMEOBJ_USE, 8);
packetgouse << loot.guid;
bot->GetSession()->HandleGameObjectUseOpcode(packetgouse);
targets.SetGOTarget(go);
faceTo = go;
}
else
{
if (Unit* creature = GetUnit(loot.guid))
{
targets.SetUnitTarget(creature);
faceTo = creature;
}
}
}
if (bot->isMoving() && spell->GetCastTime())
{
// bot->StopMoving();
SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
spell->cancel();
delete spell;
return false;
}
// spell->m_targets.SetUnitTarget(target);
// SpellCastResult spellSuccess = spell->CheckCast(true);
// if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster())) {
// LOG_DEBUG("playerbots", "Spell cast result - target name: {}, spellid: {}, bot name: {}, result: {}",
// target->GetName(), spellId, bot->GetName(), spellSuccess);
// }
// if (spellSuccess != SPELL_CAST_OK)
// return false;
SpellCastResult result = spell->prepare(&targets);
if (result != SPELL_CAST_OK)
{
// if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster())) {
// LOG_DEBUG("playerbots", "Spell cast failed. - target name: {}, spellid: {}, bot name: {}, result: {}",
// target->GetName(), spellId, bot->GetName(), result);
// }
return false;
}
// if (spellInfo->Effects[0].Effect == SPELL_EFFECT_OPEN_LOCK || spellInfo->Effects[0].Effect ==
// SPELL_EFFECT_SKINNING)
// {
// LootObject loot = *aiObjectContext->GetValue<LootObject>("loot target");
// if (!loot.IsLootPossible(bot))
// {
// spell->cancel();
// delete spell;
// if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster())) {
// LOG_DEBUG("playerbots", "Spell cast loot - target name: {}, spellid: {}, bot name: {}",
// target->GetName(), spellId, bot->GetName());
// }
// return false;
// }
// }
// WaitForSpellCast(spell);
aiObjectContext->GetValue<LastSpellCast&>("last spell cast")
->Get()
.Set(spellId, target->GetGUID(), time(nullptr));
aiObjectContext->GetValue<PositionMap&>("position")->Get()["random"].Reset();
if (oldSel)
bot->SetSelection(oldSel);
if (HasStrategy("debug spell", BOT_STATE_NON_COMBAT))
{
std::ostringstream out;
out << "Casting " << ChatHelper::FormatSpell(spellInfo);
TellMasterNoFacing(out);
}
return true;
}
bool PlayerbotAI::CastSpell(uint32 spellId, float x, float y, float z, Item* itemTarget)
{
if (!spellId)
return false;
Pet* pet = bot->GetPet();
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (pet && pet->HasSpell(spellId))
{
bool autocast = false;
for (unsigned int& m_autospell : pet->m_autospells)
{
if (m_autospell == spellId)
{
autocast = true;
break;
}
}
pet->ToggleAutocast(spellInfo, !autocast);
std::ostringstream out;
out << (autocast ? "|cffff0000|Disabling" : "|cFF00ff00|Enabling") << " pet auto-cast for ";
out << chatHelper.FormatSpell(spellInfo);
TellMaster(out);
return true;
}
// aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
// aiObjectContext->GetValue<time_t>("stay time")->Set(0);
MotionMaster& mm = *bot->GetMotionMaster();
if (bot->IsFlying() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
return false;
// bot->ClearUnitState(UNIT_STATE_CHASE);
// bot->ClearUnitState(UNIT_STATE_FOLLOW);
bool failWithDelay = false;
if (!bot->IsStandState())
{
bot->SetStandState(UNIT_STAND_STATE_STAND);
failWithDelay = true;
}
ObjectGuid oldSel = bot->GetSelectedUnit() ? bot->GetSelectedUnit()->GetGUID() : ObjectGuid();
if (!bot->isMoving())
bot->SetFacingTo(bot->GetAngle(x, y));
if (failWithDelay)
{
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
return false;
}
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
SpellCastTargets targets;
if (spellInfo->Targets & TARGET_FLAG_ITEM)
{
spell->m_CastItem =
itemTarget ? itemTarget : aiObjectContext->GetValue<Item*>("item for spell", spellId)->Get();
targets.SetItemTarget(spell->m_CastItem);
if (bot->GetTradeData())
{
bot->GetTradeData()->SetSpell(spellId);
delete spell;
return true;
}
}
else if (spellInfo->Targets & TARGET_FLAG_DEST_LOCATION)
{
WorldLocation aoe = aiObjectContext->GetValue<WorldLocation>("aoe position")->Get();
targets.SetDst(x, y, z, 0.f);
}
else if (spellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION)
{
targets.SetDst(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0.f);
}
else
{
return false;
}
if (spellInfo->Effects[0].Effect == SPELL_EFFECT_OPEN_LOCK || spellInfo->Effects[0].Effect == SPELL_EFFECT_SKINNING)
{
return false;
}
spell->prepare(&targets);
if (bot->isMoving() && spell->GetCastTime())
{
// bot->StopMoving();
SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
spell->cancel();
delete spell;
return false;
}
if (spellInfo->Effects[0].Effect == SPELL_EFFECT_OPEN_LOCK || spellInfo->Effects[0].Effect == SPELL_EFFECT_SKINNING)
{
LootObject loot = *aiObjectContext->GetValue<LootObject>("loot target");
if (!loot.IsLootPossible(bot))
{
spell->cancel();
delete spell;
return false;
}
}
// WaitForSpellCast(spell);
aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get().Set(spellId, bot->GetGUID(), time(nullptr));
aiObjectContext->GetValue<PositionMap&>("position")->Get()["random"].Reset();
if (oldSel)
bot->SetSelection(oldSel);
if (HasStrategy("debug spell", BOT_STATE_NON_COMBAT))
{
std::ostringstream out;
out << "Casting " << ChatHelper::FormatSpell(spellInfo);
TellMasterNoFacing(out);
}
return true;
}
bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
{
if (!spellId)
return false;
Vehicle* vehicle = bot->GetVehicle();
if (!vehicle)
return false;
// do not allow if no spells
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
if (!seat || !(seat->m_flags & VEHICLE_SEAT_FLAG_CAN_CAST))
return false;
Unit* vehicleBase = vehicle->GetBase();
Unit* spellTarget = target;
if (!spellTarget)
spellTarget = vehicleBase;
if (!spellTarget)
return false;
if (vehicleBase->HasSpellCooldown(spellId))
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return false;
// check BG siege position set in BG Tactics
PositionInfo siegePos = GetAiObjectContext()->GetValue<PositionMap&>("position")->Get()["bg siege"];
// do not cast spell on self if spell is location based
if (!(siegePos.isSet() || spellTarget != vehicleBase) && spellInfo->Targets & TARGET_FLAG_DEST_LOCATION)
return false;
uint32 CastingTime = !spellInfo->IsChanneled() ? spellInfo->CalcCastTime(vehicleBase) : spellInfo->GetDuration();
if (CastingTime && vehicleBase->isMoving())
return false;
if (vehicleBase != spellTarget && sServerFacade->GetDistance2d(vehicleBase, spellTarget) > 120.0f)
return false;
if (!target && siegePos.isSet())
{
if (sServerFacade->GetDistance2d(vehicleBase, siegePos.x, siegePos.y) > 120.0f)
return false;
}
Spell* spell = new Spell(vehicleBase, spellInfo, TRIGGERED_NONE);
WorldLocation dest;
if (siegePos.isSet())
dest = WorldLocation(bot->GetMapId(), siegePos.x, siegePos.y, siegePos.z, 0);
else if (spellTarget != vehicleBase)
dest = WorldLocation(spellTarget->GetMapId(), spellTarget->GetPosition());
if (spellInfo->Targets & TARGET_FLAG_DEST_LOCATION)
spell->m_targets.SetDst(dest);
else if (spellTarget != vehicleBase)
spell->m_targets.SetUnitTarget(spellTarget);
SpellCastResult result = spell->CheckCast(true);
delete spell;
switch (result)
{
case SPELL_FAILED_NOT_INFRONT:
case SPELL_FAILED_NOT_STANDING:
case SPELL_FAILED_UNIT_NOT_INFRONT:
case SPELL_FAILED_MOVING:
case SPELL_FAILED_TRY_AGAIN:
case SPELL_CAST_OK:
return true;
default:
return false;
}
return false;
}
bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
{
if (!spellId)
return false;
Vehicle* vehicle = bot->GetVehicle();
if (!vehicle)
return false;
// do not allow if no spells
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
if (!seat || !(seat->m_flags & VEHICLE_SEAT_FLAG_CAN_CAST))
return false;
Unit* vehicleBase = vehicle->GetBase();
Unit* spellTarget = target;
if (!spellTarget)
spellTarget = vehicleBase;
if (!spellTarget)
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return false;
// check BG siege position set in BG Tactics
PositionInfo siegePos = GetAiObjectContext()->GetValue<PositionMap&>("position")->Get()["bg siege"];
if (!target && siegePos.isSet())
{
if (sServerFacade->GetDistance2d(vehicleBase, siegePos.x, siegePos.y) > 120.0f)
return false;
}
// do not cast spell on self if spell is location based
if (!(siegePos.isSet() || spellTarget != vehicleBase) && (spellInfo->Targets & TARGET_FLAG_DEST_LOCATION) != 0)
return false;
if (seat->CanControl())
{
// aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
// aiObjectContext->GetValue<time_t>("stay time")->Set(0);
}
// bot->clearUnitState(UNIT_STAT_CHASE);
// bot->clearUnitState(UNIT_STAT_FOLLOW);
// ObjectGuid oldSel = bot->GetSelectionGuid();
// bot->SetSelectionGuid(target->GetGUID());
// turn vehicle if target is not in front
bool failWithDelay = false;
if (spellTarget != vehicleBase && (seat->CanControl() || (seat->m_flags & VEHICLE_SEAT_FLAG_ALLOW_TURNING)))
{
if (!vehicleBase->HasInArc(CAST_ANGLE_IN_FRONT, spellTarget, 100.0f))
{
vehicleBase->SetFacingToObject(spellTarget);
failWithDelay = true;
}
}
if (siegePos.isSet() && (seat->CanControl() || (seat->m_flags & VEHICLE_SEAT_FLAG_ALLOW_TURNING)))
{
vehicleBase->SetFacingTo(vehicleBase->GetAngle(siegePos.x, siegePos.y));
}
if (failWithDelay)
{
SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
return false;
}
Spell* spell = new Spell(vehicleBase, spellInfo, TRIGGERED_NONE);
SpellCastTargets targets;
if ((spellTarget != vehicleBase || siegePos.isSet()) && (spellInfo->Targets & TARGET_FLAG_DEST_LOCATION))
{
WorldLocation dest;
if (spellTarget != vehicleBase)
dest = WorldLocation(spellTarget->GetMapId(), spellTarget->GetPosition());
else if (siegePos.isSet())
dest = WorldLocation(bot->GetMapId(), siegePos.x + frand(-5.0f, 5.0f), siegePos.y + frand(-5.0f, 5.0f),
siegePos.z, 0.0f);
else
return false;
targets.SetDst(dest);
targets.SetSpeed(30.0f);
float dist = vehicleBase->GetPosition().GetExactDist(dest);
// very much an approximation of the real projectile arc
float elev = dist >= 110.0f ? 1.0f : pow(((dist + 10.0f) / 120.0f), 2.0f);
targets.SetElevation(elev);
}
if (spellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION)
{
targets.SetSrc(vehicleBase->GetPositionX(), vehicleBase->GetPositionY(), vehicleBase->GetPositionZ());
}
if (target && !(spellInfo->Targets & TARGET_FLAG_DEST_LOCATION))
{
targets.SetUnitTarget(spellTarget);
}
spell->prepare(&targets);
if (seat->CanControl() && vehicleBase->isMoving() && spell->GetCastTime())
{
vehicleBase->StopMoving();
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
spell->cancel();
// delete spell;
return false;
}
// WaitForSpellCast(spell);
// aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get().Set(spellId, target->GetGUID(), time(0));
// aiObjectContext->GetValue<botAI::PositionMap&>("position")->Get()["random"].Reset();
if (HasStrategy("debug spell", BOT_STATE_NON_COMBAT))
{
std::ostringstream out;
out << "Casting Vehicle Spell" << ChatHelper::FormatSpell(spellInfo);
TellMasterNoFacing(out);
}
return true;
}
bool PlayerbotAI::IsInVehicle(bool canControl, bool canCast, bool canAttack, bool canTurn, bool fixed)
{
Vehicle* vehicle = bot->GetVehicle();
if (!vehicle)
return false;
// get vehicle
Unit* vehicleBase = vehicle->GetBase();
if (!vehicleBase || !vehicleBase->IsAlive())
return false;
if (!vehicle->GetVehicleInfo())
return false;
// get seat
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
if (!seat)
return false;
if (!(canControl || canCast || canAttack || canTurn || fixed))
return true;
if (canControl)
return seat->CanControl() && !(vehicle->GetVehicleInfo()->m_flags & VEHICLE_FLAG_FIXED_POSITION);
if (canCast)
return (seat->m_flags & VEHICLE_SEAT_FLAG_CAN_CAST) != 0;
if (canAttack)
return (seat->m_flags & VEHICLE_SEAT_FLAG_CAN_ATTACK) != 0;
if (canTurn)
return (seat->m_flags & VEHICLE_SEAT_FLAG_ALLOW_TURNING) != 0;
if (fixed)
return (vehicle->GetVehicleInfo()->m_flags & VEHICLE_FLAG_FIXED_POSITION) != 0;
return false;
}
void PlayerbotAI::WaitForSpellCast(Spell* spell)
{
SpellInfo const* spellInfo = spell->GetSpellInfo();
uint32 castTime = spell->GetCastTime();
if (spellInfo->IsChanneled())
{
int32 duration = spellInfo->GetDuration();
bot->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration);
if (duration > 0)
castTime += duration;
}
SetNextCheckDelay(castTime + sPlayerbotAIConfig->reactDelay);
}
void PlayerbotAI::InterruptSpell()
{
for (uint8 type = CURRENT_MELEE_SPELL; type <= CURRENT_CHANNELED_SPELL; type++)
{
Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type);
if (!spell)
continue;
bot->InterruptSpell((CurrentSpellTypes)type);
WorldPacket data(SMSG_SPELL_FAILURE, 8 + 1 + 4 + 1);
data << bot->GetPackGUID();
data << uint8(1);
data << uint32(spell->m_spellInfo->Id);
data << uint8(0);
bot->SendMessageToSet(&data, true);
data.Initialize(SMSG_SPELL_FAILED_OTHER, 8 + 1 + 4 + 1);
data << bot->GetPackGUID();
data << uint8(1);
data << uint32(spell->m_spellInfo->Id);
data << uint8(0);
bot->SendMessageToSet(&data, true);
SpellInterrupted(spell->m_spellInfo->Id);
}
}
void PlayerbotAI::RemoveAura(std::string const name)
{
uint32 spellid = aiObjectContext->GetValue<uint32>("spell id", name)->Get();
if (spellid && HasAura(spellid, bot))
bot->RemoveAurasDueToSpell(spellid);
}
bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const spell)
{
uint32 spellid = aiObjectContext->GetValue<uint32>("spell id", spell)->Get();
if (!spellid || !target->IsNonMeleeSpellCast(true))
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid);
if (!spellInfo)
return false;
for (uint8 i = EFFECT_0; i <= EFFECT_2; i++)
{
if ((spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_INTERRUPT) &&
spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE)
return true;
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_INTERRUPT_CAST &&
!target->IsImmunedToSpellEffect(spellInfo, i))
return true;
if ((spellInfo->Effects[i].Effect == SPELL_EFFECT_APPLY_AURA) &&
spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_SILENCE)
return true;
}
return false;
}
bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
{
if (!target->IsInWorld())
{
return false;
}
bool isFriend = bot->IsFriendlyTo(target);
Unit::VisibleAuraMap const* visibleAuras = target->GetVisibleAuras();
for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr)
{
Aura* aura = itr->second->GetBase();
if (aura->IsPassive())
continue;
if (sPlayerbotAIConfig->dispelAuraDuration && aura->GetDuration() &&
aura->GetDuration() < (int32)sPlayerbotAIConfig->dispelAuraDuration)
continue;
SpellInfo const* spellInfo = aura->GetSpellInfo();
bool isPositiveSpell = spellInfo->IsPositive();
if (isPositiveSpell && isFriend)
continue;
if (!isPositiveSpell && !isFriend)
continue;
if (canDispel(spellInfo, dispelType))
return true;
}
return false;
}
#ifndef WIN32
inline int strcmpi(char const* s1, char const* s2)
{
for (; *s1 && *s2 && (toupper(*s1) == toupper(*s2)); ++s1, ++s2)
{
}
return *s1 - *s2;
}
#endif
bool PlayerbotAI::canDispel(SpellInfo const* spellInfo, uint32 dispelType)
{
if (spellInfo->Dispel != dispelType)
return false;
if (!spellInfo->SpellName[0])
{
return true;
}
for (std::string& wl : dispel_whitelist)
{
if (strcmpi((const char*)spellInfo->SpellName[0], wl.c_str()) == 0)
{
return false;
}
}
return !spellInfo->SpellName[0] || (strcmpi((const char*)spellInfo->SpellName[0], "demon skin") &&
strcmpi((const char*)spellInfo->SpellName[0], "mage armor") &&
strcmpi((const char*)spellInfo->SpellName[0], "frost armor") &&
strcmpi((const char*)spellInfo->SpellName[0], "wavering will") &&
strcmpi((const char*)spellInfo->SpellName[0], "chilled") &&
strcmpi((const char*)spellInfo->SpellName[0], "mana tap") &&
strcmpi((const char*)spellInfo->SpellName[0], "ice armor"));
}
bool IsAlliance(uint8 race)
{
return race == RACE_HUMAN || race == RACE_DWARF || race == RACE_NIGHTELF || race == RACE_GNOME ||
race == RACE_DRAENEI;
}
bool PlayerbotAI::HasRealPlayerMaster()
{
if (master)
{
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
return !masterBotAI || masterBotAI->IsRealPlayer();
}
return false;
}
bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(master); }
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
Player* PlayerbotAI::GetGroupMaster()
{
if (!bot->InBattleground())
if (Group* group = bot->GetGroup())
if (Player* player = ObjectAccessor::FindPlayer(group->GetLeaderGUID()))
return player;
return master;
}
uint32 PlayerbotAI::GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum, float cyclePerMin)
{
uint32 randseed = rand32(); // Seed random number
uint32 randnum = bot->GetGUID().GetCounter() + randseed; // Semi-random but fixed number for each bot.
if (cyclePerMin > 0)
{
uint32 cycle = floor(getMSTime() / (1000)); // Semi-random number adds 1 each second.
cycle = cycle * cyclePerMin / 60; // Cycles cyclePerMin per minute.
randnum += cycle; // Make the random number cylce.
}
randnum =
(randnum % (maxNum + 1)); // Loops the randomnumber at maxNum. Bassically removes all the numbers above 99.
return randnum; // Now we have a number unique for each bot between 0 and maxNum that increases by cyclePerMin.
}
/*
enum GrouperType
{
SOLO = 0,
MEMBER = 1,
LEADER_2 = 2,
LEADER_3 = 3,
LEADER_4 = 4,
LEADER_5 = 5
};
*/
GrouperType PlayerbotAI::GetGrouperType()
{
uint32 grouperNumber = GetFixedBotNumer(BotTypeNumber::GROUPER_TYPE_NUMBER, 100, 0);
if (grouperNumber < 20 && !HasRealPlayerMaster())
return GrouperType::SOLO;
if (grouperNumber < 80)
return GrouperType::MEMBER;
if (grouperNumber < 85)
return GrouperType::LEADER_2;
if (grouperNumber < 90)
return GrouperType::LEADER_3;
if (grouperNumber < 95)
return GrouperType::LEADER_4;
return GrouperType::LEADER_5;
}
GuilderType PlayerbotAI::GetGuilderType()
{
uint32 grouperNumber = GetFixedBotNumer(BotTypeNumber::GUILDER_TYPE_NUMBER, 100, 0);
if (grouperNumber < 20 && !HasRealPlayerMaster())
return GuilderType::SOLO;
if (grouperNumber < 30)
return GuilderType::TINY;
if (grouperNumber < 40)
return GuilderType::SMALL;
if (grouperNumber < 60)
return GuilderType::MEDIUM;
if (grouperNumber < 80)
return GuilderType::LARGE;
return GuilderType::VERY_LARGE;
}
bool PlayerbotAI::HasPlayerNearby(WorldPosition* pos, float range)
{
float sqRange = range * range;
bool nearPlayer = false;
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
{
if (!player->IsGameMaster() || player->isGMVisible())
{
if (player->GetMapId() != bot->GetMapId())
continue;
if (pos->sqDistance(WorldPosition(player)) < sqRange)
nearPlayer = true;
// if player is far check farsight/cinematic camera
WorldObject* viewObj = player->GetViewpoint();
if (viewObj && viewObj != player)
{
if (pos->sqDistance(WorldPosition(viewObj)) < sqRange)
nearPlayer = true;
}
}
}
return nearPlayer;
}
bool PlayerbotAI::HasPlayerNearby(float range)
{
WorldPosition botPos(bot);
return HasPlayerNearby(&botPos, range);
};
bool PlayerbotAI::HasManyPlayersNearby(uint32 trigerrValue, float range)
{
float sqRange = range * range;
uint32 found = 0;
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
{
if ((!player->IsGameMaster() || player->isGMVisible()) && sServerFacade->GetDistance2d(player, bot) < sqRange)
{
found++;
if (found >= trigerrValue)
return true;
}
}
return false;
}
inline bool HasRealPlayers(Map* map)
{
Map::PlayerList const& players = map->GetPlayers();
if (players.IsEmpty())
{
return false;
}
for (auto const& itr : players)
{
Player* player = itr.GetSource();
if (!player || !player->IsVisible())
{
continue;
}
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || botAI->IsRealPlayer() || botAI->HasRealPlayerMaster())
{
return true;
}
}
return false;
}
inline bool ZoneHasRealPlayers(Player* bot)
{
Map* map = bot->GetMap();
if (!bot || !map)
{
return false;
}
for (Player* player : sRandomPlayerbotMgr->GetPlayers())
{
if (player->GetMapId() != bot->GetMapId())
continue;
if (player->IsGameMaster() && !player->IsVisible())
{
continue;
}
if (player->GetZoneId() == bot->GetZoneId())
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || botAI->IsRealPlayer() || botAI->HasRealPlayerMaster())
{
return true;
}
}
}
return false;
}
bool PlayerbotAI::AllowActive(ActivityType activityType)
{
// when botActiveAlone is 100% and smartScale disabled
if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale)
{
return true;
}
// Is in combat. Always defend yourself.
if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY)
{
if (bot->IsInCombat())
{
return true;
}
}
// 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)
{
return true;
}
// bg, raid, dungeon
if (!WorldPosition(bot).isOverworld())
{
return true;
}
// bot map has active players.
if (sPlayerbotAIConfig->BotActiveAloneForceWhenInMap)
{
if (HasRealPlayers(bot->GetMap()))
{
return true;
}
}
// bot zone has active players.
if (sPlayerbotAIConfig->BotActiveAloneForceWhenInZone)
{
if (ZoneHasRealPlayers(bot))
{
return true;
}
}
// when in real guild
if (sPlayerbotAIConfig->BotActiveAloneForceWhenInGuild)
{
if (IsInRealGuild())
{
return true;
}
}
// Player is near. Always active.
if (HasPlayerNearby(sPlayerbotAIConfig->BotActiveAloneForceWhenInRadius))
{
return true;
}
// Has player master. Always active.
if (GetMaster())
{
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(GetMaster());
if (!masterBotAI || masterBotAI->IsRealPlayer())
{
return true;
}
}
// if grouped up
Group* group = bot->GetGroup();
if (group)
{
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || !member->IsInWorld() && member->GetMapId() != bot->GetMapId())
{
continue;
}
if (member == bot)
{
continue;
}
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
{
if (!memberBotAI || memberBotAI->HasRealPlayerMaster())
{
return true;
}
}
if (group->IsLeader(member->GetGUID()))
{
if (!memberBotAI->AllowActivity(PARTY_ACTIVITY))
{
return false;
}
}
}
}
// In bg queue. Speed up bg queue/join.
if (bot->InBattlegroundQueue())
{
return true;
}
bool isLFG = false;
if (group)
{
if (sLFGMgr->GetState(group->GetGUID()) != lfg::LFG_STATE_NONE)
{
isLFG = true;
}
}
if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
{
isLFG = true;
}
if (isLFG)
{
return true;
}
// HasFriend
if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend)
{
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
{
if (!player || !player->IsInWorld() || !player->GetSocial() || !bot->GetGUID())
{
continue;
}
if (player->GetSocial()->HasFriend(bot->GetGUID()))
{
return true;
}
}
}
// Force the bots to spread
if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY)
{
if (HasManyPlayersNearby(10, 40))
{
return true;
}
}
// Bots don't need to move using PathGenerator.
if (activityType == DETAILED_MOVE_ACTIVITY)
{
return false;
}
if (sPlayerbotAIConfig->botActiveAlone <= 0)
{
return false;
}
// #######################################################################################
// All mandatory conditations are checked to be active or not, from here the remaining
// situations are usable for scaling when enabled.
// #######################################################################################
// Below is code to have a specified % of bots active at all times.
// The default is 10%. With 0.1% of all bots going active or inactive each minute.
uint32 mod = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone;
if (sPlayerbotAIConfig->botActiveAloneSmartScale &&
bot->GetLevel() >= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMinLevel &&
bot->GetLevel() <= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMaxLevel)
{
mod = AutoScaleActivity(mod);
}
uint32 ActivityNumber =
GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100,
sPlayerbotAIConfig->botActiveAlone * static_cast<float>(mod) / 100 * 0.01f);
return ActivityNumber <=
(sPlayerbotAIConfig->botActiveAlone * mod) /
100; // The given percentage of bots should be active and rotate 1% of those active bots each minute.
}
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
{
if (!allowActiveCheckTimer[activityType])
allowActiveCheckTimer[activityType] = time(nullptr);
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityType] + 5))
return allowActive[activityType];
bool allowed = AllowActive(activityType);
allowActive[activityType] = allowed;
allowActiveCheckTimer[activityType] = time(nullptr);
return allowed;
}
uint32 PlayerbotAI::AutoScaleActivity(uint32 mod)
{
uint32 maxDiff = sWorldUpdateTime.GetMaxUpdateTimeOfCurrentTable();
uint32 diffLimitFloor = sPlayerbotAIConfig->botActiveAloneSmartScaleDiffLimitfloor;
uint32 diffLimitCeiling = sPlayerbotAIConfig->botActiveAloneSmartScaleDiffLimitCeiling;
double spreadSize = (double)(diffLimitCeiling - diffLimitFloor) / 6;
// apply scaling
if (maxDiff > diffLimitCeiling) return 0;
if (maxDiff > diffLimitFloor + (4 * spreadSize)) return (mod * 1) / 10;
if (maxDiff > diffLimitFloor + (3 * spreadSize)) return (mod * 3) / 10;
if (maxDiff > diffLimitFloor + (2 * spreadSize)) return (mod * 5) / 10;
if (maxDiff > diffLimitFloor + (1 * spreadSize)) return (mod * 7) / 10;
if (maxDiff > diffLimitFloor) return (mod * 9) / 10;
return mod;
}
bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); }
bool PlayerbotAI::IsOpposing(uint8 race1, uint8 race2)
{
return (IsAlliance(race1) && !IsAlliance(race2)) || (!IsAlliance(race1) && IsAlliance(race2));
}
void PlayerbotAI::RemoveShapeshift()
{
RemoveAura("bear form");
RemoveAura("dire bear form");
RemoveAura("moonkin form");
RemoveAura("travel form");
RemoveAura("cat form");
RemoveAura("flight form");
RemoveAura("swift flight form");
RemoveAura("aquatic form");
RemoveAura("ghost wolf");
// RemoveAura("tree of life");
}
uint32 PlayerbotAI::GetEquipGearScore(Player* player, bool withBags, bool withBank)
{
std::vector<uint32> gearScore(EQUIPMENT_SLOT_END);
uint32 twoHandScore = 0;
for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
{
if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
_fillGearScoreData(player, item, &gearScore, twoHandScore);
}
if (withBags)
{
// check inventory
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
{
if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
_fillGearScoreData(player, item, &gearScore, twoHandScore);
}
// check bags
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (Bag* pBag = (Bag*)player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
{
if (Item* item2 = pBag->GetItemByPos(j))
_fillGearScoreData(player, item2, &gearScore, twoHandScore);
}
}
}
}
if (withBank)
{
for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i)
{
if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
_fillGearScoreData(player, item, &gearScore, twoHandScore);
}
for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
{
if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (item->IsBag())
{
Bag* bag = (Bag*)item;
for (uint8 j = 0; j < bag->GetBagSize(); ++j)
{
if (Item* item2 = bag->GetItemByPos(j))
_fillGearScoreData(player, item2, &gearScore, twoHandScore);
}
}
}
}
}
uint8 count = EQUIPMENT_SLOT_END - 2; // ignore body and tabard slots
uint32 sum = 0;
// check if 2h hand is higher level than main hand + off hand
if (gearScore[EQUIPMENT_SLOT_MAINHAND] + gearScore[EQUIPMENT_SLOT_OFFHAND] < twoHandScore * 2)
{
gearScore[EQUIPMENT_SLOT_OFFHAND] = 0; // off hand is ignored in calculations if 2h weapon has higher score
--count;
gearScore[EQUIPMENT_SLOT_MAINHAND] = twoHandScore;
}
for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
{
sum += gearScore[i];
}
if (count)
{
uint32 res = uint32(sum / count);
return res;
}
return 0;
}
uint32 PlayerbotAI::GetMixedGearScore(Player* player, bool withBags, bool withBank, uint32 topN)
{
std::vector<uint32> gearScore(EQUIPMENT_SLOT_END);
uint32 twoHandScore = 0;
for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
{
if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
_fillGearScoreData(player, item, &gearScore, twoHandScore, true);
}
if (withBags)
{
// check inventory
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
{
if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
_fillGearScoreData(player, item, &gearScore, twoHandScore, true);
}
// check bags
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (Bag* pBag = (Bag*)player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
{
if (Item* item2 = pBag->GetItemByPos(j))
_fillGearScoreData(player, item2, &gearScore, twoHandScore, true);
}
}
}
}
if (withBank)
{
for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i)
{
if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
_fillGearScoreData(player, item, &gearScore, twoHandScore, true);
}
for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
{
if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (item->IsBag())
{
Bag* bag = (Bag*)item;
for (uint8 j = 0; j < bag->GetBagSize(); ++j)
{
if (Item* item2 = bag->GetItemByPos(j))
_fillGearScoreData(player, item2, &gearScore, twoHandScore, true);
}
}
}
}
}
if (!topN)
{
uint8 count = EQUIPMENT_SLOT_END - 2; // ignore body and tabard slots
uint32 sum = 0;
// check if 2h hand is higher level than main hand + off hand
if (gearScore[EQUIPMENT_SLOT_MAINHAND] + gearScore[EQUIPMENT_SLOT_OFFHAND] < twoHandScore * 2)
{
gearScore[EQUIPMENT_SLOT_OFFHAND] = 0; // off hand is ignored in calculations if 2h weapon has higher score
--count;
gearScore[EQUIPMENT_SLOT_MAINHAND] = twoHandScore;
}
for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
{
sum += gearScore[i];
}
if (count)
{
uint32 res = uint32(sum / count);
return res;
}
return 0;
}
// topN != 0
if (gearScore[EQUIPMENT_SLOT_MAINHAND] + gearScore[EQUIPMENT_SLOT_OFFHAND] < twoHandScore * 2)
{
gearScore[EQUIPMENT_SLOT_OFFHAND] = twoHandScore;
gearScore[EQUIPMENT_SLOT_MAINHAND] = twoHandScore;
}
std::vector<uint32> topGearScore;
for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
{
topGearScore.push_back(gearScore[i]);
}
std::sort(topGearScore.begin(), topGearScore.end(), [&](const uint32 lhs, const uint32 rhs) { return lhs > rhs; });
uint32 sum = 0;
for (int i = 0; i < std::min((uint32)topGearScore.size(), topN); i++)
{
sum += topGearScore[i];
}
return sum / topN;
}
void PlayerbotAI::_fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
bool mixed)
{
if (!item)
return;
ItemTemplate const* proto = item->GetTemplate();
if (player->CanUseItem(proto) != EQUIP_ERR_OK)
return;
uint8 type = proto->InventoryType;
uint32 level = mixed ? proto->ItemLevel * PlayerbotAI::GetItemScoreMultiplier(ItemQualities(proto->Quality)) : proto->ItemLevel;
switch (type)
{
case INVTYPE_2HWEAPON:
twoHandScore = std::max(twoHandScore, level);
break;
case INVTYPE_WEAPON:
case INVTYPE_WEAPONMAINHAND:
(*gearScore)[SLOT_MAIN_HAND] = std::max((*gearScore)[SLOT_MAIN_HAND], level);
break;
case INVTYPE_SHIELD:
case INVTYPE_WEAPONOFFHAND:
(*gearScore)[EQUIPMENT_SLOT_OFFHAND] = std::max((*gearScore)[EQUIPMENT_SLOT_OFFHAND], level);
break;
case INVTYPE_THROWN:
case INVTYPE_RANGEDRIGHT:
case INVTYPE_RANGED:
case INVTYPE_QUIVER:
case INVTYPE_RELIC:
(*gearScore)[EQUIPMENT_SLOT_RANGED] = std::max((*gearScore)[EQUIPMENT_SLOT_RANGED], level);
break;
case INVTYPE_HEAD:
(*gearScore)[EQUIPMENT_SLOT_HEAD] = std::max((*gearScore)[EQUIPMENT_SLOT_HEAD], level);
break;
case INVTYPE_NECK:
(*gearScore)[EQUIPMENT_SLOT_NECK] = std::max((*gearScore)[EQUIPMENT_SLOT_NECK], level);
break;
case INVTYPE_SHOULDERS:
(*gearScore)[EQUIPMENT_SLOT_SHOULDERS] = std::max((*gearScore)[EQUIPMENT_SLOT_SHOULDERS], level);
break;
case INVTYPE_BODY:
(*gearScore)[EQUIPMENT_SLOT_BODY] = std::max((*gearScore)[EQUIPMENT_SLOT_BODY], level);
break;
case INVTYPE_CHEST:
(*gearScore)[EQUIPMENT_SLOT_CHEST] = std::max((*gearScore)[EQUIPMENT_SLOT_CHEST], level);
break;
case INVTYPE_WAIST:
(*gearScore)[EQUIPMENT_SLOT_WAIST] = std::max((*gearScore)[EQUIPMENT_SLOT_WAIST], level);
break;
case INVTYPE_LEGS:
(*gearScore)[EQUIPMENT_SLOT_LEGS] = std::max((*gearScore)[EQUIPMENT_SLOT_LEGS], level);
break;
case INVTYPE_FEET:
(*gearScore)[EQUIPMENT_SLOT_FEET] = std::max((*gearScore)[EQUIPMENT_SLOT_FEET], level);
break;
case INVTYPE_WRISTS:
(*gearScore)[EQUIPMENT_SLOT_WRISTS] = std::max((*gearScore)[EQUIPMENT_SLOT_WRISTS], level);
break;
case INVTYPE_HANDS:
(*gearScore)[EQUIPMENT_SLOT_HEAD] = std::max((*gearScore)[EQUIPMENT_SLOT_HEAD], level);
break;
// equipped gear score check uses both rings and trinkets for calculation, assume that for bags/banks it is the
// same with keeping second highest score at second slot
case INVTYPE_FINGER:
{
if ((*gearScore)[EQUIPMENT_SLOT_FINGER1] < level)
{
(*gearScore)[EQUIPMENT_SLOT_FINGER2] = (*gearScore)[EQUIPMENT_SLOT_FINGER1];
(*gearScore)[EQUIPMENT_SLOT_FINGER1] = level;
}
else if ((*gearScore)[EQUIPMENT_SLOT_FINGER2] < level)
(*gearScore)[EQUIPMENT_SLOT_FINGER2] = level;
break;
}
case INVTYPE_TRINKET:
{
if ((*gearScore)[EQUIPMENT_SLOT_TRINKET1] < level)
{
(*gearScore)[EQUIPMENT_SLOT_TRINKET2] = (*gearScore)[EQUIPMENT_SLOT_TRINKET1];
(*gearScore)[EQUIPMENT_SLOT_TRINKET1] = level;
}
else if ((*gearScore)[EQUIPMENT_SLOT_TRINKET2] < level)
(*gearScore)[EQUIPMENT_SLOT_TRINKET2] = level;
break;
}
case INVTYPE_CLOAK:
(*gearScore)[EQUIPMENT_SLOT_BACK] = std::max((*gearScore)[EQUIPMENT_SLOT_BACK], level);
break;
default:
break;
}
}
std::string const PlayerbotAI::HandleRemoteCommand(std::string const command)
{
if (command == "state")
{
switch (currentState)
{
case BOT_STATE_COMBAT:
return "combat";
case BOT_STATE_DEAD:
return "dead";
case BOT_STATE_NON_COMBAT:
return "non-combat";
default:
return "unknown";
}
}
else if (command == "position")
{
std::ostringstream out;
out << bot->GetPositionX() << " " << bot->GetPositionY() << " " << bot->GetPositionZ() << " " << bot->GetMapId()
<< " " << bot->GetOrientation();
if (AreaTableEntry const* zoneEntry = sAreaTableStore.LookupEntry(bot->GetZoneId()))
out << " |" << zoneEntry->area_name[0] << "|";
return out.str();
}
else if (command == "tpos")
{
Unit* target = *GetAiObjectContext()->GetValue<Unit*>("current target");
if (!target)
{
return "";
}
std::ostringstream out;
out << target->GetPositionX() << " " << target->GetPositionY() << " " << target->GetPositionZ() << " "
<< target->GetMapId() << " " << target->GetOrientation();
return out.str();
}
else if (command == "movement")
{
LastMovement& data = *GetAiObjectContext()->GetValue<LastMovement&>("last movement");
std::ostringstream out;
out << data.lastMoveShort.getX() << " " << data.lastMoveShort.getY() << " " << data.lastMoveShort.getZ() << " "
<< data.lastMoveShort.getMapId() << " " << data.lastMoveShort.getO();
return out.str();
}
else if (command == "target")
{
Unit* target = *GetAiObjectContext()->GetValue<Unit*>("current target");
if (!target)
{
return "";
}
return target->GetName();
}
else if (command == "hp")
{
uint32 pct = static_cast<uint32>(bot->GetHealthPct());
std::ostringstream out;
out << pct << "%";
Unit* target = *GetAiObjectContext()->GetValue<Unit*>("current target");
if (!target)
{
return out.str();
}
pct = static_cast<uint32>(target->GetHealthPct());
out << " / " << pct << "%";
return out.str();
}
else if (command == "strategy")
{
return currentEngine->ListStrategies();
}
else if (command == "action")
{
return currentEngine->GetLastAction();
}
else if (command == "values")
{
return GetAiObjectContext()->FormatValues();
}
else if (command == "travel")
{
std::ostringstream out;
TravelTarget* target = GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get();
if (target->getDestination())
{
out << "Destination = " << target->getDestination()->getName();
out << ": " << target->getDestination()->getTitle();
out << " v: " << target->getDestination()->getVisitors();
if (!(*target->getPosition() == WorldPosition()))
{
out << "(" << target->getPosition()->getAreaName() << ")";
out << " distance: " << target->getPosition()->distance(bot) << "y";
out << " v: " << target->getPosition()->getVisitors();
}
}
out << " Status = ";
if (target->getStatus() == TRAVEL_STATUS_NONE)
out << " none";
else if (target->getStatus() == TRAVEL_STATUS_PREPARE)
out << " prepare";
else if (target->getStatus() == TRAVEL_STATUS_TRAVEL)
out << " travel";
else if (target->getStatus() == TRAVEL_STATUS_WORK)
out << " work";
else if (target->getStatus() == TRAVEL_STATUS_COOLDOWN)
out << " cooldown";
else if (target->getStatus() == TRAVEL_STATUS_EXPIRED)
out << " expired";
if (target->getStatus() != TRAVEL_STATUS_EXPIRED)
out << " Expire in " << (target->getTimeLeft() / 1000) << "s";
out << " Retry " << target->getRetryCount(true) << "/" << target->getRetryCount(false);
return out.str();
}
else if (command == "budget")
{
std::ostringstream out;
AiObjectContext* context = GetAiObjectContext();
out << "Current money: " << ChatHelper::formatMoney(bot->GetMoney()) << " free to use:"
<< ChatHelper::formatMoney(AI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::anything)) << "\n";
out << "Purpose | Available / Needed \n";
for (uint32 i = 1; i < (uint32)NeedMoneyFor::anything; i++)
{
NeedMoneyFor needMoneyFor = NeedMoneyFor(i);
switch (needMoneyFor)
{
case NeedMoneyFor::none:
out << "nothing";
break;
case NeedMoneyFor::repair:
out << "repair";
break;
case NeedMoneyFor::ammo:
out << "ammo";
break;
case NeedMoneyFor::spells:
out << "spells";
break;
case NeedMoneyFor::travel:
out << "travel";
break;
case NeedMoneyFor::consumables:
out << "consumables";
break;
case NeedMoneyFor::gear:
out << "gear";
break;
case NeedMoneyFor::guild:
out << "guild";
break;
default:
break;
}
out << " | " << ChatHelper::formatMoney(AI_VALUE2(uint32, "free money for", i)) << " / "
<< ChatHelper::formatMoney(AI_VALUE2(uint32, "money needed for", i)) << "\n";
}
return out.str();
}
std::ostringstream out;
out << "invalid command: " << command;
return out.str();
}
bool PlayerbotAI::HasSkill(SkillType skill) { return bot->HasSkill(skill) && bot->GetSkillValue(skill) > 0; }
float PlayerbotAI::GetRange(std::string const type)
{
float val = 0;
if (aiObjectContext)
val = aiObjectContext->GetValue<float>("range", type)->Get();
if (abs(val) >= 0.1f)
return val;
if (type == "spell")
return sPlayerbotAIConfig->spellDistance;
if (type == "shoot")
return sPlayerbotAIConfig->shootDistance;
if (type == "flee")
return sPlayerbotAIConfig->fleeDistance;
if (type == "heal")
return sPlayerbotAIConfig->healDistance;
if (type == "melee")
return sPlayerbotAIConfig->meleeDistance;
return 0;
}
void PlayerbotAI::Ping(float x, float y)
{
WorldPacket data(MSG_MINIMAP_PING, (8 + 4 + 4));
data << bot->GetGUID();
data << x;
data << y;
if (bot->GetGroup())
{
bot->GetGroup()->BroadcastPacket(&data, true, -1, bot->GetGUID());
}
else
{
bot->GetSession()->SendPacket(&data);
}
}
// Helper function to iterate through items in the inventory and bags
Item* PlayerbotAI::FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const
{
// List out items in the main backpack
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
{
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
{
ItemTemplate const* pItemProto = pItem->GetTemplate();
if (pItemProto && bot->CanUseItem(pItemProto) == EQUIP_ERR_OK && checkItem(pItemProto))
return pItem;
}
}
// List out items in other removable backpacks
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
{
if (Bag const* const pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag))
{
for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot)
{
if (Item* const pItem = bot->GetItemByPos(bag, slot))
{
ItemTemplate const* pItemProto = pItem->GetTemplate();
if (pItemProto && bot->CanUseItem(pItemProto) == EQUIP_ERR_OK && checkItem(pItemProto))
return pItem;
}
}
}
}
return nullptr;
}
// Find Poison
Item* PlayerbotAI::FindPoison() const
{
return FindItemInInventory([](ItemTemplate const* pItemProto) -> bool {
return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == 6;
});
}
// Find Consumable
Item* PlayerbotAI::FindConsumable(uint32 displayId) const
{
return FindItemInInventory([displayId](ItemTemplate const* pItemProto) -> bool {
return (pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_CLASS_TRADE_GOODS) && pItemProto->DisplayInfoID == displayId;
});
}
// Find Bandage
Item* PlayerbotAI::FindBandage() const
{
return FindItemInInventory([](ItemTemplate const* pItemProto) -> bool {
return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE;
});
}
static const uint32 uPriorizedSharpStoneIds[8] = {ADAMANTITE_SHARPENING_DISPLAYID, FEL_SHARPENING_DISPLAYID,
ELEMENTAL_SHARPENING_DISPLAYID, DENSE_SHARPENING_DISPLAYID,
SOLID_SHARPENING_DISPLAYID, HEAVY_SHARPENING_DISPLAYID,
COARSE_SHARPENING_DISPLAYID, ROUGH_SHARPENING_DISPLAYID};
static const uint32 uPriorizedWeightStoneIds[7] = {ADAMANTITE_WEIGHTSTONE_DISPLAYID, FEL_WEIGHTSTONE_DISPLAYID,
DENSE_WEIGHTSTONE_DISPLAYID, SOLID_WEIGHTSTONE_DISPLAYID,
HEAVY_WEIGHTSTONE_DISPLAYID, COARSE_WEIGHTSTONE_DISPLAYID,
ROUGH_WEIGHTSTONE_DISPLAYID};
/**
* FindStoneFor()
* return Item* Returns sharpening/weight stone item eligible to enchant a bot weapon
*
* params:weapon Item* the weap<61>n the function should search and return a enchanting item for
* return nullptr if no relevant item is found in bot inventory, else return a sharpening or weight
* stone based on the weapon subclass
*
*/
Item* PlayerbotAI::FindStoneFor(Item* weapon) const
{
Item* stone = nullptr;
ItemTemplate const* pProto = weapon->GetTemplate();
if (pProto && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2 ||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER))
{
for (uint8 i = 0; i < std::size(uPriorizedSharpStoneIds); ++i)
{
stone = FindConsumable(uPriorizedSharpStoneIds[i]);
if (stone)
{
return stone;
}
}
}
else if (pProto &&
(pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2))
{
for (uint8 i = 0; i < std::size(uPriorizedWeightStoneIds); ++i)
{
stone = FindConsumable(uPriorizedWeightStoneIds[i]);
if (stone)
{
return stone;
}
}
}
return stone;
}
Item* PlayerbotAI::FindOilFor(Item* weapon) const
{
if (!weapon)
return nullptr;
const ItemTemplate* item_template = weapon->GetTemplate();
if (!item_template)
return nullptr;
// static const will only get created once whatever the call amout
static const std::vector<uint32_t> uPriorizedWizardOilIds = {
MINOR_WIZARD_OIL, MINOR_MANA_OIL, LESSER_WIZARD_OIL, LESSER_MANA_OIL, BRILLIANT_WIZARD_OIL,
BRILLIANT_MANA_OIL, WIZARD_OIL, SUPERIOR_MANA_OIL, SUPERIOR_WIZARD_OIL};
// static const will only get created once whatever the call amout
static const std::vector<uint32_t> uPriorizedManaOilIds = {
MINOR_MANA_OIL, MINOR_WIZARD_OIL, LESSER_MANA_OIL, LESSER_WIZARD_OIL, BRILLIANT_MANA_OIL,
BRILLIANT_WIZARD_OIL, SUPERIOR_MANA_OIL, WIZARD_OIL, SUPERIOR_WIZARD_OIL};
Item* oil = nullptr;
if (item_template->SubClass == ITEM_SUBCLASS_WEAPON_SWORD ||
item_template->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || item_template->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)
{
for (const auto& id : uPriorizedWizardOilIds)
{
oil = FindConsumable(id);
if (oil)
return oil;
}
}
else if (item_template->SubClass == ITEM_SUBCLASS_WEAPON_MACE ||
item_template->SubClass == ITEM_SUBCLASS_WEAPON_MACE2)
{
for (const auto& id : uPriorizedManaOilIds)
{
oil = FindConsumable(id);
if (oil)
return oil;
}
}
return oil;
}
std::vector<Item*> PlayerbotAI::GetInventoryAndEquippedItems()
{
std::vector<Item*> items;
for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (Bag* pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
{
if (Item* pItem = pBag->GetItemByPos(j))
{
items.push_back(pItem);
}
}
}
}
for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
{
if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
items.push_back(pItem);
}
}
for (int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; ++i)
{
if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
items.push_back(pItem);
}
}
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++)
{
if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
{
items.push_back(pItem);
}
}
return items;
}
std::vector<Item*> PlayerbotAI::GetInventoryItems()
{
std::vector<Item*> items;
for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (Bag* pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
{
if (Item* pItem = pBag->GetItemByPos(j))
{
items.push_back(pItem);
}
}
}
}
for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
{
if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
items.push_back(pItem);
}
}
for (int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; ++i)
{
if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
items.push_back(pItem);
}
}
return items;
}
uint32 PlayerbotAI::GetInventoryItemsCountWithId(uint32 itemId)
{
uint32 count = 0;
for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (Bag* pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
{
if (Item* pItem = pBag->GetItemByPos(j))
{
if (pItem->GetTemplate()->ItemId == itemId)
{
count += pItem->GetCount();
}
}
}
}
}
for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
{
if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (pItem->GetTemplate()->ItemId == itemId)
{
count += pItem->GetCount();
}
}
}
for (int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; ++i)
{
if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (pItem->GetTemplate()->ItemId == itemId)
{
count += pItem->GetCount();
}
}
}
return count;
}
bool PlayerbotAI::HasItemInInventory(uint32 itemId)
{
for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (Bag* pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
{
if (Item* pItem = pBag->GetItemByPos(j))
{
if (pItem->GetTemplate()->ItemId == itemId)
{
return true;
}
}
}
}
}
for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
{
if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (pItem->GetTemplate()->ItemId == itemId)
{
return true;
}
}
}
for (int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; ++i)
{
if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (pItem->GetTemplate()->ItemId == itemId)
{
return true;
}
}
}
return false;
}
std::vector<std::pair<const Quest*, uint32>> PlayerbotAI::GetCurrentQuestsRequiringItemId(uint32 itemId)
{
std::vector<std::pair<const Quest*, uint32>> result;
if (!itemId)
{
return result;
}
for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 questId = bot->GetQuestSlotQuestId(slot);
if (!questId)
continue;
QuestStatus status = bot->GetQuestStatus(questId);
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
for (uint8 i = 0; i < std::size(quest->RequiredItemId); ++i)
{
if (quest->RequiredItemId[i] == itemId)
{
result.push_back(std::pair(quest, quest->RequiredItemId[i]));
break;
}
}
}
return result;
}
// on self
void PlayerbotAI::ImbueItem(Item* item) { ImbueItem(item, TARGET_FLAG_NONE, ObjectGuid::Empty); }
// item on unit
void PlayerbotAI::ImbueItem(Item* item, Unit* target)
{
if (!target)
return;
ImbueItem(item, TARGET_FLAG_UNIT, target->GetGUID());
}
// item on equipped item
void PlayerbotAI::ImbueItem(Item* item, uint8 targetInventorySlot)
{
if (targetInventorySlot >= EQUIPMENT_SLOT_END)
return;
Item* const targetItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, targetInventorySlot);
if (!targetItem)
return;
ImbueItem(item, TARGET_FLAG_ITEM, targetItem->GetGUID());
}
// generic item use method
void PlayerbotAI::ImbueItem(Item* item, uint32 targetFlag, ObjectGuid targetGUID)
{
if (!item)
return;
uint32 glyphIndex = 0;
uint8 castFlags = 0;
uint8 bagIndex = item->GetBagSlot();
uint8 slot = item->GetSlot();
uint8 cast_count = 0;
ObjectGuid item_guid = item->GetGUID();
uint32 spellId = 0;
uint8 spell_index = 0;
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
{
if (item->GetTemplate()->Spells[i].SpellId > 0)
{
spellId = item->GetTemplate()->Spells[i].SpellId;
spell_index = i;
break;
}
}
WorldPacket* packet = new WorldPacket(CMSG_USE_ITEM);
*packet << bagIndex;
*packet << slot;
*packet << cast_count;
*packet << spellId;
*packet << item_guid;
*packet << glyphIndex;
*packet << castFlags;
*packet << targetFlag;
if (targetFlag & (TARGET_FLAG_UNIT | TARGET_FLAG_ITEM | TARGET_FLAG_GAMEOBJECT))
*packet << targetGUID.WriteAsPacked();
bot->GetSession()->QueuePacket(packet);
}
void PlayerbotAI::EnchantItemT(uint32 spellid, uint8 slot)
{
Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
if (!pItem || !pItem->IsInWorld() || !pItem->GetOwner() || !pItem->GetOwner()->IsInWorld() ||
!pItem->GetOwner()->GetSession())
return;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid);
if (!spellInfo)
return;
uint32 enchantid = spellInfo->Effects[0].MiscValue;
if (!enchantid)
{
// LOG_ERROR("playerbots", "{}: Invalid enchantid ", enchantid, " report to devs", bot->GetName().c_str());
return;
}
if (!((1 << pItem->GetTemplate()->SubClass) & spellInfo->EquippedItemSubClassMask) &&
!((1 << pItem->GetTemplate()->InventoryType) & spellInfo->EquippedItemInventoryTypeMask))
{
// LOG_ERROR("playerbots", "{}: items could not be enchanted, wrong item type equipped",
// bot->GetName().c_str());
return;
}
bot->ApplyEnchantment(pItem, PERM_ENCHANTMENT_SLOT, false);
pItem->SetEnchantment(PERM_ENCHANTMENT_SLOT, enchantid, 0, 0);
bot->ApplyEnchantment(pItem, PERM_ENCHANTMENT_SLOT, true);
LOG_INFO("playerbots", "{}: items was enchanted successfully!", bot->GetName().c_str());
}
uint32 PlayerbotAI::GetBuffedCount(Player* player, std::string const spellname)
{
uint32 bcount = 0;
if (Group* group = bot->GetGroup())
{
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || !member->IsInWorld())
continue;
if (!member->IsInSameRaidWith(player))
continue;
if (HasAura(spellname, member, true))
bcount++;
}
}
return bcount;
}
int32 PlayerbotAI::GetNearGroupMemberCount(float dis)
{
int count = 1; // yourself
if (Group* group = bot->GetGroup())
{
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (member == bot) // calculated
continue;
if (!member || !member->IsInWorld())
continue;
if (member->GetMapId() != bot->GetMapId())
continue;
if (member->GetExactDist(bot) > dis)
continue;
count++;
}
}
return count;
}
bool PlayerbotAI::CanMove()
{
// do not allow if not vehicle driver
if (IsInVehicle() && !IsInVehicle(true))
return false;
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() ||
bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false;
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
}
bool PlayerbotAI::IsInRealGuild()
{
if (!bot->GetGuildId())
return false;
Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId());
if (!guild)
{
return false;
}
uint32 leaderAccount = sCharacterCache->GetCharacterAccountIdByGuid(guild->GetLeaderGUID());
if (!leaderAccount)
return false;
return !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount));
}
void PlayerbotAI::QueueChatResponse(const ChatQueuedReply chatReply) { chatReplies.push_back(std::move(chatReply)); }
bool PlayerbotAI::EqualLowercaseName(std::string s1, std::string s2)
{
if (s1.length() != s2.length())
{
return false;
}
for (int i = 0; i < s1.length(); i++)
{
if (tolower(s1[i]) != tolower(s2[i]))
{
return false;
}
}
return true;
}
// A custom CanEquipItem (remove AutoUnequipOffhand in FindEquipSlot to prevent unequip on `item usage` calculation)
InventoryResult PlayerbotAI::CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading) const
{
dest = 0;
if (pItem)
{
LOG_DEBUG("entities.player.items", "STORAGE: CanEquipItem slot = {}, item = {}, count = {}", slot,
pItem->GetEntry(), pItem->GetCount());
ItemTemplate const* pProto = pItem->GetTemplate();
if (pProto)
{
if (!sScriptMgr->CanEquipItem(bot, slot, dest, pItem, swap, not_loading))
return EQUIP_ERR_CANT_DO_RIGHT_NOW;
// item used
if (pItem->m_lootGenerated)
return EQUIP_ERR_ALREADY_LOOTED;
if (pItem->IsBindedNotWith(bot))
return EQUIP_ERR_DONT_OWN_THAT_ITEM;
InventoryResult res = bot->CanTakeMoreSimilarItems(pItem);
if (res != EQUIP_ERR_OK)
return res;
ScalingStatDistributionEntry const* ssd =
pProto->ScalingStatDistribution
? sScalingStatDistributionStore.LookupEntry(pProto->ScalingStatDistribution)
: 0;
// check allowed level (extend range to upper values if MaxLevel more or equal max player level, this let GM
// set high level with 1...max range items)
if (ssd && ssd->MaxLevel < DEFAULT_MAX_LEVEL && ssd->MaxLevel < bot->GetLevel())
return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
uint8 eslot = FindEquipSlot(pProto, slot, swap);
if (eslot == NULL_SLOT)
return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
// Xinef: dont allow to equip items on disarmed slot
if (!bot->CanUseAttackType(bot->GetAttackBySlot(eslot)))
return EQUIP_ERR_NOT_WHILE_DISARMED;
res = bot->CanUseItem(pItem, not_loading);
if (res != EQUIP_ERR_OK)
return res;
if (!swap && bot->GetItemByPos(INVENTORY_SLOT_BAG_0, eslot))
return EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE;
// if we are swapping 2 equiped items, CanEquipUniqueItem check
// should ignore the item we are trying to swap, and not the
// destination item. CanEquipUniqueItem should ignore destination
// item only when we are swapping weapon from bag
uint8 ignore = uint8(NULL_SLOT);
switch (eslot)
{
case EQUIPMENT_SLOT_MAINHAND:
ignore = EQUIPMENT_SLOT_OFFHAND;
break;
case EQUIPMENT_SLOT_OFFHAND:
ignore = EQUIPMENT_SLOT_MAINHAND;
break;
case EQUIPMENT_SLOT_FINGER1:
ignore = EQUIPMENT_SLOT_FINGER2;
break;
case EQUIPMENT_SLOT_FINGER2:
ignore = EQUIPMENT_SLOT_FINGER1;
break;
case EQUIPMENT_SLOT_TRINKET1:
ignore = EQUIPMENT_SLOT_TRINKET2;
break;
case EQUIPMENT_SLOT_TRINKET2:
ignore = EQUIPMENT_SLOT_TRINKET1;
break;
}
if (ignore == uint8(NULL_SLOT) || pItem != bot->GetItemByPos(INVENTORY_SLOT_BAG_0, ignore))
ignore = eslot;
InventoryResult res2 = bot->CanEquipUniqueItem(pItem, swap ? ignore : uint8(NULL_SLOT));
if (res2 != EQUIP_ERR_OK)
return res2;
// check unique-equipped special item classes
if (pProto->Class == ITEM_CLASS_QUIVER)
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
if (Item* pBag = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
if (pBag != pItem)
if (ItemTemplate const* pBagProto = pBag->GetTemplate())
if (pBagProto->Class == pProto->Class && (!swap || pBag->GetSlot() != eslot))
return (pBagProto->SubClass == ITEM_SUBCLASS_AMMO_POUCH)
? EQUIP_ERR_CAN_EQUIP_ONLY1_AMMOPOUCH
: EQUIP_ERR_CAN_EQUIP_ONLY1_QUIVER;
uint32 type = pProto->InventoryType;
if (eslot == EQUIPMENT_SLOT_OFFHAND)
{
// Do not allow polearm to be equipped in the offhand (rare case for the only 1h polearm 41750)
// xinef: same for fishing poles
if (type == INVTYPE_WEAPON && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM ||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE))
return EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT;
else if (type == INVTYPE_WEAPON || type == INVTYPE_WEAPONOFFHAND)
{
if (!bot->CanDualWield())
return EQUIP_ERR_CANT_DUAL_WIELD;
}
else if (type == INVTYPE_2HWEAPON)
{
if (!bot->CanDualWield() || !bot->CanTitanGrip())
return EQUIP_ERR_CANT_DUAL_WIELD;
}
if (bot->IsTwoHandUsed())
return EQUIP_ERR_CANT_EQUIP_WITH_TWOHANDED;
}
// equip two-hand weapon case (with possible unequip 2 items)
if (type == INVTYPE_2HWEAPON)
{
if (eslot == EQUIPMENT_SLOT_OFFHAND)
{
if (!bot->CanTitanGrip())
return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
}
else if (eslot != EQUIPMENT_SLOT_MAINHAND)
return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
if (!bot->CanTitanGrip())
{
// offhand item must can be stored in inventory for offhand item and it also must be unequipped
Item* offItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
ItemPosCountVec off_dest;
if (offItem && (!not_loading ||
bot->CanUnequipItem(uint16(INVENTORY_SLOT_BAG_0) << 8 | EQUIPMENT_SLOT_OFFHAND,
false) != EQUIP_ERR_OK ||
bot->CanStoreItem(NULL_BAG, NULL_SLOT, off_dest, offItem, false) != EQUIP_ERR_OK))
return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_INVENTORY_FULL;
}
}
dest = ((INVENTORY_SLOT_BAG_0 << 8) | eslot);
return EQUIP_ERR_OK;
}
}
return !swap ? EQUIP_ERR_ITEM_NOT_FOUND : EQUIP_ERR_ITEMS_CANT_BE_SWAPPED;
}
uint8 PlayerbotAI::FindEquipSlot(ItemTemplate const* proto, uint32 slot, bool swap) const
{
uint8 slots[4];
slots[0] = NULL_SLOT;
slots[1] = NULL_SLOT;
slots[2] = NULL_SLOT;
slots[3] = NULL_SLOT;
switch (proto->InventoryType)
{
case INVTYPE_HEAD:
slots[0] = EQUIPMENT_SLOT_HEAD;
break;
case INVTYPE_NECK:
slots[0] = EQUIPMENT_SLOT_NECK;
break;
case INVTYPE_SHOULDERS:
slots[0] = EQUIPMENT_SLOT_SHOULDERS;
break;
case INVTYPE_BODY:
slots[0] = EQUIPMENT_SLOT_BODY;
break;
case INVTYPE_CHEST:
case INVTYPE_ROBE:
slots[0] = EQUIPMENT_SLOT_CHEST;
break;
case INVTYPE_WAIST:
slots[0] = EQUIPMENT_SLOT_WAIST;
break;
case INVTYPE_LEGS:
slots[0] = EQUIPMENT_SLOT_LEGS;
break;
case INVTYPE_FEET:
slots[0] = EQUIPMENT_SLOT_FEET;
break;
case INVTYPE_WRISTS:
slots[0] = EQUIPMENT_SLOT_WRISTS;
break;
case INVTYPE_HANDS:
slots[0] = EQUIPMENT_SLOT_HANDS;
break;
case INVTYPE_FINGER:
slots[0] = EQUIPMENT_SLOT_FINGER1;
slots[1] = EQUIPMENT_SLOT_FINGER2;
break;
case INVTYPE_TRINKET:
slots[0] = EQUIPMENT_SLOT_TRINKET1;
slots[1] = EQUIPMENT_SLOT_TRINKET2;
break;
case INVTYPE_CLOAK:
slots[0] = EQUIPMENT_SLOT_BACK;
break;
case INVTYPE_WEAPON:
{
slots[0] = EQUIPMENT_SLOT_MAINHAND;
// suggest offhand slot only if know dual wielding
// (this will be replace mainhand weapon at auto equip instead unwonted "you don't known dual wielding" ...
if (bot->CanDualWield())
slots[1] = EQUIPMENT_SLOT_OFFHAND;
break;
}
case INVTYPE_SHIELD:
case INVTYPE_WEAPONOFFHAND:
case INVTYPE_HOLDABLE:
slots[0] = EQUIPMENT_SLOT_OFFHAND;
break;
case INVTYPE_RANGED:
case INVTYPE_RANGEDRIGHT:
case INVTYPE_THROWN:
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
case INVTYPE_2HWEAPON:
slots[0] = EQUIPMENT_SLOT_MAINHAND;
if (Item* mhWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND))
{
if (ItemTemplate const* mhWeaponProto = mhWeapon->GetTemplate())
{
if (mhWeaponProto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM ||
mhWeaponProto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF)
{
bot->AutoUnequipOffhandIfNeed(true);
break;
}
}
}
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND))
{
if (proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF)
{
break;
}
}
if (bot->CanDualWield() && bot->CanTitanGrip() && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_FISHING_POLE)
slots[1] = EQUIPMENT_SLOT_OFFHAND;
break;
case INVTYPE_TABARD:
slots[0] = EQUIPMENT_SLOT_TABARD;
break;
case INVTYPE_WEAPONMAINHAND:
slots[0] = EQUIPMENT_SLOT_MAINHAND;
break;
case INVTYPE_BAG:
slots[0] = INVENTORY_SLOT_BAG_START + 0;
slots[1] = INVENTORY_SLOT_BAG_START + 1;
slots[2] = INVENTORY_SLOT_BAG_START + 2;
slots[3] = INVENTORY_SLOT_BAG_START + 3;
break;
case INVTYPE_RELIC:
{
switch (proto->SubClass)
{
case ITEM_SUBCLASS_ARMOR_LIBRAM:
if (bot->IsClass(CLASS_PALADIN, CLASS_CONTEXT_EQUIP_RELIC))
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
case ITEM_SUBCLASS_ARMOR_IDOL:
if (bot->IsClass(CLASS_DRUID, CLASS_CONTEXT_EQUIP_RELIC))
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
case ITEM_SUBCLASS_ARMOR_TOTEM:
if (bot->IsClass(CLASS_SHAMAN, CLASS_CONTEXT_EQUIP_RELIC))
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
case ITEM_SUBCLASS_ARMOR_MISC:
if (bot->IsClass(CLASS_WARLOCK, CLASS_CONTEXT_EQUIP_RELIC))
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
case ITEM_SUBCLASS_ARMOR_SIGIL:
if (bot->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_EQUIP_RELIC))
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
}
break;
}
default:
return NULL_SLOT;
}
if (slot != NULL_SLOT)
{
if (swap || !bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
for (uint8 i = 0; i < 4; ++i)
if (slots[i] == slot)
return slot;
}
else
{
// search free slot at first
for (uint8 i = 0; i < 4; ++i)
if (slots[i] != NULL_SLOT && !bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slots[i]))
// in case 2hand equipped weapon (without titan grip) offhand slot empty but not free
if (slots[i] != EQUIPMENT_SLOT_OFFHAND || !bot->IsTwoHandUsed())
return slots[i];
// if not found free and can swap return first appropriate from used
for (uint8 i = 0; i < 4; ++i)
if (slots[i] != NULL_SLOT && swap)
return slots[i];
}
// no free position
return NULL_SLOT;
}
bool PlayerbotAI::IsSafe(Player* player)
{
return player && player->GetMapId() == bot->GetMapId() && player->GetInstanceId() == bot->GetInstanceId() &&
!player->IsBeingTeleported();
}
bool PlayerbotAI::IsSafe(WorldObject* obj)
{
return obj && obj->GetMapId() == bot->GetMapId() && obj->GetInstanceId() == bot->GetInstanceId() &&
(!obj->IsPlayer() || !((Player*)obj)->IsBeingTeleported());
}
ChatChannelSource PlayerbotAI::GetChatChannelSource(Player* bot, uint32 type, std::string channelName)
{
if (type == CHAT_MSG_CHANNEL)
{
if (channelName == "World")
return ChatChannelSource::SRC_WORLD;
else
{
ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId());
if (!cMgr)
{
return ChatChannelSource::SRC_UNDEFINED;
}
const Channel* channel = cMgr->GetChannel(channelName, bot);
if (channel)
{
switch (channel->GetChannelId())
{
case ChatChannelId::GENERAL:
{
return ChatChannelSource::SRC_GENERAL;
}
case ChatChannelId::TRADE:
{
return ChatChannelSource::SRC_TRADE;
}
case ChatChannelId::LOCAL_DEFENSE:
{
return ChatChannelSource::SRC_LOCAL_DEFENSE;
}
case ChatChannelId::WORLD_DEFENSE:
{
return ChatChannelSource::SRC_WORLD_DEFENSE;
}
case ChatChannelId::LOOKING_FOR_GROUP:
{
return ChatChannelSource::SRC_LOOKING_FOR_GROUP;
}
case ChatChannelId::GUILD_RECRUITMENT:
{
return ChatChannelSource::SRC_GUILD_RECRUITMENT;
}
default:
{
return ChatChannelSource::SRC_UNDEFINED;
}
}
}
}
}
else
{
switch (type)
{
case CHAT_MSG_WHISPER:
{
return ChatChannelSource::SRC_WHISPER;
}
case CHAT_MSG_SAY:
{
return ChatChannelSource::SRC_SAY;
}
case CHAT_MSG_YELL:
{
return ChatChannelSource::SRC_YELL;
}
case CHAT_MSG_GUILD:
{
return ChatChannelSource::SRC_GUILD;
}
case CHAT_MSG_PARTY:
{
return ChatChannelSource::SRC_PARTY;
}
case CHAT_MSG_RAID:
{
return ChatChannelSource::SRC_RAID;
}
case CHAT_MSG_EMOTE:
{
return ChatChannelSource::SRC_EMOTE;
}
case CHAT_MSG_TEXT_EMOTE:
{
return ChatChannelSource::SRC_TEXT_EMOTE;
}
default:
{
return ChatChannelSource::SRC_UNDEFINED;
}
}
}
return ChatChannelSource::SRC_UNDEFINED;
}
std::vector<const Quest*> PlayerbotAI::GetAllCurrentQuests()
{
std::vector<const Quest*> result;
for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 questId = bot->GetQuestSlotQuestId(slot);
if (!questId)
{
continue;
}
result.push_back(sObjectMgr->GetQuestTemplate(questId));
}
return result;
}
std::vector<const Quest*> PlayerbotAI::GetCurrentIncompleteQuests()
{
std::vector<const Quest*> result;
for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 questId = bot->GetQuestSlotQuestId(slot);
if (!questId)
{
continue;
}
QuestStatus status = bot->GetQuestStatus(questId);
if (status == QUEST_STATUS_INCOMPLETE || status == QUEST_STATUS_NONE)
{
result.push_back(sObjectMgr->GetQuestTemplate(questId));
}
}
return result;
}
std::set<uint32> PlayerbotAI::GetAllCurrentQuestIds()
{
std::set<uint32> result;
for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 questId = bot->GetQuestSlotQuestId(slot);
if (!questId)
{
continue;
}
result.insert(questId);
}
return result;
}
std::set<uint32> PlayerbotAI::GetCurrentIncompleteQuestIds()
{
std::set<uint32> result;
for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 questId = bot->GetQuestSlotQuestId(slot);
if (!questId)
{
continue;
}
QuestStatus status = bot->GetQuestStatus(questId);
if (status == QUEST_STATUS_INCOMPLETE || status == QUEST_STATUS_NONE)
{
result.insert(questId);
}
}
return result;
}
uint32 PlayerbotAI::GetReactDelay()
{
uint32 base = sPlayerbotAIConfig->reactDelay; // Default 100(ms)
// If dynamic react delay is disabled, use a static calculation
if (!sPlayerbotAIConfig->dynamicReactDelay)
{
if (HasRealPlayerMaster())
return base;
bool inBG = bot->InBattleground() || bot->InArena();
if (sPlayerbotAIConfig->fastReactInBG && inBG)
return base;
bool inCombat = bot->IsInCombat();
if (!inCombat)
return base * 10.0f;
else if (inCombat)
return base * 2.5f;
return base;
}
// Dynamic react delay calculation:
if (HasRealPlayerMaster())
return base;
float multiplier = 1.0f;
bool inBG = bot->InBattleground() || bot->InArena();
if (inBG)
{
if (bot->IsInCombat() || currentState == BOT_STATE_COMBAT)
{
multiplier = sPlayerbotAIConfig->fastReactInBG ? 2.5f : 5.0f;
return base * multiplier;
}
else
{
multiplier = sPlayerbotAIConfig->fastReactInBG ? 1.0f : 10.0f;
return base * multiplier;
}
}
// When in combat, return 5 times the base
if (bot->IsInCombat() || currentState == BOT_STATE_COMBAT)
{
multiplier = 5.0f;
return base * multiplier;
}
// When not resting, return 10-30 times the base
if (!bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING))
{
multiplier = urand(10, 30);
return base * multiplier;
}
// In other cases, return 20-200 times the base
multiplier = urand(20, 200);
return base * multiplier;
}
void PlayerbotAI::PetFollow()
{
Pet* pet = bot->GetPet();
if (!pet)
return;
pet->AttackStop();
pet->InterruptNonMeleeSpells(false);
pet->ClearInPetCombat();
pet->GetMotionMaster()->MoveFollow(bot, PET_FOLLOW_DIST, pet->GetFollowAngle());
if (pet->ToPet())
pet->ToPet()->ClearCastWhenWillAvailable();
CharmInfo* charmInfo = pet->GetCharmInfo();
if (!charmInfo)
return;
charmInfo->SetCommandState(COMMAND_FOLLOW);
charmInfo->SetIsCommandAttack(false);
charmInfo->SetIsAtStay(false);
charmInfo->SetIsReturning(true);
charmInfo->SetIsCommandFollow(true);
charmInfo->SetIsFollowing(false);
charmInfo->RemoveStayPosition();
charmInfo->SetForcedSpell(0);
charmInfo->SetForcedTargetGUID();
}
float PlayerbotAI::GetItemScoreMultiplier(ItemQualities quality)
{
switch (quality)
{
// each quality increase 1.1x
case ITEM_QUALITY_POOR:
return 1.0f;
break;
case ITEM_QUALITY_NORMAL:
return 1.1f;
break;
case ITEM_QUALITY_UNCOMMON:
return 1.21f;
break;
case ITEM_QUALITY_RARE:
return 1.331f;
break;
case ITEM_QUALITY_EPIC:
return 1.4641f;
break;
case ITEM_QUALITY_LEGENDARY:
return 1.61051f;
break;
default:
break;
}
return 1.0f;
}