Files
mod-playerbots/src/PlayerbotMgr.cpp
kadeshar 85c7009fe1 Codestyle fix (#1797)
Warning:
Dont change this PR as draft to make it testable


DONT REVIEW UNTIL Codestyle C++ workflow dont pass
2025-11-05 21:10:17 +01:00

1886 lines
61 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "PlayerbotMgr.h"
#include <cstdio>
#include <cstring>
#include <istream>
#include <string>
#include <openssl/sha.h>
#include <unordered_set>
#include <iomanip>
#include "ChannelMgr.h"
#include "CharacterCache.h"
#include "CharacterPackets.h"
#include "Common.h"
#include "Define.h"
#include "Group.h"
#include "GroupMgr.h"
#include "GuildMgr.h"
#include "ObjectAccessor.h"
#include "ObjectGuid.h"
#include "ObjectMgr.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotDbStore.h"
#include "PlayerbotFactory.h"
#include "PlayerbotSecurity.h"
#include "Playerbots.h"
#include "RandomPlayerbotMgr.h"
#include "SharedDefines.h"
#include "WorldSession.h"
#include "ChannelMgr.h"
#include "BroadcastHelper.h"
#include "PlayerbotDbStore.h"
#include "WorldSessionMgr.h"
#include "DatabaseEnv.h" // Added for gender choice
#include <algorithm> // Added for gender choice
class BotInitGuard
{
public:
BotInitGuard(ObjectGuid guid) : guid(guid), active(false)
{
if (!botsBeingInitialized.contains(guid))
{
botsBeingInitialized.insert(guid);
active = true;
}
}
~BotInitGuard()
{
if (active)
botsBeingInitialized.erase(guid);
}
bool IsLocked() const { return !active; }
private:
ObjectGuid guid;
bool active;
static std::unordered_set<ObjectGuid> botsBeingInitialized;
};
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
class PlayerbotLoginQueryHolder : public LoginQueryHolder
{
private:
uint32 masterAccountId;
PlayerbotHolder* playerbotHolder;
public:
PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder)
{
}
uint32 GetMasterAccountId() const { return masterAccountId; }
PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; }
};
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
{
// bot is loading
if (botLoading.find(playerGuid) != botLoading.end())
return;
// has bot already been added?
Player* bot = ObjectAccessor::FindConnectedPlayer(playerGuid);
if (bot && bot->IsInWorld())
return;
uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(playerGuid);
if (!accountId)
return;
WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr;
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
bool isRndbot = !masterAccountId;
bool sameAccount = sPlayerbotAIConfig->allowAccountBots && accountId == masterAccountId;
Guild* guild = masterPlayer ? sGuildMgr->GetGuildById(masterPlayer->GetGuildId()) : nullptr;
bool sameGuild = sPlayerbotAIConfig->allowGuildBots && guild && guild->GetMember(playerGuid);
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(playerGuid.GetCounter());
bool linkedAccount = sPlayerbotAIConfig->allowTrustedAccountBots && IsAccountLinked(accountId, masterAccountId);
bool allowed = true;
std::ostringstream out;
std::string botName;
sCharacterCache->GetCharacterNameByGuid(playerGuid, botName);
if (!isRndbot && !sameAccount && !sameGuild && !addClassBot && !linkedAccount)
{
allowed = false;
out << "Failure: You are not allowed to control bot " << botName.c_str();
}
if (masterAccountId && masterPlayer)
{
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer);
if (!mgr)
{
LOG_DEBUG("playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
return;
}
uint32 count = mgr->GetPlayerbotsCount() + botLoading.size();
if (count >= sPlayerbotAIConfig->maxAddedBots)
{
allowed = false;
out << "Failure: You have added too many bots (more than " << sPlayerbotAIConfig->maxAddedBots << ")";
}
}
if (!allowed)
{
if (masterSession)
{
ChatHandler ch(masterSession);
ch.SendSysMessage(out.str());
}
return;
}
std::shared_ptr<PlayerbotLoginQueryHolder> holder =
std::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid);
if (!holder->Initialize())
{
return;
}
botLoading.insert(playerGuid);
// Always login in with world session to avoid race condition
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
.AfterComplete([this](SQLQueryHolderBase const& holder)
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
}
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
{
QueryResult result = PlayerbotsDatabase.Query(
"SELECT 1 FROM playerbots_account_links WHERE account_id = {} AND linked_account_id = {}", accountId, linkedAccountId);
return result != nullptr;
}
void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder)
{
uint32 botAccountId = holder.GetAccountId();
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
// allows channels to work as intended)
WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
Player* bot = botSession->GetPlayer();
if (!bot)
{
// Debug log
LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
botSession->LogoutPlayer(true);
delete botSession;
botLoading.erase(holder.GetGuid());
return;
}
uint32 masterAccount = holder.GetMasterAccountId();
WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr;
// Check if masterSession->GetPlayer() is valid
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
if (masterSession && !masterPlayer)
{
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
}
sRandomPlayerbotMgr->OnPlayerLogin(bot);
OnBotLogin(bot);
botLoading.erase(holder.GetGuid());
}
void PlayerbotHolder::UpdateSessions()
{
for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr)
{
Player* const bot = itr->second;
if (bot->IsBeingTeleported())
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI)
{
botAI->HandleTeleportAck();
}
}
else if (bot->IsInWorld())
{
HandleBotPackets(bot->GetSession());
}
}
}
void PlayerbotHolder::HandleBotPackets(WorldSession* session)
{
WorldPacket* packet;
while (session->GetPacketQueue().next(packet))
{
OpcodeClient opcode = static_cast<OpcodeClient>(packet->GetOpcode());
ClientOpcodeHandler const* opHandle = opcodeTable[opcode];
if (!opHandle)
{
LOG_ERROR("playerbots", "Unhandled opcode {} queued for bot session {}. Packet dropped.", static_cast<uint32>(opcode), session->GetAccountId());
delete packet;
continue;
}
opHandle->Call(session, *packet);
delete packet;
}
}
void PlayerbotHolder::LogoutAllBots()
{
/*
while (true)
{
PlayerBotMap::const_iterator itr = GetPlayerBotsBegin();
if (itr == GetPlayerBotsEnd())
break;
Player* bot= itr->second;
if (!GET_PLAYERBOT_AI(bot)->IsRealPlayer())
LogoutPlayerBot(bot->GetGUID());
}
*/
PlayerBotMap bots = playerBots;
for (auto& itr : bots)
{
Player* bot = itr.second;
if (!bot)
continue;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI || botAI->IsRealPlayer())
continue;
LogoutPlayerBot(bot->GetGUID());
}
}
void PlayerbotMgr::CancelLogout()
{
Player* master = GetMaster();
if (!master)
return;
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI || botAI->IsRealPlayer())
continue;
if (bot->GetSession()->isLogingOut())
{
WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL);
bot->GetSession()->HandleLogoutCancelOpcode(data);
botAI->TellMaster("Logout cancelled!");
}
}
for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin();
it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI || botAI->IsRealPlayer())
continue;
if (botAI->GetMaster() != master)
continue;
if (bot->GetSession()->isLogingOut())
{
WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL);
bot->GetSession()->HandleLogoutCancelOpcode(data);
}
}
}
void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
{
if (Player* bot = GetPlayerBot(guid))
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
return;
Group* group = bot->GetGroup();
if (group && !bot->InBattleground() && !bot->InBattlegroundQueue() && botAI->HasActivePlayerMaster())
{
sPlayerbotDbStore->Save(botAI);
}
LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str());
bot->SaveToDB(false, false);
WorldSession* botWorldSessionPtr = bot->GetSession();
WorldSession* masterWorldSessionPtr = nullptr;
if (botWorldSessionPtr->isLogingOut())
return;
Player* master = botAI->GetMaster();
if (master)
masterWorldSessionPtr = master->GetSession();
// check for instant logout
bool logout = botWorldSessionPtr->ShouldLogOut(time(nullptr));
if (masterWorldSessionPtr && masterWorldSessionPtr->ShouldLogOut(time(nullptr)))
logout = true;
if (masterWorldSessionPtr && !masterWorldSessionPtr->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) ||
(masterWorldSessionPtr &&
masterWorldSessionPtr->GetSecurity() >= (AccountTypes)sWorld->getIntConfig(CONFIG_INSTANT_LOGOUT))))
{
logout = true;
}
TravelTarget* target = nullptr;
if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values.
{
target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get();
}
// Peiru: Allow bots to always instant logout to see if this resolves logout crashes
logout = true;
// if no instant logout, request normal logout
if (!logout)
{
if (bot->GetSession()->isLogingOut())
return;
else if (bot)
{
botAI->TellMaster("I'm logging out!");
WorldPackets::Character::LogoutRequest data = WorldPacket(CMSG_LOGOUT_REQUEST);
botWorldSessionPtr->HandleLogoutRequestOpcode(data);
if (!bot)
{
RemoveFromPlayerbotsMap(guid);
delete botWorldSessionPtr;
if (target)
delete target;
}
return;
}
else
{
RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
delete botWorldSessionPtr; // finally delete the bot's WorldSession
if (target)
delete target;
}
return;
} // if instant logout possible, do it
else if (bot && (logout || !botWorldSessionPtr->isLogingOut()))
{
botAI->TellMaster("Goodbye!");
RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
botWorldSessionPtr->LogoutPlayer(true); // this will delete the bot Player object and PlayerbotAI object
delete botWorldSessionPtr; // finally delete the bot's WorldSession
}
}
}
void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid)
{
if (Player* bot = GetPlayerBot(guid))
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return;
}
botAI->TellMaster("Goodbye!");
bot->StopMoving();
bot->GetMotionMaster()->Clear();
Group* group = bot->GetGroup();
if (group && !bot->InBattleground() && !bot->InBattlegroundQueue() && botAI->HasActivePlayerMaster())
{
sPlayerbotDbStore->Save(botAI);
}
LOG_DEBUG("playerbots", "Bot {} logged out", bot->GetName().c_str());
bot->SaveToDB(false, false);
if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values.
{
TravelTarget* target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get();
if (target)
delete target;
}
RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
delete botAI;
}
}
void PlayerbotHolder::RemoveFromPlayerbotsMap(ObjectGuid guid)
{
playerBots.erase(guid);
}
Player* PlayerbotHolder::GetPlayerBot(ObjectGuid playerGuid) const
{
PlayerBotMap::const_iterator it = playerBots.find(playerGuid);
return (it == playerBots.end()) ? 0 : it->second;
}
Player* PlayerbotHolder::GetPlayerBot(ObjectGuid::LowType lowGuid) const
{
ObjectGuid playerGuid = ObjectGuid::Create<HighGuid::Player>(lowGuid);
PlayerBotMap::const_iterator it = playerBots.find(playerGuid);
return (it == playerBots.end()) ? 0 : it->second;
}
void PlayerbotHolder::OnBotLogin(Player* const bot)
{
// Prevent duplicate login
if (playerBots.find(bot->GetGUID()) != playerBots.end())
{
return;
}
sPlayerbotsMgr->AddPlayerbotData(bot, true);
playerBots[bot->GetGUID()] = bot;
OnBotLoginInternal(bot);
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
// Log a warning here to indicate that the botAI is null
LOG_DEBUG("mod-playerbots", "PlayerbotAI is null for bot with GUID: {}", bot->GetGUID().GetRawValue());
return;
}
Player* master = botAI->GetMaster();
if (master)
{
ObjectGuid masterGuid = master->GetGUID();
if (master->GetGroup() && !master->GetGroup()->IsLeader(masterGuid))
master->GetGroup()->ChangeLeader(masterGuid);
}
Group* group = bot->GetGroup();
if (group)
{
bool groupValid = false;
Group::MemberSlotList const& slots = group->GetMemberSlots();
for (Group::MemberSlotList::const_iterator i = slots.begin(); i != slots.end(); ++i)
{
ObjectGuid member = i->guid;
if (master)
{
if (master->GetGUID() == member)
{
groupValid = true;
break;
}
}
// Don't disband alt groups when master goes away
// Controlled by config
if (sPlayerbotAIConfig->KeepAltsInGroup())
{
uint32 account = sCharacterCache->GetCharacterAccountIdByGuid(member);
if (!sPlayerbotAIConfig->IsInRandomAccountList(account))
{
groupValid = true;
break;
}
}
}
if (!groupValid)
{
botAI->LeaveOrDisbandGroup();
}
}
group = bot->GetGroup();
if (group)
{
botAI->ResetStrategies();
}
else
{
botAI->ResetStrategies(!sRandomPlayerbotMgr->IsRandomBot(bot));
}
sPlayerbotDbStore->Load(botAI);
if (master && !master->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
bot->GetMotionMaster()->MovementExpired();
bot->CleanupAfterTaxiFlight();
}
// check activity
botAI->AllowActivity(ALL_ACTIVITY, true);
// set delay on login
botAI->SetNextCheckDelay(urand(2000, 4000));
botAI->TellMaster("Hello!", PLAYERBOT_SECURITY_TALK);
if (master && master->GetGroup() && !group)
{
Group* mgroup = master->GetGroup();
if (mgroup->GetMembersCount() >= 5)
{
if (!mgroup->isRaidGroup() && !mgroup->isLFGGroup() && !mgroup->isBGGroup() && !mgroup->isBFGroup())
{
mgroup->ConvertToRaid();
}
if (mgroup->isRaidGroup())
{
mgroup->AddMember(bot);
}
}
else
{
mgroup->AddMember(bot);
}
}
else if (master && !group)
{
Group* newGroup = new Group();
newGroup->Create(master);
sGroupMgr->AddGroup(newGroup);
newGroup->AddMember(bot);
}
// if (master)
// {
// // bot->TeleportTo(master);
// }
uint32 accountId = bot->GetSession()->GetAccountId();
bool isRandomAccount = sPlayerbotAIConfig->IsInRandomAccountList(accountId);
if (isRandomAccount && sPlayerbotAIConfig->randomBotFixedLevel)
{
bot->SetPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN);
}
else if (isRandomAccount && !sPlayerbotAIConfig->randomBotFixedLevel)
{
bot->RemovePlayerFlag(PLAYER_FLAGS_NO_XP_GAIN);
}
bot->SaveToDB(false, false);
bool addClassBot = sRandomPlayerbotMgr->IsAccountType(accountId, 2);
if (addClassBot && master && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
{
// PlayerbotFactory factory(bot, master->GetLevel());
// factory.Randomize(false);
uint32 mixedGearScore =
PlayerbotAI::GetMixedGearScore(master, true, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
// work around: distinguish from 0 if no gear
if (mixedGearScore == 0)
mixedGearScore = 1;
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore);
factory.Randomize(false);
}
// bots join World chat if not solo oriented
if (bot->GetLevel() >= 10 && sRandomPlayerbotMgr->IsRandomBot(bot) && GET_PLAYERBOT_AI(bot) &&
GET_PLAYERBOT_AI(bot)->GetGrouperType() != GrouperType::SOLO)
{
// TODO make action/config
// Make the bot join the world channel for chat
WorldPacket pkt(CMSG_JOIN_CHANNEL);
pkt << uint32(0) << uint8(0) << uint8(0);
pkt << std::string("World");
pkt << ""; // Pass
bot->GetSession()->HandleJoinChannel(pkt);
}
// join standard channels
uint8 locale = BroadcastHelper::GetLocale();
AreaTableEntry const* current_zone = GET_PLAYERBOT_AI(bot)->GetCurrentZone();
ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId());
std::string current_zone_name = current_zone ? GET_PLAYERBOT_AI(bot)->GetLocalizedAreaName(current_zone) : "";
if (current_zone && cMgr)
{
for (uint32 i = 0; i < sChatChannelsStore.GetNumRows(); ++i)
{
ChatChannelsEntry const* channel = sChatChannelsStore.LookupEntry(i);
if (!channel)
continue;
Channel* new_channel = nullptr;
switch (channel->ChannelID)
{
case ChatChannelId::GENERAL:
case ChatChannelId::LOCAL_DEFENSE:
{
char new_channel_name_buf[100];
snprintf(new_channel_name_buf, 100, channel->pattern[locale], current_zone_name.c_str());
new_channel = cMgr->GetJoinChannel(new_channel_name_buf, channel->ChannelID);
break;
}
case ChatChannelId::TRADE:
case ChatChannelId::GUILD_RECRUITMENT:
{
char new_channel_name_buf[100];
//3459 is ID for a zone named "City" (only exists for the sake of using its name)
//Currently in magons TBC, if you switch zones, then you join "Trade - <zone>" and "GuildRecruitment - <zone>"
//which is a core bug, should be "Trade - City" and "GuildRecruitment - City" in both 1.12 and TBC
//but if you (actual player) logout in a city and log back in - you join "City" versions
snprintf(new_channel_name_buf, 100, channel->pattern[locale], GET_PLAYERBOT_AI(bot)->GetLocalizedAreaName(GetAreaEntryByAreaID(3459)).c_str());
new_channel = cMgr->GetJoinChannel(new_channel_name_buf, channel->ChannelID);
break;
}
case ChatChannelId::LOOKING_FOR_GROUP:
case ChatChannelId::WORLD_DEFENSE:
{
new_channel = cMgr->GetJoinChannel(channel->pattern[locale], channel->ChannelID);
break;
}
default:
break;
}
if (new_channel)
new_channel->JoinChannel(bot, "");
}
}
}
std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, ObjectGuid guid, ObjectGuid masterguid,
bool admin, uint32 masterAccountId, uint32 masterGuildId)
{
if (!sPlayerbotAIConfig->enabled || guid.IsEmpty())
return "bot system is disabled";
uint32 botAccount = sCharacterCache->GetCharacterAccountIdByGuid(guid);
//bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(guid.GetCounter()); //not used, line marked for removal.
//bool isRandomAccount = sPlayerbotAIConfig->IsInRandomAccountList(botAccount); //not used, shadowed, line marked for removal.
//bool isMasterAccount = (masterAccountId == botAccount); //not used, line marked for removal.
if (cmd == "add" || cmd == "addaccount" || cmd == "login")
{
if (ObjectAccessor::FindPlayer(guid))
return "player already logged in";
// For addaccount command, verify it's an account name
if (cmd == "addaccount")
{
uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guid);
if (!accountId)
return "character not found";
if (!sPlayerbotAIConfig->allowAccountBots && accountId != masterAccountId &&
!(sPlayerbotAIConfig->allowTrustedAccountBots && IsAccountLinked(accountId, masterAccountId)))
{
return "you can only add bots from your own account or linked accounts";
}
}
AddPlayerBot(guid, masterAccountId);
return "ok";
}
else if (cmd == "remove" || cmd == "logout" || cmd == "rm")
{
if (!ObjectAccessor::FindPlayer(guid))
return "player is offline";
if (!GetPlayerBot(guid))
return "not your bot";
LogoutPlayerBot(guid);
return "ok";
}
// if (admin)
// {
Player* bot = GetPlayerBot(guid);
if (!bot)
bot = sRandomPlayerbotMgr->GetPlayerBot(guid);
if (!bot)
return "bot not found";
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(guid.GetCounter());
if (!addClassBot)
return "ERROR: You can not use this command on non-addclass bot.";
if (!admin)
{
Player* master = ObjectAccessor::FindConnectedPlayer(masterguid);
if (master && (master->IsInCombat() || bot->IsInCombat()))
{
return "ERROR: You can not use this command during combat.";
}
}
if (GET_PLAYERBOT_AI(bot))
{
if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster())
{
if (master->GetSession()->GetSecurity() <= SEC_PLAYER && sPlayerbotAIConfig->autoInitOnly &&
cmd != "init=auto")
{
return "The command is not allowed, use init=auto instead.";
}
// Use boot guard
BotInitGuard guard(bot->GetGUID());
if (guard.IsLocked())
{
return "Initialization already in progress, please wait.";
}
int gs;
if (cmd == "init=white" || cmd == "init=common")
{
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_NORMAL);
factory.Randomize(false);
return "ok";
}
else if (cmd == "init=green" || cmd == "init=uncommon")
{
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_UNCOMMON);
factory.Randomize(false);
return "ok";
}
else if (cmd == "init=blue" || cmd == "init=rare")
{
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_RARE);
factory.Randomize(false);
return "ok";
}
else if (cmd == "init=epic" || cmd == "init=purple")
{
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_EPIC);
factory.Randomize(false);
return "ok";
}
else if (cmd == "init=legendary" || cmd == "init=yellow")
{
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY);
factory.Randomize(false);
return "ok";
}
else if (cmd == "init=auto")
{
uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, true, false, 12) *
sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
// work around: distinguish from 0 if no gear
if (mixedGearScore == 0)
mixedGearScore = 1;
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore);
factory.Randomize(false);
return "ok, gear score limit: " + std::to_string(mixedGearScore / PlayerbotAI::GetItemScoreMultiplier(ItemQualities(ITEM_QUALITY_EPIC))) +
"(for epic)";
}
else if (cmd.starts_with("init=") && sscanf(cmd.c_str(), "init=%d", &gs) != -1)
{
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, gs);
factory.Randomize(false);
return "ok, gear score limit: " + std::to_string(gs / PlayerbotAI::GetItemScoreMultiplier(ItemQualities(ITEM_QUALITY_EPIC))) + "(for epic)";
}
}
if (cmd == "refresh=raid")
{ // TODO: This function is not perfect yet. If you are already in a raid,
// after the command is executed, the AI needs to go back online or exit the raid and re-enter.
PlayerbotFactory factory(bot, bot->GetLevel());
factory.UnbindInstance();
return "ok";
}
}
if (cmd == "levelup" || cmd == "level")
{
PlayerbotFactory factory(bot, bot->GetLevel());
factory.Randomize(true);
return "ok";
}
else if (cmd == "refresh")
{
PlayerbotFactory factory(bot, bot->GetLevel());
factory.Refresh();
return "ok";
}
else if (cmd == "random")
{
sRandomPlayerbotMgr->Randomize(bot);
return "ok";
}
else if (cmd == "quests")
{
PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitInstanceQuests();
return "Initialization quests";
}
// }
return "unknown command";
}
// Added for gender choice : Returns the gender of an offline character: 0 = male, 1 = female.
static uint8 GetOfflinePlayerGender(ObjectGuid guid)
{
QueryResult result = CharacterDatabase.Query(
"SELECT gender FROM characters WHERE guid = {}", guid.GetCounter());
if (result)
return (*result)[0].Get<uint8>(); // 0 = male, 1 = female
return GENDER_MALE; // fallback value
}
bool PlayerbotMgr::HandlePlayerbotMgrCommand(ChatHandler* handler, char const* args)
{
if (!sPlayerbotAIConfig->enabled)
{
handler->PSendSysMessage("|cffff0000Playerbot system is currently disabled!");
return false;
}
WorldSession* m_session = handler->GetSession();
if (!m_session)
{
handler->PSendSysMessage("You may only add bots from an active session");
return false;
}
Player* player = m_session->GetPlayer();
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(player);
if (!mgr)
{
handler->PSendSysMessage("You cannot control bots yet");
return false;
}
std::vector<std::string> messages = mgr->HandlePlayerbotCommand(args, player);
if (messages.empty())
return true;
for (std::vector<std::string>::iterator i = messages.begin(); i != messages.end(); ++i)
{
handler->PSendSysMessage("{}", i->c_str());
}
return true;
}
std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* args, Player* master)
{
std::vector<std::string> messages;
if (!*args)
{
messages.push_back("usage: list/reload/tweak/self or add/addaccount/init/remove PLAYERNAME\n");
messages.push_back("usage: addclass CLASSNAME [male|female|0|1]");
return messages;
}
char* cmd = strtok((char*)args, " ");
char* charname = strtok(nullptr, " ");
char* genderArg = strtok(nullptr, " "); // Added for gender choice [male|female|0|1] optionnel
if (!cmd)
{
messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME or addclass CLASSNAME [male|female]");
return messages;
}
if (!strcmp(cmd, "initself"))
{
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
{
// OnBotLogin(master);
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_EPIC);
factory.Randomize(false);
messages.push_back("initself ok");
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
}
if (!strncmp(cmd, "initself=", 9))
{
if (!strcmp(cmd, "initself=uncommon"))
{
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
{
// OnBotLogin(master);
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_UNCOMMON);
factory.Randomize(false);
messages.push_back("initself ok");
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
}
if (!strcmp(cmd, "initself=rare"))
{
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
{
// OnBotLogin(master);
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_RARE);
factory.Randomize(false);
messages.push_back("initself ok");
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
}
if (!strcmp(cmd, "initself=epic"))
{
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
{
// OnBotLogin(master);
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_EPIC);
factory.Randomize(false);
messages.push_back("initself ok");
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
}
if (!strcmp(cmd, "initself=legendary"))
{
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
{
// OnBotLogin(master);
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_LEGENDARY);
factory.Randomize(false);
messages.push_back("initself ok");
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
}
int32 gs;
if (sscanf(cmd, "initself=%d", &gs) != -1)
{
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
{
// OnBotLogin(master);
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_LEGENDARY, gs);
factory.Randomize(false);
messages.push_back("initself ok, gs = " + std::to_string(gs));
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
}
}
if (!strcmp(cmd, "list"))
{
messages.push_back(ListBots(master));
return messages;
}
if (!strcmp(cmd, "reload"))
{
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
{
sPlayerbotAIConfig->Initialize();
messages.push_back("Config reloaded.");
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
}
if (!strcmp(cmd, "tweak"))
{
sPlayerbotAIConfig->tweakValue = sPlayerbotAIConfig->tweakValue++;
if (sPlayerbotAIConfig->tweakValue > 2)
sPlayerbotAIConfig->tweakValue = 0;
messages.push_back("Set tweakvalue to " + std::to_string(sPlayerbotAIConfig->tweakValue));
return messages;
}
if (!strcmp(cmd, "self"))
{
if (GET_PLAYERBOT_AI(master))
{
messages.push_back("Disable player botAI");
delete GET_PLAYERBOT_AI(master);
}
else if (sPlayerbotAIConfig->selfBotLevel == 0)
messages.push_back("Self-bot is disabled");
else if (sPlayerbotAIConfig->selfBotLevel == 1 && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
messages.push_back("You do not have permission to enable player botAI");
else
{
messages.push_back("Enable player botAI");
sPlayerbotsMgr->AddPlayerbotData(master, true);
GET_PLAYERBOT_AI(master)->SetMaster(master);
}
return messages;
}
if (!strcmp(cmd, "lookup"))
{
messages.push_back(LookupBots(master));
return messages;
}
if (!strcmp(cmd, "addclass"))
{
if (sPlayerbotAIConfig->addClassCommand == 0 && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
{
messages.push_back("You do not have permission to create bot by addclass command");
return messages;
}
if (!charname)
{
messages.push_back(
"addclass: invalid CLASSNAME(warrior/paladin/hunter/rogue/priest/shaman/mage/warlock/druid/dk)");
return messages;
}
uint8 claz;
if (!strcmp(charname, "warrior"))
{
claz = 1;
}
else if (!strcmp(charname, "paladin"))
{
claz = 2;
}
else if (!strcmp(charname, "hunter"))
{
claz = 3;
}
else if (!strcmp(charname, "rogue"))
{
claz = 4;
}
else if (!strcmp(charname, "priest"))
{
claz = 5;
}
else if (!strcmp(charname, "shaman"))
{
claz = 7;
}
else if (!strcmp(charname, "mage"))
{
claz = 8;
}
else if (!strcmp(charname, "warlock"))
{
claz = 9;
}
else if (!strcmp(charname, "druid"))
{
claz = 11;
}
else if (!strcmp(charname, "dk"))
{
claz = 6;
}
else
{
messages.push_back("Error: Invalid Class. Try again.");
return messages;
}
// Added for gender choice : Parsing gender
int8 gender = -1; // -1 = gender will be random
if (genderArg)
{
std::string g = genderArg;
std::transform(g.begin(), g.end(), g.begin(), ::tolower);
if (g == "male" || g == "0")
gender = GENDER_MALE; // 0
else if (g == "female" || g == "1")
gender = GENDER_FEMALE; // 1
else
{
messages.push_back("Unknown gender : " + g + " (male/female/0/1)");
return messages;
}
} //end
if (claz == 6 && master->GetLevel() < sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL))
{
messages.push_back("Your level is too low to summon Deathknight");
return messages;
}
uint8 teamId = master->GetTeamId(true);
const std::unordered_set<ObjectGuid> &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)];
for (const ObjectGuid &guid: guidCache)
{
// If the user requested a specific gender, skip any character that doesn't match.
if (gender != -1 && GetOfflinePlayerGender(guid) != gender)
continue;
if (botLoading.find(guid) != botLoading.end())
continue;
if (ObjectAccessor::FindConnectedPlayer(guid))
continue;
uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid);
if (guildId && PlayerbotAI::IsRealGuild(guildId))
continue;
AddPlayerBot(guid, master->GetSession()->GetAccountId());
messages.push_back("Add class " + std::string(charname));
return messages;
}
messages.push_back("Add class failed, no available characters!");
return messages;
}
std::string charnameStr;
if (!charname)
{
std::string name;
bool isPlayer = sCharacterCache->GetCharacterNameByGuid(master->GetTarget(), name);
// Player* tPlayer = ObjectAccessor::FindConnectedPlayer(master->GetTarget());
if (isPlayer)
{
charnameStr = name;
}
else
{
messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME");
return messages;
}
}
else
{
charnameStr = charname;
}
std::string const cmdStr = cmd;
std::unordered_set<std::string> bots;
if (charnameStr == "*" && master)
{
Group* group = master->GetGroup();
if (!group)
{
messages.push_back("you must be in group");
return messages;
}
Group::MemberSlotList slots = group->GetMemberSlots();
for (Group::member_citerator i = slots.begin(); i != slots.end(); i++)
{
ObjectGuid member = i->guid;
if (member == master->GetGUID())
continue;
std::string bot;
if (sCharacterCache->GetCharacterNameByGuid(member, bot))
bots.insert(bot);
}
}
if (charnameStr == "!" && master && master->GetSession()->GetSecurity() > SEC_GAMEMASTER)
{
for (PlayerBotMap::const_iterator i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i)
{
if (Player* bot = i->second)
if (bot->IsInWorld())
bots.insert(bot->GetName());
}
}
std::vector<std::string> chars = split(charnameStr, ',');
for (std::vector<std::string>::iterator i = chars.begin(); i != chars.end(); i++)
{
std::string const s = *i;
if (!strcmp(cmd, "addaccount"))
{
// When using addaccount, first try to get account ID directly
uint32 accountId = GetAccountId(s);
if (!accountId)
{
// If not found, try to get account ID from character name
ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(s);
if (!charGuid)
{
messages.push_back("Neither account nor character '" + s + "' found");
continue;
}
accountId = sCharacterCache->GetCharacterAccountIdByGuid(charGuid);
if (!accountId)
{
messages.push_back("Could not find account for character '" + s + "'");
continue;
}
}
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
if (results)
{
do
{
Field* fields = results->Fetch();
std::string const charName = fields[0].Get<std::string>();
bots.insert(charName);
} while (results->NextRow());
}
}
else
{
// For regular add command, only add the specific character
ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(s);
if (!charGuid)
{
messages.push_back("Character '" + s + "' not found");
continue;
}
bots.insert(s);
}
}
for (auto i = bots.begin(); i != bots.end(); ++i)
{
std::string const bot = *i;
std::ostringstream out;
out << cmdStr << ": " << bot << " - ";
ObjectGuid member = sCharacterCache->GetCharacterGuidByName(bot);
if (!member)
{
out << "character not found";
}
else if (master && member != master->GetGUID())
{
out << ProcessBotCommand(cmdStr, member, master->GetGUID(),
master->GetSession()->GetSecurity() >= SEC_GAMEMASTER,
master->GetSession()->GetAccountId(), master->GetGuildId());
}
else if (!master)
{
out << ProcessBotCommand(cmdStr, member, ObjectGuid::Empty, true, -1, -1);
}
messages.push_back(out.str());
}
return messages;
}
uint32 PlayerbotHolder::GetAccountId(std::string const name) { return AccountMgr::GetId(name); }
uint32 PlayerbotHolder::GetAccountId(ObjectGuid guid)
{
if (!guid.IsPlayer())
return 0;
// prevent DB access for online player
if (Player* player = ObjectAccessor::FindConnectedPlayer(guid))
return player->GetSession()->GetAccountId();
ObjectGuid::LowType lowguid = guid.GetCounter();
if (QueryResult result = CharacterDatabase.Query("SELECT account FROM characters WHERE guid = {}", lowguid))
{
uint32 acc = (*result)[0].Get<uint32>();
return acc;
}
return 0;
}
std::string const PlayerbotHolder::ListBots(Player* master)
{
std::set<std::string> bots;
std::map<uint8, std::string> classNames;
classNames[CLASS_DEATH_KNIGHT] = "Death Knight";
classNames[CLASS_DRUID] = "Druid";
classNames[CLASS_HUNTER] = "Hunter";
classNames[CLASS_MAGE] = "Mage";
classNames[CLASS_PALADIN] = "Paladin";
classNames[CLASS_PRIEST] = "Priest";
classNames[CLASS_ROGUE] = "Rogue";
classNames[CLASS_SHAMAN] = "Shaman";
classNames[CLASS_WARLOCK] = "Warlock";
classNames[CLASS_WARRIOR] = "Warrior";
classNames[CLASS_DEATH_KNIGHT] = "DeathKnight";
std::map<std::string, std::string> online;
std::vector<std::string> names;
std::map<std::string, std::string> classes;
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
std::string const name = bot->GetName();
bots.insert(name);
names.push_back(name);
online[name] = "+";
classes[name] = classNames[bot->getClass()];
}
if (master)
{
QueryResult results = CharacterDatabase.Query("SELECT class, name FROM characters WHERE account = {}",
master->GetSession()->GetAccountId());
if (results)
{
do
{
Field* fields = results->Fetch();
uint8 cls = fields[0].Get<uint8>();
std::string const name = fields[1].Get<std::string>();
if (bots.find(name) == bots.end() && name != master->GetSession()->GetPlayerName())
{
names.push_back(name);
online[name] = "-";
classes[name] = classNames[cls];
}
} while (results->NextRow());
}
}
std::sort(names.begin(), names.end());
if (Group* group = master->GetGroup())
{
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player* member = ObjectAccessor::FindPlayer(itr->guid);
if (member && sRandomPlayerbotMgr->IsRandomBot(member))
{
std::string const name = member->GetName();
names.push_back(name);
online[name] = "+";
classes[name] = classNames[member->getClass()];
}
}
}
std::ostringstream out;
bool first = true;
out << "Bot roster: ";
for (std::vector<std::string>::iterator i = names.begin(); i != names.end(); ++i)
{
if (first)
first = false;
else
out << ", ";
std::string const name = *i;
out << online[name] << name << " " << classes[name];
}
return out.str();
}
std::string const PlayerbotHolder::LookupBots(Player* master)
{
std::list<std::string> messages;
messages.push_back("Classes Available:");
messages.push_back("|TInterface\\icons\\INV_Sword_27.png:25:25:0:-1|t Warrior");
messages.push_back("|TInterface\\icons\\INV_Hammer_01.png:25:25:0:-1|t Paladin");
messages.push_back("|TInterface\\icons\\INV_Weapon_Bow_07.png:25:25:0:-1|t Hunter");
messages.push_back("|TInterface\\icons\\INV_ThrowingKnife_04.png:25:25:0:-1|t Rogue");
messages.push_back("|TInterface\\icons\\INV_Staff_30.png:25:25:0:-1|t Priest");
messages.push_back("|TInterface\\icons\\inv_jewelry_talisman_04.png:25:25:0:-1|t Shaman");
messages.push_back("|TInterface\\icons\\INV_staff_30.png:25:25:0:-1|t Mage");
messages.push_back("|TInterface\\icons\\INV_staff_30.png:25:25:0:-1|t Warlock");
messages.push_back("|TInterface\\icons\\Ability_Druid_Maul.png:25:25:0:-1|t Druid");
messages.push_back("DK");
messages.push_back("(Usage: .bot lookup CLASS)");
std::string ret_msg;
for (std::string msg : messages)
{
ret_msg += msg + "\n";
}
return ret_msg;
}
uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls)
{
uint32 count = 0;
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
if (bot && bot->IsInWorld() && bot->getClass() == cls)
{
count++;
}
}
return count;
}
PlayerbotMgr::PlayerbotMgr(Player* const master) : PlayerbotHolder(), master(master), lastErrorTell(0) {}
PlayerbotMgr::~PlayerbotMgr()
{
if (master)
sPlayerbotsMgr->RemovePlayerBotData(master->GetGUID(), false);
}
void PlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
{
SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
CheckTellErrors(elapsed);
}
void PlayerbotMgr::HandleCommand(uint32 type, std::string const text)
{
Player* master = GetMaster();
if (!master)
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);
}
return;
}
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI)
botAI->HandleCommand(type, text, master);
}
for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin();
it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI && botAI->GetMaster() == master)
botAI->HandleCommand(type, text, master);
}
}
void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet)
{
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
if (!bot)
continue;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI)
botAI->HandleMasterIncomingPacket(packet);
}
for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin();
it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI && botAI->GetMaster() == GetMaster())
botAI->HandleMasterIncomingPacket(packet);
}
switch (packet.GetOpcode())
{
// if master is logging out, log out all bots
case CMSG_LOGOUT_REQUEST:
{
LogoutAllBots();
break;
}
// if master cancelled logout, cancel too
case CMSG_LOGOUT_CANCEL:
{
CancelLogout();
break;
}
}
}
void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet)
{
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI)
botAI->HandleMasterOutgoingPacket(packet);
}
for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin();
it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI && botAI->GetMaster() == GetMaster())
botAI->HandleMasterOutgoingPacket(packet);
}
}
void PlayerbotMgr::SaveToDB()
{
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
bot->SaveToDB(false, false);
}
for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin();
it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
if (GET_PLAYERBOT_AI(bot) && GET_PLAYERBOT_AI(bot)->GetMaster() == GetMaster())
bot->SaveToDB(false, false);
}
}
void PlayerbotMgr::OnBotLoginInternal(Player* const bot)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return;
}
botAI->SetMaster(master);
botAI->ResetStrategies();
LOG_INFO("playerbots", "Bot {} logged in", bot->GetName().c_str());
}
void PlayerbotMgr::OnPlayerLogin(Player* player)
{
// set locale priority for bot texts
sPlayerbotTextMgr->AddLocalePriority(player->GetSession()->GetSessionDbcLocale());
if (sPlayerbotAIConfig->selfBotLevel > 2)
HandlePlayerbotCommand("self", player);
if (!sPlayerbotAIConfig->botAutologin)
return;
uint32 accountId = player->GetSession()->GetAccountId();
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
if (results)
{
std::ostringstream out;
out << "add ";
bool first = true;
do
{
Field* fields = results->Fetch();
if (first)
first = false;
else
out << ",";
out << fields[0].Get<std::string>();
} while (results->NextRow());
HandlePlayerbotCommand(out.str().c_str(), player);
}
}
void PlayerbotMgr::TellError(std::string const botName, std::string const text)
{
std::set<std::string> names = errors[text];
if (names.find(botName) == names.end())
{
names.insert(botName);
}
errors[text] = names;
}
void PlayerbotMgr::CheckTellErrors(uint32 elapsed)
{
time_t now = time(nullptr);
if ((now - lastErrorTell) < sPlayerbotAIConfig->errorDelay / 1000)
return;
lastErrorTell = now;
for (PlayerBotErrorMap::iterator i = errors.begin(); i != errors.end(); ++i)
{
std::string const text = i->first;
std::set<std::string> names = i->second;
std::ostringstream out;
bool first = true;
for (std::set<std::string>::iterator j = names.begin(); j != names.end(); ++j)
{
if (!first)
out << ", ";
else
first = false;
out << *j;
}
out << "|cfff00000: " << text;
ChatHandler(master->GetSession()).PSendSysMessage(out.str().c_str());
}
errors.clear();
}
void PlayerbotsMgr::AddPlayerbotData(Player* player, bool isBotAI)
{
if (!player)
{
return;
}
// If the guid already exists in the map, remove it
if (!isBotAI)
{
std::unordered_map<ObjectGuid, PlayerbotAIBase*>::iterator itr = _playerbotsMgrMap.find(player->GetGUID());
if (itr != _playerbotsMgrMap.end())
{
_playerbotsMgrMap.erase(itr);
}
PlayerbotMgr* playerbotMgr = new PlayerbotMgr(player);
ASSERT(_playerbotsMgrMap.emplace(player->GetGUID(), playerbotMgr).second);
playerbotMgr->OnPlayerLogin(player);
}
else
{
std::unordered_map<ObjectGuid, PlayerbotAIBase*>::iterator itr = _playerbotsAIMap.find(player->GetGUID());
if (itr != _playerbotsAIMap.end())
{
_playerbotsAIMap.erase(itr);
}
PlayerbotAI* botAI = new PlayerbotAI(player);
ASSERT(_playerbotsAIMap.emplace(player->GetGUID(), botAI).second);
}
}
void PlayerbotsMgr::RemovePlayerBotData(ObjectGuid const& guid, bool is_AI)
{
if (is_AI)
{
std::unordered_map<ObjectGuid, PlayerbotAIBase*>::iterator itr = _playerbotsAIMap.find(guid);
if (itr != _playerbotsAIMap.end())
{
_playerbotsAIMap.erase(itr);
}
}
else
{
std::unordered_map<ObjectGuid, PlayerbotAIBase*>::iterator itr = _playerbotsMgrMap.find(guid);
if (itr != _playerbotsMgrMap.end())
{
_playerbotsMgrMap.erase(itr);
}
}
}
PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player)
{
if (!(sPlayerbotAIConfig->enabled) || !player)
{
return nullptr;
}
// if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld())
// {
// return nullptr;
// }
auto itr = _playerbotsAIMap.find(player->GetGUID());
if (itr != _playerbotsAIMap.end())
{
if (itr->second->IsBotAI())
return reinterpret_cast<PlayerbotAI*>(itr->second);
}
return nullptr;
}
PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
{
if (!(sPlayerbotAIConfig->enabled) || !player)
{
return nullptr;
}
auto itr = _playerbotsMgrMap.find(player->GetGUID());
if (itr != _playerbotsMgrMap.end())
{
if (!itr->second->IsBotAI())
return reinterpret_cast<PlayerbotMgr*>(itr->second);
}
return nullptr;
}
void PlayerbotMgr::HandleSetSecurityKeyCommand(Player* player, const std::string& key)
{
uint32 accountId = player->GetSession()->GetAccountId();
// Hash the security key using SHA-256
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)key.c_str(), key.size(), hash);
// Convert the hash to a hexadecimal string
std::ostringstream hashedKey;
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i)
hashedKey << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
// Store the hashed key in the database
PlayerbotsDatabase.Execute(
"REPLACE INTO playerbots_account_keys (account_id, security_key) VALUES ({}, '{}')",
accountId, hashedKey.str());
ChatHandler(player->GetSession()).PSendSysMessage("Security key set successfully.");
}
void PlayerbotMgr::HandleLinkAccountCommand(Player* player, const std::string& accountName, const std::string& key)
{
QueryResult result = LoginDatabase.Query("SELECT id FROM account WHERE username = '{}'", accountName);
if (!result)
{
ChatHandler(player->GetSession()).PSendSysMessage("Account not found.");
return;
}
Field* fields = result->Fetch();
uint32 linkedAccountId = fields[0].Get<uint32>();
result = PlayerbotsDatabase.Query("SELECT security_key FROM playerbots_account_keys WHERE account_id = {}", linkedAccountId);
if (!result)
{
ChatHandler(player->GetSession()).PSendSysMessage("Invalid security key.");
return;
}
// Hash the provided key
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)key.c_str(), key.size(), hash);
// Convert the hash to a hexadecimal string
std::ostringstream hashedKey;
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i)
hashedKey << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
// Compare the hashed key with the stored hashed key
std::string storedKey = result->Fetch()->Get<std::string>();
if (hashedKey.str() != storedKey)
{
ChatHandler(player->GetSession()).PSendSysMessage("Invalid security key.");
return;
}
uint32 accountId = player->GetSession()->GetAccountId();
PlayerbotsDatabase.Execute(
"INSERT IGNORE INTO playerbots_account_links (account_id, linked_account_id) VALUES ({}, {})",
accountId, linkedAccountId);
PlayerbotsDatabase.Execute(
"INSERT IGNORE INTO playerbots_account_links (account_id, linked_account_id) VALUES ({}, {})",
linkedAccountId, accountId);
ChatHandler(player->GetSession()).PSendSysMessage("Account linked successfully.");
}
void PlayerbotMgr::HandleViewLinkedAccountsCommand(Player* player)
{
uint32 accountId = player->GetSession()->GetAccountId();
QueryResult result = PlayerbotsDatabase.Query("SELECT linked_account_id FROM playerbots_account_links WHERE account_id = {}", accountId);
if (!result)
{
ChatHandler(player->GetSession()).PSendSysMessage("No linked accounts.");
return;
}
ChatHandler(player->GetSession()).PSendSysMessage("Linked accounts:");
do
{
Field* fields = result->Fetch();
uint32 linkedAccountId = fields[0].Get<uint32>();
QueryResult accountResult = LoginDatabase.Query("SELECT username FROM account WHERE id = {}", linkedAccountId);
if (accountResult)
{
Field* accountFields = accountResult->Fetch();
std::string username = accountFields[0].Get<std::string>();
ChatHandler(player->GetSession()).PSendSysMessage("- {}", username.c_str());
}
else
{
ChatHandler(player->GetSession()).PSendSysMessage("- Unknown account");
}
} while (result->NextRow());
}
void PlayerbotMgr::HandleUnlinkAccountCommand(Player* player, const std::string& accountName)
{
QueryResult result = LoginDatabase.Query("SELECT id FROM account WHERE username = '{}'", accountName);
if (!result)
{
ChatHandler(player->GetSession()).PSendSysMessage("Account not found.");
return;
}
Field* fields = result->Fetch();
uint32 linkedAccountId = fields[0].Get<uint32>();
uint32 accountId = player->GetSession()->GetAccountId();
PlayerbotsDatabase.Execute("DELETE FROM playerbots_account_links WHERE (account_id = {} AND linked_account_id = {}) OR (account_id = {} AND linked_account_id = {})",
accountId, linkedAccountId, linkedAccountId, accountId);
ChatHandler(player->GetSession()).PSendSysMessage("Account unlinked successfully.");
}