Fix arena

This commit is contained in:
Keleborn 2026-04-09 16:12:26 -07:00
parent fd5910e4b6
commit 3d0fb90c4f
8 changed files with 231 additions and 239 deletions

View File

@ -11,6 +11,7 @@
#include "AiFactory.h"
#include "ArenaTeam.h"
#include "ArenaTeamMgr.h"
#include "CharacterCache.h"
#include "DBCStores.h"
#include "DBCStructure.h"
#include "GuildMgr.h"
@ -450,7 +451,6 @@ void PlayerbotFactory::Randomize(bool incremental)
if (bot->GetLevel() >= 70)
{
pmo = sPerfMonitor.start(PERF_MON_RNDBOT, "PlayerbotFactory_Arenas");
// LOG_INFO("playerbots", "Initializing arena teams...");
InitArenaTeam();
if (pmo)
pmo->finish();
@ -4049,125 +4049,112 @@ void PlayerbotFactory::InitImmersive()
void PlayerbotFactory::InitArenaTeam()
{
if (!sPlayerbotAIConfig.IsInRandomAccountList(bot->GetSession()->GetAccountId()))
return;
// Currently the teams are only remade after a server restart and if deleteRandomBotArenaTeams = 1
// This is because randomBotArenaTeams is only empty on server restart.
// A manual reinitalization (.playerbots rndbot init) is also required after the teams have been deleted.
if (sPlayerbotAIConfig.randomBotArenaTeams.empty())
if (sPlayerbotAIConfig.deleteRandomBotArenaTeams)
return;
if (bot->GetLevel() < 70)
return;
// One team max to avoid queueing conflicts
for (uint32 arena_slot = 0; arena_slot < MAX_ARENA_SLOT; ++arena_slot)
{
if (sPlayerbotAIConfig.deleteRandomBotArenaTeams)
{
LOG_INFO("playerbots", "Deleting random bot arena teams...");
for (auto it = sArenaTeamMgr->GetArenaTeams().begin(); it != sArenaTeamMgr->GetArenaTeams().end(); ++it)
{
ArenaTeam* arenateam = it->second;
if (arenateam->GetCaptain() && arenateam->GetCaptain().IsPlayer())
{
Player* bot = ObjectAccessor::FindPlayer(arenateam->GetCaptain());
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI || botAI->IsRealPlayer())
{
continue;
}
else
{
arenateam->Disband(nullptr);
}
}
}
LOG_INFO("playerbots", "Random bot arena teams deleted");
}
RandomPlayerbotFactory::CreateRandomArenaTeams(ARENA_TYPE_2v2, sPlayerbotAIConfig.randomBotArenaTeam2v2Count);
RandomPlayerbotFactory::CreateRandomArenaTeams(ARENA_TYPE_3v3, sPlayerbotAIConfig.randomBotArenaTeam3v3Count);
RandomPlayerbotFactory::CreateRandomArenaTeams(ARENA_TYPE_5v5, sPlayerbotAIConfig.randomBotArenaTeam5v5Count);
if (bot->GetArenaTeamId(arena_slot))
return;
}
std::vector<uint32> arenateams;
for (std::vector<uint32>::iterator i = sPlayerbotAIConfig.randomBotArenaTeams.begin();
i != sPlayerbotAIConfig.randomBotArenaTeams.end(); ++i)
arenateams.push_back(*i);
if (arenateams.empty())
// If captains are still needed, create a team with this bot as captain.
for (auto& [type, needed] : sRandomPlayerbotMgr.arenaCaptainsNeeded)
{
LOG_ERROR("playerbots", "No random arena team available");
if (needed > 0)
{
if (CreateArenaTeamWithCaptain(type))
--needed;
return;
}
}
if (!sRandomPlayerbotMgr.arenaTeamsFull)
AssignToArenaTeam();
}
bool PlayerbotFactory::CreateArenaTeamWithCaptain(ArenaType type)
{
std::string teamName = RandomPlayerbotFactory::CreateRandomArenaTeamName();
if (teamName.empty())
return false;
ArenaTeam* arenateam = new ArenaTeam();
if (!arenateam->Create(bot->GetGUID(), type, teamName, 0, 0, 0, 0, 0))
{
LOG_ERROR("playerbots", "Error creating arena team {}", teamName);
delete arenateam;
return false;
}
arenateam->SetCaptain(bot->GetGUID());
arenateam->SetRatingForAll(
urand(sPlayerbotAIConfig.randomBotArenaTeamMinRating, sPlayerbotAIConfig.randomBotArenaTeamMaxRating));
uint32 backgroundColor = urand(0xFF000000, 0xFFFFFFFF);
uint32 emblemStyle = urand(0, 101);
uint32 emblemColor = urand(0xFF000000, 0xFFFFFFFF);
uint32 borderStyle = urand(0, 5);
uint32 borderColor = urand(0xFF000000, 0xFFFFFFFF);
arenateam->SetEmblem(backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor);
arenateam->SaveToDB();
sArenaTeamMgr->AddArenaTeam(arenateam);
LOG_DEBUG("playerbots", "Created {}v{} arena team '{}' with captain {}",
type, type, teamName, bot->GetName());
return true;
}
void PlayerbotFactory::AssignToArenaTeam()
{
ArenaTeam* arenateam = nullptr;
for (auto const& [id, team] : sArenaTeamMgr->GetArenaTeams())
{
if (team->GetMembersSize() >= static_cast<uint32>(team->GetType()))
continue;
if (!RandomPlayerbotFactory::IsBotArenaTeam(team))
continue;
ObjectGuid captainGuid = team->GetCaptain();
CharacterCacheEntry const* captainEntry = sCharacterCache->GetCharacterCacheByGuid(captainGuid);
if (!captainEntry || Player::TeamIdForRace(captainEntry->Race) != bot->GetTeamId())
continue;
arenateam = team;
break;
}
if (!arenateam)
{
sRandomPlayerbotMgr.arenaTeamsFull = true;
return;
}
while (!arenateams.empty())
arenateam->AddMember(bot->GetGUID());
// Synchronize ratings once the team is full
if (arenateam->GetMembersSize() >= static_cast<uint32>(arenateam->GetType()))
{
int index = urand(0, arenateams.size() - 1);
uint32 arenateamID = arenateams[index];
ArenaTeam* arenateam = sArenaTeamMgr->GetArenaTeamById(arenateamID);
if (!arenateam)
uint32 teamRating = arenateam->GetRating();
arenateam->SetRatingForAll(teamRating);
for (auto& member : arenateam->GetMembers())
{
LOG_ERROR("playerbots", "Invalid arena team {}", arenateamID);
arenateams.erase(arenateams.begin() + index);
continue;
member.MatchMakerRating = member.PersonalRating;
member.MaxMMR = std::max(member.MaxMMR, member.PersonalRating);
}
if (arenateam->GetMembersSize() < ((uint32)arenateam->GetType()) && bot->GetLevel() >= 70)
{
ObjectGuid capt = arenateam->GetCaptain();
Player* botcaptain = ObjectAccessor::FindPlayer(capt);
// To avoid bots removing each other from groups when queueing, force them to only be in one team
for (uint32 arena_slot = 0; arena_slot < MAX_ARENA_SLOT; ++arena_slot)
{
uint32 arenaTeamId = bot->GetArenaTeamId(arena_slot);
if (!arenaTeamId)
continue;
ArenaTeam* team = sArenaTeamMgr->GetArenaTeamById(arenaTeamId);
if (team)
{
if (sCharacterCache->GetCharacterArenaTeamIdByGuid(bot->GetGUID(), team->GetSlot()) != 0)
{
return;
}
return;
}
}
if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need?
{
// Add bot to arena team
arenateam->AddMember(bot->GetGUID());
// Only synchronize ratings once the team is full (avoid redundant work)
// The captain was added with incorrect ratings when the team was created,
// so we fix everyone's ratings once the roster is complete
if (arenateam->GetMembersSize() >= (uint32)arenateam->GetType())
{
uint32 teamRating = arenateam->GetRating();
// Use SetRatingForAll to align all members with team rating
arenateam->SetRatingForAll(teamRating);
// For bot-only teams, keep MMR synchronized with team rating
// This ensures matchmaking reflects the artificial team strength (1000-2000 range)
// instead of being influenced by the global CONFIG_ARENA_START_MATCHMAKER_RATING
for (auto& member : arenateam->GetMembers())
{
// Set MMR to match personal rating (which already matches team rating)
member.MatchMakerRating = member.PersonalRating;
member.MaxMMR = std::max(member.MaxMMR, member.PersonalRating);
}
// Force save all member data to database
arenateam->SaveToDB(true);
}
}
}
arenateams.erase(arenateams.begin() + index);
arenateam->SaveToDB(true);
}
// bot->SaveToDB(false, false);
}
void PlayerbotFactory::ApplyEnchantTemplate()

