Merge pull request #507 from liyunfan1223/equip_init

Equip init
This commit is contained in:
Yunfan Li
2024-09-05 18:05:35 +08:00
committed by GitHub
23 changed files with 588 additions and 200 deletions

View File

@@ -216,6 +216,9 @@ AiPlayerbot.AutoEquipUpgradeLoot = 1
# Default: 1.1 (Equip when the equipment score is 1.1 times higher than the current)
AiPlayerbot.EquipUpgradeThreshold = 1.1
# Two rounds of equipment initialization to create more suitable gear
AiPlayerbot.TwoRoundsGearInit = 0
#
#
#
@@ -821,13 +824,25 @@ AiPlayerbot.PremadeSpecGlyph.3.0 = 42912,43350,42902,43351,43338,45732
AiPlayerbot.PremadeSpecLink.3.0.60 = 51200201505112243100511351
AiPlayerbot.PremadeSpecLink.3.0.80 = 51200201505112253100531351-015305021
AiPlayerbot.PremadeSpecName.3.1 = mm pve
AiPlayerbot.PremadeSpecGlyph.3.1 = 42912,43350,42915,43351,43338,45732
AiPlayerbot.PremadeSpecLink.3.1.60 = -015305101230013233135030051
AiPlayerbot.PremadeSpecGlyph.3.1 = 42912,43350,42914,43351,43338,45732
AiPlayerbot.PremadeSpecLink.3.1.60 = -015305101230013233125031051
AiPlayerbot.PremadeSpecLink.3.1.80 = 502-035305101230013233135031351-5000002
AiPlayerbot.PremadeSpecName.3.2 = surv pve
AiPlayerbot.PremadeSpecGlyph.3.2 = 42912,43350,45731,43351,43338,45732
AiPlayerbot.PremadeSpecLink.3.2.60 = --5000032500033330502135001331
AiPlayerbot.PremadeSpecLink.3.2.80 = -005305101-5000032500033330522135301331
AiPlayerbot.PremadeSpecLink.3.2.60 = --5000032500033330502135201311
AiPlayerbot.PremadeSpecLink.3.2.80 = -005305101-5000032500033330532135301321
# HUNTER PET
#
# Ferocity
AiPlayerbot.PremadeHunterPetLink.0.16 = 2100003030103010101
AiPlayerbot.PremadeHunterPetLink.0.20 = 2100013030103010122
# Tenacity
AiPlayerbot.PremadeHunterPetLink.1.16 = 21103000300120101001
AiPlayerbot.PremadeHunterPetLink.1.20 = 21303010300120101002
# Cunning
AiPlayerbot.PremadeHunterPetLink.2.16 = 2100020330000211001
AiPlayerbot.PremadeHunterPetLink.2.20 = 21000203300002110221
#
#
@@ -965,7 +980,7 @@ AiPlayerbot.PremadeSpecLink.9.0.70 = 2350022001113510053500131151--55
AiPlayerbot.PremadeSpecLink.9.0.80 = 2350022001113510253500331151--5500000501
AiPlayerbot.PremadeSpecName.9.1 = emo pve
AiPlayerbot.PremadeSpecGlyph.9.1 = 45785,43390,50077,43394,43393,42459
AiPlayerbot.PremadeSpecLink.9.1.60 = -003203301135112530131201-55
AiPlayerbot.PremadeSpecLink.9.1.60 = -003203301135112530135201051
AiPlayerbot.PremadeSpecLink.9.1.70 = -003203301135112530135201051-55
AiPlayerbot.PremadeSpecLink.9.1.80 = -003203301135112530135221351-55000005
AiPlayerbot.PremadeSpecName.9.2 = destro pve
@@ -1165,11 +1180,11 @@ AiPlayerbot.RandomClassSpecIndex.8.2 = 2
#
#
AiPlayerbot.RandomClassSpecProb.9.0 = 40
AiPlayerbot.RandomClassSpecProb.9.0 = 45
AiPlayerbot.RandomClassSpecIndex.9.0 = 0
AiPlayerbot.RandomClassSpecProb.9.1 = 40
AiPlayerbot.RandomClassSpecProb.9.1 = 45
AiPlayerbot.RandomClassSpecIndex.9.1 = 1
AiPlayerbot.RandomClassSpecProb.9.2 = 20
AiPlayerbot.RandomClassSpecProb.9.2 = 10
AiPlayerbot.RandomClassSpecIndex.9.2 = 2
#

View File

@@ -5257,6 +5257,7 @@ bool PlayerbotAI::EqualLowercaseName(std::string s1, std::string s2)
return true;
}
// A custom CanEquipItem (remove AutoUnequipOffhand in FindEquipSlot to prevent unequip on `item usage` calculation)
InventoryResult PlayerbotAI::CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading) const
{
dest = 0;
@@ -5277,11 +5278,9 @@ InventoryResult PlayerbotAI::CanEquipItem(uint8 slot, uint16& dest, Item* pItem,
if (pItem->IsBindedNotWith(bot))
return EQUIP_ERR_DONT_OWN_THAT_ITEM;
// Yunfan: skip it
// // check count of items (skip for auto move for same player from bank)
// InventoryResult res = bot->CanTakeMoreSimilarItems(pItem);
// if (res != EQUIP_ERR_OK)
// return res;
InventoryResult res = bot->CanTakeMoreSimilarItems(pItem);
if (res != EQUIP_ERR_OK)
return res;
ScalingStatDistributionEntry const* ssd =
pProto->ScalingStatDistribution
@@ -5300,7 +5299,7 @@ InventoryResult PlayerbotAI::CanEquipItem(uint8 slot, uint16& dest, Item* pItem,
if (!bot->CanUseAttackType(bot->GetAttackBySlot(eslot)))
return EQUIP_ERR_NOT_WHILE_DISARMED;
InventoryResult res = bot->CanUseItem(pItem, not_loading);
res = bot->CanUseItem(pItem, not_loading);
if (res != EQUIP_ERR_OK)
return res;

View File

@@ -331,6 +331,17 @@ bool PlayerbotAIConfig::Initialize()
parsedSpecLinkOrder[cls][spec][level] = ParseTempTalentsOrder(cls, premadeSpecLink[cls][spec][level]);
}
}
for (uint32 spec = 0; spec < 3; ++spec)
{
for (uint32 points = 0; points < 21; ++points)
{
std::ostringstream os;
os << "AiPlayerbot.PremadeHunterPetLink." << spec << "." << points;
premadeHunterPetLink[spec][points] = sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false);
parsedHunterPetLinkOrder[spec][points] =
ParseTempPetTalentsOrder(spec, premadeHunterPetLink[spec][points]);
}
}
for (uint32 spec = 0; spec < MAX_SPECNO; ++spec)
{
std::ostringstream os;
@@ -460,6 +471,7 @@ bool PlayerbotAIConfig::Initialize()
autoPickReward = sConfigMgr->GetOption<std::string>("AiPlayerbot.AutoPickReward", "yes");
autoEquipUpgradeLoot = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoEquipUpgradeLoot", true);
equipUpgradeThreshold = sConfigMgr->GetOption<float>("AiPlayerbot.EquipUpgradeThreshold", 1.1f);
twoRoundsGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.TwoRoundsGearInit", false);
syncQuestWithPlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncQuestWithPlayer", true);
syncQuestForPlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncQuestForPlayer", false);
autoTrainSpells = sConfigMgr->GetOption<std::string>("AiPlayerbot.AutoTrainSpells", "yes");
@@ -484,13 +496,13 @@ bool PlayerbotAIConfig::Initialize()
selfBotLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.SelfBotLevel", 1);
RandomPlayerbotFactory::CreateRandomBots();
if (sPlayerbotAIConfig->addClassCommand)
sRandomPlayerbotMgr->PrepareAddclassCache();
if (World::IsStopped())
{
return true;
}
if (sPlayerbotAIConfig->addClassCommand)
sRandomPlayerbotMgr->PrepareAddclassCache();
sRandomItemMgr->Init();
sRandomItemMgr->InitAfterAhBot();
sPlayerbotTextMgr->LoadBotTexts();
@@ -760,3 +772,47 @@ std::vector<std::vector<uint32>> PlayerbotAIConfig::ParseTempTalentsOrder(uint32
}
return res;
}
std::vector<std::vector<uint32>> PlayerbotAIConfig::ParseTempPetTalentsOrder(uint32 spec, std::string tab_link)
{
// check bad link
// uint32 classMask = 1 << (cls - 1);
std::vector<TalentEntry const*> spells;
std::vector<std::vector<uint32>> orders;
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
if (!talentInfo)
continue;
TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
if (!talentTabInfo)
continue;
if (!((1 << spec) & talentTabInfo->petTalentMask))
continue;
// skip some duplicate spells like dash/dive
if (talentInfo->TalentID == 2201 || talentInfo->TalentID == 2208 || talentInfo->TalentID == 2219 || talentInfo->TalentID == 2203)
continue;
spells.push_back(talentInfo);
}
std::sort(spells.begin(), spells.end(),
[&](TalentEntry const* lhs, TalentEntry const* rhs)
{ return lhs->Row != rhs->Row ? lhs->Row < rhs->Row : lhs->Col < rhs->Col; });
for (int i = 0; i < tab_link.size(); i++)
{
if (i >= spells.size())
{
break;
}
int lvl = tab_link[i] - '0';
if (lvl == 0)
continue;
orders.push_back({spells[i]->Row, spells[i]->Col, (uint32)lvl});
}
// sort by talent tab size
std::sort(orders.begin(), orders.end(), [&](auto& lhs, auto& rhs) { return lhs.size() > rhs.size(); });
return orders;
}

