Enable new rpg strategy by default (#1192)

* Add RandomBotMinLevelChance

* Save mana only for healer

* Disable addclass dk for low level player

* Target selection and debuff cast with less players in group

* Change default rpg strategy and bots count in config

* Logs clean up

* Improve init=auto

* Remove login logs after initialization

* Rndbots stats for quest

* Prediction chase in reach combat

* Poor & Normal items ensurence for init=auto
This commit is contained in:
Yunfan Li
2025-04-11 20:31:38 +08:00
committed by GitHub
parent 0d19f298da
commit 19447c3914
18 changed files with 175 additions and 73 deletions

View File

@@ -82,8 +82,8 @@ AiPlayerbot.Enabled = 1
AiPlayerbot.RandomBotAutologin = 1 AiPlayerbot.RandomBotAutologin = 1
# Random bot count # Random bot count
AiPlayerbot.MinRandomBots = 50 AiPlayerbot.MinRandomBots = 500
AiPlayerbot.MaxRandomBots = 50 AiPlayerbot.MaxRandomBots = 500
# Random bot accounts # Random bot accounts
# If you are not using any expansion at all, you may have to set this manually, then # If you are not using any expansion at all, you may have to set this manually, then
@@ -561,10 +561,13 @@ AiPlayerbot.EnableRandomBotTrading = 1
AiPlayerbot.DisableRandomLevels = 0 AiPlayerbot.DisableRandomLevels = 0
# Set randombots starting level here if "AiPlayerbot.DisableRandomLevels" enabled # Set randombots starting level here if "AiPlayerbot.DisableRandomLevels" enabled
AiPlayerbot.RandombotStartingLevel = 5 AiPlayerbot.RandombotStartingLevel = 1
# Chance random bot has max level on first randomize (default 0.15) # Chance random bot has min level on first randomize (default 0.1)
AiPlayerbot.RandomBotMaxLevelChance = 0.15 AiPlayerbot.RandomBotMinLevelChance = 0.1
# Chance random bot has max level on first randomize (default 0.1)
AiPlayerbot.RandomBotMaxLevelChance = 0.1
# Fix the level of random bot (won't level up by grinding) # Fix the level of random bot (won't level up by grinding)
# Default: 0 (disable) # Default: 0 (disable)
@@ -689,8 +692,8 @@ AiPlayerbot.AutoDoQuests = 1
# Random Bots will behave more like real players (exprimental) # Random Bots will behave more like real players (exprimental)
# This option will override AutoDoQuests # This option will override AutoDoQuests
# Default: 0 (disabled) # Default: 1 (enabled)
AiPlayerbot.EnableNewRpgStrategy = 0 AiPlayerbot.EnableNewRpgStrategy = 1
# Quest items to leave (do not destroy) # Quest items to leave (do not destroy)
AiPlayerbot.RandomBotQuestItems = "6948,5175,5176,5177,5178,16309,12382,13704,11000" AiPlayerbot.RandomBotQuestItems = "6948,5175,5176,5177,5178,16309,12382,13704,11000"

View File

@@ -617,7 +617,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
"gather", "duel", "pvp", "buff", "mount", "emote", nullptr); "gather", "duel", "pvp", "buff", "mount", "emote", nullptr);
} }
if (sPlayerbotAIConfig->autoSaveMana) if (sPlayerbotAIConfig->autoSaveMana && PlayerbotAI::IsHeal(player, true))
{ {
nonCombatEngine->addStrategy("save mana", false); nonCombatEngine->addStrategy("save mana", false);
} }

View File

@@ -120,7 +120,8 @@ bool PlayerbotAIConfig::Initialize()
randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3); randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3);
randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0); randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0);
randomBotMaxLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMaxLevelChance", 0.15f); randomBotMinLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMinLevelChance", 0.1f);
randomBotMaxLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMaxLevelChance", 0.1f);
randomBotRpgChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotRpgChance", 0.20f); randomBotRpgChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotRpgChance", 0.20f);
iterationsPerTick = sConfigMgr->GetOption<int32>("AiPlayerbot.IterationsPerTick", 100); iterationsPerTick = sConfigMgr->GetOption<int32>("AiPlayerbot.IterationsPerTick", 100);

View File