View File

@ -6,6 +6,7 @@
#ifndef _PLAYERBOT_PLAYERBOTFACTORY_H
#define _PLAYERBOT_PLAYERBOTFACTORY_H
#include "Battleground.h"
#include "InventoryAction.h"
#include "Player.h"
#include "PlayerbotAI.h"
@ -119,6 +120,8 @@ private:
void InitInventorySkill();
Item* StoreItem(uint32 itemId, uint32 count);
void InitArenaTeam();
bool CreateArenaTeamWithCaptain(ArenaType type);
void AssignToArenaTeam();
void InitImmersive();
static void AddPrevQuests(uint32 questId, std::list<uint32>& questIds);
void LoadEnchantContainer();

View File

@ -7,8 +7,10 @@
#include "AccountMgr.h"
#include "ArenaTeamMgr.h"
#include "CharacterCache.h"
#include "DatabaseEnv.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include "RaceMgr.h"
#include "ScriptMgr.h"
#include "SharedDefines.h"
@ -782,145 +784,90 @@ std::string const RandomPlayerbotFactory::CreateRandomGuildName()
return guildName;
}
void RandomPlayerbotFactory::CreateRandomArenaTeams(ArenaType type, uint32 count)
bool RandomPlayerbotFactory::IsBotArenaTeam(ArenaTeam const* team)
{
std::vector<uint32> randomBots;
if (!team)
return false;
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BOT);
stmt->SetData(0, "add");
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
uint32 bot = fields[0].Get<uint32>();
randomBots.push_back(bot);
} while (result->NextRow());
}
ObjectGuid captainGuid = team->GetCaptain();
if (!captainGuid || !captainGuid.IsPlayer())
return false;
uint32 arenaTeamNumber = 0;
GuidVector availableCaptains;
for (std::vector<uint32>::iterator i = randomBots.begin(); i != randomBots.end(); ++i)
{
ObjectGuid captain = ObjectGuid::Create<HighGuid::Player>(*i);
ArenaTeam* arenateam = sArenaTeamMgr->GetArenaTeamByCaptain(captain, type);
if (arenateam)
{
++arenaTeamNumber;
sPlayerbotAIConfig.randomBotArenaTeams.push_back(arenateam->GetId());
}
else
{
Player* player = ObjectAccessor::FindConnectedPlayer(captain);
if (!arenateam && player && player->GetLevel() >= 70)
availableCaptains.push_back(captain);
}
}
for (; arenaTeamNumber < count; ++arenaTeamNumber)
{
std::string const arenaTeamName = CreateRandomArenaTeamName();
if (arenaTeamName.empty())
continue;
if (availableCaptains.empty())
{
LOG_ERROR("playerbots", "No captains for random arena teams available");
continue;
}
uint32 index = urand(0, availableCaptains.size() - 1);
ObjectGuid captain = availableCaptains[index];
Player* player = ObjectAccessor::FindConnectedPlayer(captain);
if (!player)
{
LOG_ERROR("playerbots", "Cannot find player for captain {}", captain.ToString().c_str());
continue;
}
if (player->GetLevel() < 70)
{
LOG_ERROR("playerbots", "Bot {} must be level 70 to create an arena team", captain.ToString().c_str());
continue;
}
// Below query no longer required as now user has control over the number of each type of arena team they want
// to create. Keeping commented for potential future reference. QueryResult results =
// CharacterDatabase.Query("SELECT `type` FROM playerbots_arena_team_names WHERE name = '{}'",
// arenaTeamName.c_str()); if (!results)
// {
// LOG_ERROR("playerbots", "No valid types for arena teams");
// return;
// }
// Field* fields = results->Fetch();
// uint8 slot = fields[0].Get<uint8>();
ArenaTeam* arenateam = new ArenaTeam();
if (!arenateam->Create(player->GetGUID(), type, arenaTeamName, 0, 0, 0, 0, 0))
{
LOG_ERROR("playerbots", "Error creating arena team {}", arenaTeamName.c_str());
continue;
}
arenateam->SetCaptain(player->GetGUID());
// set random rating
arenateam->SetRatingForAll(
urand(sPlayerbotAIConfig.randomBotArenaTeamMinRating, sPlayerbotAIConfig.randomBotArenaTeamMaxRating));
// set random emblem
uint32 backgroundColor = urand(0xFF000000, 0xFFFFFFFF);
uint32 emblemStyle = urand(0, 101);
uint32 emblemColor = urand(0xFF000000, 0xFFFFFFFF);
uint32 borderStyle = urand(0, 5);
uint32 borderColor = urand(0xFF000000, 0xFFFFFFFF);
arenateam->SetEmblem(backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor);
// set random kills (wip)
// arenateam->SetStats(STAT_TYPE_GAMES_WEEK, urand(0, 30));
// arenateam->SetStats(STAT_TYPE_WINS_WEEK, urand(0, arenateam->GetStats().games_week));
// arenateam->SetStats(STAT_TYPE_GAMES_SEASON, urand(arenateam->GetStats().games_week,
// arenateam->GetStats().games_week * 5)); arenateam->SetStats(STAT_TYPE_WINS_SEASON,
// urand(arenateam->GetStats().wins_week, arenateam->GetStats().games
arenateam->SaveToDB();
sArenaTeamMgr->AddArenaTeam(arenateam);
sPlayerbotAIConfig.randomBotArenaTeams.push_back(arenateam->GetId());
}
LOG_DEBUG("playerbots", "{} random bot {}vs{} arena teams available", arenaTeamNumber, type, type);
uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(captainGuid);
return accountId && sPlayerbotAIConfig.IsInRandomAccountList(accountId);
}
std::string const RandomPlayerbotFactory::CreateRandomArenaTeamName()
void RandomPlayerbotFactory::LoadArenaTeamNames()
{
std::string arenaTeamName = "";
_availableArenaTeamNames.clear();
QueryResult result = CharacterDatabase.Query("SELECT MAX(name_id) FROM playerbots_arena_team_names");
if (!result)
{
LOG_ERROR("playerbots", "No more names left for random arena teams");
return arenaTeamName;
}
Field* fields = result->Fetch();
uint32 maxId = fields[0].Get<uint32>();
uint32 id = urand(0, maxId);
result = CharacterDatabase.Query(
"SELECT n.name FROM playerbots_arena_team_names n LEFT OUTER JOIN arena_team e ON e.name = n.name "
"WHERE e.arenateamid IS NULL AND n.name_id >= {} LIMIT 1",
id);
QueryResult result = CharacterDatabase.Query(
"SELECT n.name FROM playerbots_arena_team_names n "
"LEFT OUTER JOIN arena_team e ON e.name = n.name "
"WHERE e.arenateamid IS NULL");
if (!result)
{
LOG_ERROR("playerbots", "No more names left for random arena teams");
return arenaTeamName;
LOG_ERROR("playerbots", "No arena team names available in playerbots_arena_team_names");
return;
}
fields = result->Fetch();
arenaTeamName = fields[0].Get<std::string>();
do
{
Field* fields = result->Fetch();
_availableArenaTeamNames.push_back(fields[0].Get<std::string>());
} while (result->NextRow());
return arenaTeamName;
for (size_t i = _availableArenaTeamNames.size() - 1; i > 0; --i)
{
size_t j = urand(0, i);
std::swap(_availableArenaTeamNames[i], _availableArenaTeamNames[j]);
}
LOG_INFO("playerbots", "Loaded {} available arena team names", _availableArenaTeamNames.size());
}
uint32 RandomPlayerbotFactory::GetBotArenaTeamCount(ArenaType type)
{
uint32 count = 0;
for (auto const& [id, arenateam] : sArenaTeamMgr->GetArenaTeams())
{
if (arenateam->GetType() == type && IsBotArenaTeam(arenateam))
++count;
}
return count;
}
void RandomPlayerbotFactory::DeleteBotArenaTeams()
{
LOG_INFO("playerbots", "Deleting random bot arena teams...");
std::vector<uint32> teamsToDisband;
for (auto const& [id, arenateam] : sArenaTeamMgr->GetArenaTeams())
{
if (IsBotArenaTeam(arenateam))
teamsToDisband.push_back(id);
}
for (uint32 teamId : teamsToDisband)
{
ArenaTeam* team = sArenaTeamMgr->GetArenaTeamById(teamId);
if (team)
team->Disband(nullptr);
}
LOG_INFO("playerbots", "Deleted {} random bot arena teams", teamsToDisband.size());
}
std::string RandomPlayerbotFactory::CreateRandomArenaTeamName()
{
if (_availableArenaTeamNames.empty())
{
LOG_ERROR("playerbots", "No more names left for random arena teams");
return "";
}
std::string name = std::move(_availableArenaTeamNames.back());
_availableArenaTeamNames.pop_back();
return name;
}

