mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-02-03 19:03:49 +00:00
[HOT FIX] MS build issues regarding folder / command lenght usage or rc.exe (#2038)
This commit is contained in:
212
src/Script/PlayerbotCommandScript.cpp
Normal file
212
src/Script/PlayerbotCommandScript.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BattleGroundTactics.h"
|
||||
#include "Chat.h"
|
||||
#include "GuildTaskMgr.h"
|
||||
#include "PerfMonitor.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
|
||||
using namespace Acore::ChatCommands;
|
||||
|
||||
class playerbots_commandscript : public CommandScript
|
||||
{
|
||||
public:
|
||||
playerbots_commandscript() : CommandScript("playerbots_commandscript") {}
|
||||
|
||||
ChatCommandTable GetCommands() const override
|
||||
{
|
||||
static ChatCommandTable playerbotsDebugCommandTable = {
|
||||
{"bg", HandleDebugBGCommand, SEC_GAMEMASTER, Console::Yes},
|
||||
};
|
||||
|
||||
static ChatCommandTable playerbotsAccountCommandTable = {
|
||||
{"setKey", HandleSetSecurityKeyCommand, SEC_PLAYER, Console::No},
|
||||
{"link", HandleLinkAccountCommand, SEC_PLAYER, Console::No},
|
||||
{"linkedAccounts", HandleViewLinkedAccountsCommand, SEC_PLAYER, Console::No},
|
||||
{"unlink", HandleUnlinkAccountCommand, SEC_PLAYER, Console::No},
|
||||
};
|
||||
|
||||
static ChatCommandTable playerbotsCommandTable = {
|
||||
{"bot", HandlePlayerbotCommand, SEC_PLAYER, Console::No},
|
||||
{"gtask", HandleGuildTaskCommand, SEC_GAMEMASTER, Console::Yes},
|
||||
{"pmon", HandlePerfMonCommand, SEC_GAMEMASTER, Console::Yes},
|
||||
{"rndbot", HandleRandomPlayerbotCommand, SEC_GAMEMASTER, Console::Yes},
|
||||
{"debug", playerbotsDebugCommandTable},
|
||||
{"account", playerbotsAccountCommandTable},
|
||||
};
|
||||
|
||||
static ChatCommandTable commandTable = {
|
||||
{"playerbots", playerbotsCommandTable},
|
||||
};
|
||||
|
||||
return commandTable;
|
||||
}
|
||||
|
||||
static bool HandlePlayerbotCommand(ChatHandler* handler, char const* args)
|
||||
{
|
||||
return PlayerbotMgr::HandlePlayerbotMgrCommand(handler, args);
|
||||
}
|
||||
|
||||
static bool HandleRandomPlayerbotCommand(ChatHandler* handler, char const* args)
|
||||
{
|
||||
return RandomPlayerbotMgr::HandlePlayerbotConsoleCommand(handler, args);
|
||||
}
|
||||
|
||||
static bool HandleGuildTaskCommand(ChatHandler* handler, char const* args)
|
||||
{
|
||||
return GuildTaskMgr::HandleConsoleCommand(handler, args);
|
||||
}
|
||||
|
||||
static bool HandlePerfMonCommand(ChatHandler* handler, char const* args)
|
||||
{
|
||||
if (!strcmp(args, "reset"))
|
||||
{
|
||||
sPerfMonitor->Reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(args, "tick"))
|
||||
{
|
||||
sPerfMonitor->PrintStats(true, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(args, "stack"))
|
||||
{
|
||||
sPerfMonitor->PrintStats(false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(args, "toggle"))
|
||||
{
|
||||
sPlayerbotAIConfig->perfMonEnabled = !sPlayerbotAIConfig->perfMonEnabled;
|
||||
if (sPlayerbotAIConfig->perfMonEnabled)
|
||||
LOG_INFO("playerbots", "Performance monitor enabled");
|
||||
else
|
||||
LOG_INFO("playerbots", "Performance monitor disabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
sPerfMonitor->PrintStats();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleDebugBGCommand(ChatHandler* handler, char const* args)
|
||||
{
|
||||
return BGTactics::HandleConsoleCommand(handler, args);
|
||||
}
|
||||
|
||||
static bool HandleSetSecurityKeyCommand(ChatHandler* handler, char const* args)
|
||||
{
|
||||
if (!args || !*args)
|
||||
{
|
||||
handler->PSendSysMessage("Usage: .playerbots account setKey <securityKey>");
|
||||
return false;
|
||||
}
|
||||
|
||||
Player* player = handler->GetSession()->GetPlayer();
|
||||
std::string key = args;
|
||||
|
||||
PlayerbotMgr* mgr = sPlayerbotsMgr->GetPlayerbotMgr(player);
|
||||
if (mgr)
|
||||
{
|
||||
mgr->HandleSetSecurityKeyCommand(player, key);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
handler->PSendSysMessage("PlayerbotMgr instance not found.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool HandleLinkAccountCommand(ChatHandler* handler, char const* args)
|
||||
{
|
||||
if (!args || !*args)
|
||||
return false;
|
||||
|
||||
char* accountName = strtok((char*)args, " ");
|
||||
char* key = strtok(nullptr, " ");
|
||||
|
||||
if (!accountName || !key)
|
||||
{
|
||||
handler->PSendSysMessage("Usage: .playerbots account link <accountName> <securityKey>");
|
||||
return false;
|
||||
}
|
||||
|
||||
Player* player = handler->GetSession()->GetPlayer();
|
||||
|
||||
PlayerbotMgr* mgr = sPlayerbotsMgr->GetPlayerbotMgr(player);
|
||||
if (mgr)
|
||||
{
|
||||
mgr->HandleLinkAccountCommand(player, accountName, key);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
handler->PSendSysMessage("PlayerbotMgr instance not found.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool HandleViewLinkedAccountsCommand(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Player* player = handler->GetSession()->GetPlayer();
|
||||
|
||||
PlayerbotMgr* mgr = sPlayerbotsMgr->GetPlayerbotMgr(player);
|
||||
if (mgr)
|
||||
{
|
||||
mgr->HandleViewLinkedAccountsCommand(player);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
handler->PSendSysMessage("PlayerbotMgr instance not found.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool HandleUnlinkAccountCommand(ChatHandler* handler, char const* args)
|
||||
{
|
||||
if (!args || !*args)
|
||||
return false;
|
||||
|
||||
char* accountName = strtok((char*)args, " ");
|
||||
if (!accountName)
|
||||
{
|
||||
handler->PSendSysMessage("Usage: .playerbots account unlink <accountName>");
|
||||
return false;
|
||||
}
|
||||
|
||||
Player* player = handler->GetSession()->GetPlayer();
|
||||
|
||||
PlayerbotMgr* mgr = sPlayerbotsMgr->GetPlayerbotMgr(player);
|
||||
if (mgr)
|
||||
{
|
||||
mgr->HandleUnlinkAccountCommand(player, accountName);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
handler->PSendSysMessage("PlayerbotMgr instance not found.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void AddPlayerbotsCommandscripts() { new playerbots_commandscript(); }
|
||||
1
src/Script/PlayerbotCommandScript.h
Normal file
1
src/Script/PlayerbotCommandScript.h
Normal file
@@ -0,0 +1 @@
|
||||
void AddPlayerbotsCommandscripts();
|
||||
520
src/Script/Playerbots.cpp
Normal file
520
src/Script/Playerbots.cpp
Normal file
@@ -0,0 +1,520 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by the
|
||||
* Free Software Foundation; either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
#include "Channel.h"
|
||||
#include "Config.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DatabaseLoader.h"
|
||||
#include "GuildTaskMgr.h"
|
||||
#include "Metric.h"
|
||||
#include "PlayerScript.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "PlayerbotSpellRepository.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "PlayerbotCommandScript.h"
|
||||
#include "cmath"
|
||||
#include "BattleGroundTactics.h"
|
||||
|
||||
class PlayerbotsDatabaseScript : public DatabaseScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsDatabaseScript() : DatabaseScript("PlayerbotsDatabaseScript") {}
|
||||
|
||||
bool OnDatabasesLoading() override
|
||||
{
|
||||
DatabaseLoader playerbotLoader("server.playerbots");
|
||||
playerbotLoader.SetUpdateFlags(sConfigMgr->GetOption<bool>("Playerbots.Updates.EnableDatabases", true)
|
||||
? DatabaseLoader::DATABASE_PLAYERBOTS
|
||||
: 0);
|
||||
playerbotLoader.AddDatabase(PlayerbotsDatabase, "Playerbots");
|
||||
|
||||
return playerbotLoader.Load();
|
||||
}
|
||||
|
||||
void OnDatabasesKeepAlive() override { PlayerbotsDatabase.KeepAlive(); }
|
||||
|
||||
void OnDatabasesClosing() override { PlayerbotsDatabase.Close(); }
|
||||
|
||||
void OnDatabaseWarnAboutSyncQueries(bool apply) override { PlayerbotsDatabase.WarnAboutSyncQueries(apply); }
|
||||
|
||||
void OnDatabaseSelectIndexLogout(Player* player, uint32& statementIndex, uint32& statementParam) override
|
||||
{
|
||||
statementIndex = CHAR_UPD_CHAR_OFFLINE;
|
||||
statementParam = player->GetGUID().GetCounter();
|
||||
}
|
||||
|
||||
void OnDatabaseGetDBRevision(std::string& revision) override
|
||||
{
|
||||
if (QueryResult resultPlayerbot =
|
||||
PlayerbotsDatabase.Query("SELECT date FROM version_db_playerbots ORDER BY date DESC LIMIT 1"))
|
||||
{
|
||||
Field* fields = resultPlayerbot->Fetch();
|
||||
revision = fields[0].Get<std::string>();
|
||||
}
|
||||
|
||||
if (revision.empty())
|
||||
{
|
||||
revision = "Unknown Playerbots Database Revision";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class PlayerbotsPlayerScript : public PlayerScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", {
|
||||
PLAYERHOOK_ON_LOGIN,
|
||||
PLAYERHOOK_ON_AFTER_UPDATE,
|
||||
PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS,
|
||||
PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_GROUP_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_GUILD_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_CHANNEL_CHAT,
|
||||
PLAYERHOOK_ON_GIVE_EXP,
|
||||
PLAYERHOOK_ON_BEFORE_TELEPORT
|
||||
}) {}
|
||||
|
||||
void OnPlayerLogin(Player* player) override
|
||||
{
|
||||
if (!player->GetSession()->IsBot())
|
||||
{
|
||||
sPlayerbotsMgr->AddPlayerbotData(player, false);
|
||||
sRandomPlayerbotMgr->OnPlayerLogin(player);
|
||||
|
||||
// Before modifying the following messages, please make sure it does not violate the AGPLv3.0 license
|
||||
// especially if you are distributing a repack or hosting a public server
|
||||
// e.g. you can replace the URL with your own repository,
|
||||
// but it should be publicly accessible and include all modifications you've made
|
||||
if (sPlayerbotAIConfig->enabled)
|
||||
{
|
||||
ChatHandler(player->GetSession()).SendSysMessage(
|
||||
"|cff00ff00This server runs with |cff00ccffmod-playerbots|r "
|
||||
"|cffcccccchttps://github.com/mod-playerbots/mod-playerbots|r");
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin)
|
||||
{
|
||||
std::string roundedTime =
|
||||
std::to_string(std::ceil((sPlayerbotAIConfig->maxRandomBots * 0.11 / 60) * 10) / 10.0);
|
||||
roundedTime = roundedTime.substr(0, roundedTime.find('.') + 2);
|
||||
|
||||
ChatHandler(player->GetSession()).SendSysMessage(
|
||||
"|cff00ff00Playerbots:|r bot initialization at server startup takes about '"
|
||||
+ roundedTime + "' minutes.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeTeleport(Player* /*player*/, uint32 /*mapid*/, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
|
||||
{
|
||||
/* for now commmented out until proven its actually required
|
||||
* havent seen any proof CleanVisibilityReferences() is needed
|
||||
|
||||
// If the player is not safe to touch, do nothing
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
// If same map or not in world do nothing
|
||||
if (!player->IsInWorld() || player->GetMapId() == mapid)
|
||||
return true;
|
||||
|
||||
// If real player do nothing
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(player);
|
||||
if (!ai || ai->IsRealPlayer())
|
||||
return true;
|
||||
|
||||
// Cross-map bot teleport: defer visibility reference cleanup.
|
||||
// CleanVisibilityReferences() erases this bot's GUID from other objects' visibility containers.
|
||||
// This is intentionally done via the event queue (instead of directly here) because erasing
|
||||
// from other players' visibility maps inside the teleport call stack can hit unsafe re-entrancy
|
||||
// or iterator invalidation while visibility updates are in progress
|
||||
ObjectGuid guid = player->GetGUID();
|
||||
player->m_Events.AddEventAtOffset(
|
||||
[guid, mapid]()
|
||||
{
|
||||
// do nothing, if the player is not safe to touch
|
||||
Player* p = ObjectAccessor::FindPlayer(guid);
|
||||
if (!p || !p->IsInWorld() || p->IsDuringRemoveFromWorld())
|
||||
return;
|
||||
|
||||
// do nothing if we are already on the target map
|
||||
if (p->GetMapId() == mapid)
|
||||
return;
|
||||
|
||||
p->GetObjectVisibilityContainer().CleanVisibilityReferences();
|
||||
},
|
||||
Milliseconds(0));
|
||||
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerAfterUpdate(Player* player, uint32 diff) override
|
||||
{
|
||||
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player))
|
||||
{
|
||||
botAI->UpdateAI(diff);
|
||||
}
|
||||
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
{
|
||||
playerbotMgr->UpdateAI(diff);
|
||||
}
|
||||
}
|
||||
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Player* receiver) override
|
||||
{
|
||||
if (type == CHAT_MSG_WHISPER)
|
||||
{
|
||||
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(receiver))
|
||||
{
|
||||
botAI->HandleCommand(type, msg, player);
|
||||
|
||||
// hotfix; otherwise the server will crash when whispering logout
|
||||
// https://github.com/mod-playerbots/mod-playerbots/pull/1838
|
||||
// TODO: find the root cause and solve it. (does not happen in party chat)
|
||||
if (msg == "logout")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
{
|
||||
if (Player* member = itr->GetSource())
|
||||
{
|
||||
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(member))
|
||||
{
|
||||
botAI->HandleCommand(type, msg, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Guild* guild) override
|
||||
{
|
||||
if (type == CHAT_MSG_GUILD)
|
||||
{
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
{
|
||||
for (PlayerBotMap::const_iterator it = playerbotMgr->GetPlayerBotsBegin();
|
||||
it != playerbotMgr->GetPlayerBotsEnd(); ++it)
|
||||
{
|
||||
if (Player* const bot = it->second)
|
||||
{
|
||||
if (bot->GetGuildId() == player->GetGuildId())
|
||||
{
|
||||
GET_PLAYERBOT_AI(bot)->HandleCommand(type, msg, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
|
||||
{
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
{
|
||||
if (channel->GetFlags() & 0x18)
|
||||
{
|
||||
playerbotMgr->HandleCommand(type, msg);
|
||||
}
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->HandleCommand(type, msg, player);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
|
||||
{
|
||||
if ((sRandomPlayerbotMgr->IsRandomBot(player) || sRandomPlayerbotMgr->IsAddclassBot(player)) &&
|
||||
(achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerGiveXP(Player* player, uint32& amount, Unit* /*victim*/, uint8 /*xpSource*/) override
|
||||
{
|
||||
// early return
|
||||
if (sPlayerbotAIConfig->randomBotXPRate == 1.0 || !player)
|
||||
return;
|
||||
|
||||
// no XP multiplier, when player is no bot.
|
||||
if (!player->GetSession()->IsBot() || !sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
return;
|
||||
|
||||
// no XP multiplier, when bot is in a group with a real player.
|
||||
if (Group* group = player->GetGroup())
|
||||
{
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!member->GetSession()->IsBot())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise apply bot XP multiplier.
|
||||
amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->randomBotXPRate));
|
||||
}
|
||||
};
|
||||
|
||||
class PlayerbotsMiscScript : public MiscScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsMiscScript() : MiscScript("PlayerbotsMiscScript", {MISCHOOK_ON_DESTRUCT_PLAYER}) {}
|
||||
|
||||
void OnDestructPlayer(Player* player) override
|
||||
{
|
||||
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player))
|
||||
{
|
||||
delete botAI;
|
||||
}
|
||||
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
{
|
||||
delete playerbotMgr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class PlayerbotsServerScript : public ServerScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsServerScript() : ServerScript("PlayerbotsServerScript", {
|
||||
SERVERHOOK_CAN_PACKET_RECEIVE
|
||||
}) {}
|
||||
|
||||
void OnPacketReceived(WorldSession* session, WorldPacket const& packet) override
|
||||
{
|
||||
if (Player* player = session->GetPlayer())
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
playerbotMgr->HandleMasterIncomingPacket(packet);
|
||||
}
|
||||
};
|
||||
|
||||
class PlayerbotsWorldScript : public WorldScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsWorldScript() : WorldScript("PlayerbotsWorldScript", {
|
||||
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED,
|
||||
WORLDHOOK_ON_UPDATE
|
||||
}) {}
|
||||
|
||||
void OnBeforeWorldInitialized() override
|
||||
{
|
||||
// Before modifying the following messages, please make sure it does not violate the AGPLv3.0 license
|
||||
// especially if you are distributing a repack or hosting a public server
|
||||
// e.g. you can replace the URL with your own repository,
|
||||
// but it should be publicly accessible and include all modifications you've made
|
||||
LOG_INFO("server.loading", "╔══════════════════════════════════════════════════════════╗");
|
||||
LOG_INFO("server.loading", "║ ║");
|
||||
LOG_INFO("server.loading", "║ AzerothCore Playerbots Module ║");
|
||||
LOG_INFO("server.loading", "║ ║");
|
||||
LOG_INFO("server.loading", "╟──────────────────────────────────────────────────────────╢");
|
||||
LOG_INFO("server.loading", "║ mod-playerbots is a community-driven open-source ║");
|
||||
LOG_INFO("server.loading", "║ project based on AzerothCore, licensed under AGPLv3.0 ║");
|
||||
LOG_INFO("server.loading", "╟──────────────────────────────────────────────────────────╢");
|
||||
LOG_INFO("server.loading", "║ https://github.com/mod-playerbots/mod-playerbots ║");
|
||||
LOG_INFO("server.loading", "╚══════════════════════════════════════════════════════════╝");
|
||||
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
LOG_INFO("server.loading", " ");
|
||||
LOG_INFO("server.loading", "Load Playerbots Config...");
|
||||
|
||||
sPlayerbotAIConfig->Initialize();
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
|
||||
LOG_INFO("server.loading", " ");
|
||||
|
||||
sPlayerbotSpellRepository->Initialize();
|
||||
|
||||
LOG_INFO("server.loading", "Playerbots World Thread Processor initialized");
|
||||
}
|
||||
|
||||
void OnUpdate(uint32 diff) override
|
||||
{
|
||||
sPlayerbotWorldProcessor->Update(diff);
|
||||
sRandomPlayerbotMgr->UpdateAI(diff); // World thread only
|
||||
}
|
||||
};
|
||||
|
||||
class PlayerbotsScript : public PlayerbotScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsScript() : PlayerbotScript("PlayerbotsScript") {}
|
||||
|
||||
bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList) override
|
||||
{
|
||||
bool nonBotFound = false;
|
||||
for (ObjectGuid const& guid : guidsList.guids)
|
||||
{
|
||||
Player* player = ObjectAccessor::FindPlayer(guid);
|
||||
if (guid.IsGroup() || (player && !GET_PLAYERBOT_AI(player)))
|
||||
{
|
||||
nonBotFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nonBotFound;
|
||||
}
|
||||
|
||||
void OnPlayerbotCheckKillTask(Player* player, Unit* victim) override
|
||||
{
|
||||
if (player)
|
||||
sGuildTaskMgr->CheckKillTask(player, victim);
|
||||
}
|
||||
|
||||
void OnPlayerbotCheckPetitionAccount(Player* player, bool& found) override
|
||||
{
|
||||
if (found && GET_PLAYERBOT_AI(player))
|
||||
found = false;
|
||||
}
|
||||
|
||||
bool OnPlayerbotCheckUpdatesToSend(Player* player) override
|
||||
{
|
||||
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player))
|
||||
return botAI->IsRealPlayer();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerbotPacketSent(Player* player, WorldPacket const* packet) override
|
||||
{
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player))
|
||||
{
|
||||
botAI->HandleBotOutgoingPacket(*packet);
|
||||
}
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
{
|
||||
playerbotMgr->HandleMasterOutgoingPacket(*packet);
|
||||
}
|
||||
}
|
||||
|
||||
void OnPlayerbotUpdate(uint32 diff) override
|
||||
{
|
||||
sRandomPlayerbotMgr->UpdateSessions(); // Per-bot updates only
|
||||
}
|
||||
|
||||
void OnPlayerbotUpdateSessions(Player* player) override
|
||||
{
|
||||
if (player)
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
playerbotMgr->UpdateSessions();
|
||||
}
|
||||
|
||||
void OnPlayerbotLogout(Player* player) override
|
||||
{
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
{
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
|
||||
if (!botAI || botAI->IsRealPlayer())
|
||||
{
|
||||
playerbotMgr->LogoutAllBots();
|
||||
}
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->OnPlayerLogout(player);
|
||||
}
|
||||
|
||||
void OnPlayerbotLogoutBots() override
|
||||
{
|
||||
LOG_INFO("playerbots", "Logging out all bots...");
|
||||
sRandomPlayerbotMgr->LogoutAllBots();
|
||||
}
|
||||
};
|
||||
|
||||
class PlayerBotsBGScript : public BGScript
|
||||
{
|
||||
public:
|
||||
PlayerBotsBGScript() : BGScript("PlayerBotsBGScript") {}
|
||||
|
||||
void OnBattlegroundStart(Battleground* bg) override
|
||||
{
|
||||
BGStrategyData data;
|
||||
|
||||
switch (bg->GetBgTypeID())
|
||||
{
|
||||
case BATTLEGROUND_WS:
|
||||
data.allianceStrategy = urand(0, WS_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, WS_STRATEGY_MAX - 1);
|
||||
break;
|
||||
case BATTLEGROUND_AB:
|
||||
data.allianceStrategy = urand(0, AB_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, AB_STRATEGY_MAX - 1);
|
||||
break;
|
||||
case BATTLEGROUND_AV:
|
||||
data.allianceStrategy = urand(0, AV_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, AV_STRATEGY_MAX - 1);
|
||||
break;
|
||||
case BATTLEGROUND_EY:
|
||||
data.allianceStrategy = urand(0, EY_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, EY_STRATEGY_MAX - 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
bgStrategies[bg->GetInstanceID()] = data;
|
||||
}
|
||||
|
||||
void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); }
|
||||
};
|
||||
|
||||
void AddPlayerbotsSecureLoginScripts();
|
||||
|
||||
void AddPlayerbotsScripts()
|
||||
{
|
||||
new PlayerbotsDatabaseScript();
|
||||
new PlayerbotsPlayerScript();
|
||||
new PlayerbotsMiscScript();
|
||||
new PlayerbotsServerScript();
|
||||
new PlayerbotsWorldScript();
|
||||
new PlayerbotsScript();
|
||||
new PlayerBotsBGScript();
|
||||
AddPlayerbotsSecureLoginScripts();
|
||||
AddPlayerbotsCommandscripts();
|
||||
PlayerBotsGuildValidationScript();
|
||||
}
|
||||
52
src/Script/Playerbots.h
Normal file
52
src/Script/Playerbots.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_H
|
||||
#define _PLAYERBOT_H
|
||||
|
||||
#include "AiObjectContext.h"
|
||||
#include "Group.h"
|
||||
#include "Pet.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "SharedValueContext.h"
|
||||
#include "Spell.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "TravelNode.h"
|
||||
|
||||
std::vector<std::string> split(std::string const s, char delim);
|
||||
void split(std::vector<std::string>& dest, std::string const str, char const* delim);
|
||||
#ifndef WIN32
|
||||
int strcmpi(char const* s1, char const* s2);
|
||||
#endif
|
||||
|
||||
#define CAST_ANGLE_IN_FRONT (2.f * static_cast<float>(M_PI) / 3.f)
|
||||
#define EMOTE_ANGLE_IN_FRONT (2.f * static_cast<float>(M_PI) / 6.f)
|
||||
|
||||
#define GET_PLAYERBOT_AI(object) sPlayerbotsMgr->GetPlayerbotAI(object)
|
||||
#define GET_PLAYERBOT_MGR(object) sPlayerbotsMgr->GetPlayerbotMgr(object)
|
||||
|
||||
#define AI_VALUE(type, name) context->GetValue<type>(name)->Get()
|
||||
#define AI_VALUE2(type, name, param) context->GetValue<type>(name, param)->Get()
|
||||
|
||||
#define AI_VALUE_LAZY(type, name) context->GetValue<type>(name)->LazyGet()
|
||||
#define AI_VALUE2_LAZY(type, name, param) context->GetValue<type>(name, param)->LazyGet()
|
||||
|
||||
#define AI_VALUE_REF(type, name) context->GetValue<type>(name)->RefGet()
|
||||
|
||||
#define SET_AI_VALUE(type, name, value) context->GetValue<type>(name)->Set(value)
|
||||
#define SET_AI_VALUE2(type, name, param, value) context->GetValue<type>(name, param)->Set(value)
|
||||
#define RESET_AI_VALUE(type, name) context->GetValue<type>(name)->Reset()
|
||||
#define RESET_AI_VALUE2(type, name, param) context->GetValue<type>(name, param)->Reset()
|
||||
|
||||
#define PAI_VALUE(type, name) sPlayerbotsMgr->GetPlayerbotAI(player)->GetAiObjectContext()->GetValue<type>(name)->Get()
|
||||
#define PAI_VALUE2(type, name, param) \
|
||||
sPlayerbotsMgr->GetPlayerbotAI(player)->GetAiObjectContext()->GetValue<type>(name, param)->Get()
|
||||
#define GAI_VALUE(type, name) sSharedValueContext->getGlobalValue<type>(name)->Get()
|
||||
#define GAI_VALUE2(type, name, param) sSharedValueContext->getGlobalValue<type>(name, param)->Get()
|
||||
|
||||
#endif
|
||||
82
src/Script/PlayerbotsSecureLogin.cpp
Normal file
82
src/Script/PlayerbotsSecureLogin.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "ScriptMgr.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectAccessor.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
static Player* FindOnlineAltbotByGuid(ObjectGuid guid)
|
||||
{
|
||||
if (!guid)
|
||||
return nullptr;
|
||||
|
||||
Player* p = ObjectAccessor::FindPlayer(guid);
|
||||
if (!p)
|
||||
return nullptr;
|
||||
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(p);
|
||||
if (!ai || ai->IsRealPlayer())
|
||||
return nullptr;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static void ForceLogoutViaPlayerbotHolder(Player* target)
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(target);
|
||||
if (!ai)
|
||||
return;
|
||||
|
||||
if (Player* master = ai->GetMaster())
|
||||
{
|
||||
if (PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(master))
|
||||
{
|
||||
mgr->LogoutPlayerBot(target->GetGUID());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sRandomPlayerbotMgr)
|
||||
{
|
||||
sRandomPlayerbotMgr->LogoutPlayerBot(target->GetGUID());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerbotsSecureLoginServerScript : public ServerScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsSecureLoginServerScript()
|
||||
: ServerScript("PlayerbotsSecureLoginServerScript", { SERVERHOOK_CAN_PACKET_RECEIVE }) {}
|
||||
|
||||
bool CanPacketReceive(WorldSession* /*session*/, WorldPacket& packet) override
|
||||
{
|
||||
if (packet.GetOpcode() != CMSG_PLAYER_LOGIN)
|
||||
return true;
|
||||
|
||||
auto const oldPos = packet.rpos();
|
||||
ObjectGuid loginGuid;
|
||||
packet >> loginGuid;
|
||||
packet.rpos(oldPos);
|
||||
|
||||
if (!loginGuid)
|
||||
return true;
|
||||
|
||||
Player* existingAltbot = FindOnlineAltbotByGuid(loginGuid);
|
||||
if (existingAltbot)
|
||||
ForceLogoutViaPlayerbotHolder(existingAltbot);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void AddPlayerbotsSecureLoginScripts()
|
||||
{
|
||||
new PlayerbotsSecureLoginServerScript();
|
||||
}
|
||||
93
src/Script/WorldThr/PlayerbotOperation.h
Normal file
93
src/Script/WorldThr/PlayerbotOperation.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_OPERATION_H
|
||||
#define _PLAYERBOT_OPERATION_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* @brief Base class for thread-unsafe operations that must be executed in the world thread
|
||||
*
|
||||
* PlayerbotOperation represents an operation that needs to be deferred from a map thread
|
||||
* to the world thread for safe execution. Examples include group modifications, LFG operations,
|
||||
* guild operations, etc.
|
||||
*
|
||||
* Thread Safety:
|
||||
* - The constructor and data members must be thread-safe (use copies, not pointers)
|
||||
* - Execute() is called in the world thread and can safely perform thread-unsafe operations
|
||||
* - Subclasses must not store raw pointers to (core/world thread) game object (use ObjectGuid instead)
|
||||
*/
|
||||
class PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
virtual ~PlayerbotOperation() = default;
|
||||
|
||||
/**
|
||||
* @brief Execute this operation in the world thread
|
||||
*
|
||||
* This method is called by PlayerbotWorldThreadProcessor::Update() which runs in the world thread.
|
||||
* It's safe to perform any thread-unsafe operation here (Group, LFG, Guild, etc.)
|
||||
*
|
||||
* @return true if operation succeeded, false if it failed
|
||||
*/
|
||||
virtual bool Execute() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the bot GUID this operation is for (optional)
|
||||
*
|
||||
* Used for logging and debugging purposes.
|
||||
*
|
||||
* @return ObjectGuid of the bot, or ObjectGuid::Empty if not applicable
|
||||
*/
|
||||
virtual ObjectGuid GetBotGuid() const { return ObjectGuid::Empty; }
|
||||
|
||||
/**
|
||||
* @brief Get the operation priority (higher = more urgent)
|
||||
*
|
||||
* Priority levels:
|
||||
* - 100: Critical (crash prevention, cleanup operations)
|
||||
* - 50: High (player-facing operations like group invites)
|
||||
* - 10: Normal (background operations)
|
||||
* - 0: Low (statistics, logging)
|
||||
*
|
||||
* @return Priority value (0-100)
|
||||
*/
|
||||
virtual uint32 GetPriority() const { return 10; }
|
||||
|
||||
/**
|
||||
* @brief Get a human-readable name for this operation
|
||||
*
|
||||
* Used for logging and debugging.
|
||||
*
|
||||
* @return Operation name
|
||||
*/
|
||||
virtual std::string GetName() const { return "Unknown Operation"; }
|
||||
|
||||
/**
|
||||
* @brief Check if this operation is still valid
|
||||
*
|
||||
* Called before Execute() to check if the operation should still be executed.
|
||||
* For example, if a bot logged out, group invite operations for that bot can be skipped.
|
||||
*
|
||||
* @return true if operation should be executed, false to skip
|
||||
*/
|
||||
virtual bool IsValid() const { return true; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Comparison operator for priority queue (higher priority first)
|
||||
*/
|
||||
struct PlayerbotOperationComparator
|
||||
{
|
||||
bool operator()(const std::unique_ptr<PlayerbotOperation>& a, const std::unique_ptr<PlayerbotOperation>& b) const
|
||||
{
|
||||
return a->GetPriority() < b->GetPriority(); // Lower priority goes to back of queue
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
513
src/Script/WorldThr/PlayerbotOperations.h
Normal file
513
src/Script/WorldThr/PlayerbotOperations.h
Normal file
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_OPERATIONS_H
|
||||
#define _PLAYERBOT_OPERATIONS_H
|
||||
|
||||
#include "Group.h"
|
||||
#include "GroupMgr.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "PlayerbotOperation.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "PlayerbotRepository.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "WorldSession.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
|
||||
// Group invite operation
|
||||
class GroupInviteOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupInviteOperation(ObjectGuid botGuid, ObjectGuid targetGuid)
|
||||
: m_botGuid(botGuid), m_targetGuid(targetGuid)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||
|
||||
if (!bot || !target)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Bot or target not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if target is already in a group
|
||||
if (target->GetGroup())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Target {} is already in a group", target->GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
|
||||
// Create group if bot doesn't have one
|
||||
if (!group)
|
||||
{
|
||||
group = new Group;
|
||||
if (!group->Create(bot))
|
||||
{
|
||||
delete group;
|
||||
LOG_ERROR("playerbots", "GroupInviteOperation: Failed to create group for bot {}", bot->GetName());
|
||||
return false;
|
||||
}
|
||||
sGroupMgr->AddGroup(group);
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Created new group for bot {}", bot->GetName());
|
||||
}
|
||||
|
||||
// Convert to raid if needed (more than 5 members)
|
||||
if (!group->isRaidGroup() && group->GetMembersCount() >= 5)
|
||||
{
|
||||
group->ConvertToRaid();
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Converted group to raid");
|
||||
}
|
||||
|
||||
// Add member to group
|
||||
if (group->AddMember(target))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Successfully added {} to group", target->GetName());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR("playerbots", "GroupInviteOperation: Failed to add {} to group", target->GetName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; } // High priority (player-facing)
|
||||
|
||||
std::string GetName() const override { return "GroupInvite"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
// Check if bot still exists and is online
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||
return bot && target;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
ObjectGuid m_targetGuid;
|
||||
};
|
||||
|
||||
// Remove member from group
|
||||
class GroupRemoveMemberOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupRemoveMemberOperation(ObjectGuid botGuid, ObjectGuid targetGuid)
|
||||
: m_botGuid(botGuid), m_targetGuid(targetGuid)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||
|
||||
if (!bot || !target)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Bot is not in a group");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!group->IsMember(target->GetGUID()))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Target is not in bot's group");
|
||||
return false;
|
||||
}
|
||||
|
||||
group->RemoveMember(target->GetGUID());
|
||||
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Removed {} from group", target->GetName());
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; }
|
||||
|
||||
std::string GetName() const override { return "GroupRemoveMember"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
return bot != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
ObjectGuid m_targetGuid;
|
||||
};
|
||||
|
||||
// Convert group to raid
|
||||
class GroupConvertToRaidOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupConvertToRaidOperation(ObjectGuid botGuid) : m_botGuid(botGuid) {}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Bot is not in a group");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (group->isRaidGroup())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Group is already a raid");
|
||||
return true; // Success - already in desired state
|
||||
}
|
||||
|
||||
group->ConvertToRaid();
|
||||
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Converted group to raid");
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; }
|
||||
|
||||
std::string GetName() const override { return "GroupConvertToRaid"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
return bot != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
};
|
||||
|
||||
// Set group leader
|
||||
class GroupSetLeaderOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupSetLeaderOperation(ObjectGuid botGuid, ObjectGuid newLeaderGuid)
|
||||
: m_botGuid(botGuid), m_newLeaderGuid(newLeaderGuid)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* newLeader = ObjectAccessor::FindPlayer(m_newLeaderGuid);
|
||||
|
||||
if (!bot || !newLeader)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: Bot is not in a group");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!group->IsMember(newLeader->GetGUID()))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: New leader is not in the group");
|
||||
return false;
|
||||
}
|
||||
|
||||
group->ChangeLeader(newLeader->GetGUID());
|
||||
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: Changed leader to {}", newLeader->GetName());
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; }
|
||||
|
||||
std::string GetName() const override { return "GroupSetLeader"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* newLeader = ObjectAccessor::FindPlayer(m_newLeaderGuid);
|
||||
return bot && newLeader;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
ObjectGuid m_newLeaderGuid;
|
||||
};
|
||||
|
||||
// Form arena group
|
||||
class ArenaGroupFormationOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
ArenaGroupFormationOperation(ObjectGuid leaderGuid, std::vector<ObjectGuid> memberGuids,
|
||||
uint32 requiredSize, uint32 arenaTeamId, std::string arenaTeamName)
|
||||
: m_leaderGuid(leaderGuid), m_memberGuids(memberGuids),
|
||||
m_requiredSize(requiredSize), m_arenaTeamId(arenaTeamId), m_arenaTeamName(arenaTeamName)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* leader = ObjectAccessor::FindPlayer(m_leaderGuid);
|
||||
if (!leader)
|
||||
{
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Leader not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: Remove all members from their existing groups
|
||||
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||
{
|
||||
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||
if (!member)
|
||||
continue;
|
||||
|
||||
Group* memberGroup = member->GetGroup();
|
||||
if (memberGroup)
|
||||
{
|
||||
memberGroup->RemoveMember(memberGuid);
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Removed {} from their existing group",
|
||||
member->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Disband leader's existing group
|
||||
Group* leaderGroup = leader->GetGroup();
|
||||
if (leaderGroup)
|
||||
{
|
||||
leaderGroup->Disband(true);
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Disbanded leader's existing group");
|
||||
}
|
||||
|
||||
// Step 3: Create new group with leader
|
||||
Group* newGroup = new Group();
|
||||
if (!newGroup->Create(leader))
|
||||
{
|
||||
delete newGroup;
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Failed to create arena group for leader {}",
|
||||
leader->GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
sGroupMgr->AddGroup(newGroup);
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Created new arena group with leader {}",
|
||||
leader->GetName());
|
||||
|
||||
// Step 4: Add members to the new group
|
||||
uint32 addedMembers = 0;
|
||||
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||
{
|
||||
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||
if (!member)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Member {} not found, skipping",
|
||||
memberGuid.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member->GetLevel() < 70)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Member {} is below level 70, skipping",
|
||||
member->GetName());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newGroup->AddMember(member))
|
||||
{
|
||||
addedMembers++;
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Added {} to arena group",
|
||||
member->GetName());
|
||||
}
|
||||
else
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Failed to add {} to arena group",
|
||||
member->GetName());
|
||||
}
|
||||
|
||||
if (addedMembers == 0)
|
||||
{
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: No members were added to the arena group");
|
||||
newGroup->Disband();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5: Teleport members to leader and reset AI
|
||||
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||
{
|
||||
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||
if (!member || !newGroup->IsMember(memberGuid))
|
||||
continue;
|
||||
|
||||
PlayerbotAI* memberBotAI = sPlayerbotsMgr->GetPlayerbotAI(member);
|
||||
if (memberBotAI)
|
||||
memberBotAI->Reset();
|
||||
|
||||
member->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||
member->TeleportTo(leader->GetMapId(), leader->GetPositionX(), leader->GetPositionY(),
|
||||
leader->GetPositionZ(), 0);
|
||||
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Teleported {} to leader", member->GetName());
|
||||
}
|
||||
|
||||
// Check if we have enough members
|
||||
if (newGroup->GetMembersCount() < m_requiredSize)
|
||||
{
|
||||
LOG_INFO("playerbots", "Team #{} <{}> Group is not ready for match (not enough members: {}/{})",
|
||||
m_arenaTeamId, m_arenaTeamName, newGroup->GetMembersCount(), m_requiredSize);
|
||||
newGroup->Disband();
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("playerbots", "Team #{} <{}> Group is ready for match with {} members",
|
||||
m_arenaTeamId, m_arenaTeamName, newGroup->GetMembersCount());
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_leaderGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 60; } // Very high priority (arena/BG operations)
|
||||
|
||||
std::string GetName() const override { return "ArenaGroupFormation"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* leader = ObjectAccessor::FindPlayer(m_leaderGuid);
|
||||
return leader != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_leaderGuid;
|
||||
std::vector<ObjectGuid> m_memberGuids;
|
||||
uint32 m_requiredSize;
|
||||
uint32 m_arenaTeamId;
|
||||
std::string m_arenaTeamName;
|
||||
};
|
||||
|
||||
// Bot logout group cleanup operation
|
||||
class BotLogoutGroupCleanupOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
BotLogoutGroupCleanupOperation(ObjectGuid botGuid) : m_botGuid(botGuid) {}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
PlayerbotAI* botAI = sPlayerbotsMgr->GetPlayerbotAI(bot);
|
||||
if (!botAI)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group && !bot->InBattleground() && !bot->InBattlegroundQueue() && botAI->HasActivePlayerMaster())
|
||||
sPlayerbotRepository->Save(botAI);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
uint32 GetPriority() const override { return 70; }
|
||||
std::string GetName() const override { return "BotLogoutGroupCleanup"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
return bot != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
};
|
||||
|
||||
// Add player bot operation (for logging in bots from map threads)
|
||||
class AddPlayerBotOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
AddPlayerBotOperation(ObjectGuid botGuid, uint32 masterAccountId)
|
||||
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
sRandomPlayerbotMgr->AddPlayerBot(m_botGuid, m_masterAccountId);
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; } // High priority
|
||||
|
||||
std::string GetName() const override { return "AddPlayerBot"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
return !ObjectAccessor::FindConnectedPlayer(m_botGuid);
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
uint32 m_masterAccountId;
|
||||
};
|
||||
|
||||
class OnBotLoginOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
OnBotLoginOperation(ObjectGuid botGuid, uint32 masterAccountId)
|
||||
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
// find and verify bot still exists
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid);
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
PlayerbotHolder* holder = sRandomPlayerbotMgr;
|
||||
if (m_masterAccountId)
|
||||
{
|
||||
WorldSession* masterSession = sWorldSessionMgr->FindSession(m_masterAccountId);
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterPlayer)
|
||||
holder = GET_PLAYERBOT_MGR(masterPlayer);
|
||||
}
|
||||
|
||||
if (!holder)
|
||||
return false;
|
||||
|
||||
holder->OnBotLogin(bot);
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
uint32 GetPriority() const override { return 100; }
|
||||
std::string GetName() const override { return "OnBotLogin"; }
|
||||
|
||||
bool IsValid() const override { return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr; }
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
uint32 m_masterAccountId = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
217
src/Script/WorldThr/PlayerbotWorldThreadProcessor.cpp
Normal file
217
src/Script/WorldThr/PlayerbotWorldThreadProcessor.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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 "PlayerbotWorldThreadProcessor.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
PlayerbotWorldThreadProcessor::PlayerbotWorldThreadProcessor()
|
||||
: m_enabled(true), m_maxQueueSize(10000), m_batchSize(100), m_queueWarningThreshold(80),
|
||||
m_timeSinceLastUpdate(0), m_updateInterval(50) // Process at least every 50ms
|
||||
{
|
||||
LOG_INFO("playerbots", "PlayerbotWorldThreadProcessor initialized");
|
||||
}
|
||||
|
||||
PlayerbotWorldThreadProcessor::~PlayerbotWorldThreadProcessor() { ClearQueue(); }
|
||||
|
||||
PlayerbotWorldThreadProcessor* PlayerbotWorldThreadProcessor::instance()
|
||||
{
|
||||
static PlayerbotWorldThreadProcessor instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::Update(uint32 diff)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
// Accumulate time
|
||||
m_timeSinceLastUpdate += diff;
|
||||
|
||||
// Don't process too frequently to reduce overhead
|
||||
if (m_timeSinceLastUpdate < m_updateInterval)
|
||||
return;
|
||||
|
||||
m_timeSinceLastUpdate = 0;
|
||||
|
||||
// Check queue health (warn if getting full)
|
||||
CheckQueueHealth();
|
||||
|
||||
// Process a batch of operations
|
||||
ProcessBatch();
|
||||
}
|
||||
|
||||
bool PlayerbotWorldThreadProcessor::QueueOperation(std::unique_ptr<PlayerbotOperation> operation)
|
||||
{
|
||||
if (!operation)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Attempted to queue null operation");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
|
||||
// Check if queue is full
|
||||
if (m_operationQueue.size() >= m_maxQueueSize)
|
||||
{
|
||||
LOG_ERROR("playerbots",
|
||||
"PlayerbotWorldThreadProcessor queue is full ({} operations). Dropping operation: {}",
|
||||
m_maxQueueSize, operation->GetName());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsSkipped++;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Queue the operation
|
||||
m_operationQueue.push(std::move(operation));
|
||||
|
||||
// Update statistics
|
||||
{
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.currentQueueSize = static_cast<uint32>(m_operationQueue.size());
|
||||
m_stats.maxQueueSize = std::max(m_stats.maxQueueSize, m_stats.currentQueueSize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::ProcessBatch()
|
||||
{
|
||||
// Extract a batch of operations from the queue
|
||||
std::vector<std::unique_ptr<PlayerbotOperation>> batch;
|
||||
batch.reserve(m_batchSize);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
|
||||
// Extract up to batchSize operations
|
||||
while (!m_operationQueue.empty() && batch.size() < m_batchSize)
|
||||
{
|
||||
batch.push_back(std::move(m_operationQueue.front()));
|
||||
m_operationQueue.pop();
|
||||
}
|
||||
|
||||
// Update current queue size stat
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.currentQueueSize = static_cast<uint32>(m_operationQueue.size());
|
||||
}
|
||||
|
||||
// Execute operations outside of lock to avoid blocking queue
|
||||
uint32 totalExecutionTime = 0;
|
||||
for (auto& operation : batch)
|
||||
{
|
||||
if (!operation)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
// Check if operation is still valid
|
||||
if (!operation->IsValid())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "Skipping invalid operation: {}", operation->GetName());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Time the execution
|
||||
uint32 startTime = getMSTime();
|
||||
|
||||
// Execute the operation
|
||||
bool success = operation->Execute();
|
||||
|
||||
uint32 executionTime = GetMSTimeDiffToNow(startTime);
|
||||
totalExecutionTime += executionTime;
|
||||
|
||||
// Log slow operations
|
||||
if (executionTime > 100)
|
||||
LOG_WARN("playerbots", "Slow operation: {} took {}ms", operation->GetName(), executionTime);
|
||||
|
||||
// Update statistics
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
if (success)
|
||||
m_stats.totalOperationsProcessed++;
|
||||
else
|
||||
{
|
||||
m_stats.totalOperationsFailed++;
|
||||
LOG_DEBUG("playerbots", "Operation failed: {}", operation->GetName());
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Exception in operation {}: {}", operation->GetName(), e.what());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsFailed++;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Unknown exception in operation {}", operation->GetName());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsFailed++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update average execution time
|
||||
if (!batch.empty())
|
||||
{
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
uint32 avgTime = totalExecutionTime / static_cast<uint32>(batch.size());
|
||||
// Exponential moving average
|
||||
m_stats.averageExecutionTimeMs =
|
||||
(m_stats.averageExecutionTimeMs * 9 + avgTime) / 10; // 90% old, 10% new
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::CheckQueueHealth()
|
||||
{
|
||||
uint32 queueSize = GetQueueSize();
|
||||
uint32 threshold = (m_maxQueueSize * m_queueWarningThreshold) / 100;
|
||||
|
||||
if (queueSize >= threshold)
|
||||
{
|
||||
LOG_WARN("playerbots",
|
||||
"PlayerbotWorldThreadProcessor queue is {}% full ({}/{}). "
|
||||
"Consider increasing update frequency or batch size.",
|
||||
(queueSize * 100) / m_maxQueueSize, queueSize, m_maxQueueSize);
|
||||
}
|
||||
}
|
||||
|
||||
uint32 PlayerbotWorldThreadProcessor::GetQueueSize() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
return static_cast<uint32>(m_operationQueue.size());
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::ClearQueue()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
|
||||
uint32 cleared = static_cast<uint32>(m_operationQueue.size());
|
||||
if (cleared > 0)
|
||||
LOG_INFO("playerbots", "Clearing {} queued operations", cleared);
|
||||
|
||||
// Clear the queue
|
||||
while (!m_operationQueue.empty())
|
||||
{
|
||||
m_operationQueue.pop();
|
||||
}
|
||||
|
||||
// Reset queue size stat
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.currentQueueSize = 0;
|
||||
}
|
||||
|
||||
PlayerbotWorldThreadProcessor::Statistics PlayerbotWorldThreadProcessor::GetStatistics() const
|
||||
{
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
return m_stats; // Return a copy
|
||||
}
|
||||
142
src/Script/WorldThr/PlayerbotWorldThreadProcessor.h
Normal file
142
src/Script/WorldThr/PlayerbotWorldThreadProcessor.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_WORLD_THREAD_PROCESSOR_H
|
||||
#define _PLAYERBOT_WORLD_THREAD_PROCESSOR_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "PlayerbotOperation.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
/**
|
||||
* @brief Processes thread-unsafe bot operations in the world thread
|
||||
*
|
||||
* The PlayerbotWorldThreadProcessor manages a queue of operations that must be executed
|
||||
* in the world thread rather than map threads. This ensures thread safety for operations
|
||||
* like group modifications, LFG, guilds, battlegrounds, etc.
|
||||
*
|
||||
* Architecture:
|
||||
* - Map threads queue operations via QueueOperation()
|
||||
* - World thread processes operations via Update() (called from WorldScript::OnUpdate)
|
||||
* - Operations are processed in priority order
|
||||
* - Thread-safe queue protected by mutex
|
||||
*
|
||||
* Usage:
|
||||
* auto op = std::make_unique<MyOperation>(botGuid, params);
|
||||
* sPlayerbotWorldProcessor->QueueOperation(std::move(op));
|
||||
*/
|
||||
class PlayerbotWorldThreadProcessor
|
||||
{
|
||||
public:
|
||||
PlayerbotWorldThreadProcessor();
|
||||
~PlayerbotWorldThreadProcessor();
|
||||
|
||||
static PlayerbotWorldThreadProcessor* instance();
|
||||
|
||||
/**
|
||||
* @brief Update and process queued operations (called from world thread)
|
||||
*
|
||||
* This method should be called from WorldScript::OnUpdate hook, which runs in the world thread.
|
||||
* It processes a batch of queued operations.
|
||||
*
|
||||
* @param diff Time since last update in milliseconds
|
||||
*/
|
||||
void Update(uint32 diff);
|
||||
|
||||
/**
|
||||
* @brief Queue an operation for execution in the world thread
|
||||
*
|
||||
* Thread-safe method that can be called from any thread (typically map threads).
|
||||
* The operation will be executed later during Update().
|
||||
*
|
||||
* @param operation Unique pointer to the operation (ownership is transferred)
|
||||
* @return true if operation was queued, false if queue is full
|
||||
*/
|
||||
bool QueueOperation(std::unique_ptr<PlayerbotOperation> operation);
|
||||
|
||||
/**
|
||||
* @brief Get current queue size
|
||||
*
|
||||
* Thread-safe method for monitoring queue size.
|
||||
*
|
||||
* @return Number of operations waiting to be processed
|
||||
*/
|
||||
uint32 GetQueueSize() const;
|
||||
|
||||
/**
|
||||
* @brief Clear all queued operations
|
||||
*
|
||||
* Used during shutdown or emergency situations.
|
||||
*/
|
||||
void ClearQueue();
|
||||
|
||||
/**
|
||||
* @brief Get statistics about operation processing
|
||||
*/
|
||||
struct Statistics
|
||||
{
|
||||
uint64 totalOperationsProcessed = 0;
|
||||
uint64 totalOperationsFailed = 0;
|
||||
uint64 totalOperationsSkipped = 0;
|
||||
uint32 currentQueueSize = 0;
|
||||
uint32 maxQueueSize = 0;
|
||||
uint32 averageExecutionTimeMs = 0;
|
||||
};
|
||||
|
||||
Statistics GetStatistics() const;
|
||||
|
||||
/**
|
||||
* @brief Enable/disable operation processing
|
||||
*
|
||||
* When disabled, operations are still queued but not processed.
|
||||
* Useful for testing or temporary suspension.
|
||||
*
|
||||
* @param enabled true to enable processing, false to disable
|
||||
*/
|
||||
void SetEnabled(bool enabled) { m_enabled = enabled; }
|
||||
|
||||
bool IsEnabled() const { return m_enabled; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Process a single batch of operations
|
||||
*
|
||||
* Extracts operations from queue and executes them.
|
||||
* Called internally by Update().
|
||||
*/
|
||||
void ProcessBatch();
|
||||
|
||||
/**
|
||||
* @brief Check if queue is approaching capacity
|
||||
*
|
||||
* Logs warning if queue is getting full.
|
||||
*/
|
||||
void CheckQueueHealth();
|
||||
|
||||
// Thread-safe queue
|
||||
mutable std::mutex m_queueMutex;
|
||||
std::queue<std::unique_ptr<PlayerbotOperation>> m_operationQueue;
|
||||
|
||||
// Configuration
|
||||
bool m_enabled;
|
||||
uint32 m_maxQueueSize; // Maximum operations in queue
|
||||
uint32 m_batchSize; // Operations to process per Update()
|
||||
uint32 m_queueWarningThreshold; // Warn when queue reaches this percentage
|
||||
|
||||
// Statistics
|
||||
mutable std::mutex m_statsMutex;
|
||||
Statistics m_stats;
|
||||
|
||||
// Timing
|
||||
uint32 m_timeSinceLastUpdate;
|
||||
uint32 m_updateInterval; // Minimum ms between updates
|
||||
};
|
||||
|
||||
#define sPlayerbotWorldProcessor PlayerbotWorldThreadProcessor::instance()
|
||||
|
||||
#endif
|
||||
139
src/Script/WorldThr/Queue.cpp
Normal file
139
src/Script/WorldThr/Queue.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 "Queue.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Log.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
|
||||
void Queue::Push(ActionBasket* action)
|
||||
{
|
||||
if (!action)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (ActionBasket* basket : actions)
|
||||
{
|
||||
if (action->getAction()->getName() == basket->getAction()->getName())
|
||||
{
|
||||
updateExistingBasket(basket, action);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
actions.push_back(action);
|
||||
}
|
||||
|
||||
ActionNode* Queue::Pop()
|
||||
{
|
||||
ActionBasket* highestRelevanceBasket = findHighestRelevanceBasket();
|
||||
if (!highestRelevanceBasket)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return extractAndDeleteBasket(highestRelevanceBasket);
|
||||
}
|
||||
|
||||
ActionBasket* Queue::Peek()
|
||||
{
|
||||
return findHighestRelevanceBasket();
|
||||
}
|
||||
|
||||
uint32 Queue::Size()
|
||||
{
|
||||
return actions.size();
|
||||
}
|
||||
|
||||
void Queue::RemoveExpired()
|
||||
{
|
||||
if (!sPlayerbotAIConfig->expireActionTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::list<ActionBasket*> expiredBaskets;
|
||||
collectExpiredBaskets(expiredBaskets);
|
||||
removeAndDeleteBaskets(expiredBaskets);
|
||||
}
|
||||
|
||||
// Private helper methods
|
||||
void Queue::updateExistingBasket(ActionBasket* existing, ActionBasket* newBasket)
|
||||
{
|
||||
if (existing->getRelevance() < newBasket->getRelevance())
|
||||
{
|
||||
existing->setRelevance(newBasket->getRelevance());
|
||||
}
|
||||
|
||||
if (ActionNode* actionNode = newBasket->getAction())
|
||||
{
|
||||
delete actionNode;
|
||||
}
|
||||
|
||||
delete newBasket;
|
||||
}
|
||||
|
||||
ActionBasket* Queue::findHighestRelevanceBasket() const
|
||||
{
|
||||
if (actions.empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
float maxRelevance = -1.0f;
|
||||
ActionBasket* selection = nullptr;
|
||||
|
||||
for (ActionBasket* basket : actions)
|
||||
{
|
||||
if (!basket)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (basket->getRelevance() > maxRelevance)
|
||||
{
|
||||
maxRelevance = basket->getRelevance();
|
||||
selection = basket;
|
||||
}
|
||||
}
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
||||
ActionNode* Queue::extractAndDeleteBasket(ActionBasket* basket)
|
||||
{
|
||||
ActionNode* action = basket->getAction();
|
||||
actions.remove(basket);
|
||||
delete basket;
|
||||
return action;
|
||||
}
|
||||
|
||||
void Queue::collectExpiredBaskets(std::list<ActionBasket*>& expiredBaskets)
|
||||
{
|
||||
uint32 expiryTime = sPlayerbotAIConfig->expireActionTime;
|
||||
for (ActionBasket* basket : actions)
|
||||
{
|
||||
if (basket->isExpired(expiryTime))
|
||||
{
|
||||
expiredBaskets.push_back(basket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Queue::removeAndDeleteBaskets(std::list<ActionBasket*>& basketsToRemove)
|
||||
{
|
||||
for (ActionBasket* basket : basketsToRemove)
|
||||
{
|
||||
actions.remove(basket);
|
||||
|
||||
if (ActionNode* action = basket->getAction())
|
||||
{
|
||||
delete action;
|
||||
}
|
||||
|
||||
delete basket;
|
||||
}
|
||||
}
|
||||
94
src/Script/WorldThr/Queue.h
Normal file
94
src/Script/WorldThr/Queue.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef PLAYERBOT_QUEUE_H
|
||||
#define PLAYERBOT_QUEUE_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "Common.h"
|
||||
|
||||
/**
|
||||
* @class Queue
|
||||
* @brief Manages a priority queue of actions for the playerbot system
|
||||
*
|
||||
* This queue maintains a list of ActionBasket objects, each containing an action
|
||||
* and its relevance score. Actions with higher relevance scores are prioritized.
|
||||
*/
|
||||
class Queue
|
||||
{
|
||||
public:
|
||||
Queue() = default;
|
||||
~Queue() = default;
|
||||
|
||||
/**
|
||||
* @brief Adds an action to the queue or updates existing action's relevance
|
||||
* @param action Pointer to the ActionBasket to be added
|
||||
*
|
||||
* If an action with the same name exists, updates its relevance if the new
|
||||
* relevance is higher, then deletes the new action. Otherwise, adds the new
|
||||
* action to the queue.
|
||||
*/
|
||||
void Push(ActionBasket* action);
|
||||
|
||||
/**
|
||||
* @brief Removes and returns the action with highest relevance
|
||||
* @return Pointer to the highest relevance ActionNode, or nullptr if queue is empty
|
||||
*
|
||||
* Ownership of the returned ActionNode is transferred to the caller.
|
||||
* The associated ActionBasket is deleted.
|
||||
*/
|
||||
ActionNode* Pop();
|
||||
|
||||
/**
|
||||
* @brief Returns the action with highest relevance without removing it
|
||||
* @return Pointer to the ActionBasket with highest relevance, or nullptr if queue is empty
|
||||
*/
|
||||
ActionBasket* Peek();
|
||||
|
||||
/**
|
||||
* @brief Returns the current size of the queue
|
||||
* @return Number of actions in the queue
|
||||
*/
|
||||
uint32 Size();
|
||||
|
||||
/**
|
||||
* @brief Removes and deletes expired actions from the queue
|
||||
*
|
||||
* Uses sPlayerbotAIConfig->expireActionTime to determine if actions have expired.
|
||||
* Both the ActionNode and ActionBasket are deleted for expired actions.
|
||||
*/
|
||||
void RemoveExpired();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Updates existing basket with new relevance and cleans up new basket
|
||||
*/
|
||||
void updateExistingBasket(ActionBasket* existing, ActionBasket* newBasket);
|
||||
|
||||
/**
|
||||
* @brief Finds the basket with the highest relevance score
|
||||
* @return Pointer to the highest relevance basket, or nullptr if queue is empty
|
||||
*/
|
||||
ActionBasket* findHighestRelevanceBasket() const;
|
||||
|
||||
/**
|
||||
* @brief Extracts action from basket and handles basket cleanup
|
||||
*/
|
||||
ActionNode* extractAndDeleteBasket(ActionBasket* basket);
|
||||
|
||||
/**
|
||||
* @brief Collects all expired baskets into the provided list
|
||||
*/
|
||||
void collectExpiredBaskets(std::list<ActionBasket*>& expiredBaskets);
|
||||
|
||||
/**
|
||||
* @brief Removes and deletes all baskets in the provided list
|
||||
*/
|
||||
void removeAndDeleteBaskets(std::list<ActionBasket*>& basketsToRemove);
|
||||
|
||||
std::list<ActionBasket*> actions; /**< Container for action baskets */
|
||||
};
|
||||
|
||||
#endif
|
||||
22
src/Script/playerbots_loader.cpp
Normal file
22
src/Script/playerbots_loader.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// From SC
|
||||
void AddPlayerbotsScripts();
|
||||
|
||||
// Add all
|
||||
void Addmod_playerbotsScripts() { AddPlayerbotsScripts(); }
|
||||
Reference in New Issue
Block a user