@@ -86,7 +86,7 @@ public:
float randomGearLoweringChance; float randomGearLoweringChance;
int32 randomGearQualityLimit; int32 randomGearQualityLimit;
int32 randomGearScoreLimit; int32 randomGearScoreLimit;
float randomBotMaxLevelChance; float randomBotMinLevelChance, randomBotMaxLevelChance;
float randomBotRpgChance; float randomBotRpgChance;
uint32 minRandomBots, maxRandomBots; uint32 minRandomBots, maxRandomBots;
uint32 randomBotUpdateInterval, randomBotCountChangeMinInterval, randomBotCountChangeMaxInterval; uint32 randomBotUpdateInterval, randomBotCountChangeMinInterval, randomBotCountChangeMaxInterval;

View File

@@ -275,7 +275,7 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
sPlayerbotDbStore->Save(botAI); sPlayerbotDbStore->Save(botAI);
} }
LOG_INFO("playerbots", "Bot {} logging out", bot->GetName().c_str()); LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str());
bot->SaveToDB(false, false); bot->SaveToDB(false, false);
WorldSession* botWorldSessionPtr = bot->GetSession(); WorldSession* botWorldSessionPtr = bot->GetSession();
@@ -543,12 +543,15 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
bot->SaveToDB(false, false); bot->SaveToDB(false, false);
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(bot->GetGUID().GetCounter()); bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(bot->GetGUID().GetCounter());
if (addClassBot && master && isRandomAccount && master->GetLevel() < bot->GetLevel()) if (addClassBot && master && isRandomAccount && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
{ {
// PlayerbotFactory factory(bot, master->GetLevel()); // PlayerbotFactory factory(bot, master->GetLevel());
// factory.Randomize(false); // factory.Randomize(false);
uint32 mixedGearScore = uint32 mixedGearScore =
PlayerbotAI::GetMixedGearScore(master, true, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio; PlayerbotAI::GetMixedGearScore(master, true, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
// work around: distinguish from 0 if no gear
if (mixedGearScore == 0)
mixedGearScore = 1;
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore); PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore);
factory.Randomize(false); factory.Randomize(false);
} }
@@ -728,6 +731,9 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
{ {
uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, true, false, 12) * uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, true, false, 12) *
sPlayerbotAIConfig->autoInitEquipLevelLimitRatio; sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
// work around: distinguish from 0 if no gear
if (mixedGearScore == 0)
mixedGearScore = 1;
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore); PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore);
factory.Randomize(false); factory.Randomize(false);
return "ok, gear score limit: " + std::to_string(mixedGearScore / PlayerbotAI::GetItemScoreMultiplier(ItemQualities(ITEM_QUALITY_EPIC))) + return "ok, gear score limit: " + std::to_string(mixedGearScore / PlayerbotAI::GetItemScoreMultiplier(ItemQualities(ITEM_QUALITY_EPIC))) +
@@ -1051,6 +1057,11 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
messages.push_back("Error: Invalid Class. Try again."); messages.push_back("Error: Invalid Class. Try again.");
return messages; return messages;
} }
if (claz == 6 && master->GetLevel() < sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL))
{
messages.push_back("Your level is too low to summon Deathknight");
return messages;
}
uint8 teamId = master->GetTeamId(true); uint8 teamId = master->GetTeamId(true);
const std::unordered_set<ObjectGuid> &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)]; const std::unordered_set<ObjectGuid> &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)];
for (const ObjectGuid &guid: guidCache) for (const ObjectGuid &guid: guidCache)

View File

@@ -379,7 +379,11 @@ public:
sRandomPlayerbotMgr->OnPlayerLogout(player); sRandomPlayerbotMgr->OnPlayerLogout(player);
} }
void OnPlayerbotLogoutBots() override { sRandomPlayerbotMgr->LogoutAllBots(); } void OnPlayerbotLogoutBots() override
{
LOG_INFO("playerbots", "Logging out all bots...");
sRandomPlayerbotMgr->LogoutAllBots();
}
}; };
void AddPlayerbotsScripts() void AddPlayerbotsScripts()

View File

@@ -2362,7 +2362,7 @@ void RandomItemMgr::BuildPotionCache()
continue; continue;
uint32 requiredLevel = proto->RequiredLevel; uint32 requiredLevel = proto->RequiredLevel;
if (requiredLevel > level || (level > 15 && requiredLevel < level - 15)) if (requiredLevel > level || (level > 13 && requiredLevel < level - 13))
continue; continue;
if (proto->RequiredSkill) if (proto->RequiredSkill)

View File