View File

@ -7,17 +7,17 @@
#define _PLAYERBOT_RANDOMPLAYERBOTFACTORY_H
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
#include "ArenaTeam.h"
#include "Common.h"
#include "DBCEnums.h"
class Player;
class WorldSession;
enum ArenaType : uint8;
class RandomPlayerbotFactory
{
public:
@ -51,15 +51,22 @@ public:
Player* CreateRandomBot(WorldSession* session, uint8 cls, std::unordered_map<NameRaceAndGender, std::vector<std::string>>& names);
static void CreateRandomBots();
static void CreateRandomArenaTeams(ArenaType slot, uint32 count);
static std::string const CreateRandomGuildName();
static uint32 CalculateTotalAccountCount();
static uint32 CalculateAvailableCharsPerAccount();
// Arena team utilities (static — no bot instance needed)
static void DeleteBotArenaTeams();
static uint32 GetBotArenaTeamCount(ArenaType type);
static bool IsBotArenaTeam(ArenaTeam const* team);
static void LoadArenaTeamNames();
static std::string CreateRandomArenaTeamName();
private:
static bool IsValidRaceClassCombination(uint8 race, uint8 class_, uint32 expansion);
std::string const CreateRandomBotName(NameRaceAndGender raceAndGender);
static std::string const CreateRandomArenaTeamName();
static inline std::vector<std::string> _availableArenaTeamNames;
};
#endif

