mod-playerbots/src/Bot/RandomPlayerbotMgr.h
Keleborn 957eca0263
Feat. Enable multi node flying, and refactor into travel manager (#2156)
# Pull Request

Feature - Enable multi node flying for bots
- Bots currently only do node to node flying. This PR makes it so they
can connect multiple noted.
-- This is enabled by sending a vector containing the node sequence
instead of a single destination node
-- To minimize the run-time cost of searching for available nodes and
connection, a cache of all possible connections is prepared at start up
using a BFS search algorithm.

Refactor 
- Move all world destination logic (cities, banks, inns) to existing
Travel manager
- Eliminate flightmastercache and integrate to new manager
- replace SQLs calls with in-memory data search by core
- Add in new map that stores creature areas by template. 

Clean up
- Move other rpg files to related folder. 
(Next steps) The selection for where bots fly to should be smarter than
it is. Instead of trying to determine where a bot can go, it should
first decide where it should go, and then identify the correct way to
get there.
---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

Does this change add new decision branches?
- - [x[ No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
The call itself is fairly infrequent, and although now there are a
greater number of paths available for the bots, I dont think it would be
significant.

## Defaults & Configuration

Does this change modify default bot behavior?
- - [ ] No
- - [x] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [ ] No
- - [x] Yes (**explain below**)
Gemini first suggested the use of a BFS algorithm. This was rewritten by
me to actually work as intended. Verification by additional logging not
present in final code.

Claude code converted the SQL filtering to the atrocious if statements
found in PrepareDestinationCache, but after verifying them it works. If
there are better ways to do this Im open to it.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-03-20 20:39:53 +01:00

263 lines
9.4 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.
*/
#ifndef _PLAYERBOT_RANDOMPLAYERBOTMGR_H
#define _PLAYERBOT_RANDOMPLAYERBOTMGR_H
#include "NewRpgInfo.h"
#include "ObjectGuid.h"
#include "PlayerbotMgr.h"
#include "GameTime.h"
#include "PlayerbotCommandServer.h"
struct BattlegroundInfo
{
std::vector<uint32> bgInstances;
std::vector<uint32> ratedArenaInstances;
std::vector<uint32> skirmishArenaInstances;
uint32 bgInstanceCount = 0;
uint32 ratedArenaInstanceCount = 0;
uint32 skirmishArenaInstanceCount = 0;
uint32 minLevel = 0;
uint32 maxLevel = 0;
uint32 activeRatedArenaQueue = 0; // 0 = Inactive, 1 = Active
uint32 activeSkirmishArenaQueue = 0; // 0 = Inactive, 1 = Active
uint32 activeBgQueue = 0; // 0 = Inactive, 1 = Active
// Bots (Arena)
uint32 ratedArenaBotCount = 0;
uint32 skirmishArenaBotCount = 0;
// Bots (Battleground)
uint32 bgHordeBotCount = 0;
uint32 bgAllianceBotCount = 0;
// Players (Arena)
uint32 ratedArenaPlayerCount = 0;
uint32 skirmishArenaPlayerCount = 0;
// Players (Battleground)
uint32 bgHordePlayerCount = 0;
uint32 bgAlliancePlayerCount = 0;
};
class ChatHandler;
class PerfMonitorOperation;
class WorldLocation;
struct CachedEvent
{
uint32 value = 0;
uint32 lastChangeTime = 0;
uint32 validIn = 0;
std::string data;
bool IsEmpty() const { return !lastChangeTime; }
};
struct BotEventCache
{
bool loaded = false;
std::unordered_map<std::string, CachedEvent> events;
};
// https://gist.github.com/bradley219/5373998
class botPIDImpl;
class botPID
{
public:
// Kp - proportional gain
// Ki - Integral gain
// Kd - derivative gain
// dt - loop interval time
// max - maximum value of manipulated variable
// min - minimum value of manipulated variable
botPID(double dt, double max, double min, double Kp, double Ki, double Kd);
void adjust(double Kp, double Ki, double Kd);
void reset();
double calculate(double setpoint, double pv);
~botPID();
private:
botPIDImpl* pimpl;
};
class RandomPlayerbotMgr : public PlayerbotHolder
{
public:
static RandomPlayerbotMgr& instance()
{
static RandomPlayerbotMgr instance;
return instance;
}
void LogPlayerLocation();
void UpdateAIInternal(uint32 elapsed, bool minimal = false) override;
uint32 activeBots = 0;
static bool HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args);
bool IsRandomBot(Player* bot);
bool IsRandomBot(ObjectGuid::LowType bot);
bool IsAddclassBot(Player* bot);
bool IsAddclassBot(ObjectGuid::LowType bot);
void Randomize(Player* bot);
void Clear(Player* bot);
void RandomizeFirst(Player* bot);
void RandomizeMin(Player* bot);
void IncreaseLevel(Player* bot);
void ScheduleTeleport(uint32 bot, uint32 time = 0);
void ScheduleChangeStrategy(uint32 bot, uint32 time = 0);
void HandleCommand(uint32 type, std::string const text, Player* fromPlayer, std::string channelName = "");
std::string const HandleRemoteCommand(std::string const request);
void OnPlayerLogout(Player* player);
void OnPlayerLogin(Player* player);
void OnPlayerLoginError(uint32 bot);
Player* GetRandomPlayer();
std::vector<Player*> GetPlayers() { return players; };
PlayerBotMap GetAllBots() { return playerBots; };
void PrintStats();
double GetBuyMultiplier(Player* bot);
double GetSellMultiplier(Player* bot);
void AddTradeDiscount(Player* bot, Player* master, int32 value);
void SetTradeDiscount(Player* bot, Player* master, uint32 value);
uint32 GetTradeDiscount(Player* bot, Player* master);
void Refresh(Player* bot);
void RandomTeleportForLevel(Player* bot);
void RandomTeleportGrindForLevel(Player* bot);
void RandomTeleportForRpg(Player* bot);
uint32 GetMaxAllowedBotCount();
bool ProcessBot(Player* player);
void Revive(Player* player);
void ChangeStrategy(Player* player);
void ChangeStrategyOnce(Player* player);
uint32 GetValue(Player* bot, std::string const& type);
uint32 GetValue(uint32 bot, std::string const& type);
std::string GetData(uint32 bot, std::string const& type);
void SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data = "");
void SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data = "");
void Remove(Player* bot);
ObjectGuid GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
CreatureData const* GetCreatureDataByEntry(uint32 entry);
void LoadBattleMastersCache();
std::map<uint32, std::map<uint32, BattlegroundInfo>> BattlegroundData;
std::map<uint32, std::map<uint32, std::map<TeamId, uint32>>> VisualBots;
std::map<uint32, std::map<uint32, std::map<uint32, uint32>>> Supporters;
std::map<TeamId, std::vector<uint32>> LfgDungeons;
void CheckBgQueue();
void CheckLfgQueue();
void CheckPlayers();
void LogBattlegroundInfo();
std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> getBattleMastersCache()
{
return BattleMastersCache;
}
float getActivityMod() { return activityMod; }
float getActivityPercentage() { return activityMod * 100.0f; }
void setActivityPercentage(float percentage) { activityMod = percentage / 100.0f; }
static uint8 GetTeamClassIdx(bool isAlliance, uint8 claz) { return isAlliance * 20 + claz; }
void PrepareAddclassCache();
void Init();
std::map<uint8, std::unordered_set<ObjectGuid>> addclassCache;
// Account type management
void AssignAccountTypes();
bool IsAccountType(uint32 accountId, uint8 accountType);
protected:
void OnBotLoginInternal(Player* const bot) override;
private:
RandomPlayerbotMgr() : PlayerbotHolder(), processTicks(0)
{
this->playersLevel = sPlayerbotAIConfig.randombotStartingLevel;
if (sPlayerbotAIConfig.enabled || sPlayerbotAIConfig.randomBotAutologin)
{
PlayerbotCommandServer::instance().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)
{
this->BattlegroundData[queueType][bracket] = BattlegroundInfo();
}
}
this->BgCheckTimer = 0;
this->LfgCheckTimer = 0;
this->PlayersCheckTimer = 0;
}
~RandomPlayerbotMgr() = default;
RandomPlayerbotMgr(const RandomPlayerbotMgr&) = delete;
RandomPlayerbotMgr& operator=(const RandomPlayerbotMgr&) = delete;
RandomPlayerbotMgr(RandomPlayerbotMgr&&) = delete;
RandomPlayerbotMgr& operator=(RandomPlayerbotMgr&&) = delete;
// pid values are set in constructor
botPID pid = botPID(1, 50, -50, 0, 0, 0);
float activityMod = 0.25;
bool _isBotInitializing = true;
bool _isBotLogging = true;
NewRpgStatistic rpgStasticTotal;
CachedEvent* FindEvent(uint32 bot, std::string const& event);
uint32 GetEventValue(uint32 bot, std::string const& event);
std::string GetEventData(uint32 bot, std::string const& event);
uint32 SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
std::string const& data = "");
void GetBots();
std::vector<uint32> GetBgBots(uint32 bracket);
time_t BgCheckTimer;
time_t LfgCheckTimer;
time_t PlayersCheckTimer;
time_t RealPlayerLastTimeSeen = 0;
time_t DelayLoginBotsTimer;
time_t printStatsTimer;
uint32 AddRandomBots();
bool ProcessBot(uint32 bot);
void ScheduleRandomize(uint32 bot, uint32 time);
void RandomTeleport(Player* bot);
void RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth = false);
uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ);
typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*);
std::vector<Player*> players;
uint32 processTicks;
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache;
std::unordered_map<uint32, BotEventCache> eventCache;
std::list<uint32> currentBots;
uint32 bgBotsCount;
uint32 playersLevel;
// Account lists
std::vector<uint32> rndBotTypeAccounts; // Accounts marked as RNDbot (type 1)
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
//void ScaleBotActivity(); // Deprecated function
static inline uint32 NowSeconds() { return static_cast<uint32>(GameTime::GetGameTime().count()); }
};
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()
#endif