View File

@@ -195,7 +195,9 @@ public:
std::string premadeSpecGlyph[MAX_CLASSES][MAX_SPECNO];
std::vector<uint32> parsedSpecGlyph[MAX_CLASSES][MAX_SPECNO];
std::string premadeSpecLink[MAX_CLASSES][MAX_SPECNO][MAX_LEVEL];
std::string premadeHunterPetLink[3][21];
std::vector<std::vector<uint32>> parsedSpecLinkOrder[MAX_CLASSES][MAX_SPECNO][MAX_LEVEL];
std::vector<std::vector<uint32>> parsedHunterPetLinkOrder[3][21];
uint32 randomClassSpecProb[MAX_CLASSES][MAX_SPECNO];
uint32 randomClassSpecIndex[MAX_CLASSES][MAX_SPECNO];
@@ -269,6 +271,7 @@ public:
std::string autoPickReward;
bool autoEquipUpgradeLoot;
float equipUpgradeThreshold;
bool twoRoundsGearInit;
bool syncQuestWithPlayer;
bool syncQuestForPlayer;
std::string autoTrainSpells;
@@ -325,6 +328,7 @@ public:
void loadWorldBuf(uint32 factionId, uint32 classId, uint32 minLevel, uint32 maxLevel);
static std::vector<std::vector<uint32>> ParseTempTalentsOrder(uint32 cls, std::string temp_talents_order);
static std::vector<std::vector<uint32>> ParseTempPetTalentsOrder(uint32 spec, std::string temp_talents_order);
};
#define sPlayerbotAIConfig PlayerbotAIConfig::instance()

View File