View File

@ -1763,6 +1763,47 @@ void RandomPlayerbotMgr::Init()
PlayerbotsDatabase.Execute("DELETE FROM playerbots_random_bots WHERE event = 'add'");
}
void RandomPlayerbotMgr::InitArenaTeams()
{
if (sPlayerbotAIConfig.deleteRandomBotArenaTeams)
{
RandomPlayerbotFactory::DeleteBotArenaTeams();
return;
}
RandomPlayerbotFactory::LoadArenaTeamNames();
uint32 existing2v2 = RandomPlayerbotFactory::GetBotArenaTeamCount(ARENA_TYPE_2v2);
uint32 existing3v3 = RandomPlayerbotFactory::GetBotArenaTeamCount(ARENA_TYPE_3v3);
uint32 existing5v5 = RandomPlayerbotFactory::GetBotArenaTeamCount(ARENA_TYPE_5v5);
auto deficit = [](uint32 existing, uint32 target) -> uint32
{
return existing < target ? target - existing : 0;
};
arenaCaptainsNeeded[ARENA_TYPE_2v2] = deficit(existing2v2, sPlayerbotAIConfig.randomBotArenaTeam2v2Count);
arenaCaptainsNeeded[ARENA_TYPE_3v3] = deficit(existing3v3, sPlayerbotAIConfig.randomBotArenaTeam3v3Count);
arenaCaptainsNeeded[ARENA_TYPE_5v5] = deficit(existing5v5, sPlayerbotAIConfig.randomBotArenaTeam5v5Count);
LOG_INFO("playerbots", "Bot arena teams: 2v2={}/{}, 3v3={}/{}, 5v5={}/{} (captains needed: {}/{}/{})",
existing2v2, sPlayerbotAIConfig.randomBotArenaTeam2v2Count,
existing3v3, sPlayerbotAIConfig.randomBotArenaTeam3v3Count,
existing5v5, sPlayerbotAIConfig.randomBotArenaTeam5v5Count,
arenaCaptainsNeeded[ARENA_TYPE_2v2],
arenaCaptainsNeeded[ARENA_TYPE_3v3],
arenaCaptainsNeeded[ARENA_TYPE_5v5]);
}
void RandomPlayerbotMgr::OnBotLoginArenaTeam(Player* bot)
{
if (!bot)
return;
PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitArenaTeam();
}
void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot)
{
if (bot->InBattleground())
@ -2539,6 +2580,9 @@ void RandomPlayerbotMgr::OnBotLoginInternal(Player* const bot)
factory.InitGuild();
}
// Assign bot to an arena team if eligible
OnBotLoginArenaTeam(bot);
if (sPlayerbotAIConfig.randomBotFixedLevel)
{
bot->SetPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN);

