/* * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it * and/or modify it under version 2 of the License, or (at your option), any later version. */ #include "RandomPlayerbotMgr.h" #include #include #include #include #include #include #include #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; }; 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 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); // } uint32 RandomPlayerbotMgr::AddRandomBots() { uint32 maxAllowedBotCount = GetEventValue(0, "bot_count"); if (currentBots.size() < maxAllowedBotCount) { maxAllowedBotCount -= currentBots.size(); maxAllowedBotCount = std::min(sPlayerbotAIConfig->randomBotsPerInterval, maxAllowedBotCount); 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++; } uint32 allowedHordeCount = maxAllowedBotCount - allowedAllianceCount; for (std::vector::iterator i = sPlayerbotAIConfig->randomBotAccounts.begin(); i != sPlayerbotAIConfig->randomBotAccounts.end(); i++) { uint32 accountId = *i; if (sPlayerbotAIConfig->enablePeriodicOnlineOffline) { // minus addclass bots account int32 baseAccount = RandomPlayerbotFactory::CalculateTotalAccountCount() - sPlayerbotAIConfig->addClassAccountPoolSize; if (baseAccount <= 0 || baseAccount > sPlayerbotAIConfig->randomBotAccounts.size()) { LOG_ERROR("playerbots", "Account calculation error with PeriodicOnlineOffline"); return 0; } uint32 index = urand(0, baseAccount - 1); accountId = sPlayerbotAIConfig->randomBotAccounts[index]; } CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID); stmt->SetData(0, accountId); PreparedQueryResult result = CharacterDatabase.Query(stmt); if (!result) continue; std::vector allGuidInfos; do { Field* fields = result->Fetch(); GuidClassRaceInfo info; info.guid = fields[0].Get(); info.rClass = fields[1].Get(); info.rRace = fields[2].Get(); allGuidInfos.push_back(info); } while (result->NextRow()); // random shuffle for class balance std::mt19937 rnd(time(0)); std::shuffle(allGuidInfos.begin(), allGuidInfos.end(), rnd); std::vector guids; for (const auto& info : allGuidInfos) { ObjectGuid::LowType guid = info.guid; uint32 rClass = info.rClass; uint32 rRace = info.rRace; if (GetEventValue(guid, "add")) continue; if (GetEventValue(guid, "logout")) continue; if (GetPlayerBot(guid)) continue; if (std::find(currentBots.begin(), currentBots.end(), guid) != currentBots.end()) continue; if (sPlayerbotAIConfig->disableDeathKnightLogin) { if (rClass == CLASS_DEATH_KNIGHT) { continue; } } uint32 isAlliance = IsAlliance(rRace); bool factionNotAllowed = (!allowedAllianceCount && isAlliance) || (!allowedHordeCount && !isAlliance); if (factionNotAllowed) continue; if (isAlliance) { allowedAllianceCount--; } else { allowedHordeCount--; } uint32 add_time = sPlayerbotAIConfig->enablePeriodicOnlineOffline ? urand(sPlayerbotAIConfig->minRandomBotInWorldTime, sPlayerbotAIConfig->maxRandomBotInWorldTime) : sPlayerbotAIConfig->permanantlyInWorldTime; SetEventValue(guid, "add", 1, add_time); SetEventValue(guid, "logout", 0, 0); currentBots.push_back(guid); maxAllowedBotCount--; if (!maxAllowedBotCount) break; } if (!maxAllowedBotCount) break; } if (maxAllowedBotCount) LOG_ERROR("playerbots", "Not enough random bot accounts available. Try to increase RandomBotAccountCount " "in your conf file", ceil(maxAllowedBotCount / 10)); } 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 bgTypeId = fields[1].Get(); 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 Battmemaster #{} 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 parseBrackets(const std::string& str) { std::vector brackets; std::stringstream ss(str); std::string item; while (std::getline(ss, item, ',')) { brackets.push_back(static_cast(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* 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* 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 icBrackets = parseBrackets(sPlayerbotAIConfig->randomBotAutoJoinICBrackets); std::vector eyBrackets = parseBrackets(sPlayerbotAIConfig->randomBotAutoJoinEYBrackets); std::vector avBrackets = parseBrackets(sPlayerbotAIConfig->randomBotAutoJoinAVBrackets); std::vector abBrackets = parseBrackets(sPlayerbotAIConfig->randomBotAutoJoinABBrackets); std::vector 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 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 (const auto& queueTypePair : BattlegroundData) { uint8 queueType = queueTypePair.first; BattlegroundQueueTypeId queueTypeId = BattlegroundQueueTypeId(queueType); if (uint8 type = BattlegroundMgr::BGArenaType(queueTypeId)) { for (const auto& 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 (const auto& 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::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::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(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(randomBotUpdateInterval * 0.5)), std::max(12, static_cast(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(randomBotUpdateInterval * 0.4))); ScheduleRandomize(bot, randomTime); } if (!GetEventValue(bot, "teleport")) { randomTime = urand(std::max(7, static_cast(randomBotUpdateInterval * 0.7)), std::max(14, static_cast(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("random bot update")->Set(true); bool update = true; if (botAI) { // botAI->GetAiObjectContext()->GetValue("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* player) { uint32 bot = player->GetGUID().GetCounter(); if (player->InBattleground()) return false; if (player->InBattlegroundQueue()) return false; // if death revive if (player->isDead()) { if (!GetEventValue(bot, "dead")) { uint32 randomTime = urand(sPlayerbotAIConfig->minRandomBotReviveTime, sPlayerbotAIConfig->maxRandomBotReviveTime); LOG_DEBUG("playerbots", "Mark bot {} as dead, will be revived in {}s.", player->GetName().c_str(), randomTime); SetEventValue(bot, "dead", 1, sPlayerbotAIConfig->maxRandomBotInWorldTime); SetEventValue(bot, "revive", 1, randomTime); return false; } if (!GetEventValue(bot, "revive")) { Revive(player); return true; } return false; } // leave group if leader is rndbot Group* group = player->GetGroup(); if (group && !group->isLFGGroup() && IsRandomBot(group->GetLeader())) { player->RemoveFromGroup(); LOG_INFO("playerbots", "Bot {} remove from group since leader is random bot.", player->GetName().c_str()); } // only randomize and teleport idle bots bool idleBot = false; PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); if (botAI) { if (TravelTarget* target = botAI->GetAiObjectContext()->GetValue("travel target")->Get()) { if (target->getTravelState() == TravelState::TRAVEL_STATE_IDLE) { idleBot = true; } } else { idleBot = true; } } if (idleBot) { // randomize uint32 randomize = GetEventValue(bot, "randomize"); if (!randomize) { // bool randomiser = true; // if (player->GetGuildId()) // { // if (Guild* guild = sGuildMgr->GetGuildById(player->GetGuildId())) // { // if (guild->GetLeaderGUID() == player->GetGUID()) // { // for (std::vector::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(player); LOG_DEBUG("playerbots", "Bot #{} {}:{} <{}>: randomized", bot, player->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", player->GetLevel(), player->GetName()); uint32 randomTime = urand(sPlayerbotAIConfig->minRandomBotRandomizeTime, sPlayerbotAIConfig->maxRandomBotRandomizeTime); ScheduleRandomize(bot, 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(bot, "teleport"); if (!teleport) { LOG_DEBUG("playerbots", "Bot #{} <{}>: teleport for level and refresh", bot, player->GetName()); Refresh(player); RandomTeleportForLevel(player); uint32 time = urand(sPlayerbotAIConfig->minRandomBotTeleportInterval, sPlayerbotAIConfig->maxRandomBotTeleportInterval); ScheduleTeleport(bot, 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& 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 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::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; } if (tlocs.empty()) { LOG_DEBUG("playerbots", "Cannot teleport bot {} - no locations available", 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->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(); float x = fields[1].Get(); float y = fields[2].Get(); float z = fields[3].Get(); uint32 min_level = fields[4].Get(); uint32 max_level = fields[5].Get(); 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(); float x = fields[1].Get(); float y = fields[2].Get(); float z = fields[3].Get(); float orient = fields[4].Get(); uint32 faction = fields[5].Get(); uint32 tEntry = fields[6].Get(); uint32 tNpcflag = fields[7].Get(); uint32 guid = fields[8].Get(); 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 " "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(); float x = fields[1].Get(); float y = fields[2].Get(); float z = fields[3].Get(); float orient = fields[4].Get(); uint32 level = fields[5].Get(); WorldLocation loc(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI); collected_locs++; for (int32 l = 1; l <= maxLevel; l++) { if (l <= 60 && level >= 60) { continue; } if (l <= 70 && level >= 70) { continue; } if (l >= 70 && level >= 60 && level <= 70) { continue; } if (l >= 30 && level <= 30) { continue; } bankerLocsPerLevelCache[(uint8)l].push_back(loc); } } while (results->NextRow()); } LOG_INFO("playerbots", ">> {} banker locations for level collected.", collected_locs); } void RandomPlayerbotMgr::PrepareAddclassCache() { /// @FIXME: Modifying RandomBotAccountCount may cause the original addclass bots to be converted into rndbots, // which needs to be fixed by separating the two accounts in implementation size_t poolSize = sPlayerbotAIConfig->addClassAccountPoolSize; size_t start = sPlayerbotAIConfig->randomBotAccounts.size() > poolSize ? sPlayerbotAIConfig->randomBotAccounts.size() - poolSize : 0; int32 collected = 0; for (size_t i = start; i < sPlayerbotAIConfig->randomBotAccounts.size(); i++) { 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 " "ORDER BY account DESC", sPlayerbotAIConfig->randomBotAccounts[i], claz); if (results) { do { Field* fields = results->Fetch(); ObjectGuid guid = ObjectGuid(HighGuid::Player, fields[0].Get()); uint32 race = fields[1].Get(); 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.", collected); } 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* 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()); if (level >= 10 && urand(0, 100) < sPlayerbotAIConfig->probTeleToBankers * 100) { RandomTeleport(bot, bankerLocsPerLevelCache[level], true); } else { RandomTeleport(bot, *locs); } } void RandomPlayerbotMgr::RandomTeleportGrindForLevel(Player* bot) { if (bot->InBattleground()) return; uint32 level = bot->GetLevel(); uint8 race = bot->getRace(); std::vector* 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 locs; std::list targets; float range = sPlayerbotAIConfig->randomBotTeleportDistance; Acore::AnyUnitInObjectRangeCheck u_check(bot, range); Acore::UnitListSearcher searcher(bot, targets, u_check); Cell::VisitAllObjects(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) { 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 if (GET_PLAYERBOT_AI(bot)) GET_PLAYERBOT_AI(bot)->Reset(true); if (bot->GetGroup()) bot->RemoveFromGroup(); if (pmo) pmo->finish(); RandomTeleportForLevel(bot); } void RandomPlayerbotMgr::RandomizeMin(Player* bot) { 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 if (GET_PLAYERBOT_AI(bot)) GET_PLAYERBOT_AI(bot)->Reset(true); if (bot->GetGroup()) bot->RemoveFromGroup(); 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 maxLevel = fields[1].Get(); 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()) bot->RemoveFromGroup(); 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(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(bot); 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; } } 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(); if (GetEventValue(bot, "add")) currentBots.push_back(bot); if (currentBots.size() >= maxAllowedBotCount) break; } while (result->NextRow()); } } std::vector RandomPlayerbotMgr::GetBgBots(uint32 bracket) { // if (!currentBgBots.empty()) return currentBgBots; std::vector 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(); 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(); CachedEvent e; e.value = fields[1].Get(); e.lastChangeTime = fields[2].Get(); e.validIn = fields[3].Get(); e.data = fields[4].Get(); 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(); e.lastChangeTime = fields[1].Get(); e.validIn = fields[2].Get(); e.data = fields[3].Get(); } } */ 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(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 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::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 botIds; for (std::vector::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(); ObjectGuid guid = ObjectGuid::Create(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::iterator i = botIds.begin(); i != botIds.end(); ++i) { ObjectGuid guid = ObjectGuid::Create(*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 messages = sRandomPlayerbotMgr->HandlePlayerbotCommand(args); // for (std::vector::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::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 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->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 alliance, horde; for (uint32 i = 0; i < 10; ++i) { alliance[i] = 0; horde[i] = 0; } std::map perRace; std::map perClass; std::map lvlPerRace; std::map 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 rpgStatusCount; // static NewRpgStatistic rpgStasticTotal; std::unordered_map 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("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((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(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 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; }