@@ -724,14 +724,14 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore);
factory.Randomize(false);
return "ok, gear score limit: " + std::to_string(mixedGearScore / (ITEM_QUALITY_EPIC + 1)) +
return "ok, gear score limit: " + std::to_string(mixedGearScore / PlayerbotAI::GetItemScoreMultiplier(ItemQualities(ITEM_QUALITY_EPIC))) +
"(for epic)";
}
else if (cmd.starts_with("init=") && sscanf(cmd.c_str(), "init=%d", &gs) != -1)
{
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, gs);
factory.Randomize(false);
return "ok, gear score limit: " + std::to_string(gs / (ITEM_QUALITY_EPIC + 1)) + "(for epic)";
return "ok, gear score limit: " + std::to_string(gs / PlayerbotAI::GetItemScoreMultiplier(ItemQualities(ITEM_QUALITY_EPIC))) + "(for epic)";
}
}
@@ -845,6 +845,22 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
if (!strncmp(cmd, "initself=", 9))
{
if (!strcmp(cmd, "initself=uncommon"))
{
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
{
// OnBotLogin(master);
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_UNCOMMON);
factory.Randomize(false);
messages.push_back("initself ok");
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
}
if (!strcmp(cmd, "initself=rare"))
{
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
@@ -877,6 +893,22 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
return messages;
}
}
if (!strcmp(cmd, "initself=legendary"))
{
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
{
// OnBotLogin(master);
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_LEGENDARY);
factory.Randomize(false);
messages.push_back("initself ok");
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
}
int32 gs;
if (sscanf(cmd, "initself=%d", &gs) != -1)
{

View File

@@ -370,7 +370,7 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
break;
}
if (loginBots)
if (loginBots && botLoading.empty())
{
loginBots += updateBots;
loginBots = std::min(loginBots, maxNewBots);
@@ -1041,6 +1041,9 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
SetEventValue(bot, "login", 0, 0);
if (!player->IsInWorld())
return false;
if (player->GetGroup() || player->HasUnitState(UNIT_STATE_IN_FLIGHT))
return false;

View File

@@ -39,6 +39,8 @@
#define PLAYER_SKILL_INDEX(x) (PLAYER_SKILL_INFO_1_1 + ((x)*3))
const uint64 diveMask = (1LL << 7) | (1LL << 44) | (1LL << 37) | (1LL << 38) | (1LL << 26) | (1LL << 30) | (1LL << 27) |
(1LL << 33) | (1LL << 24) | (1LL << 34);
uint32 PlayerbotFactory::tradeSkills[] = {SKILL_ALCHEMY, SKILL_ENCHANTING, SKILL_SKINNING, SKILL_TAILORING,
SKILL_LEATHERWORKING, SKILL_ENGINEERING, SKILL_HERBALISM, SKILL_MINING,
SKILL_BLACKSMITHING, SKILL_COOKING, SKILL_FIRST_AID, SKILL_FISHING,
@@ -233,6 +235,7 @@ void PlayerbotFactory::Randomize(bool incremental)
pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Spells1");
LOG_DEBUG("playerbots", "Initializing spells (step 1)...");
// bot->LearnDefaultSkills();
bot->LearnDefaultSkills();
InitClassSpells();
InitAvailableSpells();
if (pmo)
@@ -286,7 +289,7 @@ void PlayerbotFactory::Randomize(bool incremental)
LOG_DEBUG("playerbots", "Initializing equipmemt...");
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
InitEquipment(incremental);
InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig->twoRoundsGearInit);
}
// bot->SaveToDB(false, false);
if (pmo)
@@ -423,7 +426,10 @@ void PlayerbotFactory::Refresh()
InitFood();
InitReagents();
// InitPotions();
InitTalentsTree(true, true, true);
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
InitTalentsTree(true, true, true);
}
InitPet();
InitPetTalents();
InitClassSpells();
@@ -611,8 +617,9 @@ void PlayerbotFactory::InitPetTalents()
// pet_family->petTalentType);
return;
}
// pet->resetTalents();
std::unordered_map<uint32, std::vector<TalentEntry const*>> spells;
bool diveTypePet = (1LL << ci->family) & diveMask;
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
@@ -624,49 +631,115 @@ void PlayerbotFactory::InitPetTalents()
// prevent learn talent for different family (cheating)
if (!((1 << pet_family->petTalentType) & talentTabInfo->petTalentMask))
continue;
bool diveClass = talentInfo->TalentID == 2201 || talentInfo->TalentID == 2208 || talentInfo->TalentID == 2219 ||
talentInfo->TalentID == 2203;
if (diveClass && !diveTypePet)
continue;
bool dashClass = talentInfo->TalentID == 2119 || talentInfo->TalentID == 2207 || talentInfo->TalentID == 2111 ||
talentInfo->TalentID == 2109;
if (dashClass && diveTypePet)
continue;
spells[talentInfo->Row].push_back(talentInfo);
}
uint32 curTalentPoints = pet->GetFreeTalentPoints();
std::vector<std::vector<uint32>> order =
sPlayerbotAIConfig->parsedHunterPetLinkOrder[pet_family->petTalentType][20];
uint32 maxTalentPoints = pet->GetMaxTalentPointsForLevel(pet->GetLevel());
int row = 0;
// LOG_INFO("playerbots", "{} learning, max talent points: {}, cur: {}", bot->GetName().c_str(), maxTalentPoints,
// curTalentPoints);
for (auto i = spells.begin(); i != spells.end(); ++i, ++row)
{
std::vector<TalentEntry const*>& spells_row = i->second;
if (spells_row.empty())
{
LOG_INFO("playerbots", "{}: No spells for talent row {}", bot->GetName().c_str(), i->first);
continue;
}
int attemptCount = 0;
// keep learning for the last row
while (!spells_row.empty() &&
((((int)maxTalentPoints - (int)pet->GetFreeTalentPoints()) < 3 * (row + 1)) || (row == 5)) &&
attemptCount++ < 10 && pet->GetFreeTalentPoints())
{
int index = urand(0, spells_row.size() - 1);
TalentEntry const* talentInfo = spells_row[index];
int maxRank = 0;
for (int rank = 0; rank < std::min((uint32)MAX_TALENT_RANK, (uint32)pet->GetFreeTalentPoints()); ++rank)
{
uint32 spellId = talentInfo->RankID[rank];
if (!spellId)
continue;
maxRank = rank;
}
// LOG_INFO("playerbots", "{} learn pet talent {}({})", bot->GetName().c_str(), talentInfo->TalentID,
// maxRank);
if (talentInfo->DependsOn)
if (order.empty())
{
int row = 0;
for (auto i = spells.begin(); i != spells.end(); ++i, ++row)
{
std::vector<TalentEntry const*>& spells_row = i->second;
if (spells_row.empty())
{
bot->LearnPetTalent(pet->GetGUID(), talentInfo->DependsOn,
std::min(talentInfo->DependsOnRank, bot->GetFreeTalentPoints() - 1));
LOG_INFO("playerbots", "{}: No spells for talent row {}", bot->GetName().c_str(), i->first);
continue;
}
int attemptCount = 0;
// keep learning for the last row
while (!spells_row.empty() &&
((((int)maxTalentPoints - (int)pet->GetFreeTalentPoints()) < 3 * (row + 1)) || (row == 5)) &&
attemptCount++ < 10 && pet->GetFreeTalentPoints())
{
int index = urand(0, spells_row.size() - 1);
TalentEntry const* talentInfo = spells_row[index];
int maxRank = 0;
for (int rank = 0; rank < std::min((uint32)MAX_TALENT_RANK, (uint32)pet->GetFreeTalentPoints()); ++rank)
{
uint32 spellId = talentInfo->RankID[rank];
if (!spellId)
continue;
maxRank = rank;
}
// LOG_INFO("playerbots", "{} learn pet talent {}({})", bot->GetName().c_str(), talentInfo->TalentID,
// maxRank);
if (talentInfo->DependsOn)
{
bot->LearnPetTalent(pet->GetGUID(), talentInfo->DependsOn,
std::min(talentInfo->DependsOnRank, bot->GetFreeTalentPoints() - 1));
}
bot->LearnPetTalent(pet->GetGUID(), talentInfo->TalentID, maxRank);
spells_row.erase(spells_row.begin() + index);
}
}
}
else
{
uint32 spec = pet_family->petTalentType;
uint32 startPoints = pet->GetMaxTalentPointsForLevel(pet->GetLevel());
while (startPoints > 1 && startPoints < 20 &&
sPlayerbotAIConfig->parsedHunterPetLinkOrder[spec][startPoints].size() == 0)
{
startPoints--;
}
for (uint32 points = startPoints; points <= 20; points++)
{
if (sPlayerbotAIConfig->parsedHunterPetLinkOrder[spec][points].size() == 0)
continue;
for (std::vector<uint32>& p : sPlayerbotAIConfig->parsedHunterPetLinkOrder[spec][points])
{
uint32 row = p[0], col = p[1], lvl = p[2];
uint32 talentID = 0;
uint32 learnLevel = 0;
std::vector<TalentEntry const*>& spell = spells[row];
for (TalentEntry const* talentInfo : spell)
{
if (talentInfo->Col != col)
{
continue;
}
if (talentInfo->DependsOn)
{
bot->LearnPetTalent(pet->GetGUID(), talentInfo->DependsOn,
std::min(talentInfo->DependsOnRank, bot->GetFreeTalentPoints() - 1));
}
talentID = talentInfo->TalentID;
uint32 currentTalentRank = 0;
for (uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
{
if (talentInfo->RankID[rank] && pet->HasSpell(talentInfo->RankID[rank]))
{
currentTalentRank = rank + 1;
break;
}
}
learnLevel = std::min(lvl, pet->GetFreeTalentPoints() + currentTalentRank) - 1;
}
bot->LearnPetTalent(pet->GetGUID(), talentID, learnLevel);
if (pet->GetFreeTalentPoints() == 0)
{
break;
}
}
if (pet->GetFreeTalentPoints() == 0)
{
break;
}
bot->LearnPetTalent(pet->GetGUID(), talentInfo->TalentID, maxRank);
spells_row.erase(spells_row.begin() + index);
}
}
bot->SendTalentsInfoData(true);
@@ -816,7 +889,7 @@ void PlayerbotFactory::ClearEverything()
ClearSpells();
ClearInventory();
ResetQuests();
bot->SaveToDB(false, false);
// bot->SaveToDB(false, false);
}
void PlayerbotFactory::ClearSpells()
@@ -881,12 +954,18 @@ void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_templa
/// @todo: match current talent with template
specTab = AiFactory::GetPlayerSpecTab(bot);
/// @todo: fix cat druid hardcode
if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(16931))
if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 &&
!bot->HasAura(16931))
specTab = 3;
}
}
else
{
uint32 point = urand(1, 100);
uint32 pointSum = 0;
for (int i = 0; i < MAX_SPECNO; i++)
{
pointSum += sPlayerbotAIConfig->randomClassSpecProb[cls][i];
}
uint32 point = urand(1, pointSum);
uint32 currentP = 0;
int i;
for (i = 0; i < MAX_SPECNO; i++)
@@ -1291,7 +1370,8 @@ bool PlayerbotFactory::CanEquipItem(ItemTemplate const* proto)
return true;
uint32 requiredLevel = proto->RequiredLevel;
bool hasItem = bot->HasItemCount(proto->ItemId, 1, true);
// disable since bad performance
bool hasItem = bot->HasItemCount(proto->ItemId, 1, false);
// bot->GetItemCount()
// !requiredLevel -> it's a quest reward item
if (!requiredLevel && hasItem)
@@ -1447,7 +1527,7 @@ void Shuffle(std::vector<uint32>& items)
// }
// }
void PlayerbotFactory::InitEquipment(bool incremental)
void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
{
std::unordered_map<uint8, std::vector<uint32>> items;
// int tab = AiFactory::GetPlayerSpecTab(bot);
@@ -1467,6 +1547,7 @@ void PlayerbotFactory::InitEquipment(bool incremental)
else if (blevel == 80)
delta = 9;
StatsWeightCalculator calculator(bot);
for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot)
{
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
@@ -1484,6 +1565,15 @@ void PlayerbotFactory::InitEquipment(bool incremental)
if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2))
continue;
Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
if (second_chance && oldItem)
{
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
}
oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
uint32 desiredQuality = itemQuality;
if (urand(0, 100) < 100 * sPlayerbotAIConfig->randomGearLoweringChance && desiredQuality > ITEM_QUALITY_NORMAL)
{
@@ -1498,10 +1588,13 @@ void PlayerbotFactory::InitEquipment(bool incremental)
{
for (uint32 itemId : sRandomItemMgr->GetCachedEquipments(requiredLevel, inventoryType))
{
if (itemId == 46978)
{ // shaman earth ring totem
if (itemId == 46978) // shaman earth ring totem
{
continue;
}
uint32 skipProb = 25;
if (urand(1, 100) <= skipProb)
continue;
// disable next expansion gear
if (sPlayerbotAIConfig->limitGearExpansion && bot->GetLevel() <= 60 && itemId >= 23728)
@@ -1527,10 +1620,10 @@ void PlayerbotFactory::InitEquipment(bool incremental)
if (proto->Quality != desiredQuality)
continue;
// delay heavy check
// if (!CanEquipItem(proto))
// continue;
if (!CanEquipItem(proto))
continue;
if (proto->Class == ITEM_CLASS_ARMOR &&
(slot == EQUIPMENT_SLOT_HEAD || slot == EQUIPMENT_SLOT_SHOULDERS ||
slot == EQUIPMENT_SLOT_CHEST || slot == EQUIPMENT_SLOT_WAIST ||
@@ -1545,95 +1638,153 @@ void PlayerbotFactory::InitEquipment(bool incremental)
if (slot == EQUIPMENT_SLOT_OFFHAND && bot->getClass() == CLASS_ROGUE &&
proto->Class != ITEM_CLASS_WEAPON)
continue;
uint16 dest = 0;
if (CanEquipUnseenItem(slot, dest, itemId))
items[slot].push_back(itemId);
// delay heavy check
// uint16 dest = 0;
// if (CanEquipUnseenItem(slot, dest, itemId))
items[slot].push_back(itemId);
}
}
if (items[slot].size() >= 25)
break;
}
} while (items[slot].size() < 25 && desiredQuality-- > ITEM_QUALITY_NORMAL);
}
StatsWeightCalculator calculator(bot);
for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot)
{
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
continue;
if (level < 40 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2))
continue;
if (level < 25 && slot == EQUIPMENT_SLOT_NECK)
continue;
if (level < 25 && slot == EQUIPMENT_SLOT_HEAD)
continue;
std::vector<uint32>& ids = items[slot];
if (ids.empty())
{
continue;
}
Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
if (incremental && !IsDesiredReplacement(oldItem))
{
continue;
}
float bestScoreForSlot = -1;
uint32 bestItemForSlot = 0;
for (int index = 0; index < ids.size(); index++)
{
uint32 skipProb = 25;
if (urand(0, 100) <= skipProb)
continue;
uint32 newItemId = ids[index];
uint16 dest;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(newItemId);
if (!CanEquipItem(proto))
continue;
if (oldItem && oldItem->GetTemplate()->ItemId == newItemId)
continue;
if (!CanEquipUnseenItem(slot, dest, newItemId))
continue;
float cur_score = calculator.CalculateItem(newItemId);
if (cur_score > bestScoreForSlot)
{
// delay heavy check to here
if (!CanEquipItem(proto))
continue;
uint16 dest;
if (!CanEquipUnseenItem(slot, dest, newItemId))
continue;
bestScoreForSlot = cur_score;
bestItemForSlot = newItemId;
}
}
if (bestItemForSlot == 0)
{
continue;
}
if (oldItem)
{
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
}
uint16 dest;
if (!CanEquipUnseenItem(slot, dest, bestItemForSlot))
{
continue;
}
Item* newItem = bot->EquipNewItem(dest, bestItemForSlot, true);
if (newItem)
if (incremental && oldItem)
{
newItem->AddToWorld();
newItem->AddToUpdateQueueOf(bot);
// bot->AutoUnequipOffhandIfNeed();
// EnchantItem(newItem);
float old_score = calculator.CalculateItem(oldItem->GetEntry());
if (bestScoreForSlot < 1.2f * old_score)
continue;
}
if (oldItem)
{
uint8 bagIndex = oldItem->GetBagSlot();
uint8 slot = oldItem->GetSlot();
uint8 dstBag = NULL_BAG;
WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3);
packet << bagIndex << slot << dstBag;
bot->GetSession()->HandleAutoStoreBagItemOpcode(packet);
}
oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
// fail to store in bag
if (oldItem)
continue;
Item* newItem = bot->EquipNewItem(dest, bestItemForSlot, true);
bot->AutoUnequipOffhandIfNeed();
// if (newItem)
// {
// newItem->AddToWorld();
// newItem->AddToUpdateQueueOf(bot);
// }
}
// Secondary init for better equips
/// @todo: clean up duplicate code
if (second_chance)
{
for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot)
{
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
continue;
if (level < 40 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2))
continue;
if (level < 30 && slot == EQUIPMENT_SLOT_NECK)
continue;
if (level < 25 && slot == EQUIPMENT_SLOT_HEAD)
continue;
if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2))
continue;
if (Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
std::vector<uint32>& ids = items[slot];
if (ids.empty())
continue;
float bestScoreForSlot = -1;
uint32 bestItemForSlot = 0;
for (int index = 0; index < ids.size(); index++)
{
uint32 newItemId = ids[index];
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(newItemId);
float cur_score = calculator.CalculateItem(newItemId);
if (cur_score > bestScoreForSlot)
{
// delay heavy check to here
if (!CanEquipItem(proto))
continue;
uint16 dest;
if (!CanEquipUnseenItem(slot, dest, newItemId))
continue;
bestScoreForSlot = cur_score;
bestItemForSlot = newItemId;
}
}
if (bestItemForSlot == 0)
{
continue;
}
uint16 dest;
if (!CanEquipUnseenItem(slot, dest, bestItemForSlot))
{
continue;
}
Item* newItem = bot->EquipNewItem(dest, bestItemForSlot, true);
bot->AutoUnequipOffhandIfNeed();
// if (newItem)
// {
// newItem->AddToWorld();
// newItem->AddToUpdateQueueOf(bot);
// }
}
}
}
@@ -1805,11 +1956,11 @@ void PlayerbotFactory::InitBags(bool destroyOld)
continue;
}
Item* newItem = bot->EquipNewItem(dest, newItemId, true);
if (newItem)
{
newItem->AddToWorld();
newItem->AddToUpdateQueueOf(bot);
}
// if (newItem)
// {
// newItem->AddToWorld();
// newItem->AddToUpdateQueueOf(bot);
// }
}
}
@@ -1909,7 +2060,8 @@ bool PlayerbotFactory::CanEquipUnseenItem(uint8 slot, uint16& dest, uint32 item)
if (Item* pItem = Item::CreateItem(item, 1, bot, false, 0, true))
{
InventoryResult result = bot->CanEquipItem(slot, dest, pItem, true, false);
InventoryResult result = botAI ? botAI->CanEquipItem(slot, dest, pItem, true, true)
: bot->CanEquipItem(slot, dest, pItem, true, true);
pItem->RemoveFromUpdateQueueOf(bot);
delete pItem;
return result == EQUIP_ERR_OK;
@@ -2164,8 +2316,6 @@ void PlayerbotFactory::SetRandomSkill(uint16 id)
void PlayerbotFactory::InitAvailableSpells()
{
bot->LearnDefaultSkills();
if (trainerIdCache.empty())
{
CreatureTemplateContainer const* creatureTemplateContainer = sObjectMgr->GetCreatureTemplates();
@@ -2183,6 +2333,8 @@ void PlayerbotFactory::InitAvailableSpells()
trainerIdCache.push_back(trainerId);
}
}
// uint32 learnedCounter = 0;
// uint32 oktest = 0;
for (uint32 trainerId : trainerIdCache)
{
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
@@ -2226,16 +2378,19 @@ void PlayerbotFactory::InitAvailableSpells()
{
continue;
}
// oktest++;
if (tSpell->learnedSpell[0])
{
bot->learnSpell(tSpell->learnedSpell[0], false);
}
else
{
botAI->CastSpell(tSpell->spell, bot);
}
// else
// {
// botAI->CastSpell(tSpell->spell, bot);
// }
}
// LOG_INFO("playerbots", "C: {}, ok: {}", ++learnedCounter, oktest);
// if (++learnedCounter > 20)
// break;
}
}
@@ -2645,7 +2800,10 @@ void PlayerbotFactory::InitAmmo()
bot->SetAmmo(entry);
}
uint32 PlayerbotFactory::CalcMixedGearScore(uint32 gs, uint32 quality) { return gs * PlayerbotAI::GetItemScoreMultiplier(ItemQualities(quality)); }
uint32 PlayerbotFactory::CalcMixedGearScore(uint32 gs, uint32 quality)
{
return gs * PlayerbotAI::GetItemScoreMultiplier(ItemQualities(quality));
}
void PlayerbotFactory::InitMounts()
{
@@ -2813,7 +2971,8 @@ std::vector<uint32> PlayerbotFactory::GetCurrentGemsCount()
Item* pItem2 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i);
if (pItem2 && !pItem2->IsBroken() && pItem2->HasSocket())
{
for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot <= PRISMATIC_ENCHANTMENT_SLOT; ++enchant_slot)
for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot <= PRISMATIC_ENCHANTMENT_SLOT;
++enchant_slot)
{
if (enchant_slot == BONUS_ENCHANTMENT_SLOT)
continue;
@@ -3321,10 +3480,10 @@ void PlayerbotFactory::InitInventoryEquip()
if (proto->Class == ITEM_CLASS_WEAPON && !CanEquipWeapon(proto))
continue;
if (proto->Quality != desiredQuality)
continue;
if (!CanEquipItem(proto))
continue;
@@ -3771,7 +3930,9 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
{
continue;
}
if (enchant->requiredSkill && (!bot->HasSkill(enchant->requiredSkill) || (bot->GetSkillValue(enchant->requiredSkill) < enchant->requiredSkillValue)))
if (enchant->requiredSkill &&
(!bot->HasSkill(enchant->requiredSkill) ||
(bot->GetSkillValue(enchant->requiredSkill) < enchant->requiredSkillValue)))
{
continue;
}
@@ -3808,7 +3969,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
int32 enchantIdChosen = -1;
int32 colorChosen;
float bestGemScore = -1;
for (uint32 &enchantGem : availableGems)
for (uint32& enchantGem : availableGems)
{
ItemTemplate const* gemTemplate = sObjectMgr->GetItemTemplate(enchantGem);
if (!gemTemplate)
@@ -3818,7 +3979,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
if (!gemProperties)
continue;
if ((socketColor & gemProperties->color) == 0)
if ((socketColor & gemProperties->color) == 0 && gemProperties->color == 1) // meta socket
continue;
uint32 enchant_id = gemProperties->spellitemenchantement;
@@ -3829,6 +3990,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
StatsWeightCalculator calculator(bot);
float score = calculator.CalculateEnchant(enchant_id);
if (curCount[0] != 0)
{
// Ensure meta gem activation
for (int i = 1; i < curCount.size(); i++)
{
@@ -3838,6 +4000,9 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
break;
}
}
}
if (socketColor & gemProperties->color)
score *= 1.2;
if (score > bestGemScore)
{
enchantIdChosen = enchant_id;

View File

@@ -122,7 +122,7 @@ public:
static void InitTalentsByParsedSpecLink(Player* bot, std::vector<std::vector<uint32>> parsedSpecLink, bool reset);
void InitAvailableSpells();
void InitClassSpells();
void InitEquipment(bool incremental);
void InitEquipment(bool incremental, bool second_chance = false);
void InitPet();
void InitAmmo();
static uint32 CalcMixedGearScore(uint32 gs, uint32 quality);

View File

@@ -233,7 +233,7 @@ bool StatsCollector::SpecialSpellFilter(uint32 spellId) {
if (type_ != CollectorType::SPELL_HEAL)
stats[STATS_TYPE_CRIT] += 50;
return true;
case 59620:
case 59620: // Berserk
if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_ATTACK_POWER] += 120;
return true;
@@ -254,6 +254,10 @@ bool StatsCollector::SpecialSpellFilter(uint32 spellId) {
/// Noticing that heroic item can not be triggered, probably a bug to report to AC
if (type_ == CollectorType::SPELL_HEAL)
return true;
break;
case 71903: // Shadowmourne
stats[STATS_TYPE_STRENGTH] += 200;
return true;
default:
break;
}

View File

@@ -32,6 +32,15 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
cls = player->getClass();
tab = AiFactory::GetPlayerSpecTab(player);
if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
hitOverflowType_ = CollectorType::SPELL;
else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)
hitOverflowType_ = CollectorType::SPELL;
else if (cls == CLASS_ROGUE)
hitOverflowType_ = CollectorType::SPELL;
else
hitOverflowType_ = type_;
enable_overflow_penalty_ = true;
enable_item_set_bonus_ = true;
enable_quality_blend_ = true;
@@ -58,6 +67,9 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId)
collector_->CollectItemStats(proto);
if (enable_overflow_penalty_)
ApplyOverflowPenalty(player_);
GenerateWeights(player_);
for (uint32 i = 0; i < STATS_TYPE_MAX; i++)
{
@@ -89,6 +101,9 @@ float StatsWeightCalculator::CalculateEnchant(uint32 enchantId)
collector_->CollectEnchantStats(enchant);
if (enable_overflow_penalty_)
ApplyOverflowPenalty(player_);
GenerateWeights(player_);
for (uint32 i = 0; i < STATS_TYPE_MAX; i++)
{
@@ -102,9 +117,7 @@ void StatsWeightCalculator::GenerateWeights(Player* player)
{
GenerateBasicWeights(player);
GenerateAdditionalWeights(player);
if (enable_overflow_penalty_)
ApplyOverflowPenalty(player);
ApplyWeightFinetune(player);
}
void StatsWeightCalculator::GenerateBasicWeights(Player* player)
@@ -219,15 +232,15 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
}
else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) // retribution
{
stats_weights_[STATS_TYPE_AGILITY] += 1.1f;
stats_weights_[STATS_TYPE_AGILITY] += 1.3f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f;
stats_weights_[STATS_TYPE_INTELLECT] += 0.15f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_SPELL_POWER] += 0.3f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 0.5f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 0.7f;
stats_weights_[STATS_TYPE_HIT] += 1.9f;
stats_weights_[STATS_TYPE_CRIT] += 1.2f;
stats_weights_[STATS_TYPE_HASTE] += 1.3f;
stats_weights_[STATS_TYPE_CRIT] += 1.3f;
stats_weights_[STATS_TYPE_HASTE] += 1.2f;
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
}
@@ -414,12 +427,12 @@ void StatsWeightCalculator::CalculateSocketBonus(Player* player, ItemTemplate co
void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
{
// penalty for different type armor
if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass >= ITEM_SUBCLASS_ARMOR_CLOTH &&
proto->SubClass <= ITEM_SUBCLASS_ARMOR_PLATE && NotBestArmorType(proto->SubClass))
{
weight_ *= 0.8;
}
// // penalty for different type armor
// if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass >= ITEM_SUBCLASS_ARMOR_CLOTH &&
// proto->SubClass <= ITEM_SUBCLASS_ARMOR_PLATE && NotBestArmorType(proto->SubClass))
// {
// weight_ *= 1.0;
// }
// double hand
if (proto->Class == ITEM_CLASS_WEAPON)
{
@@ -444,7 +457,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
}
// spec with double hand
// fury without duel wield, arms, bear, retribution, blood dk
if (isDoubleHand &&
if (!isDoubleHand &&
((cls == CLASS_HUNTER && !player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) ||
@@ -452,13 +465,13 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
(cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD) ||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield())))
{
weight_ *= 10;
weight_ *= 0.1;
}
// fury with titan's grip
if (isDoubleHand && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM &&
if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) &&
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && player_->CanTitanGrip()))
{
weight_ *= 10;
weight_ *= 0.1;
}
}
if (proto->Class == ITEM_CLASS_WEAPON)
@@ -481,6 +494,9 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
{
weight_ *= 1.1;
}
bool slowDelay = proto->Delay > 2500;
if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && slowDelay)
weight_ *= 1.1;
}
}
@@ -505,37 +521,57 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
{
{
float hit_current, hit_overflow;
if (type_ == CollectorType::SPELL)
float validPoints;
// m_modMeleeHitChance = (float)GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE);
// m_modMeleeHitChance += GetRatingBonusValue(CR_HIT_MELEE);
if (hitOverflowType_ == CollectorType::SPELL)
{
hit_current = player->GetRatingBonusValue(CR_HIT_SPELL);
hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_SPELL_HIT_CHANCE);
hit_current += player->GetRatingBonusValue(CR_HIT_SPELL);
hit_overflow = SPELL_HIT_OVERFLOW;
if (hit_overflow > hit_current)
validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_SPELL);
else
validPoints = 0;
}
else if (type_ == CollectorType::MELEE)
else if (hitOverflowType_ == CollectorType::MELEE)
{
hit_current = player->GetRatingBonusValue(CR_HIT_MELEE);
hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE);
hit_current += player->GetRatingBonusValue(CR_HIT_MELEE);
hit_overflow = MELEE_HIT_OVERFLOW;
if (hit_overflow > hit_current)
validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_MELEE);
else
validPoints = 0;
}
else
{
hit_current = player->GetRatingBonusValue(CR_HIT_RANGED);
hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE);
hit_current += player->GetRatingBonusValue(CR_HIT_RANGED);
hit_overflow = RANGED_HIT_OVERFLOW;
if (hit_overflow > hit_current)
validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_RANGED);
else
validPoints = 0;
}
if (hit_current >= hit_overflow)
stats_weights_[STATS_TYPE_HIT] = 0.0f;
else if (hit_current >= hit_overflow * 0.8)
stats_weights_[STATS_TYPE_HIT] /= 1.5;
collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], (int)validPoints);
}
{
if (type_ == CollectorType::MELEE)
{
float expertise_current, expertise_overflow;
expertise_current = player->GetRatingBonusValue(CR_EXPERTISE);
expertise_current = player->GetUInt32Value(PLAYER_EXPERTISE);
expertise_current += player->GetRatingBonusValue(CR_EXPERTISE);
expertise_overflow = EXPERTISE_OVERFLOW;
if (expertise_current >= expertise_overflow)
stats_weights_[STATS_TYPE_EXPERTISE] = 0.0f;
else if (expertise_current >= expertise_overflow * 0.8)
stats_weights_[STATS_TYPE_EXPERTISE] /= 1.5;
float validPoints;
if (expertise_overflow > expertise_current)
validPoints = (expertise_overflow - expertise_current) / player->GetRatingMultiplier(CR_EXPERTISE);
else
validPoints = 0;
collector_->stats[STATS_TYPE_EXPERTISE] = std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints);
}
}
@@ -545,10 +581,14 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
float defense_current, defense_overflow;
defense_current = player->GetRatingBonusValue(CR_DEFENSE_SKILL);
defense_overflow = DEFENSE_OVERFLOW;
if (defense_current >= defense_overflow)
stats_weights_[STATS_TYPE_DEFENSE] /= 2;
else if (defense_current >= defense_overflow * 0.8)
stats_weights_[STATS_TYPE_DEFENSE] /= 1.5;
float validPoints;
if (defense_overflow > defense_current)
validPoints = (defense_overflow - defense_current) / player->GetRatingMultiplier(CR_DEFENSE_SKILL);
else
validPoints = 0;
collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], (int)validPoints);
}
}
@@ -558,10 +598,27 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
float armor_penetration_current, armor_penetration_overflow;
armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION);
armor_penetration_overflow = ARMOR_PENETRATION_OVERFLOW;
if (armor_penetration_current >= armor_penetration_overflow)
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] = 0.0f;
if (armor_penetration_current >= armor_penetration_overflow * 0.8)
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] /= 1.5;
float validPoints;
if (armor_penetration_overflow > armor_penetration_current)
validPoints = (armor_penetration_overflow - armor_penetration_current) / player->GetRatingMultiplier(CR_ARMOR_PENETRATION);
else
validPoints = 0;
collector_->stats[STATS_TYPE_ARMOR_PENETRATION] = std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints);
}
}
}
}
void StatsWeightCalculator::ApplyWeightFinetune(Player* player)
{
{
if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED)
{
float armor_penetration_current, armor_penetration_overflow;
armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION);
if (armor_penetration_current > 50)
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] *= 1.2f;
}
}
}