@@ -981,7 +981,7 @@ void RandomPlayerbotFactory::CreateRandomArenaTeams(ArenaType type, uint32 count
sPlayerbotAIConfig->randomBotArenaTeams.push_back(arenateam->GetId()); sPlayerbotAIConfig->randomBotArenaTeams.push_back(arenateam->GetId());
} }
LOG_INFO("playerbots", "{} random bot {}vs{} arena teams available", arenaTeamNumber, type, type); LOG_DEBUG("playerbots", "{} random bot {}vs{} arena teams available", arenaTeamNumber, type, type);
} }
std::string const RandomPlayerbotFactory::CreateRandomArenaTeamName() std::string const RandomPlayerbotFactory::CreateRandomArenaTeamName()

View File

@@ -386,7 +386,8 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
} }
else else
{ {
activatePrintStatsThread(); sRandomPlayerbotMgr->PrintStats();
// activatePrintStatsThread();
} }
} }
uint32 updateBots = sPlayerbotAIConfig->randomBotsPerInterval * onlineBotFocus / 100; uint32 updateBots = sPlayerbotAIConfig->randomBotsPerInterval * onlineBotFocus / 100;
@@ -415,7 +416,7 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
loginBots += updateBots; loginBots += updateBots;
loginBots = std::min(loginBots, maxNewBots); loginBots = std::min(loginBots, maxNewBots);
LOG_INFO("playerbots", "{} new bots", loginBots); LOG_DEBUG("playerbots", "{} new bots prepared to login", loginBots);
// Log in bots // Log in bots
for (auto bot : availableBots) for (auto bot : availableBots)
@@ -1103,10 +1104,10 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
if (!player || !player->GetGroup()) if (!player || !player->GetGroup())
{ {
if (player) if (player)
LOG_INFO("playerbots", "Bot #{} {}:{} <{}>: log out", bot, IsAlliance(player->getRace()) ? "A" : "H", LOG_DEBUG("playerbots", "Bot #{} {}:{} <{}>: log out", bot, IsAlliance(player->getRace()) ? "A" : "H",
player->GetLevel(), player->GetName().c_str()); player->GetLevel(), player->GetName().c_str());
else else
LOG_INFO("playerbots", "Bot #{}: log out", bot); LOG_DEBUG("playerbots", "Bot #{}: log out", bot);
SetEventValue(bot, "add", 0, 0); SetEventValue(bot, "add", 0, 0);
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end()); currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
@@ -1189,7 +1190,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
uint32 logout = GetEventValue(bot, "logout"); uint32 logout = GetEventValue(bot, "logout");
if (player && !logout && !isValid) if (player && !logout && !isValid)
{ {
LOG_INFO("playerbots", "Bot #{} {}:{} <{}>: log out", bot, IsAlliance(player->getRace()) ? "A" : "H", LOG_DEBUG("playerbots", "Bot #{} {}:{} <{}>: log out", bot, IsAlliance(player->getRace()) ? "A" : "H",
player->GetLevel(), player->GetName().c_str()); player->GetLevel(), player->GetName().c_str());
LogoutPlayerBot(botGUID); LogoutPlayerBot(botGUID);
currentBots.remove(bot); currentBots.remove(bot);
@@ -1434,6 +1435,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
z = 0.05f + ground; z = 0.05f + ground;
PlayerInfo const* pInfo = sObjectMgr->GetPlayerInfo(bot->getRace(true), bot->getClass()); PlayerInfo const* pInfo = sObjectMgr->GetPlayerInfo(bot->getRace(true), bot->getClass());
float dis = loc.GetExactDist(pInfo->positionX, pInfo->positionY, pInfo->positionZ); float dis = loc.GetExactDist(pInfo->positionX, pInfo->positionY, pInfo->positionZ);
// yunfan: distance check for low level // yunfan: distance check for low level
if (bot->GetLevel() <= 4 && (loc.GetMapId() != pInfo->mapId || dis > 500.0f)) if (bot->GetLevel() <= 4 && (loc.GetMapId() != pInfo->mapId || dis > 500.0f))
{ {
@@ -1462,6 +1464,12 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
bot->SetHomebind(loc, zone->ID); bot->SetHomebind(loc, zone->ID);
} }
// Prevent blink to be detected by visible real players
if (dis < 150.0f && botAI->HasPlayerNearby(150.0f))
{
break;
}
bot->GetMotionMaster()->Clear(); bot->GetMotionMaster()->Clear();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI) if (botAI)
@@ -1478,8 +1486,8 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
if (pmo) if (pmo)
pmo->finish(); pmo->finish();
LOG_ERROR("playerbots", "Cannot teleport bot {} - no locations available ({} locations)", bot->GetName().c_str(), // LOG_ERROR("playerbots", "Cannot teleport bot {} - no locations available ({} locations)", bot->GetName().c_str(),
tlocs.size()); // tlocs.size());
} }
void RandomPlayerbotMgr::PrepareZone2LevelBracket() void RandomPlayerbotMgr::PrepareZone2LevelBracket()
@@ -1969,6 +1977,13 @@ void RandomPlayerbotMgr::RandomizeFirst(Player* bot)
if (sPlayerbotAIConfig->syncLevelWithPlayers) if (sPlayerbotAIConfig->syncLevelWithPlayers)
maxLevel = std::max(sPlayerbotAIConfig->randomBotMinLevel, maxLevel = std::max(sPlayerbotAIConfig->randomBotMinLevel,
std::min(playersLevel, sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))); 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"); PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "RandomizeFirst");
@@ -1987,14 +2002,19 @@ void RandomPlayerbotMgr::RandomizeFirst(Player* bot)
} }
else else
{ {
level = urand(sPlayerbotAIConfig->randomBotMinLevel, maxLevel); uint32 roll = urand(1, 100);
if (urand(1, 100) < 100 * sPlayerbotAIConfig->randomBotMaxLevelChance) if (roll <= 100 * sPlayerbotAIConfig->randomBotMaxLevelChance)
{
level = maxLevel; level = maxLevel;
}
if (bot->getClass() == CLASS_DEATH_KNIGHT) else if (roll <= (100 * (sPlayerbotAIConfig->randomBotMaxLevelChance + sPlayerbotAIConfig->randomBotMinLevelChance)))
level = urand( {
std::max(sPlayerbotAIConfig->randomBotMinLevel, sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL)), level = minLevel;
std::max(sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL), maxLevel)); }
else
{
level = urand(minLevel, maxLevel);
}
} }
if (sPlayerbotAIConfig->disableRandomLevels) if (sPlayerbotAIConfig->disableRandomLevels)
@@ -2394,7 +2414,8 @@ bool RandomPlayerbotMgr::HandlePlayerbotConsoleCommand(ChatHandler* handler, cha
if (cmd == "stats") if (cmd == "stats")
{ {
activatePrintStatsThread(); sRandomPlayerbotMgr->PrintStats();
// activatePrintStatsThread();
return true; return true;
} }
@@ -2536,8 +2557,16 @@ void RandomPlayerbotMgr::OnPlayerLogout(Player* player)
void RandomPlayerbotMgr::OnBotLoginInternal(Player* const bot) void RandomPlayerbotMgr::OnBotLoginInternal(Player* const bot)
{ {
LOG_INFO("playerbots", "{}/{} Bot {} logged in", playerBots.size(), sRandomPlayerbotMgr->GetMaxAllowedBotCount(), if (_isBotLogging)
bot->GetName().c_str()); {
LOG_INFO("playerbots", "{}/{} Bot {} logged in", playerBots.size(), sRandomPlayerbotMgr->GetMaxAllowedBotCount(),
bot->GetName().c_str());
if (playerBots.size() == sRandomPlayerbotMgr->GetMaxAllowedBotCount())
{
_isBotLogging = false;
}
}
if (sPlayerbotAIConfig->randomBotFixedLevel) if (sPlayerbotAIConfig->randomBotFixedLevel)
{ {
@@ -2697,7 +2726,7 @@ void RandomPlayerbotMgr::PrintStats()
uint32 engine_combat = 0; uint32 engine_combat = 0;
uint32 engine_dead = 0; uint32 engine_dead = 0;
std::unordered_map<NewRpgStatus, int> rpgStatusCount; std::unordered_map<NewRpgStatus, int> rpgStatusCount;
NewRpgStatistic rpgStasticTotal; // static NewRpgStatistic rpgStasticTotal;
std::unordered_map<uint32, int> zoneCount; std::unordered_map<uint32, int> zoneCount;
uint8 maxBotLevel = 0; uint8 maxBotLevel = 0;
for (PlayerBotMap::iterator i = playerBots.begin(); i != playerBots.end(); ++i) for (PlayerBotMap::iterator i = playerBots.begin(); i != playerBots.end(); ++i)
@@ -2775,6 +2804,7 @@ void RandomPlayerbotMgr::PrintStats()
{ {
rpgStatusCount[botAI->rpgInfo.status]++; rpgStatusCount[botAI->rpgInfo.status]++;
rpgStasticTotal += botAI->rpgStatistic; rpgStasticTotal += botAI->rpgStatistic;
botAI->rpgStatistic = NewRpgStatistic();
} }
} }
@@ -2792,7 +2822,8 @@ void RandomPlayerbotMgr::PrintStats()
if (((i + 1) % step == 0) || i == maxBotLevel) if (((i + 1) % step == 0) || i == maxBotLevel)
{ {
LOG_INFO("playerbots", " {}..{}: {} alliance, {} horde", from, i, currentAlliance, currentHorde); if (currentAlliance || currentHorde)
LOG_INFO("playerbots", " {}..{}: {} alliance, {} horde", from, i, currentAlliance, currentHorde);
currentAlliance = 0; currentAlliance = 0;
currentHorde = 0; currentHorde = 0;
from = i + 1; from = i + 1;

View File

@@ -6,6 +6,7 @@
#ifndef _PLAYERBOT_RANDOMPLAYERBOTMGR_H #ifndef _PLAYERBOT_RANDOMPLAYERBOTMGR_H
#define _PLAYERBOT_RANDOMPLAYERBOTMGR_H #define _PLAYERBOT_RANDOMPLAYERBOTMGR_H
#include "NewRpgInfo.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "PlayerbotMgr.h" #include "PlayerbotMgr.h"
@@ -193,6 +194,8 @@ private:
botPID pid = botPID(1, 50, -50, 0, 0, 0); botPID pid = botPID(1, 50, -50, 0, 0, 0);
float activityMod = 0.25; float activityMod = 0.25;
bool _isBotInitializing = true; bool _isBotInitializing = true;
bool _isBotLogging = true;
NewRpgStatistic rpgStasticTotal;
uint32 GetEventValue(uint32 bot, std::string const event); uint32 GetEventValue(uint32 bot, std::string const event);
std::string const GetEventData(uint32 bot, std::string const event); std::string const GetEventData(uint32 bot, std::string const event);
uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn, uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,

View File

@@ -42,6 +42,12 @@
const uint64 diveMask = (1LL << 7) | (1LL << 44) | (1LL << 37) | (1LL << 38) | (1LL << 26) | (1LL << 30) | (1LL << 27) | const uint64 diveMask = (1LL << 7) | (1LL << 44) | (1LL << 37) | (1LL << 38) | (1LL << 26) | (1LL << 30) | (1LL << 27) |
(1LL << 33) | (1LL << 24) | (1LL << 34); (1LL << 33) | (1LL << 24) | (1LL << 34);
static std::vector<uint32> initSlotsOrder = {EQUIPMENT_SLOT_TRINKET1, EQUIPMENT_SLOT_TRINKET2, EQUIPMENT_SLOT_MAINHAND,
EQUIPMENT_SLOT_OFFHAND, EQUIPMENT_SLOT_RANGED, EQUIPMENT_SLOT_HEAD, EQUIPMENT_SLOT_SHOULDERS, EQUIPMENT_SLOT_CHEST,
EQUIPMENT_SLOT_LEGS, EQUIPMENT_SLOT_HANDS, EQUIPMENT_SLOT_NECK, EQUIPMENT_SLOT_BODY, EQUIPMENT_SLOT_WAIST,
EQUIPMENT_SLOT_FEET, EQUIPMENT_SLOT_WRISTS, EQUIPMENT_SLOT_FINGER1, EQUIPMENT_SLOT_FINGER2, EQUIPMENT_SLOT_BACK};
uint32 PlayerbotFactory::tradeSkills[] = {SKILL_ALCHEMY, SKILL_ENCHANTING, SKILL_SKINNING, SKILL_TAILORING, uint32 PlayerbotFactory::tradeSkills[] = {SKILL_ALCHEMY, SKILL_ENCHANTING, SKILL_SKINNING, SKILL_TAILORING,
SKILL_LEATHERWORKING, SKILL_ENGINEERING, SKILL_HERBALISM, SKILL_MINING, SKILL_LEATHERWORKING, SKILL_ENGINEERING, SKILL_HERBALISM, SKILL_MINING,
SKILL_BLACKSMITHING, SKILL_COOKING, SKILL_FIRST_AID, SKILL_FISHING, SKILL_BLACKSMITHING, SKILL_COOKING, SKILL_FIRST_AID, SKILL_FISHING,
@@ -208,7 +214,7 @@ void PlayerbotFactory::Randomize(bool incremental)
// { // {
// return; // return;
// } // }
LOG_INFO("playerbots", "{} randomizing {} (level {} class = {})...", (incremental ? "Incremental" : "Full"), LOG_DEBUG("playerbots", "{} randomizing {} (level {} class = {})...", (incremental ? "Incremental" : "Full"),
bot->GetName().c_str(), level, bot->getClass()); bot->GetName().c_str(), level, bot->getClass());
// LOG_DEBUG("playerbots", "Preparing to {} randomize...", (incremental ? "incremental" : "full")); // LOG_DEBUG("playerbots", "Preparing to {} randomize...", (incremental ? "incremental" : "full"));
Prepare(); Prepare();
@@ -1595,8 +1601,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
int32 delta = std::min(blevel, 10u); int32 delta = std::min(blevel, 10u);
StatsWeightCalculator calculator(bot); StatsWeightCalculator calculator(bot);
// Reverse order may work better for (int32 slot : initSlotsOrder)
for (int32 slot = (int32)EQUIPMENT_SLOT_TABARD; slot >= (int32)EQUIPMENT_SLOT_START; slot--)
{ {
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
continue; continue;
@@ -1604,10 +1609,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
if (level < 50 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2)) if (level < 50 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2))
continue; continue;
if (level < 30 && slot == EQUIPMENT_SLOT_NECK) if (level < 30 && (slot == EQUIPMENT_SLOT_NECK || slot == EQUIPMENT_SLOT_HEAD))
continue;
if (level < 25 && slot == EQUIPMENT_SLOT_HEAD)
continue; continue;
if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2)) if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2))
@@ -1627,7 +1629,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
uint32 desiredQuality = itemQuality; int32 desiredQuality = itemQuality;
if (urand(0, 100) < 100 * sPlayerbotAIConfig->randomGearLoweringChance && desiredQuality > ITEM_QUALITY_NORMAL) if (urand(0, 100) < 100 * sPlayerbotAIConfig->randomGearLoweringChance && desiredQuality > ITEM_QUALITY_NORMAL)
{ {
desiredQuality--; desiredQuality--;
@@ -1662,8 +1664,10 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto) if (!proto)
continue; continue;
bool shouldCheckGS = desiredQuality > ITEM_QUALITY_NORMAL;
if (gearScoreLimit != 0 && if (shouldCheckGS && gearScoreLimit != 0 &&
CalcMixedGearScore(proto->ItemLevel, proto->Quality) > gearScoreLimit) CalcMixedGearScore(proto->ItemLevel, proto->Quality) > gearScoreLimit)
{ {
continue; continue;
@@ -1692,7 +1696,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
} }
} }
} }
} while (items[slot].size() < 25 && desiredQuality-- > ITEM_QUALITY_NORMAL); } while (items[slot].size() < 25 && desiredQuality-- > ITEM_QUALITY_POOR);
std::vector<uint32>& ids = items[slot]; std::vector<uint32>& ids = items[slot];
if (ids.empty()) if (ids.empty())
@@ -1766,7 +1770,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
/// @todo: clean up duplicate code /// @todo: clean up duplicate code
if (second_chance) if (second_chance)
{ {
for (int32 slot = (int32)EQUIPMENT_SLOT_TABARD; slot >= (int32)EQUIPMENT_SLOT_START; slot--) for (int32 slot : initSlotsOrder)
{ {
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
continue; continue;
@@ -1774,17 +1778,15 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
if (level < 50 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2)) if (level < 50 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2))
continue; continue;
if (level < 30 && slot == EQUIPMENT_SLOT_NECK) if (level < 30 && (slot == EQUIPMENT_SLOT_NECK || slot == EQUIPMENT_SLOT_HEAD))
continue;
if (level < 25 && slot == EQUIPMENT_SLOT_HEAD)
continue; continue;
if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2)) if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2))
continue; continue;
if (level < 5 && (slot != EQUIPMENT_SLOT_MAINHAND) && (slot != EQUIPMENT_SLOT_OFFHAND) && if (level < 5 && (slot != EQUIPMENT_SLOT_MAINHAND) && (slot != EQUIPMENT_SLOT_OFFHAND) &&
(slot != EQUIPMENT_SLOT_FEET) && (slot != EQUIPMENT_SLOT_LEGS) && (slot != EQUIPMENT_SLOT_CHEST)) (slot != EQUIPMENT_SLOT_FEET) && (slot != EQUIPMENT_SLOT_LEGS) && (slot != EQUIPMENT_SLOT_CHEST) &&
(slot != EQUIPMENT_SLOT_RANGED))
continue; continue;
if (Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) if (Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
@@ -3967,6 +3969,9 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
{ {
continue; continue;
} }
if (item->GetTemplate() && item->GetTemplate()->Quality < ITEM_QUALITY_UNCOMMON)
continue;
int32 bestEnchantId = -1; int32 bestEnchantId = -1;
float bestScore = 0; float bestScore = 0;
for (const uint32& enchantSpell : enchantSpellIdCache) for (const uint32& enchantSpell : enchantSpellIdCache)

View File

@@ -129,6 +129,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_STAMINA] += 0.1f; stats_weights_[STATS_TYPE_STAMINA] += 0.1f;
stats_weights_[STATS_TYPE_ARMOR] += 0.001f; stats_weights_[STATS_TYPE_ARMOR] += 0.001f;
stats_weights_[STATS_TYPE_BONUS] += 1.0f; stats_weights_[STATS_TYPE_BONUS] += 1.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL)) if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL))
{ {

View File

@@ -8,6 +8,17 @@
#include "CreatureAI.h" #include "CreatureAI.h"
#include "Playerbots.h" #include "Playerbots.h"
enum PetSpells
{
PET_PROWL_1 = 24450,
PET_PROWL_2 = 24452,
PET_PROWL_3 = 24453,
PET_COWER = 1742,
PET_LEAP = 47482
};
static std::vector<uint32> disabledPetSpells = {PET_PROWL_1, PET_PROWL_2, PET_PROWL_3, PET_COWER, PET_LEAP};
bool MeleeAction::isUseful() bool MeleeAction::isUseful()
{ {
// do not allow if can't attack from vehicle // do not allow if can't attack from vehicle
@@ -44,18 +55,20 @@ bool TogglePetSpellAutoCastAction::Execute(Event event)
{ {
if (itr->second.state == PETSPELL_REMOVED) if (itr->second.state == PETSPELL_REMOVED)
continue; continue;
uint32 spellId = itr->first; uint32 spellId = itr->first;
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo->IsAutocastable()) if (!spellInfo->IsAutocastable())
continue; continue;
bool shouldApply = true; bool shouldApply = true;
if (spellId == 1742 /*cower*/ || spellId == 24450 /*Prowl*/ || for (uint32 disabledSpell : disabledPetSpells)
spellId == 47482 /*Leap*/ /* || spellId == 47481 Gnaw*/)
{ {
if (spellId == disabledSpell)
shouldApply = false; {
shouldApply = false;
break;
}
} }
bool isAutoCast = false; bool isAutoCast = false;
for (unsigned int& m_autospell : pet->m_autospells) for (unsigned int& m_autospell : pet->m_autospells)

View File

@@ -847,6 +847,26 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
float tx = target->GetPositionX(); float tx = target->GetPositionX();
float ty = target->GetPositionY(); float ty = target->GetPositionY();
float tz = target->GetPositionZ(); float tz = target->GetPositionZ();
float targetOrientation = target->GetOrientation();
float deltaAngle = Position::NormalizeOrientation(targetOrientation - target->GetAngle(bot));
if (deltaAngle > M_PI)
deltaAngle -= 2.0f * M_PI; // -PI..PI
// if target is moving forward and moving far away, predict the position
bool behind = fabs(deltaAngle) > M_PI_2;
if (target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) && behind) {
float predictDis = std::min(3.0f, target->GetObjectSize() * 2);
tx += cos(target->GetOrientation()) * predictDis;
ty += sin(target->GetOrientation()) * predictDis;
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(),
tx, ty, tz))
{
tx = target->GetPositionX();
ty = target->GetPositionY();
tz = target->GetPositionZ();
}
}
float combatDistance = bot->GetCombatReach() + target->GetCombatReach(); float combatDistance = bot->GetCombatReach() + target->GetCombatReach();
distance += combatDistance; distance += combatDistance;
@@ -863,7 +883,7 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
// Avoid walking too far when moving towards each other // Avoid walking too far when moving towards each other
float disToGo = bot->GetExactDist(tx, ty, tz) - distance; float disToGo = bot->GetExactDist(tx, ty, tz) - distance;
if (disToGo >= 10.0f) if (disToGo >= 6.0f)
shortenTo = disToGo / 2 + distance; shortenTo = disToGo / 2 + distance;
// if (bot->GetExactDist(tx, ty, tz) <= shortenTo) // if (bot->GetExactDist(tx, ty, tz) <= shortenTo)

View File

@@ -79,7 +79,8 @@ void FrostMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", ACTION_HIGH + 1), nullptr))); new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", ACTION_HIGH + 1), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("medium health", NextAction::array(0, new NextAction("ice barrier", ACTION_NORMAL), nullptr))); new TriggerNode("medium health", NextAction::array(0, new NextAction("ice barrier", ACTION_NORMAL), nullptr)));
triggers.push_back(
new TriggerNode("being attacked", NextAction::array(0, new NextAction("ice barrier", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"brain freeze", NextAction::array(0, new NextAction("frostfire bolt", ACTION_NORMAL + 3), nullptr))); "brain freeze", NextAction::array(0, new NextAction("frostfire bolt", ACTION_NORMAL + 3), nullptr)));
// Combo cast the last charge of fingers of frost for double crits. // Combo cast the last charge of fingers of frost for double crits.

