diff --git a/src/Ai/Base/Actions/EquipAction.cpp b/src/Ai/Base/Actions/EquipAction.cpp index a4086f371..4cd11de3c 100644 --- a/src/Ai/Base/Actions/EquipAction.cpp +++ b/src/Ai/Base/Actions/EquipAction.cpp @@ -154,9 +154,11 @@ void EquipAction::EquipItem(Item* item) calculator.SetOverflowPenalty(false); // Calculate item scores once and store them - float newItemScore = calculator.CalculateItem(itemId); - float mainHandScore = mainHandItem ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId) : 0.0f; - float offHandScore = offHandItem ? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId) : 0.0f; + float newItemScore = calculator.CalculateItem(itemId, item->GetItemRandomPropertyId()); + float mainHandScore = mainHandItem + ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId, mainHandItem->GetItemRandomPropertyId()) : 0.0f; + float offHandScore = offHandItem + ? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId, offHandItem->GetItemRandomPropertyId()) : 0.0f; // Determine where this weapon can go bool canGoMain = (invType == INVTYPE_WEAPON || diff --git a/src/Bot/Factory/PlayerbotFactory.cpp b/src/Bot/Factory/PlayerbotFactory.cpp index 161410c3f..a1adfdeaf 100644 --- a/src/Bot/Factory/PlayerbotFactory.cpp +++ b/src/Bot/Factory/PlayerbotFactory.cpp @@ -2081,7 +2081,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) return; } - std::unordered_map> items; + std::unordered_map>> items; // int tab = AiFactory::GetPlayerSpecTab(bot); uint32 blevel = bot->GetLevel(); @@ -2228,13 +2228,17 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) if (slot == EQUIPMENT_SLOT_OFFHAND && bot->getClass() == CLASS_ROGUE && proto->Class != ITEM_CLASS_WEAPON) continue; - items[slot].push_back(itemId); + + int32 bestRandomProp = 0; + if (proto->RandomProperty || proto->RandomSuffix) + bestRandomProp = calculator.PickBestRandomPropertyId(itemId); + items[slot].push_back({itemId, bestRandomProp}); } } } } while (items[slot].size() < 25 && desiredQuality-- > ITEM_QUALITY_POOR); - std::vector& ids = items[slot]; + std::vector>& ids = items[slot]; if (ids.empty()) { continue; @@ -2242,13 +2246,15 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) float bestScoreForSlot = -1; uint32 bestItemForSlot = 0; + int32 bestRandomPropForSlot = 0; for (int index = 0; index < ids.size(); index++) { - uint32 newItemId = ids[index]; + uint32 newItemId = ids[index].first; + int32 newItemProp = ids[index].second; ItemTemplate const* proto = sObjectMgr->GetItemTemplate(newItemId); - float cur_score = calculator.CalculateItem(newItemId, 0, slot); + float cur_score = calculator.CalculateItem(newItemId, newItemProp, slot); if (cur_score > 0.0f && proto && proto->Class == ITEM_CLASS_ARMOR && sPlayerbotAIConfig.preferClassArmorType) { @@ -2267,6 +2273,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) continue; bestScoreForSlot = cur_score; bestItemForSlot = newItemId; + bestRandomPropForSlot = newItemProp; } } @@ -2304,7 +2311,16 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) if (oldItem) continue; - bot->EquipNewItem(dest, bestItemForSlot, true); + if (Item* equipped = bot->EquipNewItem(dest, bestItemForSlot, true)) + { + if (bestRandomPropForSlot != 0) + { + uint8 equipSlot = equipped->GetSlot(); + bot->_ApplyItemMods(equipped, equipSlot, false); + equipped->SetItemRandomProperties(bestRandomPropForSlot); + bot->_ApplyItemMods(equipped, equipSlot, true); + } + } bot->AutoUnequipOffhandIfNeed(); // if (newItem) // { @@ -2345,19 +2361,21 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) if (Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true); - std::vector& ids = items[slot]; + std::vector>& ids = items[slot]; if (ids.empty()) continue; float bestScoreForSlot = -1; uint32 bestItemForSlot = 0; + int32 bestRandomPropForSlot = 0; for (int index = 0; index < ids.size(); index++) { - uint32 newItemId = ids[index]; + uint32 newItemId = ids[index].first; + int32 newItemProp = ids[index].second; ItemTemplate const* proto = sObjectMgr->GetItemTemplate(newItemId); - float cur_score = calculator.CalculateItem(newItemId, 0, slot); + float cur_score = calculator.CalculateItem(newItemId, newItemProp, slot); if (cur_score > 0.0f && proto && proto->Class == ITEM_CLASS_ARMOR && sPlayerbotAIConfig.preferClassArmorType) { @@ -2376,6 +2394,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) continue; bestScoreForSlot = cur_score; bestItemForSlot = newItemId; + bestRandomPropForSlot = newItemProp; } } @@ -2386,7 +2405,16 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) if (!CanEquipUnseenItem(slot, dest, bestItemForSlot)) continue; - bot->EquipNewItem(dest, bestItemForSlot, true); + if (Item* equipped = bot->EquipNewItem(dest, bestItemForSlot, true)) + { + if (bestRandomPropForSlot != 0) + { + uint8 equipSlot = equipped->GetSlot(); + bot->_ApplyItemMods(equipped, equipSlot, false); + equipped->SetItemRandomProperties(bestRandomPropForSlot); + bot->_ApplyItemMods(equipped, equipSlot, true); + } + } bot->AutoUnequipOffhandIfNeed(); } } diff --git a/src/Mgr/Item/RandomItemMgr.cpp b/src/Mgr/Item/RandomItemMgr.cpp index e08f2c385..c57080644 100644 --- a/src/Mgr/Item/RandomItemMgr.cpp +++ b/src/Mgr/Item/RandomItemMgr.cpp @@ -160,6 +160,7 @@ void RandomItemMgr::Init() BuildPotionCache(); BuildFoodCache(); BuildTradeCache(); + LoadEnchantmentPool(); } void RandomItemMgr::InitAfterAhBot() @@ -452,6 +453,39 @@ std::vector RandomItemMgr::GetCachedEquipments(uint32 requiredLevel, uin return equipCacheNew[requiredLevel][inventoryType]; } +void RandomItemMgr::LoadEnchantmentPool() +{ + enchPoolCache.clear(); + + QueryResult result = WorldDatabase.Query("SELECT entry, ench FROM item_enchantment_template"); + if (!result) + { + LOG_WARN("playerbots", "item_enchantment_template empty; bot autogear cannot evaluate random suffixes"); + return; + } + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + uint32 entry = fields[0].Get(); + uint32 ench = fields[1].Get(); + enchPoolCache[entry].push_back(ench); + ++count; + } while (result->NextRow()); + + LOG_INFO("playerbots", "Loaded {} item enchantment pool rows for bot autogear", count); +} + +std::vector const& RandomItemMgr::GetEnchantmentPool(uint32 entry) const +{ + static std::vector const empty; + auto it = enchPoolCache.find(entry); + if (it == enchPoolCache.end()) + return empty; + return it->second; +} + bool RandomItemMgr::ShouldEquipArmorForSpec(uint8 playerclass, uint8 spec, ItemTemplate const* proto) { if (proto->InventoryType == INVTYPE_TABARD) diff --git a/src/Mgr/Item/RandomItemMgr.h b/src/Mgr/Item/RandomItemMgr.h index 54e2e6351..bc19d75da 100644 --- a/src/Mgr/Item/RandomItemMgr.h +++ b/src/Mgr/Item/RandomItemMgr.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -172,9 +173,11 @@ public: static bool IsUsedBySkill(ItemTemplate const* proto, uint32 skillId); bool IsTestItem(uint32 itemId) { return itemForTest.find(itemId) != itemForTest.end(); } std::vector GetCachedEquipments(uint32 requiredLevel, uint32 inventoryType); + std::vector const& GetEnchantmentPool(uint32 entry) const; private: void BuildRandomItemCache(); + void LoadEnchantmentPool(); void BuildEquipCache(); void BuildEquipCacheNew(); void BuildItemInfoCache(); @@ -217,6 +220,8 @@ private: static std::set itemCache; // equipCacheNew[RequiredLevel][InventoryType] std::map>> equipCacheNew; + // enchPoolCache[item_enchantment_template.entry] -> list of enchantment ids + std::unordered_map> enchPoolCache; }; #define sRandomItemMgr RandomItemMgr::instance() diff --git a/src/Mgr/Item/StatsWeightCalculator.cpp b/src/Mgr/Item/StatsWeightCalculator.cpp index b232b1e6b..2e11f0a38 100644 --- a/src/Mgr/Item/StatsWeightCalculator.cpp +++ b/src/Mgr/Item/StatsWeightCalculator.cpp @@ -14,6 +14,7 @@ #include "ObjectMgr.h" #include "PlayerbotAI.h" #include "PlayerbotFactory.h" +#include "RandomItemMgr.h" #include "SharedDefines.h" #include "SpellAuraDefines.h" #include "SpellMgr.h" @@ -190,6 +191,53 @@ void StatsWeightCalculator::CalculateRandomProperty(int32 randomPropertyId, uint } } +int32 StatsWeightCalculator::PickBestRandomPropertyId(uint32 itemId) +{ + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); + if (!proto) + return 0; + + bool isSuffix = false; + uint32 poolEntry = proto->RandomProperty; + if (!poolEntry) + { + poolEntry = proto->RandomSuffix; + isSuffix = true; + } + if (!poolEntry) + return 0; + + std::vector const& pool = sRandomItemMgr.GetEnchantmentPool(poolEntry); + if (pool.empty()) + return 0; + + Reset(); + GenerateWeights(player_); + + int32 bestId = 0; + float bestScore = 0.0f; + for (uint32 enchId : pool) + { + int32 candidate = isSuffix ? -static_cast(enchId) : static_cast(enchId); + + collector_->Reset(); + CalculateRandomProperty(candidate, itemId); + + float score = 0.0f; + for (uint32 i = 0; i < STATS_TYPE_MAX; ++i) + score += stats_weights_[i] * collector_->stats[i]; + + if (bestId == 0 || score > bestScore) + { + bestId = candidate; + bestScore = score; + } + } + + collector_->Reset(); + return bestId; +} + void StatsWeightCalculator::GenerateWeights(Player* player) { GenerateBasicWeights(player); diff --git a/src/Mgr/Item/StatsWeightCalculator.h b/src/Mgr/Item/StatsWeightCalculator.h index d97dafbb3..1049adcb4 100644 --- a/src/Mgr/Item/StatsWeightCalculator.h +++ b/src/Mgr/Item/StatsWeightCalculator.h @@ -30,6 +30,7 @@ public: void Reset(); float CalculateItem(uint32 itemId, int32 randomPropertyId = 0, int32 slot = -1); float CalculateEnchant(uint32 enchantId); + int32 PickBestRandomPropertyId(uint32 itemId); void SetOverflowPenalty(bool apply) { enable_overflow_penalty_ = apply; } void SetItemSetBonus(bool apply) { enable_item_set_bonus_ = apply; }