Files
mod-playerbots/src/RandomPlayerbotMgr.cpp
nick a37dd2b9ae Clarify random bot timing configuration section and parameter descriptions (#1826)
This update reorganizes and rewrites the random bot timing configuration
section for clarity and accuracy. The previous section was mislabeled as
"INTERVALS" and lacked precise descriptions. The new version:

1. Renames the header to RANDOM BOT TIMING AND BEHAVIOR
2. Adds concise, standardized comments for each parameter
3. Corrects misleading terminology (not all values are intervals)
4. Documents defaults and actual behavior clearly for easier tuning and
maintenance
5. No functional code changes — documentation and readability only.

Note, this is derived information from reading the code.
Please double check if I have captured each param accurately!
2025-11-11 09:21:13 +01:00

3584 lines
124 KiB
C++

/*
* 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 "RandomPlayerbotMgr.h"
#include <WorldSessionMgr.h>
#include <algorithm>
#include <boost/thread/thread.hpp>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <random>
#include "AccountMgr.h"
#include "AiFactory.h"
#include "ArenaTeamMgr.h"
#include "Battleground.h"
#include "BattlegroundMgr.h"
#include "CellImpl.h"
#include "ChannelMgr.h"
#include "DBCStores.h"
#include "DBCStructure.h"
#include "DatabaseEnv.h"
#include "Define.h"
#include "FleeManager.h"
#include "GameTime.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "GuildMgr.h"
#include "GuildTaskMgr.h"
#include "LFGMgr.h"
#include "MapMgr.h"
#include "NewRpgInfo.h"
#include "NewRpgStrategy.h"
#include "ObjectGuid.h"
#include "PerformanceMonitor.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotCommandServer.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "Position.h"
#include "Random.h"
#include "RandomPlayerbotFactory.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
#include "TravelMgr.h"
#include "Unit.h"
#include "UpdateTime.h"
#include "World.h"
struct GuidClassRaceInfo
{
ObjectGuid::LowType guid;
uint32 rClass;
uint32 rRace;
};
enum class CityId : uint8 {
STORMWIND, IRONFORGE, DARNASSUS, EXODAR,
ORGRIMMAR, UNDERCITY, THUNDER_BLUFF, SILVERMOON_CITY,
SHATTRATH_CITY, DALARAN
};
enum class FactionId : uint8 { ALLIANCE, HORDE, NEUTRAL };
// Map of banker entry → city + faction
static const std::unordered_map<uint16, std::pair<CityId, FactionId>> bankerToCity = {
{2455, {CityId::STORMWIND, FactionId::ALLIANCE}}, {2456, {CityId::STORMWIND, FactionId::ALLIANCE}}, {2457, {CityId::STORMWIND, FactionId::ALLIANCE}},
{2460, {CityId::IRONFORGE, FactionId::ALLIANCE}}, {2461, {CityId::IRONFORGE, FactionId::ALLIANCE}}, {5099, {CityId::IRONFORGE, FactionId::ALLIANCE}},
{4155, {CityId::DARNASSUS, FactionId::ALLIANCE}}, {4208, {CityId::DARNASSUS, FactionId::ALLIANCE}}, {4209, {CityId::DARNASSUS, FactionId::ALLIANCE}},
{17773, {CityId::EXODAR, FactionId::ALLIANCE}}, {18350, {CityId::EXODAR, FactionId::ALLIANCE}}, {16710, {CityId::EXODAR, FactionId::ALLIANCE}},
{3320, {CityId::ORGRIMMAR, FactionId::HORDE}}, {3309, {CityId::ORGRIMMAR, FactionId::HORDE}}, {3318, {CityId::ORGRIMMAR, FactionId::HORDE}},
{4549, {CityId::UNDERCITY, FactionId::HORDE}}, {2459, {CityId::UNDERCITY, FactionId::HORDE}}, {2458, {CityId::UNDERCITY, FactionId::HORDE}}, {4550, {CityId::UNDERCITY, FactionId::HORDE}},
{2996, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, {8356, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, {8357, {CityId::THUNDER_BLUFF, FactionId::HORDE}},
{17631, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {17632, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {17633, {CityId::SILVERMOON_CITY, FactionId::HORDE}},
{16615, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {16616, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {16617, {CityId::SILVERMOON_CITY, FactionId::HORDE}},
{19246, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19338, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}},
{19034, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19318, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}},
{30604, {CityId::DALARAN, FactionId::NEUTRAL}}, {30605, {CityId::DALARAN, FactionId::NEUTRAL}}, {30607, {CityId::DALARAN, FactionId::NEUTRAL}},
{28675, {CityId::DALARAN, FactionId::NEUTRAL}}, {28676, {CityId::DALARAN, FactionId::NEUTRAL}}, {28677, {CityId::DALARAN, FactionId::NEUTRAL}}
};
// Map of city → available banker entries
static const std::unordered_map<CityId, std::vector<uint16>> cityToBankers = {
{CityId::STORMWIND, {2455, 2456, 2457}},
{CityId::IRONFORGE, {2460, 2461, 5099}},
{CityId::DARNASSUS, {4155, 4208, 4209}},
{CityId::EXODAR, {17773, 18350, 16710}},
{CityId::ORGRIMMAR, {3320, 3309, 3318}},
{CityId::UNDERCITY, {4549, 2459, 2458, 4550}},
{CityId::THUNDER_BLUFF, {2996, 8356, 8357}},
{CityId::SILVERMOON_CITY, {17631, 17632, 17633, 16615, 16616, 16617}},
{CityId::SHATTRATH_CITY, {19246, 19338, 19034, 19318}},
{CityId::DALARAN, {30604, 30605, 30607, 28675, 28676, 28677, 29530}}
};
// Quick lookup map: banker entry → location
static std::unordered_map<uint32, WorldLocation> bankerEntryToLocation;
void PrintStatsThread() { sRandomPlayerbotMgr->PrintStats(); }
void activatePrintStatsThread()
{
boost::thread t(PrintStatsThread);
t.detach();
}
void CheckBgQueueThread() { sRandomPlayerbotMgr->CheckBgQueue(); }
void activateCheckBgQueueThread()
{
boost::thread t(CheckBgQueueThread);
t.detach();
}
void CheckLfgQueueThread() { sRandomPlayerbotMgr->CheckLfgQueue(); }
void activateCheckLfgQueueThread()
{
boost::thread t(CheckLfgQueueThread);
t.detach();
}
void CheckPlayersThread() { sRandomPlayerbotMgr->CheckPlayers(); }
void activateCheckPlayersThread()
{
boost::thread t(CheckPlayersThread);
t.detach();
}
class botPIDImpl
{
public:
botPIDImpl(double dt, double max, double min, double Kp, double Ki, double Kd);
~botPIDImpl();
double calculate(double setpoint, double pv);
void adjust(double Kp, double Ki, double Kd)
{
_Kp = Kp;
_Ki = Ki;
_Kd = Kd;
}
void reset() { _integral = 0; }
private:
double _dt;
double _max;
double _min;
double _Kp;
double _Ki;
double _Kd;
double _pre_error;
double _integral;
};
botPID::botPID(double dt, double max, double min, double Kp, double Ki, double Kd)
{
pimpl = new botPIDImpl(dt, max, min, Kp, Ki, Kd);
}
void botPID::adjust(double Kp, double Ki, double Kd) { pimpl->adjust(Kp, Ki, Kd); }
void botPID::reset() { pimpl->reset(); }
double botPID::calculate(double setpoint, double pv) { return pimpl->calculate(setpoint, pv); }
botPID::~botPID() { delete pimpl; }
/**
* Implementation
*/
botPIDImpl::botPIDImpl(double dt, double max, double min, double Kp, double Ki, double Kd)
: _dt(dt), _max(max), _min(min), _Kp(Kp), _Ki(Ki), _Kd(Kd), _pre_error(0), _integral(0)
{
}
double botPIDImpl::calculate(double setpoint, double pv)
{
// Calculate error
double error = setpoint - pv;
// Proportional term
double Pout = _Kp * error;
// Integral term
_integral += error * _dt;
double Iout = _Ki * _integral;
// Derivative term
double derivative = (error - _pre_error) / _dt;
double Dout = _Kd * derivative;
// Calculate total output
double output = Pout + Iout + Dout;
// Restrict to max/min
if (output > _max)
{
output = _max;
_integral -= error * _dt; // Stop integral buildup at max
}
else if (output < _min)
{
output = _min;
_integral -= error * _dt; // Stop integral buildup at min
}
// Save error to previous error
_pre_error = error;
return output;
}
botPIDImpl::~botPIDImpl() {}
RandomPlayerbotMgr::RandomPlayerbotMgr() : PlayerbotHolder(), processTicks(0)
{
playersLevel = sPlayerbotAIConfig->randombotStartingLevel;
if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin)
{
sPlayerbotCommandServer->Start();
}
BattlegroundData.clear(); // Clear here and here only.
// Cleanup on server start: orphaned pet data that's often left behind by bot pets that no longer exist in the DB
CharacterDatabase.Execute("DELETE FROM pet_aura WHERE guid NOT IN (SELECT id FROM character_pet)");
CharacterDatabase.Execute("DELETE FROM pet_spell WHERE guid NOT IN (SELECT id FROM character_pet)");
CharacterDatabase.Execute("DELETE FROM pet_spell_cooldown WHERE guid NOT IN (SELECT id FROM character_pet)");
for (int bracket = BG_BRACKET_ID_FIRST; bracket < MAX_BATTLEGROUND_BRACKETS; ++bracket)
{
for (int queueType = BATTLEGROUND_QUEUE_AV; queueType < MAX_BATTLEGROUND_QUEUE_TYPES; ++queueType)
{
BattlegroundData[queueType][bracket] = BattlegroundInfo();
}
}
BgCheckTimer = 0;
LfgCheckTimer = 0;
PlayersCheckTimer = 0;
}
RandomPlayerbotMgr::~RandomPlayerbotMgr() {}
uint32 RandomPlayerbotMgr::GetMaxAllowedBotCount() { return GetEventValue(0, "bot_count"); }
void RandomPlayerbotMgr::LogPlayerLocation()
{
activeBots = 0;
try
{
sPlayerbotAIConfig->openLog("player_location.csv", "w");
if (sPlayerbotAIConfig->randomBotAutologin)
{
for (auto i : GetAllBots())
{
Player* bot = i.second;
if (!bot)
continue;
std::ostringstream out;
out << sPlayerbotAIConfig->GetTimestampStr() << "+00,";
out << "RND"
<< ",";
out << bot->GetName() << ",";
out << std::fixed << std::setprecision(2);
WorldPosition(bot).printWKT(out);
out << bot->GetOrientation() << ",";
out << std::to_string(bot->getRace()) << ",";
out << std::to_string(bot->getClass()) << ",";
out << bot->GetMapId() << ",";
out << bot->GetLevel() << ",";
out << bot->GetHealth() << ",";
out << bot->GetPowerPct(bot->getPowerType()) << ",";
out << bot->GetMoney() << ",";
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot))
{
out << std::to_string(uint8(botAI->GetGrouperType())) << ",";
out << std::to_string(uint8(botAI->GetGuilderType())) << ",";
out << (botAI->AllowActivity(ALL_ACTIVITY) ? "active" : "inactive") << ",";
out << (botAI->IsActive() ? "active" : "delay") << ",";
out << botAI->HandleRemoteCommand("state") << ",";
if (botAI->AllowActivity(ALL_ACTIVITY))
activeBots++;
}
else
{
out << 0 << "," << 0 << ",err,err,err,";
}
out << (bot->IsInCombat() ? "combat" : "safe") << ",";
out << (bot->isDead() ? (bot->GetCorpse() ? "ghost" : "dead") : "alive");
sPlayerbotAIConfig->log("player_location.csv", out.str().c_str());
}
for (auto i : GetPlayers())
{
Player* bot = i;
if (!bot)
continue;
std::ostringstream out;
out << sPlayerbotAIConfig->GetTimestampStr() << "+00,";
out << "PLR"
<< ",";
out << bot->GetName() << ",";
out << std::fixed << std::setprecision(2);
WorldPosition(bot).printWKT(out);
out << bot->GetOrientation() << ",";
out << std::to_string(bot->getRace()) << ",";
out << std::to_string(bot->getClass()) << ",";
out << bot->GetMapId() << ",";
out << bot->GetLevel() << ",";
out << bot->GetHealth() << ",";
out << bot->GetPowerPct(bot->getPowerType()) << ",";
out << bot->GetMoney() << ",";
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot))
{
out << std::to_string(uint8(botAI->GetGrouperType())) << ",";
out << std::to_string(uint8(botAI->GetGuilderType())) << ",";
out << (botAI->AllowActivity(ALL_ACTIVITY) ? "active" : "inactive") << ",";
out << (botAI->IsActive() ? "active" : "delay") << ",";
out << botAI->HandleRemoteCommand("state") << ",";
if (botAI->AllowActivity(ALL_ACTIVITY))
activeBots++;
}
else
{
out << 0 << "," << 0 << ",player,player,player,";
}
out << (bot->IsInCombat() ? "combat" : "safe") << ",";
out << (bot->isDead() ? (bot->GetCorpse() ? "ghost" : "dead") : "alive");
sPlayerbotAIConfig->log("player_location.csv", out.str().c_str());
}
}
}
catch (...)
{
return;
// This is to prevent some thread-unsafeness. Crashes would happen if bots get added or removed.
// We really don't care here. Just skip a log. Making this thread-safe is not worth the effort.
}
}
void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
{
if (totalPmo)
totalPmo->finish();
totalPmo = sPerformanceMonitor->start(PERF_MON_TOTAL, "RandomPlayerbotMgr::FullTick");
if (!sPlayerbotAIConfig->randomBotAutologin || !sPlayerbotAIConfig->enabled)
return;
/*if (sPlayerbotAIConfig->enablePrototypePerformanceDiff)
{
LOG_INFO("playerbots", "---------------------------------------");
LOG_INFO("playerbots",
"PROTOTYPE: Playerbot performance enhancements are active. Issues and instability may occur.");
LOG_INFO("playerbots", "---------------------------------------");
ScaleBotActivity();
}*/
uint32 maxAllowedBotCount = GetEventValue(0, "bot_count");
if (!maxAllowedBotCount || (maxAllowedBotCount < sPlayerbotAIConfig->minRandomBots ||
maxAllowedBotCount > sPlayerbotAIConfig->maxRandomBots))
{
maxAllowedBotCount = urand(sPlayerbotAIConfig->minRandomBots, sPlayerbotAIConfig->maxRandomBots);
SetEventValue(0, "bot_count", maxAllowedBotCount,
urand(sPlayerbotAIConfig->randomBotCountChangeMinInterval,
sPlayerbotAIConfig->randomBotCountChangeMaxInterval));
}
GetBots();
std::list<uint32> availableBots = currentBots;
uint32 availableBotCount = availableBots.size();
uint32 onlineBotCount = playerBots.size();
uint32 onlineBotFocus = 75;
if (onlineBotCount < (uint32)(sPlayerbotAIConfig->minRandomBots * 90 / 100))
onlineBotFocus = 25;
// only keep updating till initializing time has completed,
// which prevents unneeded expensive GameTime calls.
if (_isBotInitializing)
{
_isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * (0.11 + 0.4);
}
uint32 updateIntervalTurboBoost = _isBotInitializing ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval;
SetNextCheckDelay(updateIntervalTurboBoost * (onlineBotFocus + 25) * 10);
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(
PERF_MON_TOTAL,
onlineBotCount < maxAllowedBotCount ? "RandomPlayerbotMgr::Login" : "RandomPlayerbotMgr::UpdateAIInternal");
bool realPlayerIsLogged = false;
if (sPlayerbotAIConfig->disabledWithoutRealPlayer)
{
if (sWorldSessionMgr->GetActiveAndQueuedSessionCount() > 0)
{
RealPlayerLastTimeSeen = time(nullptr);
realPlayerIsLogged = true;
if (DelayLoginBotsTimer == 0)
{
DelayLoginBotsTimer = time(nullptr) + sPlayerbotAIConfig->disabledWithoutRealPlayerLoginDelay;
}
}
else
{
if (DelayLoginBotsTimer)
{
DelayLoginBotsTimer = 0;
}
if (RealPlayerLastTimeSeen != 0 && onlineBotCount > 0 &&
time(nullptr) > RealPlayerLastTimeSeen + sPlayerbotAIConfig->disabledWithoutRealPlayerLogoutDelay)
{
LogoutAllBots();
LOG_INFO("playerbots", "Logout all bots due no real player session.");
}
}
if (availableBotCount < maxAllowedBotCount &&
(sPlayerbotAIConfig->disabledWithoutRealPlayer == false ||
(realPlayerIsLogged && DelayLoginBotsTimer != 0 && time(nullptr) >= DelayLoginBotsTimer)))
{
AddRandomBots();
}
}
else if (availableBotCount < maxAllowedBotCount)
{
AddRandomBots();
}
if (sPlayerbotAIConfig->syncLevelWithPlayers && !players.empty())
{
if (time(nullptr) > (PlayersCheckTimer + 60))
sRandomPlayerbotMgr->CheckPlayers();
}
if (sPlayerbotAIConfig->randomBotJoinBG /* && !players.empty()*/)
{
if (time(nullptr) > (BgCheckTimer + 35))
sRandomPlayerbotMgr->CheckBgQueue();
}
if (sPlayerbotAIConfig->randomBotJoinLfg /* && !players.empty()*/)
{
if (time(nullptr) > (LfgCheckTimer + 30))
sRandomPlayerbotMgr->CheckLfgQueue();
}
if (sPlayerbotAIConfig->randomBotAutologin && time(nullptr) > (printStatsTimer + 300))
{
if (!printStatsTimer)
{
printStatsTimer = time(nullptr);
}
else
{
sRandomPlayerbotMgr->PrintStats();
// activatePrintStatsThread();
}
}
uint32 updateBots = sPlayerbotAIConfig->randomBotsPerInterval * onlineBotFocus / 100;
uint32 maxNewBots =
onlineBotCount < maxAllowedBotCount &&
(sPlayerbotAIConfig->disabledWithoutRealPlayer == false ||
(realPlayerIsLogged && DelayLoginBotsTimer != 0 && time(nullptr) >= DelayLoginBotsTimer))
? maxAllowedBotCount - onlineBotCount
: 0;
uint32 loginBots = std::min(sPlayerbotAIConfig->randomBotsPerInterval - updateBots, maxNewBots);
if (!availableBots.empty())
{
// Update bots
for (auto bot : availableBots)
{
if (!GetPlayerBot(bot))
continue;
if (ProcessBot(bot))
{
updateBots--;
}
if (!updateBots)
break;
}
if (loginBots && botLoading.empty())
{
loginBots += updateBots;
loginBots = std::min(loginBots, maxNewBots);
LOG_DEBUG("playerbots", "{} new bots prepared to login", loginBots);
// Log in bots
for (auto bot : availableBots)
{
if (GetPlayerBot(bot))
continue;
if (ProcessBot(bot))
{
loginBots--;
}
if (!loginBots)
break;
}
DelayLoginBotsTimer = 0;
}
}
if (pmo)
pmo->finish();
if (sPlayerbotAIConfig->hasLog("player_location.csv"))
{
LogPlayerLocation();
}
}
// void RandomPlayerbotMgr::ScaleBotActivity()
//{
// float activityPercentage = getActivityPercentage();
//
// // if (activityPercentage >= 100.0f || activityPercentage <= 0.0f) pid.reset(); //Stop integer buildup during
// // max/min activity
//
// // % increase/decrease wanted diff , avg diff
// float activityPercentageMod = pid.calculate(
// sRandomPlayerbotMgr->GetPlayers().empty() ? sPlayerbotAIConfig->diffEmpty :
// sPlayerbotAIConfig->diffWithPlayer, sWorldUpdateTime.GetAverageUpdateTime());
//
// activityPercentage = activityPercentageMod + 50;
//
// // Cap the percentage between 0 and 100.
// activityPercentage = std::max(0.0f, std::min(100.0f, activityPercentage));
//
// setActivityPercentage(activityPercentage);
// }
// Assigns accounts as RNDbot accounts (type 1) based on MaxRandomBots and EnablePeriodicOnlineOffline and its ratio,
// and assigns accounts as AddClass accounts (type 2) based AddClassAccountPoolSize. Type 1 and 2 assignments are
// permenant, unless MaxRandomBots or AddClassAccountPoolSize are set to 0. If so, their associated accounts will
// be unassigned (type 0)
void RandomPlayerbotMgr::AssignAccountTypes()
{
LOG_INFO("playerbots", "Assigning account types for random bot accounts...");
// Clear existing filtered lists
rndBotTypeAccounts.clear();
addClassTypeAccounts.clear();
// First, get ALL randombot accounts from the database
std::vector<uint32> allRandomBotAccounts;
QueryResult allAccounts = LoginDatabase.Query(
"SELECT id FROM account WHERE username LIKE '{}%%' ORDER BY id",
sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
if (allAccounts)
{
do
{
Field* fields = allAccounts->Fetch();
uint32 accountId = fields[0].Get<uint32>();
allRandomBotAccounts.push_back(accountId);
} while (allAccounts->NextRow());
}
LOG_INFO("playerbots", "Found {} total randombot accounts in database", allRandomBotAccounts.size());
// Check existing assignments
QueryResult existingAssignments = PlayerbotsDatabase.Query("SELECT account_id, account_type FROM playerbots_account_type");
std::map<uint32, uint8> currentAssignments;
if (existingAssignments)
{
do
{
Field* fields = existingAssignments->Fetch();
uint32 accountId = fields[0].Get<uint32>();
uint8 accountType = fields[1].Get<uint8>();
currentAssignments[accountId] = accountType;
} while (existingAssignments->NextRow());
}
// Mark ALL randombot accounts as unassigned if not already assigned
for (uint32 accountId : allRandomBotAccounts)
{
if (currentAssignments.find(accountId) == currentAssignments.end())
{
PlayerbotsDatabase.Execute("INSERT INTO playerbots_account_type (account_id, account_type) VALUES ({}, 0) ON DUPLICATE KEY UPDATE account_type = account_type", accountId);
currentAssignments[accountId] = 0;
}
}
// Calculate needed RNDbot accounts
uint32 neededRndBotAccounts = 0;
if (sPlayerbotAIConfig->maxRandomBots > 0)
{
int divisor = RandomPlayerbotFactory::CalculateAvailableCharsPerAccount();
int maxBots = sPlayerbotAIConfig->maxRandomBots;
// Take periodic online-offline into account
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
{
maxBots *= sPlayerbotAIConfig->periodicOnlineOfflineRatio;
}
// Calculate base accounts needed for RNDbots, ensuring round up for maxBots not cleanly divisible by the divisor
neededRndBotAccounts = (maxBots + divisor - 1) / divisor;
}
// Count existing assigned accounts
uint32 existingRndBotAccounts = 0;
uint32 existingAddClassAccounts = 0;
for (auto const& [accountId, accountType] : currentAssignments)
{
if (accountType == 1) existingRndBotAccounts++;
else if (accountType == 2) existingAddClassAccounts++;
}
// Assign RNDbot accounts from lowest position if needed
if (existingRndBotAccounts < neededRndBotAccounts)
{
uint32 toAssign = neededRndBotAccounts - existingRndBotAccounts;
uint32 assigned = 0;
for (uint32 i = 0; i < allRandomBotAccounts.size() && assigned < toAssign; i++)
{
uint32 accountId = allRandomBotAccounts[i];
if (currentAssignments[accountId] == 0) // Unassigned
{
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 1, assignment_date = NOW() WHERE account_id = {}", accountId);
currentAssignments[accountId] = 1;
assigned++;
}
}
if (assigned < toAssign)
{
LOG_ERROR("playerbots", "Not enough unassigned accounts to fulfill RNDbot requirements. Need {} more accounts.", toAssign - assigned);
}
}
// Assign AddClass accounts from highest position if needed
uint32 neededAddClassAccounts = sPlayerbotAIConfig->addClassAccountPoolSize;
if (existingAddClassAccounts < neededAddClassAccounts)
{
uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts;
uint32 assigned = 0;
for (int i = allRandomBotAccounts.size() - 1; i >= 0 && assigned < toAssign; i--)
{
uint32 accountId = allRandomBotAccounts[i];
if (currentAssignments[accountId] == 0) // Unassigned
{
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId);
currentAssignments[accountId] = 2;
assigned++;
}
}
if (assigned < toAssign)
{
LOG_ERROR("playerbots", "Not enough unassigned accounts to fulfill AddClass requirements. Need {} more accounts.", toAssign - assigned);
}
}
// Populate filtered account lists with ALL accounts of each type
for (auto const& [accountId, accountType] : currentAssignments)
{
if (accountType == 1) rndBotTypeAccounts.push_back(accountId);
else if (accountType == 2) addClassTypeAccounts.push_back(accountId);
}
LOG_INFO("playerbots", "Account type assignment complete: {} RNDbot accounts, {} AddClass accounts, {} unassigned",
rndBotTypeAccounts.size(), addClassTypeAccounts.size(),
currentAssignments.size() - rndBotTypeAccounts.size() - addClassTypeAccounts.size());
}
bool RandomPlayerbotMgr::IsAccountType(uint32 accountId, uint8 accountType)
{
QueryResult result = PlayerbotsDatabase.Query("SELECT 1 FROM playerbots_account_type WHERE account_id = {} AND account_type = {}", accountId, accountType);
return result != nullptr;
}
// Logs-in bots in 4 phases. Phase 1 logs Alliance bots up to how much is expected according to the faction ratio,
// and Phase 2 logs-in the remainder Horde bots to reach the total maxAllowedBotCount. If maxAllowedBotCount is not
// reached after Phase 2, the function goes back to log-in Alliance bots and reach maxAllowedBotCount. This is done
// because not every account is guaranteed 5A/5H bots, so the true ratio might be skewed by few percentages. Finally,
// Phase 4 is reached if and only if the value of RandomBotAccountCount is lower than it should.
uint32 RandomPlayerbotMgr::AddRandomBots()
{
uint32 maxAllowedBotCount = GetEventValue(0, "bot_count");
static time_t missingBotsTimer = 0;
if (currentBots.size() < maxAllowedBotCount)
{
// Calculate how many bots to add
maxAllowedBotCount -= currentBots.size();
maxAllowedBotCount = std::min(sPlayerbotAIConfig->randomBotsPerInterval, maxAllowedBotCount);
// Single RNG instance for all shuffling
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
// Only need to track the Alliance count, as it's in Phase 1
uint32 totalRatio = sPlayerbotAIConfig->randomBotAllianceRatio + sPlayerbotAIConfig->randomBotHordeRatio;
uint32 allowedAllianceCount = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) / totalRatio;
uint32 remainder = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) % totalRatio;
// Fix #1082: Randomly add one based on reminder
if (remainder && urand(1, totalRatio) <= remainder)
{
allowedAllianceCount++;
}
// Determine which accounts to use based on EnablePeriodicOnlineOffline
std::vector<uint32> accountsToUse;
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
{
// Calculate how many accounts can be used
// With enablePeriodicOnlineOffline, don't use all of rndBotTypeAccounts right away. Fraction results are rounded up
uint32 accountsToUseCount = (rndBotTypeAccounts.size() + sPlayerbotAIConfig->periodicOnlineOfflineRatio - 1)
/ sPlayerbotAIConfig->periodicOnlineOfflineRatio;
// Randomly select accounts
std::vector<uint32> shuffledAccounts = rndBotTypeAccounts;
std::shuffle(shuffledAccounts.begin(), shuffledAccounts.end(), rng);
for (uint32 i = 0; i < accountsToUseCount && i < shuffledAccounts.size(); i++)
{
accountsToUse.push_back(shuffledAccounts[i]);
}
}
else
{
accountsToUse = rndBotTypeAccounts;
}
// Pre-map all characters from selected accounts
struct CharacterInfo
{
uint32 guid;
uint8 rClass;
uint8 rRace;
uint32 accountId;
};
std::vector<CharacterInfo> allCharacters;
for (uint32 accountId : accountsToUse)
{
CharacterDatabasePreparedStatement* stmt =
CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID);
stmt->SetData(0, accountId);
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (!result)
continue;
do
{
Field* fields = result->Fetch();
CharacterInfo info;
info.guid = fields[0].Get<uint32>();
info.rClass = fields[1].Get<uint8>();
info.rRace = fields[2].Get<uint8>();
info.accountId = accountId;
allCharacters.push_back(info);
} while (result->NextRow());
}
// Shuffle for class balance
std::shuffle(allCharacters.begin(), allCharacters.end(), rng);
// Separate characters by faction for phased login
std::vector<CharacterInfo> allianceChars;
std::vector<CharacterInfo> hordeChars;
for (auto const& charInfo : allCharacters)
{
if (IsAlliance(charInfo.rRace))
allianceChars.push_back(charInfo);
else
hordeChars.push_back(charInfo);
}
// Lambda to handle bot login logic
auto tryLoginBot = [&](const CharacterInfo& charInfo) -> bool
{
if (GetEventValue(charInfo.guid, "add") ||
GetEventValue(charInfo.guid, "logout") ||
GetPlayerBot(charInfo.guid) ||
std::find(currentBots.begin(), currentBots.end(), charInfo.guid) != currentBots.end() ||
(sPlayerbotAIConfig->disableDeathKnightLogin && charInfo.rClass == CLASS_DEATH_KNIGHT))
{
return false;
}
uint32 add_time = sPlayerbotAIConfig->enablePeriodicOnlineOffline
? urand(sPlayerbotAIConfig->minRandomBotInWorldTime,
sPlayerbotAIConfig->maxRandomBotInWorldTime)
: sPlayerbotAIConfig->permanentlyInWorldTime;
SetEventValue(charInfo.guid, "add", 1, add_time);
SetEventValue(charInfo.guid, "logout", 0, 0);
currentBots.push_back(charInfo.guid);
return true;
};
// PHASE 1: Log-in Alliance bots up to allowedAllianceCount
for (auto const& charInfo : allianceChars)
{
if (!allowedAllianceCount)
break;
if (tryLoginBot(charInfo))
{
maxAllowedBotCount--;
allowedAllianceCount--;
}
}
// PHASE 2: Log-in Horde bots up to maxAllowedBotCount
for (auto const& charInfo : hordeChars)
{
if (!maxAllowedBotCount)
break;
if (tryLoginBot(charInfo))
maxAllowedBotCount--;
}
// PHASE 3: If maxAllowedBotCount wasn't reached, log-in more Alliance bots
for (auto const& charInfo : allianceChars)
{
if (!maxAllowedBotCount)
break;
if (tryLoginBot(charInfo))
maxAllowedBotCount--;
}
// PHASE 4: An error is given if maxAllowedBotCount is still not reached
if (maxAllowedBotCount)
{
if (missingBotsTimer == 0)
missingBotsTimer = time(nullptr);
if (time(nullptr) - missingBotsTimer >= 10)
{
int divisor = RandomPlayerbotFactory::CalculateAvailableCharsPerAccount();
uint32 moreAccountsNeeded = (maxAllowedBotCount + divisor - 1) / divisor;
LOG_ERROR("playerbots",
"Can't log-in all the requested bots. Try increasing RandomBotAccountCount in your conf file.\n"
"{} more accounts needed.", moreAccountsNeeded);
missingBotsTimer = 0; // Reset timer so error is not spammed every tick
}
}
else
{
missingBotsTimer = 0; // Reset timer if logins for this interval were successful
}
}
else
{
missingBotsTimer = 0; // Reset timer if there's enough bots
}
return currentBots.size();
}
void RandomPlayerbotMgr::LoadBattleMastersCache()
{
BattleMastersCache.clear();
LOG_INFO("playerbots", "Loading Battlemasters Cache...");
QueryResult result = WorldDatabase.Query("SELECT `entry`,`bg_template` FROM `battlemaster_entry`");
uint32 count = 0;
if (!result)
{
return;
}
do
{
++count;
Field* fields = result->Fetch();
uint32 entry = fields[0].Get<uint32>();
uint32 bgTypeId = fields[1].Get<uint32>();
CreatureTemplate const* bmaster = sObjectMgr->GetCreatureTemplate(entry);
if (!bmaster)
continue;
FactionTemplateEntry const* bmFaction = sFactionTemplateStore.LookupEntry(bmaster->faction);
uint32 bmFactionId = bmFaction->faction;
FactionEntry const* bmParentFaction = sFactionStore.LookupEntry(bmFactionId);
uint32 bmParentTeam = bmParentFaction->team;
TeamId bmTeam = TEAM_NEUTRAL;
if (bmParentTeam == 891)
bmTeam = TEAM_ALLIANCE;
if (bmFactionId == 189)
bmTeam = TEAM_ALLIANCE;
if (bmParentTeam == 892)
bmTeam = TEAM_HORDE;
if (bmFactionId == 66)
bmTeam = TEAM_HORDE;
BattleMastersCache[bmTeam][BattlegroundTypeId(bgTypeId)].insert(
BattleMastersCache[bmTeam][BattlegroundTypeId(bgTypeId)].end(), entry);
LOG_DEBUG("playerbots", "Cached Battlemaster #{} for BG Type {} ({})", entry, bgTypeId,
bmTeam == TEAM_ALLIANCE ? "Alliance"
: bmTeam == TEAM_HORDE ? "Horde"
: "Neutral");
} while (result->NextRow());
LOG_INFO("playerbots", ">> Loaded {} battlemaster entries", count);
}
std::vector<uint32> parseBrackets(const std::string& str)
{
std::vector<uint32> brackets;
std::stringstream ss(str);
std::string item;
while (std::getline(ss, item, ','))
{
brackets.push_back(static_cast<uint32>(std::stoi(item)));
}
return brackets;
}
void RandomPlayerbotMgr::CheckBgQueue()
{
if (!BgCheckTimer)
{
BgCheckTimer = time(nullptr);
return; // Exit immediately after initializing the timer
}
if (time(nullptr) < BgCheckTimer)
{
return; // No need to proceed if the current time is less than the timer
}
// Update the timer to the current time
BgCheckTimer = time(nullptr);
LOG_DEBUG("playerbots", "Checking BG Queue...");
// Initialize Battleground Data (do not clear here)
for (int bracket = BG_BRACKET_ID_FIRST; bracket < MAX_BATTLEGROUND_BRACKETS; ++bracket)
{
for (int queueType = BATTLEGROUND_QUEUE_AV; queueType < MAX_BATTLEGROUND_QUEUE_TYPES; ++queueType)
{
BattlegroundData[queueType][bracket] = BattlegroundInfo();
}
}
// Process real players and populate Battleground Data with player/queue count
// Opens a queue for bots to join
for (Player* player : players)
{
// Skip player if not currently in a queue
if (!player->InBattlegroundQueue())
continue;
Battleground* bg = player->GetBattleground();
if (bg && bg->GetStatus() == STATUS_WAIT_LEAVE)
continue;
TeamId teamId = player->GetTeamId();
for (uint8 queueType = 0; queueType < PLAYER_MAX_BATTLEGROUND_QUEUES; ++queueType)
{
BattlegroundQueueTypeId queueTypeId = player->GetBattlegroundQueueTypeId(queueType);
if (queueTypeId == BATTLEGROUND_QUEUE_NONE)
continue;
// Check if real player is able to create/join this queue
BattlegroundTypeId bgTypeId = sBattlegroundMgr->BGTemplateId(queueTypeId);
uint32 mapId = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId)->GetMapId();
PvPDifficultyEntry const* pvpDiff = GetBattlegroundBracketByLevel(mapId, player->GetLevel());
if (!pvpDiff)
continue;
// If player is allowed, populate the BattlegroundData with the appropriate level requirements
BattlegroundBracketId bracketId = pvpDiff->GetBracketId();
BattlegroundData[queueTypeId][bracketId].minLevel = pvpDiff->minLevel;
BattlegroundData[queueTypeId][bracketId].maxLevel = pvpDiff->maxLevel;
// Arena logic
bool isRated = false;
if (uint8 arenaType = BattlegroundMgr::BGArenaType(queueTypeId))
{
BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(queueTypeId);
GroupQueueInfo ginfo;
if (bgQueue.GetPlayerGroupInfoData(player->GetGUID(), &ginfo))
{
isRated = ginfo.IsRated;
}
if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) ||
(player->InArena() && player->GetBattleground()->isRated()))
isRated = true;
if (isRated)
BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount++;
else
BattlegroundData[queueTypeId][bracketId].skirmishArenaPlayerCount++;
}
// BG Logic
else
{
if (teamId == TEAM_ALLIANCE)
BattlegroundData[queueTypeId][bracketId].bgAlliancePlayerCount++;
else
BattlegroundData[queueTypeId][bracketId].bgHordePlayerCount++;
// If a player has joined the BG, update the instance count in BattlegroundData (for consistency)
if (player->InBattleground())
{
std::vector<uint32>* instanceIds = nullptr;
uint32 instanceId = player->GetBattleground()->GetInstanceID();
instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances;
if (instanceIds &&
std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end())
instanceIds->push_back(instanceId);
BattlegroundData[queueTypeId][bracketId].bgInstanceCount = instanceIds->size();
}
}
if (!player->IsInvitedForBattlegroundInstance() && !player->InBattleground())
{
if (BattlegroundMgr::BGArenaType(queueTypeId))
{
if (isRated)
BattlegroundData[queueTypeId][bracketId].activeRatedArenaQueue = 1;
else
BattlegroundData[queueTypeId][bracketId].activeSkirmishArenaQueue = 1;
}
else
{
BattlegroundData[queueTypeId][bracketId].activeBgQueue = 1;
}
}
}
}
// Process player bots
for (auto& [guid, bot] : playerBots)
{
if (!bot || !bot->InBattlegroundQueue() || !bot->IsInWorld() || !IsRandomBot(bot))
continue;
Battleground* bg = bot->GetBattleground();
if (bg && bg->GetStatus() == STATUS_WAIT_LEAVE)
continue;
TeamId teamId = bot->GetTeamId();
for (uint8 queueType = 0; queueType < PLAYER_MAX_BATTLEGROUND_QUEUES; ++queueType)
{
BattlegroundQueueTypeId queueTypeId = bot->GetBattlegroundQueueTypeId(queueType);
if (queueTypeId == BATTLEGROUND_QUEUE_NONE)
continue;
BattlegroundTypeId bgTypeId = sBattlegroundMgr->BGTemplateId(queueTypeId);
uint32 mapId = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId)->GetMapId();
PvPDifficultyEntry const* pvpDiff = GetBattlegroundBracketByLevel(mapId, bot->GetLevel());
if (!pvpDiff)
continue;
BattlegroundBracketId bracketId = pvpDiff->GetBracketId();
BattlegroundData[queueTypeId][bracketId].minLevel = pvpDiff->minLevel;
BattlegroundData[queueTypeId][bracketId].maxLevel = pvpDiff->maxLevel;
if (uint8 arenaType = BattlegroundMgr::BGArenaType(queueTypeId))
{
bool isRated = false;
BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(queueTypeId);
GroupQueueInfo ginfo;
if (bgQueue.GetPlayerGroupInfoData(guid, &ginfo))
{
isRated = ginfo.IsRated;
}
if (bgQueue.IsPlayerInvitedToRatedArena(guid) || (bot->InArena() && bot->GetBattleground()->isRated()))
isRated = true;
if (isRated)
BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount++;
else
BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount++;
}
else
{
if (teamId == TEAM_ALLIANCE)
BattlegroundData[queueTypeId][bracketId].bgAllianceBotCount++;
else
BattlegroundData[queueTypeId][bracketId].bgHordeBotCount++;
}
if (bot->InBattleground())
{
std::vector<uint32>* instanceIds = nullptr;
uint32 instanceId = bot->GetBattleground()->GetInstanceID();
bool isArena = false;
bool isRated = false;
// Arena logic
if (bot->InArena())
{
isArena = true;
if (bot->GetBattleground()->isRated())
{
isRated = true;
instanceIds = &BattlegroundData[queueTypeId][bracketId].ratedArenaInstances;
}
else
{
instanceIds = &BattlegroundData[queueTypeId][bracketId].skirmishArenaInstances;
}
}
// BG Logic
else
{
instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances;
}
if (instanceIds &&
std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end())
instanceIds->push_back(instanceId);
if (isArena)
{
if (isRated)
BattlegroundData[queueTypeId][bracketId].ratedArenaInstanceCount = instanceIds->size();
else
BattlegroundData[queueTypeId][bracketId].skirmishArenaInstanceCount = instanceIds->size();
}
else
{
BattlegroundData[queueTypeId][bracketId].bgInstanceCount = instanceIds->size();
}
}
}
}
// If enabled, wait for all bots to have logged in before queueing for Arena's / BG's
if (sPlayerbotAIConfig->randomBotAutoJoinBG && playerBots.size() >= GetMaxAllowedBotCount())
{
uint32 randomBotAutoJoinArenaBracket = sPlayerbotAIConfig->randomBotAutoJoinArenaBracket;
uint32 randomBotAutoJoinBGRatedArena2v2Count = sPlayerbotAIConfig->randomBotAutoJoinBGRatedArena2v2Count;
uint32 randomBotAutoJoinBGRatedArena3v3Count = sPlayerbotAIConfig->randomBotAutoJoinBGRatedArena3v3Count;
uint32 randomBotAutoJoinBGRatedArena5v5Count = sPlayerbotAIConfig->randomBotAutoJoinBGRatedArena5v5Count;
uint32 randomBotAutoJoinBGICCount = sPlayerbotAIConfig->randomBotAutoJoinBGICCount;
uint32 randomBotAutoJoinBGEYCount = sPlayerbotAIConfig->randomBotAutoJoinBGEYCount;
uint32 randomBotAutoJoinBGAVCount = sPlayerbotAIConfig->randomBotAutoJoinBGAVCount;
uint32 randomBotAutoJoinBGABCount = sPlayerbotAIConfig->randomBotAutoJoinBGABCount;
uint32 randomBotAutoJoinBGWSCount = sPlayerbotAIConfig->randomBotAutoJoinBGWSCount;
std::vector<uint32> icBrackets = parseBrackets(sPlayerbotAIConfig->randomBotAutoJoinICBrackets);
std::vector<uint32> eyBrackets = parseBrackets(sPlayerbotAIConfig->randomBotAutoJoinEYBrackets);
std::vector<uint32> avBrackets = parseBrackets(sPlayerbotAIConfig->randomBotAutoJoinAVBrackets);
std::vector<uint32> abBrackets = parseBrackets(sPlayerbotAIConfig->randomBotAutoJoinABBrackets);
std::vector<uint32> wsBrackets = parseBrackets(sPlayerbotAIConfig->randomBotAutoJoinWSBrackets);
// Check both bgInstanceCount / bgInstances.size
// to help counter against potentional inconsistencies
auto updateRatedArenaInstanceCount = [&](uint32 queueType, uint32 bracket, uint32 minCount)
{
if (BattlegroundData[queueType][bracket].activeRatedArenaQueue == 0 &&
BattlegroundData[queueType][bracket].ratedArenaInstanceCount < minCount &&
BattlegroundData[queueType][bracket].ratedArenaInstances.size() < minCount)
BattlegroundData[queueType][bracket].activeRatedArenaQueue = 1;
};
auto updateBGInstanceCount = [&](uint32 queueType, std::vector<uint32> brackets, uint32 minCount)
{
for (uint32 bracket : brackets)
{
if (BattlegroundData[queueType][bracket].activeBgQueue == 0 &&
BattlegroundData[queueType][bracket].bgInstanceCount < minCount &&
BattlegroundData[queueType][bracket].bgInstances.size() < minCount)
BattlegroundData[queueType][bracket].activeBgQueue = 1;
}
};
// Update rated arena instance counts
updateRatedArenaInstanceCount(BATTLEGROUND_QUEUE_2v2, randomBotAutoJoinArenaBracket,
randomBotAutoJoinBGRatedArena2v2Count);
updateRatedArenaInstanceCount(BATTLEGROUND_QUEUE_3v3, randomBotAutoJoinArenaBracket,
randomBotAutoJoinBGRatedArena3v3Count);
updateRatedArenaInstanceCount(BATTLEGROUND_QUEUE_5v5, randomBotAutoJoinArenaBracket,
randomBotAutoJoinBGRatedArena5v5Count);
// Update battleground instance counts
updateBGInstanceCount(BATTLEGROUND_QUEUE_IC, icBrackets, randomBotAutoJoinBGICCount);
updateBGInstanceCount(BATTLEGROUND_QUEUE_EY, eyBrackets, randomBotAutoJoinBGEYCount);
updateBGInstanceCount(BATTLEGROUND_QUEUE_AV, avBrackets, randomBotAutoJoinBGAVCount);
updateBGInstanceCount(BATTLEGROUND_QUEUE_AB, abBrackets, randomBotAutoJoinBGABCount);
updateBGInstanceCount(BATTLEGROUND_QUEUE_WS, wsBrackets, randomBotAutoJoinBGWSCount);
}
LogBattlegroundInfo();
}
void RandomPlayerbotMgr::LogBattlegroundInfo()
{
for (auto const& queueTypePair : BattlegroundData)
{
uint8 queueType = queueTypePair.first;
BattlegroundQueueTypeId queueTypeId = BattlegroundQueueTypeId(queueType);
if (uint8 type = BattlegroundMgr::BGArenaType(queueTypeId))
{
for (auto const& bracketIdPair : queueTypePair.second)
{
auto& bgInfo = bracketIdPair.second;
if (bgInfo.minLevel == 0)
continue;
LOG_INFO("playerbots",
"ARENA:{} {}: Player (Skirmish:{}, Rated:{}) Bots (Skirmish:{}, Rated:{}) Total (Skirmish:{} "
"Rated:{}), Instances (Skirmish:{} Rated:{})",
type == ARENA_TYPE_2v2 ? "2v2"
: type == ARENA_TYPE_3v3 ? "3v3"
: "5v5",
std::to_string(bgInfo.minLevel) + "-" + std::to_string(bgInfo.maxLevel),
bgInfo.skirmishArenaPlayerCount, bgInfo.ratedArenaPlayerCount, bgInfo.skirmishArenaBotCount,
bgInfo.ratedArenaBotCount, bgInfo.skirmishArenaPlayerCount + bgInfo.skirmishArenaBotCount,
bgInfo.ratedArenaPlayerCount + bgInfo.ratedArenaBotCount, bgInfo.skirmishArenaInstanceCount,
bgInfo.ratedArenaInstanceCount);
}
continue;
}
BattlegroundTypeId bgTypeId = BattlegroundMgr::BGTemplateId(queueTypeId);
std::string _bgType;
switch (bgTypeId)
{
case BATTLEGROUND_AV:
_bgType = "AV";
break;
case BATTLEGROUND_WS:
_bgType = "WSG";
break;
case BATTLEGROUND_AB:
_bgType = "AB";
break;
case BATTLEGROUND_EY:
_bgType = "EotS";
break;
case BATTLEGROUND_RB:
_bgType = "Random";
break;
case BATTLEGROUND_SA:
_bgType = "SotA";
break;
case BATTLEGROUND_IC:
_bgType = "IoC";
break;
default:
_bgType = "Other";
break;
}
for (auto const& bracketIdPair : queueTypePair.second)
{
auto& bgInfo = bracketIdPair.second;
if (bgInfo.minLevel == 0)
continue;
LOG_INFO("playerbots",
"BG:{} {}: Player ({}:{}) Bot ({}:{}) Total (A:{} H:{}), Instances {}, Active Queue: {}", _bgType,
std::to_string(bgInfo.minLevel) + "-" + std::to_string(bgInfo.maxLevel),
bgInfo.bgAlliancePlayerCount, bgInfo.bgHordePlayerCount, bgInfo.bgAllianceBotCount,
bgInfo.bgHordeBotCount, bgInfo.bgAlliancePlayerCount + bgInfo.bgAllianceBotCount,
bgInfo.bgHordePlayerCount + bgInfo.bgHordeBotCount, bgInfo.bgInstanceCount, bgInfo.activeBgQueue);
}
}
LOG_DEBUG("playerbots", "BG Queue check finished");
}
void RandomPlayerbotMgr::CheckLfgQueue()
{
if (!LfgCheckTimer || time(nullptr) > (LfgCheckTimer + 30))
LfgCheckTimer = time(nullptr);
LOG_DEBUG("playerbots", "Checking LFG Queue...");
// Clear LFG list
LfgDungeons[TEAM_ALLIANCE].clear();
LfgDungeons[TEAM_HORDE].clear();
for (std::vector<Player*>::iterator i = players.begin(); i != players.end(); ++i)
{
Player* player = *i;
if (!player || !player->IsInWorld())
continue;
Group* group = player->GetGroup();
ObjectGuid guid = group ? group->GetGUID() : player->GetGUID();
lfg::LfgState gState = sLFGMgr->GetState(guid);
if (gState != lfg::LFG_STATE_NONE && gState < lfg::LFG_STATE_DUNGEON)
{
lfg::LfgDungeonSet const& dList = sLFGMgr->GetSelectedDungeons(player->GetGUID());
for (lfg::LfgDungeonSet::const_iterator itr = dList.begin(); itr != dList.end(); ++itr)
{
lfg::LFGDungeonData const* dungeon = sLFGMgr->GetLFGDungeon(*itr);
if (!dungeon)
continue;
LfgDungeons[player->GetTeamId()].push_back(dungeon->id);
}
}
}
LOG_DEBUG("playerbots", "LFG Queue check finished");
}
void RandomPlayerbotMgr::CheckPlayers()
{
if (!PlayersCheckTimer || time(nullptr) > (PlayersCheckTimer + 60))
PlayersCheckTimer = time(nullptr);
LOG_INFO("playerbots", "Checking Players...");
if (!playersLevel)
playersLevel = sPlayerbotAIConfig->randombotStartingLevel;
for (std::vector<Player*>::iterator i = players.begin(); i != players.end(); ++i)
{
Player* player = *i;
if (player->IsGameMaster())
continue;
// if (player->GetSession()->GetSecurity() > SEC_PLAYER)
// continue;
if (player->GetLevel() > playersLevel)
playersLevel = player->GetLevel() + 3;
}
LOG_INFO("playerbots", "Max player level is {}, max bot level set to {}", playersLevel - 3, playersLevel);
}
void RandomPlayerbotMgr::ScheduleRandomize(uint32 bot, uint32 time) { SetEventValue(bot, "randomize", 1, time); }
void RandomPlayerbotMgr::ScheduleTeleport(uint32 bot, uint32 time)
{
if (!time)
time = 60 + urand(sPlayerbotAIConfig->randomBotUpdateInterval, sPlayerbotAIConfig->randomBotUpdateInterval * 3);
SetEventValue(bot, "teleport", 1, time);
}
void RandomPlayerbotMgr::ScheduleChangeStrategy(uint32 bot, uint32 time)
{
if (!time)
time = urand(sPlayerbotAIConfig->minRandomBotChangeStrategyTime,
sPlayerbotAIConfig->maxRandomBotChangeStrategyTime);
SetEventValue(bot, "change_strategy", 1, time);
}
bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
{
ObjectGuid botGUID = ObjectGuid::Create<HighGuid::Player>(bot);
Player* player = GetPlayerBot(botGUID);
PlayerbotAI* botAI = player ? GET_PLAYERBOT_AI(player) : nullptr;
uint32 isValid = GetEventValue(bot, "add");
if (!isValid)
{
if (!player || !player->GetGroup())
{
if (player)
LOG_DEBUG("playerbots", "Bot #{} {}:{} <{}>: log out", bot, IsAlliance(player->getRace()) ? "A" : "H",
player->GetLevel(), player->GetName().c_str());
else
LOG_DEBUG("playerbots", "Bot #{}: log out", bot);
SetEventValue(bot, "add", 0, 0);
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
if (player)
LogoutPlayerBot(botGUID);
}
return false;
}
uint32 randomTime;
if (!player)
{
AddPlayerBot(botGUID, 0);
randomTime = urand(1, 2);
uint32 randomBotUpdateInterval = _isBotInitializing ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval;
randomTime = urand(std::max(5, static_cast<int>(randomBotUpdateInterval * 0.5)),
std::max(12, static_cast<int>(randomBotUpdateInterval * 2)));
SetEventValue(bot, "update", 1, randomTime);
// do not randomize or teleport immediately after server start (prevent lagging)
if (!GetEventValue(bot, "randomize"))
{
randomTime = urand(3, std::max(4, static_cast<int>(randomBotUpdateInterval * 0.4)));
ScheduleRandomize(bot, randomTime);
}
if (!GetEventValue(bot, "teleport"))
{
randomTime = urand(std::max(7, static_cast<int>(randomBotUpdateInterval * 0.7)),
std::max(14, static_cast<int>(randomBotUpdateInterval * 1.4)));
ScheduleTeleport(bot, randomTime);
}
return true;
}
if (!player->IsInWorld())
return false;
if (player->GetGroup() || player->HasUnitState(UNIT_STATE_IN_FLIGHT))
return false;
uint32 update = GetEventValue(bot, "update");
if (!update)
{
if (botAI)
botAI->GetAiObjectContext()->GetValue<bool>("random bot update")->Set(true);
bool update = true;
if (botAI)
{
// botAI->GetAiObjectContext()->GetValue<bool>("random bot update")->Set(true);
if (!sRandomPlayerbotMgr->IsRandomBot(player))
update = false;
if (player->GetGroup() && botAI->GetGroupMaster())
{
PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
{
update = false;
}
}
// if (botAI->HasPlayerNearby(sPlayerbotAIConfig->grindDistance))
// update = false;
}
if (update)
ProcessBot(player);
randomTime = urand(sPlayerbotAIConfig->minRandomBotReviveTime, sPlayerbotAIConfig->maxRandomBotReviveTime);
SetEventValue(bot, "update", 1, randomTime);
return true;
}
uint32 logout = GetEventValue(bot, "logout");
if (player && !logout && !isValid)
{
LOG_DEBUG("playerbots", "Bot #{} {}:{} <{}>: log out", bot, IsAlliance(player->getRace()) ? "A" : "H",
player->GetLevel(), player->GetName().c_str());
LogoutPlayerBot(botGUID);
currentBots.remove(bot);
SetEventValue(bot, "logout", 1,
urand(sPlayerbotAIConfig->minRandomBotInWorldTime, sPlayerbotAIConfig->maxRandomBotInWorldTime));
return true;
}
return false;
}
bool RandomPlayerbotMgr::ProcessBot(Player* bot)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
return false;
if (bot->InBattleground())
return false;
if (bot->InBattlegroundQueue())
return false;
uint32 botId = bot->GetGUID().GetCounter();
// if death revive
if (bot->isDead())
{
if (!GetEventValue(botId, "dead"))
{
uint32 randomTime =
urand(sPlayerbotAIConfig->minRandomBotReviveTime, sPlayerbotAIConfig->maxRandomBotReviveTime);
LOG_DEBUG("playerbots", "Mark bot {} as dead, will be revived in {}s.", bot->GetName().c_str(),
randomTime);
SetEventValue(botId, "dead", 1, sPlayerbotAIConfig->maxRandomBotInWorldTime);
SetEventValue(botId, "revive", 1, randomTime);
return false;
}
if (!GetEventValue(botId, "revive"))
{
Revive(bot);
return true;
}
return false;
}
// leave group if leader is rndbot
Group* group = bot->GetGroup();
if (group && !group->isLFGGroup() && IsRandomBot(group->GetLeader()))
{
botAI->LeaveOrDisbandGroup();
LOG_INFO("playerbots", "Bot {} remove from group since leader is random bot.", bot->GetName().c_str());
}
// only randomize and teleport idle bots
bool idleBot = false;
if (TravelTarget* target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get())
{
if (target->getTravelState() == TravelState::TRAVEL_STATE_IDLE)
{
idleBot = true;
}
}
else
{
idleBot = true;
}
if (idleBot)
{
// randomize
uint32 randomize = GetEventValue(botId, "randomize");
if (!randomize)
{
// bool randomiser = true;
// if (player->GetGuildId())
// {
// if (Guild* guild = sGuildMgr->GetGuildById(player->GetGuildId()))
// {
// if (guild->GetLeaderGUID() == player->GetGUID())
// {
// for (std::vector<Player*>::iterator i = players.begin(); i != players.end(); ++i)
// sGuildTaskMgr->Update(*i, player);
// }
// uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guild->GetLeaderGUID());
// if (!sPlayerbotAIConfig->IsInRandomAccountList(accountId))
// {
// uint8 rank = player->GetRank();
// randomiser = rank < 4 ? false : true;
// }
// }
// }
// if (randomiser)
// {
Randomize(bot);
LOG_DEBUG("playerbots", "Bot #{} {}:{} <{}>: randomized", botId,
bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), bot->GetName());
uint32 randomTime =
urand(sPlayerbotAIConfig->minRandomBotRandomizeTime, sPlayerbotAIConfig->maxRandomBotRandomizeTime);
ScheduleRandomize(botId, randomTime);
return true;
}
// uint32 changeStrategy = GetEventValue(bot, "change_strategy");
// if (!changeStrategy)
// {
// LOG_INFO("playerbots", "Changing strategy for bot #{} <{}>", bot, player->GetName().c_str());
// ChangeStrategy(player);
// return true;
// }
uint32 teleport = GetEventValue(botId, "teleport");
if (!teleport)
{
LOG_DEBUG("playerbots", "Bot #{} <{}>: teleport for level and refresh", botId, bot->GetName());
Refresh(bot);
RandomTeleportForLevel(bot);
uint32 time = urand(sPlayerbotAIConfig->minRandomBotTeleportInterval,
sPlayerbotAIConfig->maxRandomBotTeleportInterval);
ScheduleTeleport(botId, time);
return true;
}
}
return false;
}
void RandomPlayerbotMgr::Revive(Player* player)
{
uint32 bot = player->GetGUID().GetCounter();
// LOG_INFO("playerbots", "Bot {} revived", player->GetName().c_str());
SetEventValue(bot, "dead", 0, 0);
SetEventValue(bot, "revive", 0, 0);
Refresh(player);
RandomTeleportGrindForLevel(player);
}
void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth)
{
// ignore when alrdy teleported or not in the world yet.
if (bot->IsBeingTeleported() || !bot->IsInWorld())
return;
// ignore when in queue for battle grounds.
if (bot->InBattlegroundQueue())
return;
// ignore when in battle grounds or arena.
if (bot->InBattleground() || bot->InArena())
return;
// ignore when in group (e.g. world, dungeons, raids) and leader is not a player.
if (bot->GetGroup() && !bot->GetGroup()->IsLeader(bot->GetGUID()))
return;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI)
{
// ignore when in when taxi with boat/zeppelin and has players nearby
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING) &&
botAI->HasPlayerNearby())
return;
}
// if (sPlayerbotAIConfig->randomBotRpgChance < 0)
// return;
if (locs.empty())
{
LOG_DEBUG("playerbots", "Cannot teleport bot {} - no locations available", bot->GetName().c_str());
return;
}
std::vector<WorldPosition> tlocs;
for (auto& loc : locs)
tlocs.push_back(WorldPosition(loc));
// Do not teleport to maps disabled in config
tlocs.erase(std::remove_if(tlocs.begin(), tlocs.end(),
[bot](WorldPosition l)
{
std::vector<uint32>::iterator i =
find(sPlayerbotAIConfig->randomBotMaps.begin(),
sPlayerbotAIConfig->randomBotMaps.end(), l.getMapId());
return i == sPlayerbotAIConfig->randomBotMaps.end();
}),
tlocs.end());
if (tlocs.empty())
{
LOG_DEBUG("playerbots", "Cannot teleport bot {} - all locations removed by filter", bot->GetName().c_str());
return;
}
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "RandomTeleportByLocations");
std::shuffle(std::begin(tlocs), std::end(tlocs), RandomEngine::Instance());
for (uint32 i = 0; i < tlocs.size(); i++)
{
WorldLocation loc = tlocs[i];
float x = loc.GetPositionX(); // + (attemtps > 0 ? urand(0, sPlayerbotAIConfig->grindDistance) -
// sPlayerbotAIConfig->grindDistance / 2 : 0);
float y = loc.GetPositionY(); // + (attemtps > 0 ? urand(0, sPlayerbotAIConfig->grindDistance) -
// sPlayerbotAIConfig->grindDistance / 2 : 0);
float z = loc.GetPositionZ();
Map* map = sMapMgr->FindMap(loc.GetMapId(), 0);
if (!map)
continue;
AreaTableEntry const* zone = sAreaTableStore.LookupEntry(map->GetZoneId(bot->GetPhaseMask(), x, y, z));
if (!zone)
continue;
AreaTableEntry const* area = sAreaTableStore.LookupEntry(map->GetAreaId(bot->GetPhaseMask(), x, y, z));
if (!area)
continue;
// Do not teleport to enemy zones if level is low
if (zone->team == 4 && bot->GetTeamId() == TEAM_ALLIANCE)
continue;
if (zone->team == 2 && bot->GetTeamId() == TEAM_HORDE)
continue;
if (map->IsInWater(bot->GetPhaseMask(), x, y, z, bot->GetCollisionHeight()))
continue;
float ground = map->GetHeight(bot->GetPhaseMask(), x, y, z + 0.5f);
if (ground <= INVALID_HEIGHT)
continue;
z = 0.05f + ground;
if (!botAI->CheckLocationDistanceByLevel(bot, loc, true))
continue;
const LocaleConstant& locale = sWorld->GetDefaultDbcLocale();
LOG_DEBUG("playerbots",
"Random teleporting bot {} (level {}) to Map: {} ({}) Zone: {} ({}) Area: {} ({}) ZoneLevel: {} "
"AreaLevel: {} {},{},{} ({}/{} "
"locations)",
bot->GetName().c_str(), bot->GetLevel(), map->GetId(), map->GetMapName(), zone->ID,
zone->area_name[locale], area->ID, area->area_name[locale], zone->area_level, area->area_level, x, y,
z, i + 1, tlocs.size());
if (hearth)
{
bot->SetHomebind(loc, zone->ID);
}
// Prevent blink to be detected by visible real players
if (botAI->HasPlayerNearby(150.0f))
{
break;
}
bot->GetMotionMaster()->Clear();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI)
botAI->Reset(true);
bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
bot->TeleportTo(loc.GetMapId(), x, y, z, 0);
bot->SendMovementFlagUpdate();
if (pmo)
pmo->finish();
return;
}
if (pmo)
pmo->finish();
// LOG_ERROR("playerbots", "Cannot teleport bot {} - no locations available ({} locations)", bot->GetName().c_str(),
// tlocs.size());
}
void RandomPlayerbotMgr::PrepareZone2LevelBracket()
{
// Classic WoW - Low - level zones
zone2LevelBracket[1] = {5, 12}; // Dun Morogh
zone2LevelBracket[12] = {5, 12}; // Elwynn Forest
zone2LevelBracket[14] = {5, 12}; // Durotar
zone2LevelBracket[85] = {5, 12}; // Tirisfal Glades
zone2LevelBracket[141] = {5, 12}; // Teldrassil
zone2LevelBracket[215] = {5, 12}; // Mulgore
zone2LevelBracket[3430] = {5, 12}; // Eversong Woods
zone2LevelBracket[3524] = {5, 12}; // Azuremyst Isle
// Classic WoW - Mid - level zones
zone2LevelBracket[17] = {10, 25}; // Barrens
zone2LevelBracket[38] = {10, 20}; // Loch Modan
zone2LevelBracket[40] = {10, 21}; // Westfall
zone2LevelBracket[130] = {10, 23}; // Silverpine Forest
zone2LevelBracket[148] = {10, 21}; // Darkshore
zone2LevelBracket[3433] = {10, 22}; // Ghostlands
zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle
// Classic WoW - High - level zones
zone2LevelBracket[10] = {19, 33}; // Deadwind Pass
zone2LevelBracket[11] = {21, 30}; // Wetlands
zone2LevelBracket[44] = {16, 28}; // Redridge Mountains
zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills
zone2LevelBracket[331] = {18, 33}; // Ashenvale
zone2LevelBracket[400] = {24, 36}; // Thousand Needles
zone2LevelBracket[406] = {16, 29}; // Stonetalon Mountains
// Classic WoW - Higher - level zones
zone2LevelBracket[3] = {36, 46}; // Badlands
zone2LevelBracket[8] = {36, 46}; // Swamp of Sorrows
zone2LevelBracket[15] = {35, 46}; // Dustwallow Marsh
zone2LevelBracket[16] = {45, 52}; // Azshara
zone2LevelBracket[33] = {32, 47}; // Stranglethorn Vale
zone2LevelBracket[45] = {30, 42}; // Arathi Highlands
zone2LevelBracket[47] = {42, 51}; // Hinterlands
zone2LevelBracket[51] = {45, 51}; // Searing Gorge
zone2LevelBracket[357] = {40, 52}; // Feralas
zone2LevelBracket[405] = {30, 41}; // Desolace
zone2LevelBracket[440] = {41, 52}; // Tanaris
// Classic WoW - Top - level zones
zone2LevelBracket[4] = {52, 57}; // Blasted Lands
zone2LevelBracket[28] = {50, 60}; // Western Plaguelands
zone2LevelBracket[46] = {51, 60}; // Burning Steppes
zone2LevelBracket[139] = {54, 62}; // Eastern Plaguelands
zone2LevelBracket[361] = {47, 57}; // Felwood
zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater
zone2LevelBracket[618] = {54, 61}; // Winterspring
zone2LevelBracket[1377] = {54, 63}; // Silithus
// The Burning Crusade - Zones
zone2LevelBracket[3483] = {58, 66}; // Hellfire Peninsula
zone2LevelBracket[3518] = {64, 70}; // Nagrand
zone2LevelBracket[3519] = {62, 73}; // Terokkar Forest
zone2LevelBracket[3520] = {66, 73}; // Shadowmoon Valley
zone2LevelBracket[3521] = {60, 67}; // Zangarmarsh
zone2LevelBracket[3522] = {64, 73}; // Blade's Edge Mountains
zone2LevelBracket[3523] = {67, 73}; // Netherstorm
zone2LevelBracket[4080] = {68, 73}; // Isle of Quel'Danas
// Wrath of the Lich King - Zones
zone2LevelBracket[65] = {71, 77}; // Dragonblight
zone2LevelBracket[66] = {74, 80}; // Zul'Drak
zone2LevelBracket[67] = {77, 80}; // Storm Peaks
zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier
zone2LevelBracket[394] = {72, 78}; // Grizzly Hills
zone2LevelBracket[495] = {68, 74}; // Howling Fjord
zone2LevelBracket[2817] = {77, 80}; // Crystalsong Forest
zone2LevelBracket[3537] = {68, 75}; // Borean Tundra
zone2LevelBracket[3711] = {75, 80}; // Sholazar Basin
zone2LevelBracket[4197] = {79, 80}; // Wintergrasp
// Override with values from config
for (auto const& [zoneId, bracketPair] : sPlayerbotAIConfig->zoneBrackets)
{
zone2LevelBracket[zoneId] = {bracketPair.first, bracketPair.second};
}
}
void RandomPlayerbotMgr::PrepareTeleportCache()
{
uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
LOG_INFO("playerbots", "Preparing random teleport caches for {} levels...", maxLevel);
QueryResult results = WorldDatabase.Query(
"SELECT "
"g.map, "
"position_x, "
"position_y, "
"position_z, "
"t.minlevel, "
"t.maxlevel "
"FROM "
"(SELECT "
"map, "
"MIN( c.guid ) guid "
"FROM "
"creature c "
"INNER JOIN creature_template t ON c.id1 = t.entry "
"WHERE "
"t.npcflag = 0 "
"AND t.lootid != 0 "
"AND t.maxlevel - t.minlevel < 3 "
"AND map IN ({}) "
"AND t.entry not in (32820, 24196, 30627, 30617) "
"AND c.spawntimesecs < 1000 "
"AND t.faction not in (11, 71, 79, 85, 188, 1575) "
"AND (t.unit_flags & 256) = 0 "
"AND (t.unit_flags & 4096) = 0 "
"AND t.rank = 0 "
// "AND (t.flags_extra & 32768) = 0 "
"GROUP BY "
"map, "
"ROUND(position_x / 50), "
"ROUND(position_y / 50), "
"ROUND(position_z / 50) "
"HAVING "
"count(*) >= 2) "
"AS g "
"INNER JOIN creature c ON g.guid = c.guid "
"INNER JOIN creature_template t on c.id1 = t.entry "
"ORDER BY "
"t.minlevel;",
sPlayerbotAIConfig->randomBotMapsAsString.c_str());
uint32 collected_locs = 0;
if (results)
{
do
{
Field* fields = results->Fetch();
uint16 mapId = fields[0].Get<uint16>();
float x = fields[1].Get<float>();
float y = fields[2].Get<float>();
float z = fields[3].Get<float>();
uint32 min_level = fields[4].Get<uint32>();
uint32 max_level = fields[5].Get<uint32>();
uint32 level = (min_level + max_level + 1) / 2;
WorldLocation loc(mapId, x, y, z, 0);
collected_locs++;
for (int32 l = (int32)level - (int32)sPlayerbotAIConfig->randomBotTeleLowerLevel;
l <= (int32)level + (int32)sPlayerbotAIConfig->randomBotTeleHigherLevel; l++)
{
if (l < 1 || l > maxLevel)
{
continue;
}
locsPerLevelCache[(uint8)l].push_back(loc);
}
} while (results->NextRow());
}
LOG_INFO("playerbots", ">> {} locations for level collected.", collected_locs);
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
PrepareZone2LevelBracket();
LOG_INFO("playerbots", "Preparing innkeepers / flightmasters locations for level...");
results = WorldDatabase.Query(
"SELECT "
"map, "
"position_x, "
"position_y, "
"position_z, "
"orientation, "
"t.faction, "
"t.entry, "
"t.npcflag, "
"c.guid "
"FROM "
"creature c "
"INNER JOIN creature_template t on c.id1 = t.entry "
"WHERE "
"t.npcflag & 73728 "
"AND map IN ({}) "
"ORDER BY "
"t.minlevel;",
sPlayerbotAIConfig->randomBotMapsAsString.c_str());
collected_locs = 0;
if (results)
{
do
{
Field* fields = results->Fetch();
uint16 mapId = fields[0].Get<uint16>();
float x = fields[1].Get<float>();
float y = fields[2].Get<float>();
float z = fields[3].Get<float>();
float orient = fields[4].Get<float>();
uint32 faction = fields[5].Get<uint32>();
uint32 tEntry = fields[6].Get<uint32>();
uint32 tNpcflag = fields[7].Get<uint32>();
uint32 guid = fields[8].Get<uint32>();
if (tEntry == 3838 || tEntry == 29480)
continue;
const FactionTemplateEntry* entry = sFactionTemplateStore.LookupEntry(faction);
WorldLocation loc(mapId, x + cos(orient) * 5.0f, y + sin(orient) * 5.0f, z + 0.5f, orient + M_PI);
collected_locs++;
Map* map = sMapMgr->FindMap(loc.GetMapId(), 0);
if (!map)
continue;
bool forHorde = !(entry->hostileMask & 4);
bool forAlliance = !(entry->hostileMask & 2);
if (tNpcflag & UNIT_NPC_FLAG_FLIGHTMASTER)
{
if (forHorde)
{
hordeFlightMasterCache.push_back(guid);
}
if (forAlliance)
{
allianceFlightMasterCache.push_back(guid);
}
}
const AreaTableEntry* area = sAreaTableStore.LookupEntry(map->GetAreaId(PHASEMASK_NORMAL, x, y, z));
uint32 zoneId = area->zone ? area->zone : area->ID;
if (zone2LevelBracket.find(zoneId) == zone2LevelBracket.end())
continue;
LevelBracket bracket = zone2LevelBracket[zoneId];
for (int i = bracket.low; i <= bracket.high; i++)
{
if (forHorde)
{
hordeStarterPerLevelCache[i].push_back(loc);
}
if (forAlliance)
{
allianceStarterPerLevelCache[i].push_back(loc);
}
}
} while (results->NextRow());
}
// add all initial position
for (uint32 i = 1; i < MAX_RACES; i++)
{
for (uint32 j = 1; j < MAX_CLASSES; j++)
{
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(i, j);
if (!info)
continue;
WorldPosition pos(info->mapId, info->positionX, info->positionY, info->positionZ, info->orientation);
for (int32 l = 1; l <= 5; l++)
{
if ((1 << (i - 1)) & RACEMASK_ALLIANCE)
allianceStarterPerLevelCache[(uint8)l].push_back(pos);
else
hordeStarterPerLevelCache[(uint8)l].push_back(pos);
}
break;
}
}
LOG_INFO("playerbots", ">> {} innkeepers locations for level collected.", collected_locs);
}
results = WorldDatabase.Query(
"SELECT "
"map, "
"position_x, "
"position_y, "
"position_z, "
"orientation, "
"t.minlevel, "
"t.entry "
"FROM "
"creature c "
"INNER JOIN creature_template t on c.id1 = t.entry "
"WHERE "
"t.npcflag & 131072 "
"AND t.npcflag != 135298 "
"AND t.minlevel != 55 "
"AND t.minlevel != 65 "
"AND t.faction not in (35, 474, 69, 57) "
"AND t.entry not in (30606, 30608, 29282) "
"AND map IN ({}) "
"ORDER BY "
"t.minlevel;",
sPlayerbotAIConfig->randomBotMapsAsString.c_str());
collected_locs = 0;
if (results)
{
do
{
Field* fields = results->Fetch();
uint16 mapId = fields[0].Get<uint16>();
float x = fields[1].Get<float>();
float y = fields[2].Get<float>();
float z = fields[3].Get<float>();
float orient = fields[4].Get<float>();
uint32 level = fields[5].Get<uint32>();
uint32 entry = fields[6].Get<uint32>();
BankerLocation bLoc;
bLoc.loc = WorldLocation(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI);
bLoc.entry = entry;
collected_locs++;
for (int32 l = 1; l <= maxLevel; l++)
{
// Bots 1-60 go to base game bankers (all have minlevel 30 or 45)
if (l <=60 && level > 45)
{
continue;
}
// Bots 61-70 go to Shattrath bankers (all have minlevel 60 or 70)
if ((l >=61 && l <=70) && (level < 60 || level > 70))
{
continue;
}
// Bots 71+ go to Dalaran bankers (all have minlevel 75)
if ((l >=71) && level != 75)
{
continue;
}
bankerLocsPerLevelCache[(uint8)l].push_back(bLoc);
bankerEntryToLocation[bLoc.entry] = bLoc.loc;
}
} while (results->NextRow());
}
LOG_INFO("playerbots", ">> {} banker locations for level collected.", collected_locs);
}
void RandomPlayerbotMgr::PrepareAddclassCache()
{
// Using accounts marked as type 2 (AddClass)
int32 collected = 0;
for (uint32 accountId : addClassTypeAccounts)
{
for (uint8 claz = CLASS_WARRIOR; claz <= CLASS_DRUID; claz++)
{
if (claz == 10)
continue;
QueryResult results = CharacterDatabase.Query(
"SELECT guid, race FROM characters "
"WHERE account = {} AND class = '{}' AND online = 0",
accountId, claz);
if (results)
{
do
{
Field* fields = results->Fetch();
ObjectGuid guid = ObjectGuid(HighGuid::Player, fields[0].Get<uint32>());
uint32 race = fields[1].Get<uint32>();
bool isAlliance = race == 1 || race == 3 || race == 4 || race == 7 || race == 11;
addclassCache[GetTeamClassIdx(isAlliance, claz)].insert(guid);
collected++;
} while (results->NextRow());
}
}
}
LOG_INFO("playerbots", ">> {} characters collected for addclass command from {} AddClass accounts.", collected, addClassTypeAccounts.size());
}
void RandomPlayerbotMgr::Init()
{
if (sPlayerbotAIConfig->addClassCommand)
sRandomPlayerbotMgr->PrepareAddclassCache();
if (sPlayerbotAIConfig->enabled)
{
sRandomPlayerbotMgr->PrepareTeleportCache();
}
if (sPlayerbotAIConfig->randomBotJoinBG)
sRandomPlayerbotMgr->LoadBattleMastersCache();
PlayerbotsDatabase.Execute("DELETE FROM playerbots_random_bots WHERE event = 'add'");
}
void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot)
{
if (bot->InBattleground())
return;
uint32 level = bot->GetLevel();
uint8 race = bot->getRace();
std::vector<WorldLocation>* locs = nullptr;
if (sPlayerbotAIConfig->enableNewRpgStrategy)
locs = IsAlliance(race) ? &allianceStarterPerLevelCache[level] : &hordeStarterPerLevelCache[level];
else
locs = &locsPerLevelCache[level];
if (level >= 10 && urand(0, 100) < sPlayerbotAIConfig->probTeleToBankers * 100)
{
std::vector<WorldLocation> fallbackLocs;
for (auto& bLoc : bankerLocsPerLevelCache[level])
fallbackLocs.push_back(bLoc.loc);
if (!sPlayerbotAIConfig->enableWeightTeleToCityBankers)
{
RandomTeleport(bot, fallbackLocs, true);
return;
}
// Collect valid cities based on bot faction.
std::unordered_set<CityId> validBankerCities;
for (auto& loc : bankerLocsPerLevelCache[level])
{
auto cityIt = bankerToCity.find(loc.entry);
if (cityIt == bankerToCity.end()) continue;
CityId cityId = cityIt->second.first;
FactionId cityFactionId = cityIt->second.second;
if ((IsAlliance(bot->getRace()) && cityFactionId == FactionId::ALLIANCE) ||
(!IsAlliance(bot->getRace()) && cityFactionId == FactionId::HORDE) ||
(cityFactionId == FactionId::NEUTRAL))
{
validBankerCities.insert(cityId);
}
}
// Fallback if no valid cities
if (validBankerCities.empty())
{
RandomTeleport(bot, fallbackLocs, true);
return;
}
// Apply weights to valid cities
std::vector<CityId> weightedCities;
for (CityId city : validBankerCities)
{
int weight = 0;
switch (city)
{
case CityId::STORMWIND: weight = sPlayerbotAIConfig->weightTeleToStormwind; break;
case CityId::IRONFORGE: weight = sPlayerbotAIConfig->weightTeleToIronforge; break;
case CityId::DARNASSUS: weight = sPlayerbotAIConfig->weightTeleToDarnassus; break;
case CityId::EXODAR: weight = sPlayerbotAIConfig->weightTeleToExodar; break;
case CityId::ORGRIMMAR: weight = sPlayerbotAIConfig->weightTeleToOrgrimmar; break;
case CityId::UNDERCITY: weight = sPlayerbotAIConfig->weightTeleToUndercity; break;
case CityId::THUNDER_BLUFF: weight = sPlayerbotAIConfig->weightTeleToThunderBluff; break;
case CityId::SILVERMOON_CITY: weight = sPlayerbotAIConfig->weightTeleToSilvermoonCity; break;
case CityId::SHATTRATH_CITY: weight = sPlayerbotAIConfig->weightTeleToShattrathCity; break;
case CityId::DALARAN: weight = sPlayerbotAIConfig->weightTeleToDalaran; break;
default: weight = 0; break;
}
if (weight <= 0) continue;
for (int i = 0; i < weight; ++i)
{
weightedCities.push_back(city);
}
}
// Fallback if no valid cities
if (weightedCities.empty())
{
RandomTeleport(bot, fallbackLocs, true);
return;
}
// Pick a weighted city randomly, then a random banker in that city
// then teleport to that banker
CityId selectedCity = weightedCities[urand(0, weightedCities.size() - 1)];
auto const& bankers = cityToBankers.at(selectedCity);
uint32 selectedBankerEntry = bankers[urand(0, bankers.size() - 1)];
auto locIt = bankerEntryToLocation.find(selectedBankerEntry);
if (locIt != bankerEntryToLocation.end())
{
std::vector<WorldLocation> teleportTarget = { locIt->second };
RandomTeleport(bot, teleportTarget, true);
return;
}
// Fallback if something went wrong
RandomTeleport(bot, *locs);
}
else
{
RandomTeleport(bot, *locs);
}
}
void RandomPlayerbotMgr::RandomTeleportGrindForLevel(Player* bot)
{
if (bot->InBattleground())
return;
uint32 level = bot->GetLevel();
uint8 race = bot->getRace();
std::vector<WorldLocation>* locs = nullptr;
if (sPlayerbotAIConfig->enableNewRpgStrategy)
locs = IsAlliance(race) ? &allianceStarterPerLevelCache[level] : &hordeStarterPerLevelCache[level];
else
locs = &locsPerLevelCache[level];
LOG_DEBUG("playerbots", "Random teleporting bot {} for level {} ({} locations available)", bot->GetName().c_str(),
bot->GetLevel(), locs->size());
RandomTeleport(bot, *locs);
}
void RandomPlayerbotMgr::RandomTeleport(Player* bot)
{
if (bot->InBattleground())
return;
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "RandomTeleport");
std::vector<WorldLocation> locs;
std::list<Unit*> targets;
float range = sPlayerbotAIConfig->randomBotTeleportDistance;
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitObjects(bot, searcher, range);
if (!targets.empty())
{
for (Unit* unit : targets)
{
bot->UpdatePosition(*unit);
FleeManager manager(bot, sPlayerbotAIConfig->sightDistance, 0, true);
float rx, ry, rz;
if (manager.CalculateDestination(&rx, &ry, &rz))
{
WorldLocation loc(bot->GetMapId(), rx, ry, rz);
locs.push_back(loc);
}
}
}
else
{
RandomTeleportForLevel(bot);
}
if (pmo)
pmo->finish();
Refresh(bot);
}
void RandomPlayerbotMgr::Randomize(Player* bot)
{
if (bot->InBattleground())
return;
if (bot->GetLevel() < 3 || (bot->GetLevel() < 56 && bot->getClass() == CLASS_DEATH_KNIGHT))
{
RandomizeFirst(bot);
}
else if (bot->GetLevel() < sPlayerbotAIConfig->randomBotMaxLevel || !sPlayerbotAIConfig->downgradeMaxLevelBot)
{
uint8 level = bot->GetLevel();
PlayerbotFactory factory(bot, level);
factory.Randomize(true);
// IncreaseLevel(bot);
}
else
{
RandomizeFirst(bot);
}
}
void RandomPlayerbotMgr::IncreaseLevel(Player* bot)
{
uint32 maxLevel = sPlayerbotAIConfig->randomBotMaxLevel;
if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "IncreaseLevel");
uint32 lastLevel = GetValue(bot, "level");
uint8 level = bot->GetLevel() + 1;
if (level > maxLevel)
{
level = maxLevel;
}
if (lastLevel != level)
{
PlayerbotFactory factory(bot, level);
factory.Randomize(true);
}
if (pmo)
pmo->finish();
}
void RandomPlayerbotMgr::RandomizeFirst(Player* bot)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
return;
uint32 maxLevel = sPlayerbotAIConfig->randomBotMaxLevel;
if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
// if lvl sync is enabled, max level is limited by online players lvl
if (sPlayerbotAIConfig->syncLevelWithPlayers)
maxLevel = std::max(sPlayerbotAIConfig->randomBotMinLevel,
std::min(playersLevel, sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)));
uint32 minLevel = sPlayerbotAIConfig->randomBotMinLevel;
if (bot->getClass() == CLASS_DEATH_KNIGHT)
{
maxLevel = std::max(maxLevel, sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL));
minLevel = std::max(minLevel, sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL));
}
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "RandomizeFirst");
uint32 level;
if (sPlayerbotAIConfig->downgradeMaxLevelBot && bot->GetLevel() >= sPlayerbotAIConfig->randomBotMaxLevel)
{
if (bot->getClass() == CLASS_DEATH_KNIGHT)
{
level = sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL);
}
else
{
level = sPlayerbotAIConfig->randomBotMinLevel;
}
}
else
{
uint32 roll = urand(1, 100);
if (roll <= 100 * sPlayerbotAIConfig->randomBotMaxLevelChance)
{
level = maxLevel;
}
else if (roll <=
(100 * (sPlayerbotAIConfig->randomBotMaxLevelChance + sPlayerbotAIConfig->randomBotMinLevelChance)))
{
level = minLevel;
}
else
{
level = urand(minLevel, maxLevel);
}
}
if (sPlayerbotAIConfig->disableRandomLevels)
{
level = bot->getClass() == CLASS_DEATH_KNIGHT ? std::max(sPlayerbotAIConfig->randombotStartingLevel,
sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL))
: sPlayerbotAIConfig->randombotStartingLevel;
}
SetValue(bot, "level", level);
PlayerbotFactory factory(bot, level);
factory.Randomize(false);
uint32 randomTime =
urand(sPlayerbotAIConfig->minRandomBotRandomizeTime, sPlayerbotAIConfig->maxRandomBotRandomizeTime);
uint32 inworldTime =
urand(sPlayerbotAIConfig->minRandomBotInWorldTime, sPlayerbotAIConfig->maxRandomBotInWorldTime);
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_UPD_RANDOM_BOTS);
stmt->SetData(0, randomTime);
stmt->SetData(1, "bot_delete");
stmt->SetData(2, bot->GetGUID().GetCounter());
PlayerbotsDatabase.Execute(stmt);
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_UPD_RANDOM_BOTS);
stmt->SetData(0, inworldTime);
stmt->SetData(1, "logout");
stmt->SetData(2, bot->GetGUID().GetCounter());
PlayerbotsDatabase.Execute(stmt);
// teleport to a random inn for bot level
botAI->Reset(true);
if (bot->GetGroup())
botAI->LeaveOrDisbandGroup();
if (pmo)
pmo->finish();
RandomTeleportForLevel(bot);
}
void RandomPlayerbotMgr::RandomizeMin(Player* bot)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
return;
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "RandomizeMin");
uint32 level = sPlayerbotAIConfig->randomBotMinLevel;
SetValue(bot, "level", level);
PlayerbotFactory factory(bot, level);
factory.Randomize(false);
uint32 randomTime =
urand(sPlayerbotAIConfig->minRandomBotRandomizeTime, sPlayerbotAIConfig->maxRandomBotRandomizeTime);
uint32 inworldTime =
urand(sPlayerbotAIConfig->minRandomBotInWorldTime, sPlayerbotAIConfig->maxRandomBotInWorldTime);
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_UPD_RANDOM_BOTS);
stmt->SetData(0, randomTime);
stmt->SetData(1, "bot_delete");
stmt->SetData(2, bot->GetGUID().GetCounter());
PlayerbotsDatabase.Execute(stmt);
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_UPD_RANDOM_BOTS);
stmt->SetData(0, inworldTime);
stmt->SetData(1, "logout");
stmt->SetData(2, bot->GetGUID().GetCounter());
PlayerbotsDatabase.Execute(stmt);
// teleport to a random inn for bot level
botAI->Reset(true);
if (bot->GetGroup())
botAI->LeaveOrDisbandGroup();
if (pmo)
pmo->finish();
}
void RandomPlayerbotMgr::Clear(Player* bot)
{
PlayerbotFactory factory(bot, bot->GetLevel());
factory.ClearEverything();
}
uint32 RandomPlayerbotMgr::GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ)
{
uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
uint32 level = 0;
QueryResult results = WorldDatabase.Query(
"SELECT AVG(t.minlevel) minlevel, AVG(t.maxlevel) maxlevel FROM creature c "
"INNER JOIN creature_template t ON c.id1 = t.entry WHERE map = {} AND minlevel > 1 AND ABS(position_x - {}) < "
"{} AND ABS(position_y - {}) < {}",
mapId, teleX, sPlayerbotAIConfig->randomBotTeleportDistance / 2, teleY,
sPlayerbotAIConfig->randomBotTeleportDistance / 2);
if (results)
{
Field* fields = results->Fetch();
uint8 minLevel = fields[0].Get<uint8>();
uint8 maxLevel = fields[1].Get<uint8>();
level = urand(minLevel, maxLevel);
if (level > maxLevel)
level = maxLevel;
}
else
{
level = urand(1, maxLevel);
}
return level;
}
void RandomPlayerbotMgr::Refresh(Player* bot)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
return;
if (bot->isDead())
{
bot->ResurrectPlayer(1.0f);
bot->SpawnCorpseBones();
botAI->ResetStrategies(false);
}
// if (sPlayerbotAIConfig->disableRandomLevels)
// return;
if (bot->InBattleground())
return;
LOG_DEBUG("playerbots", "Refreshing bot {} <{}>", bot->GetGUID().ToString().c_str(), bot->GetName().c_str());
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "Refresh");
botAI->Reset();
bot->DurabilityRepairAll(false, 1.0f, false);
bot->SetFullHealth();
bot->SetPvP(true);
PlayerbotFactory factory(bot, bot->GetLevel());
factory.Refresh();
if (bot->GetMaxPower(POWER_MANA) > 0)
bot->SetPower(POWER_MANA, bot->GetMaxPower(POWER_MANA));
if (bot->GetMaxPower(POWER_ENERGY) > 0)
bot->SetPower(POWER_ENERGY, bot->GetMaxPower(POWER_ENERGY));
uint32 money = bot->GetMoney();
bot->SetMoney(money + 500 * sqrt(urand(1, bot->GetLevel() * 5)));
if (bot->GetGroup())
botAI->LeaveOrDisbandGroup();
if (pmo)
pmo->finish();
}
bool RandomPlayerbotMgr::IsRandomBot(Player* bot)
{
if (bot && GET_PLAYERBOT_AI(bot))
{
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
return false;
}
if (bot)
{
return IsRandomBot(bot->GetGUID().GetCounter());
}
return false;
}
bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot)
{
ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(bot);
if (!sPlayerbotAIConfig->IsInRandomAccountList(sCharacterCache->GetCharacterAccountIdByGuid(guid)))
return false;
if (std::find(currentBots.begin(), currentBots.end(), bot) != currentBots.end())
return true;
return false;
}
bool RandomPlayerbotMgr::IsAddclassBot(Player* bot)
{
if (bot && GET_PLAYERBOT_AI(bot))
{
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
return false;
}
if (bot)
{
return IsAddclassBot(bot->GetGUID().GetCounter());
}
return false;
}
bool RandomPlayerbotMgr::IsAddclassBot(ObjectGuid::LowType bot)
{
ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(bot);
// Check the cache with faction considerations
for (uint8 claz = CLASS_WARRIOR; claz <= CLASS_DRUID; claz++)
{
if (claz == 10)
continue;
for (uint8 isAlliance = 0; isAlliance <= 1; isAlliance++)
{
if (addclassCache[GetTeamClassIdx(isAlliance, claz)].find(guid) !=
addclassCache[GetTeamClassIdx(isAlliance, claz)].end())
{
return true;
}
}
}
// If not in cache, check the account type
uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guid);
if (accountId && IsAccountType(accountId, 2)) // Type 2 = AddClass
{
return true;
}
return false;
}
void RandomPlayerbotMgr::GetBots()
{
if (!currentBots.empty())
return;
PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_EVENT);
stmt->SetData(0, 0);
stmt->SetData(1, "add");
uint32 maxAllowedBotCount = GetEventValue(0, "bot_count");
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
uint32 bot = fields[0].Get<uint32>();
if (GetEventValue(bot, "add"))
currentBots.push_back(bot);
if (currentBots.size() >= maxAllowedBotCount)
break;
} while (result->NextRow());
}
}
std::vector<uint32> RandomPlayerbotMgr::GetBgBots(uint32 bracket)
{
// if (!currentBgBots.empty()) return currentBgBots;
std::vector<uint32> BgBots;
PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_EVENT_AND_VALUE);
stmt->SetData(0, "bg");
stmt->SetData(1, bracket);
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
uint32 bot = fields[0].Get<uint32>();
BgBots.push_back(bot);
} while (result->NextRow());
}
return std::move(BgBots);
}
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event)
{
// load all events at once on first event load
if (eventCache[bot].empty())
{
PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT);
stmt->SetData(0, 0);
stmt->SetData(1, bot);
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
std::string const eventName = fields[0].Get<std::string>();
CachedEvent e;
e.value = fields[1].Get<uint32>();
e.lastChangeTime = fields[2].Get<uint32>();
e.validIn = fields[3].Get<uint32>();
e.data = fields[4].Get<std::string>();
eventCache[bot][eventName] = std::move(e);
} while (result->NextRow());
}
}
CachedEvent& e = eventCache[bot][event];
/*if (e.IsEmpty())
{
QueryResult results = PlayerbotsDatabase.Query("SELECT `value`, `time`, validIn, `data` FROM
playerbots_random_bots WHERE owner = 0 AND bot = {} AND event = {}", bot, event.c_str());
if (results)
{
Field* fields = results->Fetch();
e.value = fields[0].Get<uint32>();
e.lastChangeTime = fields[1].Get<uint32>();
e.validIn = fields[2].Get<uint32>();
e.data = fields[3].Get<std::string>();
}
}
*/
if ((time(0) - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
e.value = 0;
return e.value;
}
std::string const RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const event)
{
std::string data = "";
if (GetEventValue(bot, event))
{
CachedEvent e = eventCache[bot][event];
data = e.data;
}
return data;
}
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
std::string const data)
{
PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction();
PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_DEL_RANDOM_BOTS_BY_OWNER_AND_EVENT);
stmt->SetData(0, 0);
stmt->SetData(1, bot);
stmt->SetData(2, event.c_str());
trans->Append(stmt);
if (value)
{
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RANDOM_BOTS);
stmt->SetData(0, 0);
stmt->SetData(1, bot);
stmt->SetData(2, static_cast<uint32>(GameTime::GetGameTime().count()));
stmt->SetData(3, validIn);
stmt->SetData(4, event.c_str());
stmt->SetData(5, value);
if (data != "")
{
stmt->SetData(6, data.c_str());
}
else
{
stmt->SetData(6);
}
trans->Append(stmt);
}
PlayerbotsDatabase.CommitTransaction(trans);
CachedEvent e(value, (uint32)time(nullptr), validIn, data);
eventCache[bot][event] = std::move(e);
return value;
}
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const type) { return GetEventValue(bot, type); }
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const type)
{
return GetValue(bot->GetGUID().GetCounter(), type);
}
std::string const RandomPlayerbotMgr::GetData(uint32 bot, std::string const type) { return GetEventData(bot, type); }
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const type, uint32 value, std::string const data)
{
SetEventValue(bot, type, value, sPlayerbotAIConfig->maxRandomBotInWorldTime, data);
}
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const type, uint32 value, std::string const data)
{
SetValue(bot->GetGUID().GetCounter(), type, value, data);
}
bool RandomPlayerbotMgr::HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args)
{
if (!sPlayerbotAIConfig->enabled)
{
LOG_ERROR("playerbots", "Playerbots system is currently disabled!");
return false;
}
if (!args || !*args)
{
LOG_ERROR("playerbots", "Usage: rndbot stats/update/reset/init/refresh/add/remove");
return false;
}
std::string const cmd = args;
if (cmd == "reset")
{
PlayerbotsDatabase.Execute(PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_DEL_RANDOM_BOTS));
sRandomPlayerbotMgr->eventCache.clear();
LOG_INFO("playerbots", "Random bots were reset for all players. Please restart the Server.");
return true;
}
if (cmd == "stats")
{
sRandomPlayerbotMgr->PrintStats();
// activatePrintStatsThread();
return true;
}
if (cmd == "reload")
{
sPlayerbotAIConfig->Initialize();
return true;
}
if (cmd == "update")
{
sRandomPlayerbotMgr->UpdateAIInternal(0);
return true;
}
std::map<std::string, ConsoleCommandHandler> handlers;
// handlers["initmin"] = &RandomPlayerbotMgr::RandomizeMin;
handlers["init"] = &RandomPlayerbotMgr::RandomizeFirst;
handlers["clear"] = &RandomPlayerbotMgr::Clear;
handlers["levelup"] = handlers["level"] = &RandomPlayerbotMgr::IncreaseLevel;
handlers["refresh"] = &RandomPlayerbotMgr::Refresh;
handlers["teleport"] = &RandomPlayerbotMgr::RandomTeleportForLevel;
// handlers["rpg"] = &RandomPlayerbotMgr::RandomTeleportForRpg;
handlers["revive"] = &RandomPlayerbotMgr::Revive;
handlers["grind"] = &RandomPlayerbotMgr::RandomTeleport;
handlers["change_strategy"] = &RandomPlayerbotMgr::ChangeStrategy;
for (std::map<std::string, ConsoleCommandHandler>::iterator j = handlers.begin(); j != handlers.end(); ++j)
{
std::string const prefix = j->first;
if (cmd.find(prefix) != 0)
continue;
std::string const name = cmd.size() > prefix.size() + 1 ? cmd.substr(1 + prefix.size()) : "%";
std::vector<uint32> botIds;
for (std::vector<uint32>::iterator i = sPlayerbotAIConfig->randomBotAccounts.begin();
i != sPlayerbotAIConfig->randomBotAccounts.end(); ++i)
{
uint32 account = *i;
if (QueryResult results = CharacterDatabase.Query(
"SELECT guid FROM characters WHERE account = {} AND name like '{}'", account, name.c_str()))
{
do
{
Field* fields = results->Fetch();
uint32 botId = fields[0].Get<uint32>();
ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(botId);
if (!sRandomPlayerbotMgr->IsRandomBot(guid.GetCounter()))
{
continue;
}
Player* bot = ObjectAccessor::FindPlayer(guid);
if (!bot)
continue;
botIds.push_back(botId);
} while (results->NextRow());
}
}
if (botIds.empty())
{
LOG_INFO("playerbots", "Nothing to do");
return false;
}
uint32 processed = 0;
for (std::vector<uint32>::iterator i = botIds.begin(); i != botIds.end(); ++i)
{
ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(*i);
Player* bot = ObjectAccessor::FindPlayer(guid);
if (!bot)
continue;
LOG_INFO("playerbots", "[{}/{}] Processing command {} for bot {}", processed++, botIds.size(), cmd.c_str(),
bot->GetName().c_str());
ConsoleCommandHandler handler = j->second;
(sRandomPlayerbotMgr->*handler)(bot);
}
return true;
}
// std::vector<std::string> messages = sRandomPlayerbotMgr->HandlePlayerbotCommand(args);
// for (std::vector<std::string>::iterator i = messages.begin(); i != messages.end(); ++i)
// {
// LOG_INFO("playerbots", "{}", i->c_str());
// }
return true;
}
void RandomPlayerbotMgr::HandleCommand(uint32 type, std::string const text, Player* fromPlayer, std::string channelName)
{
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
if (!bot)
continue;
if (!channelName.empty())
{
if (ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId()))
{
Channel* chn = cMgr->GetChannel(channelName, bot);
if (!chn)
continue;
}
}
GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer);
}
}
void RandomPlayerbotMgr::OnPlayerLogout(Player* player)
{
DisablePlayerBot(player->GetGUID());
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI && player == botAI->GetMaster())
{
botAI->SetMaster(nullptr);
if (!bot->InBattleground())
{
botAI->ResetStrategies();
}
}
}
std::vector<Player*>::iterator i = std::find(players.begin(), players.end(), player);
if (i != players.end())
players.erase(i);
}
void RandomPlayerbotMgr::OnBotLoginInternal(Player* const bot)
{
if (_isBotLogging)
{
LOG_INFO("playerbots", "{}/{} Bot {} logged in", playerBots.size(),
sRandomPlayerbotMgr->GetMaxAllowedBotCount(), bot->GetName().c_str());
if (playerBots.size() == sRandomPlayerbotMgr->GetMaxAllowedBotCount())
{
_isBotLogging = false;
}
}
if (sPlayerbotAIConfig->randomBotFixedLevel)
{
bot->SetPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN);
}
else
{
bot->RemovePlayerFlag(PLAYER_FLAGS_NO_XP_GAIN);
}
}
void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
{
uint32 botsNearby = 0;
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
{
Player* const bot = it->second;
if (player == bot /* || GET_PLAYERBOT_AI(player)*/) // TEST
continue;
Cell playerCell(player->GetPositionX(), player->GetPositionY());
Cell botCell(bot->GetPositionX(), bot->GetPositionY());
// if (playerCell == botCell)
// botsNearby++;
Group* group = bot->GetGroup();
if (!group)
continue;
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI && member == player && (!botAI->GetMaster() || GET_PLAYERBOT_AI(botAI->GetMaster())))
{
if (!bot->InBattleground())
{
botAI->SetMaster(player);
botAI->ResetStrategies();
botAI->TellMaster("Hello");
}
break;
}
}
}
if (botsNearby > 100 && false)
{
WorldPosition botPos(player);
// botPos.GetReachableRandomPointOnGround(player, sPlayerbotAIConfig->reactDistance * 2, true);
// player->TeleportTo(botPos);
// player->Relocate(botPos.coord_x, botPos.coord_y, botPos.coord_z, botPos.orientation);
if (!player->GetFactionTemplateEntry())
{
botPos.GetReachableRandomPointOnGround(player, sPlayerbotAIConfig->reactDistance * 2, true);
}
else
{
std::vector<TravelDestination*> dests = sTravelMgr->getRpgTravelDestinations(player, true, true, 200000.0f);
do
{
RpgTravelDestination* dest = (RpgTravelDestination*)dests[urand(0, dests.size() - 1)];
CreatureTemplate const* cInfo = dest->GetCreatureTemplate();
if (!cInfo)
continue;
FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(cInfo->faction);
ReputationRank reaction = Unit::GetFactionReactionTo(player->GetFactionTemplateEntry(), factionEntry);
if (reaction > REP_NEUTRAL && dest->nearestPoint(&botPos)->m_mapId == player->GetMapId())
{
botPos = *dest->nearestPoint(&botPos);
break;
}
} while (true);
}
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
player->TeleportTo(botPos);
// player->Relocate(botPos.getX(), botPos.getY(), botPos.getZ(), botPos.getO());
}
if (IsRandomBot(player))
{
// ObjectGuid::LowType guid = player->GetGUID().GetCounter(); //not used, conditional could be rewritten for
// simplicity. line marked for removal.
}
else
{
players.push_back(player);
LOG_DEBUG("playerbots", "Including non-random bot player {} into random bot update", player->GetName().c_str());
}
}
void RandomPlayerbotMgr::OnPlayerLoginError(uint32 bot)
{
SetEventValue(bot, "add", 0, 0);
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
}
Player* RandomPlayerbotMgr::GetRandomPlayer()
{
if (players.empty())
return nullptr;
uint32 index = urand(0, players.size() - 1);
return players[index];
}
void RandomPlayerbotMgr::PrintStats()
{
printStatsTimer = time(nullptr);
LOG_INFO("playerbots", "Random Bots Stats: {} online", playerBots.size());
std::map<uint8, uint32> alliance, horde;
for (uint32 i = 0; i < 10; ++i)
{
alliance[i] = 0;
horde[i] = 0;
}
std::map<uint8, uint32> perRace;
std::map<uint8, uint32> perClass;
std::map<uint8, uint32> lvlPerRace;
std::map<uint8, uint32> lvlPerClass;
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
{
perRace[race] = 0;
lvlPerRace[race] = 0;
}
for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls)
{
perClass[cls] = 0;
lvlPerClass[cls] = 0;
}
uint32 dps = 0;
uint32 heal = 0;
uint32 tank = 0;
uint32 active = 0;
uint32 update = 0;
uint32 randomize = 0;
uint32 teleport = 0;
uint32 changeStrategy = 0;
uint32 dead = 0;
uint32 combat = 0;
// uint32 revive = 0; //not used, line marked for removal.
uint32 inFlight = 0;
uint32 moving = 0;
uint32 mounted = 0;
uint32 inBg = 0;
uint32 rest = 0;
uint32 engine_noncombat = 0;
uint32 engine_combat = 0;
uint32 engine_dead = 0;
std::unordered_map<NewRpgStatus, int> rpgStatusCount;
// static NewRpgStatistic rpgStasticTotal;
std::unordered_map<uint32, int> zoneCount;
uint8 maxBotLevel = 0;
for (PlayerBotMap::iterator i = playerBots.begin(); i != playerBots.end(); ++i)
{
Player* bot = i->second;
if (IsAlliance(bot->getRace()))
++alliance[bot->GetLevel()];
else
++horde[bot->GetLevel()];
maxBotLevel = std::max(maxBotLevel, bot->GetLevel());
++perRace[bot->getRace()];
++perClass[bot->getClass()];
lvlPerClass[bot->getClass()] += bot->GetLevel();
lvlPerRace[bot->getRace()] += bot->GetLevel();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI->AllowActivity())
++active;
if (botAI->GetAiObjectContext()->GetValue<bool>("random bot update")->Get())
++update;
uint32 botId = bot->GetGUID().GetCounter();
if (!GetEventValue(botId, "randomize"))
++randomize;
if (!GetEventValue(botId, "teleport"))
++teleport;
if (!GetEventValue(botId, "change_strategy"))
++changeStrategy;
if (bot->isDead())
{
++dead;
// if (!GetEventValue(botId, "dead"))
//++revive;
}
if (bot->IsInCombat())
{
++combat;
}
if (bot->isMoving())
{
++moving;
}
if (bot->IsInFlight())
{
++inFlight;
}
if (bot->IsMounted())
{
++mounted;
}
if (bot->InBattleground() || bot->InArena())
{
++inBg;
}
if (bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING))
{
++rest;
}
if (botAI->GetState() == BOT_STATE_NON_COMBAT)
++engine_noncombat;
else if (botAI->GetState() == BOT_STATE_COMBAT)
++engine_combat;
else
++engine_dead;
if (botAI->IsHeal(bot, true))
++heal;
else if (botAI->IsTank(bot, true))
++tank;
else
++dps;
zoneCount[bot->GetZoneId()]++;
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
rpgStatusCount[botAI->rpgInfo.status]++;
rpgStasticTotal += botAI->rpgStatistic;
botAI->rpgStatistic = NewRpgStatistic();
}
}
LOG_INFO("playerbots", "Bots level:");
// uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
uint32_t currentAlliance = 0, currentHorde = 0;
uint32_t step = std::max(1, static_cast<int>((maxBotLevel + 4) / 8));
uint32_t from = 1;
for (uint8 i = 1; i <= maxBotLevel; ++i)
{
currentAlliance += alliance[i];
currentHorde += horde[i];
if (((i + 1) % step == 0) || i == maxBotLevel)
{
if (currentAlliance || currentHorde)
LOG_INFO("playerbots", " {}..{}: {} alliance, {} horde", from, i, currentAlliance, currentHorde);
currentAlliance = 0;
currentHorde = 0;
from = i + 1;
}
}
LOG_INFO("playerbots", "Bots race:");
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
{
if (perRace[race])
{
uint32 lvl = lvlPerRace[race] * 10 / perRace[race];
float flvl = lvl / 10.0f;
LOG_INFO("playerbots", " {}: {}, avg lvl: {}", ChatHelper::FormatRace(race).c_str(), perRace[race],
flvl);
}
}
LOG_INFO("playerbots", "Bots class:");
for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls)
{
if (perClass[cls])
{
uint32 lvl = lvlPerClass[cls] * 10 / perClass[cls];
float flvl = lvl / 10.0f;
LOG_INFO("playerbots", " {}: {}, avg lvl: {}", ChatHelper::FormatClass(cls).c_str(), perClass[cls],
flvl);
}
}
LOG_INFO("playerbots", "Bots role:");
LOG_INFO("playerbots", " tank: {}, heal: {}, dps: {}", tank, heal, dps);
LOG_INFO("playerbots", "Bots status:");
LOG_INFO("playerbots", " Active: {}", active);
LOG_INFO("playerbots", " Moving: {}", moving);
// LOG_INFO("playerbots", "Bots to:");
// LOG_INFO("playerbots", " update: {}", update);
// LOG_INFO("playerbots", " randomize: {}", randomize);
// LOG_INFO("playerbots", " teleport: {}", teleport);
// LOG_INFO("playerbots", " change_strategy: {}", changeStrategy);
// LOG_INFO("playerbots", " revive: {}", revive);
LOG_INFO("playerbots", " In flight: {}", inFlight);
LOG_INFO("playerbots", " On mount: {}", mounted);
LOG_INFO("playerbots", " In combat: {}", combat);
LOG_INFO("playerbots", " In BG: {}", inBg);
LOG_INFO("playerbots", " In Rest: {}", rest);
LOG_INFO("playerbots", " Dead: {}", dead);
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
LOG_INFO("playerbots", "Bots rpg status:");
LOG_INFO("playerbots",
" Idle: {}, Rest: {}, GoGrind: {}, GoCamp: {}, MoveRandom: {}, MoveNpc: {}, DoQuest: {}, "
"TravelFlight: {}",
rpgStatusCount[RPG_IDLE], rpgStatusCount[RPG_REST], rpgStatusCount[RPG_GO_GRIND],
rpgStatusCount[RPG_GO_CAMP], rpgStatusCount[RPG_WANDER_RANDOM], rpgStatusCount[RPG_WANDER_NPC],
rpgStatusCount[RPG_DO_QUEST], rpgStatusCount[RPG_TRAVEL_FLIGHT]);
LOG_INFO("playerbots", "Bots total quests:");
LOG_INFO("playerbots", " Accepted: {}, Rewarded: {}, Dropped: {}", rpgStasticTotal.questAccepted,
rpgStasticTotal.questRewarded, rpgStasticTotal.questDropped);
}
LOG_INFO("playerbots", "Bots engine:", dead);
LOG_INFO("playerbots", " Non-combat: {}, Combat: {}, Dead: {}", engine_noncombat, engine_combat, engine_dead);
}
double RandomPlayerbotMgr::GetBuyMultiplier(Player* bot)
{
uint32 id = bot->GetGUID().GetCounter();
uint32 value = GetEventValue(id, "buymultiplier");
if (!value)
{
value = urand(50, 120);
uint32 validIn = urand(sPlayerbotAIConfig->minRandomBotsPriceChangeInterval,
sPlayerbotAIConfig->maxRandomBotsPriceChangeInterval);
SetEventValue(id, "buymultiplier", value, validIn);
}
return (double)value / 100.0;
}
double RandomPlayerbotMgr::GetSellMultiplier(Player* bot)
{
uint32 id = bot->GetGUID().GetCounter();
uint32 value = GetEventValue(id, "sellmultiplier");
if (!value)
{
value = urand(80, 250);
uint32 validIn = urand(sPlayerbotAIConfig->minRandomBotsPriceChangeInterval,
sPlayerbotAIConfig->maxRandomBotsPriceChangeInterval);
SetEventValue(id, "sellmultiplier", value, validIn);
}
return (double)value / 100.0;
}
void RandomPlayerbotMgr::AddTradeDiscount(Player* bot, Player* master, int32 value)
{
if (!master)
return;
uint32 discount = GetTradeDiscount(bot, master);
int32 result = (int32)discount + value;
discount = (result < 0 ? 0 : result);
SetTradeDiscount(bot, master, discount);
}
void RandomPlayerbotMgr::SetTradeDiscount(Player* bot, Player* master, uint32 value)
{
if (!master)
return;
uint32 botId = bot->GetGUID().GetCounter();
uint32 masterId = master->GetGUID().GetCounter();
std::ostringstream name;
name << "trade_discount_" << masterId;
SetEventValue(botId, name.str(), value, sPlayerbotAIConfig->maxRandomBotInWorldTime);
}
uint32 RandomPlayerbotMgr::GetTradeDiscount(Player* bot, Player* master)
{
if (!master)
return 0;
uint32 botId = bot->GetGUID().GetCounter();
uint32 masterId = master->GetGUID().GetCounter();
std::ostringstream name;
name << "trade_discount_" << masterId;
return GetEventValue(botId, name.str());
}
std::string const RandomPlayerbotMgr::HandleRemoteCommand(std::string const request)
{
std::string::const_iterator pos = std::find(request.begin(), request.end(), ',');
if (pos == request.end())
{
std::ostringstream out;
out << "invalid request: " << request;
return out.str();
}
std::string const command = std::string(request.begin(), pos);
ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(atoi(std::string(pos + 1, request.end()).c_str()));
Player* bot = GetPlayerBot(guid);
if (!bot)
return "invalid guid";
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
return "invalid guid";
return botAI->HandleRemoteCommand(command);
}
void RandomPlayerbotMgr::ChangeStrategy(Player* player)
{
uint32 bot = player->GetGUID().GetCounter();
if (frand(0.f, 100.f) > sPlayerbotAIConfig->randomBotRpgChance)
{
LOG_INFO("playerbots", "Bot #{} <{}>: sent to grind spot", bot, player->GetName().c_str());
ScheduleTeleport(bot, 30);
}
else
{
LOG_INFO("playerbots", "Changing strategy for bot #{} <{}> to RPG", bot, player->GetName().c_str());
LOG_INFO("playerbots", "Bot #{} <{}>: sent to inn", bot, player->GetName().c_str());
RandomTeleportForLevel(player);
SetEventValue(bot, "teleport", 1, sPlayerbotAIConfig->maxRandomBotInWorldTime);
}
ScheduleChangeStrategy(bot);
}
void RandomPlayerbotMgr::ChangeStrategyOnce(Player* player)
{
uint32 bot = player->GetGUID().GetCounter();
if (frand(0.f, 100.f) > sPlayerbotAIConfig->randomBotRpgChance) // select grind / pvp
{
LOG_INFO("playerbots", "Bot #{} <{}>: sent to grind spot", bot, player->GetName().c_str());
RandomTeleportForLevel(player);
Refresh(player);
}
else
{
LOG_INFO("playerbots", "Bot #{} <{}>: sent to inn", bot, player->GetName().c_str());
RandomTeleportForLevel(player);
}
}
void RandomPlayerbotMgr::RandomTeleportForRpg(Player* bot)
{
uint32 race = bot->getRace();
uint32 level = bot->GetLevel();
LOG_DEBUG("playerbots", "Random teleporting bot {} for RPG ({} locations available)", bot->GetName().c_str(),
rpgLocsCacheLevel[race].size());
RandomTeleport(bot, rpgLocsCacheLevel[race][level], true);
}
void RandomPlayerbotMgr::Remove(Player* bot)
{
ObjectGuid owner = bot->GetGUID();
PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_DEL_RANDOM_BOTS_BY_OWNER);
stmt->SetData(0, 0);
stmt->SetData(1, owner.GetCounter());
PlayerbotsDatabase.Execute(stmt);
eventCache[owner.GetCounter()].clear();
LogoutPlayerBot(owner);
}
CreatureData const* RandomPlayerbotMgr::GetCreatureDataByEntry(uint32 entry)
{
if (entry != 0)
{
for (auto const& itr : sObjectMgr->GetAllCreatureData())
if (itr.second.id1 == entry)
return &itr.second;
}
return nullptr;
}
ObjectGuid const RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
{
ObjectGuid battleMasterGUID = ObjectGuid::Empty;
TeamId team = bot->GetTeamId();
std::vector<uint32> Bms;
for (auto i = std::begin(BattleMastersCache[team][bgTypeId]); i != std::end(BattleMastersCache[team][bgTypeId]);
++i)
{
Bms.insert(Bms.end(), *i);
}
for (auto i = std::begin(BattleMastersCache[TEAM_NEUTRAL][bgTypeId]);
i != std::end(BattleMastersCache[TEAM_NEUTRAL][bgTypeId]); ++i)
{
Bms.insert(Bms.end(), *i);
}
if (Bms.empty())
return battleMasterGUID;
float dist1 = FLT_MAX;
for (auto i = begin(Bms); i != end(Bms); ++i)
{
CreatureData const* data = sRandomPlayerbotMgr->GetCreatureDataByEntry(*i);
if (!data)
continue;
Unit* Bm = PlayerbotAI::GetUnit(data);
if (!Bm)
continue;
if (bot->GetMapId() != Bm->GetMapId())
continue;
// return first available guid on map if queue from anywhere
if (!BattlegroundMgr::IsArenaType(bgTypeId))
{
battleMasterGUID = Bm->GetGUID();
break;
}
AreaTableEntry const* zone = sAreaTableStore.LookupEntry(Bm->GetZoneId());
if (!zone)
continue;
if (zone->team == 4 && bot->GetTeamId() == TEAM_ALLIANCE)
continue;
if (zone->team == 2 && bot->GetTeamId() == TEAM_HORDE)
continue;
if (Bm->getDeathState() == DeathState::Dead)
continue;
float dist2 = sServerFacade->GetDistance2d(bot, data->posX, data->posY);
if (dist2 < dist1)
{
dist1 = dist2;
battleMasterGUID = Bm->GetGUID();
}
}
return battleMasterGUID;
}