View File

@@ -56,6 +56,8 @@ void GenericPriestStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("enemy too close for spell", triggers.push_back(new TriggerNode("enemy too close for spell",
NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr))); NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr)));
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr))); triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
triggers.push_back(new TriggerNode("being attacked",
NextAction::array(0, new NextAction("power word: shield", ACTION_HIGH + 1), nullptr)));
} }
PriestCureStrategy::PriestCureStrategy(PlayerbotAI* botAI) : Strategy(botAI) PriestCureStrategy::PriestCureStrategy(PlayerbotAI* botAI) : Strategy(botAI)

View File

@@ -135,11 +135,11 @@ protected:
float targetExpectedLifeTime; float targetExpectedLifeTime;
}; };
// non caster // General
class NonCasterFindTargetSmartStrategy : public FindTargetStrategy class GeneralFindTargetSmartStrategy : public FindTargetStrategy
{ {
public: public:
NonCasterFindTargetSmartStrategy(PlayerbotAI* botAI, float dps) GeneralFindTargetSmartStrategy(PlayerbotAI* botAI, float dps)
: FindTargetStrategy(botAI), dps_(dps), targetExpectedLifeTime(1000000) : FindTargetStrategy(botAI), dps_(dps), targetExpectedLifeTime(1000000)
{ {
} }
@@ -178,7 +178,6 @@ public:
{ {
float new_time = new_unit->GetHealth() / dps_; float new_time = new_unit->GetHealth() / dps_;
float old_time = old_unit->GetHealth() / dps_; float old_time = old_unit->GetHealth() / dps_;
// [5-20] > (5-0] > (20-inf)
int new_level = GetIntervalLevel(new_unit); int new_level = GetIntervalLevel(new_unit);
int old_level = GetIntervalLevel(old_unit); int old_level = GetIntervalLevel(old_unit);
if (new_level != old_level) if (new_level != old_level)
@@ -297,20 +296,24 @@ Unit* DpsTargetValue::Calculate()
if (rti) if (rti)
return rti; return rti;
// FindLeastHpTargetStrategy strategy(botAI);
Group* group = bot->GetGroup();
float dps = AI_VALUE(float, "estimated group dps"); float dps = AI_VALUE(float, "estimated group dps");
if (group && botAI->IsCaster(bot))
if (botAI->GetNearGroupMemberCount() > 3)
{ {
CasterFindTargetSmartStrategy strategy(botAI, dps); if (botAI->IsCaster(bot))
return TargetValue::FindTarget(&strategy); {
// Caster find target strategy avoids casting spells on enemies
// with too low health to ensure the effectiveness of casting
CasterFindTargetSmartStrategy strategy(botAI, dps);
return TargetValue::FindTarget(&strategy);
}
else if (botAI->IsCombo(bot))
{
ComboFindTargetSmartStrategy strategy(botAI, dps);
return TargetValue::FindTarget(&strategy);
}
} }
else if (botAI->IsCombo(bot)) GeneralFindTargetSmartStrategy strategy(botAI, dps);
{
ComboFindTargetSmartStrategy strategy(botAI, dps);
return TargetValue::FindTarget(&strategy);
}
NonCasterFindTargetSmartStrategy strategy(botAI, dps);
return TargetValue::FindTarget(&strategy); return TargetValue::FindTarget(&strategy);
} }

View File

@@ -36,6 +36,10 @@ float EstimatedGroupDpsValue::Calculate()
if (member == bot) // calculated if (member == bot) // calculated
continue; continue;
// ignore real player as they may not help with damage
if (!GET_PLAYERBOT_AI(member) || GET_PLAYERBOT_AI(member)->IsRealPlayer())
continue;
if (!member || !member->IsInWorld() || !member->IsAlive()) if (!member || !member->IsInWorld() || !member->IsAlive())
continue; continue;