View File

@@ -15,7 +15,7 @@
enum StatsOverflowThreshold
{
SPELL_HIT_OVERFLOW = 17,
SPELL_HIT_OVERFLOW = 14,
MELEE_HIT_OVERFLOW = 8,
RANGED_HIT_OVERFLOW = 8,
EXPERTISE_OVERFLOW = 26,
@@ -48,10 +48,12 @@ private:
bool NotBestArmorType(uint32 item_subclass_armor);
void ApplyOverflowPenalty(Player* player);
void ApplyWeightFinetune(Player* player);
private:
Player* player_;
CollectorType type_;
CollectorType hitOverflowType_;
std::unique_ptr<StatsCollector> collector_;
uint8 cls;
int tab;

View File

@@ -11,13 +11,13 @@
std::map<uint32, SkillLineAbilityEntry const*> ListSpellsAction::skillSpells;
std::set<uint32> ListSpellsAction::vendorItems;
bool CompareSpells(std::pair<uint32, std::string>& s1, std::pair<uint32, std::string>& s2)
bool CompareSpells(const std::pair<uint32, std::string>& s1, const std::pair<uint32, std::string>& s2)
{
SpellInfo const* si1 = sSpellMgr->GetSpellInfo(s1.first);
SpellInfo const* si2 = sSpellMgr->GetSpellInfo(s2.first);
if (!si1 || !si2)
{
LOG_ERROR("playerbots", "SpellInfo missing.");
LOG_ERROR("playerbots", "SpellInfo missing. {} {}", s1.first, s2.first);
return false;
}
uint32 p1 = si1->SchoolMask * 20000;
@@ -54,7 +54,7 @@ bool CompareSpells(std::pair<uint32, std::string>& s1, std::pair<uint32, std::st
if (p1 == p2)
{
return strcmp(si1->SpellName[0], si1->SpellName[1]) > 0;
return strcmp(si1->SpellName[0], si2->SpellName[0]) > 0;
}
return p1 > p2;
@@ -273,7 +273,11 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
if (out.str().empty())
continue;
if (itr->first == 0)
{
LOG_ERROR("playerbots", "?! {}", itr->first);
}
spells.push_back(std::pair<uint32, std::string>(itr->first, out.str()));
alreadySeenList += spellInfo->SpellName[0];
alreadySeenList += ",";

View File

@@ -41,12 +41,13 @@ DpsHunterStrategy::DpsHunterStrategy(PlayerbotAI* botAI) : GenericHunterStrategy
NextAction** DpsHunterStrategy::getDefaultActions()
{
return NextAction::array(
0, new NextAction("kill shot", ACTION_DEFAULT + 0.6f), new NextAction("chimera shot", ACTION_DEFAULT + 0.5f),
new NextAction("explosive shot", ACTION_DEFAULT + 0.4f), new NextAction("aimed shot", ACTION_DEFAULT + 0.3f),
/*new NextAction("arcane shot", ACTION_DEFAULT + 0.2f),*/ new NextAction("steady shot", ACTION_DEFAULT + 0.1f),
0, new NextAction("kill shot", ACTION_DEFAULT + 0.8f), new NextAction("chimera shot", ACTION_DEFAULT + 0.7f),
new NextAction("explosive shot", ACTION_DEFAULT + 0.6f), new NextAction("aimed shot", ACTION_DEFAULT + 0.5f),
new NextAction("silencing shot", ACTION_DEFAULT + 0.4f),
new NextAction("kill command", ACTION_DEFAULT + 0.3f),
// new NextAction("arcane shot", ACTION_DEFAULT + 0.2f),
new NextAction("steady shot", ACTION_DEFAULT + 0.1f),
new NextAction("auto shot", ACTION_DEFAULT), nullptr);
// return NextAction::array(0, new NextAction("explosive shot", 11.0f), new NextAction("auto shot", 10.0f), new
// NextAction("auto attack", 9.0f), nullptr);
}
void DpsHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -95,7 +95,7 @@ void GenericHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("enemy too close for auto shot",
NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr)));
triggers.push_back(
new TriggerNode("misdirection on main tank",
new TriggerNode("low tank threat",
NextAction::array(0, new NextAction("misdirection on main tank", ACTION_HIGH + 7), NULL)));
triggers.push_back(
new TriggerNode("low health", NextAction::array(0, new NextAction("deterrence", ACTION_HIGH + 5), nullptr)));

View File

@@ -78,6 +78,9 @@ END_SPELL_ACTION()
BEGIN_RANGED_SPELL_ACTION(CastKillShotAction, "kill shot")
END_SPELL_ACTION()
BEGIN_RANGED_SPELL_ACTION(CastSilencingShotAction, "silencing shot")
END_SPELL_ACTION()
BEGIN_RANGED_SPELL_ACTION(CastTranquilizingShotAction, "tranquilizing shot")
END_SPELL_ACTION()
@@ -139,6 +142,14 @@ public:
std::string const GetTargetName() override { return "pet target"; }
};
class CastKillCommandAction : public CastAuraSpellAction
{
public:
CastKillCommandAction(PlayerbotAI* botAI) : CastAuraSpellAction(botAI, "kill command") {}
std::string const GetTargetName() override { return "pet target"; }
};
class CastRevivePetAction : public CastBuffSpellAction
{
public:

View File

@@ -143,6 +143,7 @@ public:
creators["scorpid sting"] = &HunterAiObjectContextInternal::scorpid_sting;
creators["hunter's mark"] = &HunterAiObjectContextInternal::hunters_mark;
creators["mend pet"] = &HunterAiObjectContextInternal::mend_pet;
creators["kill command"] = &HunterAiObjectContextInternal::kill_command;
creators["revive pet"] = &HunterAiObjectContextInternal::revive_pet;
creators["call pet"] = &HunterAiObjectContextInternal::call_pet;
creators["black arrow"] = &HunterAiObjectContextInternal::black_arrow;
@@ -171,6 +172,7 @@ public:
creators["steady shot"] = &HunterAiObjectContextInternal::steady_shot;
creators["kill shot"] = &HunterAiObjectContextInternal::kill_shot;
creators["misdirection on main tank"] = &HunterAiObjectContextInternal::misdirection_on_main_tank;
creators["silencing shot"] = &HunterAiObjectContextInternal::silencing_shot;
}
private:
@@ -196,6 +198,7 @@ private:
static Action* scorpid_sting(PlayerbotAI* botAI) { return new CastScorpidStingAction(botAI); }
static Action* hunters_mark(PlayerbotAI* botAI) { return new CastHuntersMarkAction(botAI); }
static Action* mend_pet(PlayerbotAI* botAI) { return new CastMendPetAction(botAI); }
static Action* kill_command(PlayerbotAI* botAI) { return new CastKillCommandAction(botAI); }
static Action* revive_pet(PlayerbotAI* botAI) { return new CastRevivePetAction(botAI); }
static Action* call_pet(PlayerbotAI* botAI) { return new CastCallPetAction(botAI); }
static Action* black_arrow(PlayerbotAI* botAI) { return new CastBlackArrow(botAI); }
@@ -217,6 +220,8 @@ private:
static Action* steady_shot(PlayerbotAI* ai) { return new CastSteadyShotAction(ai); }
static Action* kill_shot(PlayerbotAI* ai) { return new CastKillShotAction(ai); }
static Action* misdirection_on_main_tank(PlayerbotAI* ai) { return new CastMisdirectionOnMainTankAction(ai); }
static Action* silencing_shot(PlayerbotAI* ai) { return new CastSilencingShotAction(ai); }
};
HunterAiObjectContext::HunterAiObjectContext(PlayerbotAI* botAI) : AiObjectContext(botAI)