View File

@ -6,6 +6,7 @@
#ifndef _PLAYERBOT_RANDOMPLAYERBOTMGR_H
#define _PLAYERBOT_RANDOMPLAYERBOTMGR_H
#include "Battleground.h"
#include "NewRpgInfo.h"
#include "ObjectGuid.h"
#include "PlayerbotMgr.h"
@ -120,6 +121,10 @@ public:
Player* GetRandomPlayer();
std::vector<Player*> GetPlayers() { return players; };
PlayerBotMap GetAllBots() { return playerBots; };
void InitArenaTeams();
void OnBotLoginArenaTeam(Player* bot);
std::map<ArenaType, uint32> arenaCaptainsNeeded;
bool arenaTeamsFull = false;
void PrintStats();
double GetBuyMultiplier(Player* bot);
double GetSellMultiplier(Player* bot);

View File

@ -681,6 +681,7 @@ bool PlayerbotAIConfig::Initialize()
}
PlayerbotGuildMgr::instance().Init();
sRandomPlayerbotMgr.InitArenaTeams();
sRandomItemMgr.Init();
sRandomItemMgr.InitAfterAhBot();
PlayerbotTextMgr::instance().LoadBotTexts();

View File

@ -375,14 +375,12 @@ public:
int32 enableRandomBotTrading;
uint32 tweakValue; // Debugging config
uint32 randomBotArenaTeamCount;
uint32 randomBotArenaTeamMaxRating;
uint32 randomBotArenaTeamMinRating;
uint32 randomBotArenaTeam2v2Count;
uint32 randomBotArenaTeam3v3Count;
uint32 randomBotArenaTeam5v5Count;
bool deleteRandomBotArenaTeams;
std::vector<uint32> randomBotArenaTeams;
uint32 selfBotLevel;
bool downgradeMaxLevelBot;