View File

@@ -83,18 +83,20 @@ DpsPaladinStrategy::DpsPaladinStrategy(PlayerbotAI* botAI) : GenericPaladinStrat
NextAction** DpsPaladinStrategy::getDefaultActions()
{
return NextAction::array(0,
new NextAction("crusader strike", ACTION_DEFAULT + 0.6f),
new NextAction("hammer of wrath", ACTION_DEFAULT + 0.5f),
new NextAction("hammer of wrath", ACTION_DEFAULT + 0.6f),
new NextAction("crusader strike", ACTION_DEFAULT + 0.5f),
new NextAction("judgement of wisdom", ACTION_DEFAULT + 0.4f),
new NextAction("divine storm", ACTION_DEFAULT + 0.3f),
new NextAction("consecration", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_DEFAULT), NULL);
new NextAction("melee", ACTION_DEFAULT), nullptr);
}
void DpsPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericPaladinStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode(
// "enough mana", NextAction::array(0, new NextAction("consecration", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(
new TriggerNode("art of war", NextAction::array(0, new NextAction("exorcism", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(

View File

@@ -83,7 +83,7 @@ void AssassinationRogueStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("fan of knives", ACTION_NORMAL + 5), NULL)));
triggers.push_back(new TriggerNode(
"tricks of the trade on main tank",
"low tank threat",
NextAction::array(0, new NextAction("tricks of the trade on main tank", ACTION_HIGH + 7), NULL)));
triggers.push_back(new TriggerNode(

View File

@@ -141,7 +141,7 @@ void DpsRogueStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction::array(0, new NextAction("expose armor", ACTION_HIGH + 3), nullptr)));
triggers.push_back(new TriggerNode(
"tricks of the trade on main tank",
"low tank threat",
NextAction::array(0, new NextAction("tricks of the trade on main tank", ACTION_HIGH + 7), nullptr)));
}

View File

@@ -14,6 +14,7 @@
#include "Playerbots.h"
#include "SharedDefines.h"
#include "TemporarySummon.h"
#include "ThreatMgr.h"
#include "Timer.h"
bool LowManaTrigger::IsActive()
@@ -185,6 +186,22 @@ bool MyAttackerCountTrigger::IsActive()
return AI_VALUE2(bool, "combat", "self target") && AI_VALUE(uint8, "my attacker count") >= amount;
}
bool LowTankThreatTrigger::IsActive()
{
Unit* mt = AI_VALUE(Unit*, "main tank");
if (!mt)
return false;
Unit* current_target = AI_VALUE(Unit*, "current target");
if (!current_target)
return false;
ThreatMgr& mgr = current_target->GetThreatMgr();
float threat = mgr.GetThreat(bot);
float tankThreat = mgr.GetThreat(mt);
return tankThreat == 0.0f || threat > tankThreat * 0.5f;
}
bool AoeTrigger::IsActive()
{
Unit* current_target = AI_VALUE(Unit*, "current target");

View File

@@ -10,6 +10,7 @@
#include "HealthTriggers.h"
#include "RangeTriggers.h"
#include "Trigger.h"
class PlayerbotAI;
class Unit;
@@ -248,6 +249,13 @@ public:
MediumThreatTrigger(PlayerbotAI* botAI) : MyAttackerCountTrigger(botAI, 2) {}
};
class LowTankThreatTrigger : public Trigger
{
public:
LowTankThreatTrigger(PlayerbotAI* botAI) : Trigger(botAI, "low tank threat") {}
bool IsActive() override;
};
class AoeTrigger : public AttackerCountTrigger
{
public:

View File

@@ -102,6 +102,7 @@ public:
creators["combo points not full and high energy"] = &TriggerContext::ComboPointsNotFullAndHighEnergy;
creators["medium threat"] = &TriggerContext::MediumThreat;
creators["low tank threat"] = &TriggerContext::low_tank_threat;
creators["dead"] = &TriggerContext::Dead;
creators["corpse near"] = &TriggerContext::corpse_near;
@@ -321,6 +322,8 @@ private:
static Trigger* ComboPointsNotFull(PlayerbotAI* botAI) { return new ComboPointsNotFullTrigger(botAI); }
static Trigger* ComboPointsNotFullAndHighEnergy(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "combo points not full", "high energy available"); }
static Trigger* MediumThreat(PlayerbotAI* botAI) { return new MediumThreatTrigger(botAI); }
static Trigger* low_tank_threat(PlayerbotAI* botAI) { return new LowTankThreatTrigger(botAI); }
// static Trigger* MediumThreat(PlayerbotAI* botAI) { return new MediumThreatTrigger(botAI); }
static Trigger* Dead(PlayerbotAI* botAI) { return new DeadTrigger(botAI); }
static Trigger* corpse_near(PlayerbotAI* botAI) { return new CorpseNearTrigger(botAI); }
static Trigger* PartyMemberDead(PlayerbotAI* botAI) { return new PartyMemberDeadTrigger(botAI); }

View File

@@ -166,7 +166,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
return ITEM_USAGE_NONE;
uint16 dest;
InventoryResult result = botAI->CanEquipItem(NULL_SLOT, dest, pItem, true, false);
InventoryResult result = botAI->CanEquipItem(NULL_SLOT, dest, pItem, true, true);
pItem->RemoveFromUpdateQueueOf(bot);
delete pItem;