/* * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it * and/or modify it under version 3 of the License, or (at your option), any later version. */ #include "RandomItemMgr.h" #include "ItemTemplate.h" #include "LootValues.h" #include "Playerbots.h" char* strstri(char const* str1, char const* str2); std::set RandomItemMgr::itemCache; uint64 BotEquipKey::GetKey() { return level + 100 * clazz + 10000 * slot + 1000000 * quality; } class RandomItemGuildTaskPredicate : public RandomItemPredicate { public: bool Apply(ItemTemplate const* proto) override { if (proto->Bonding == BIND_WHEN_PICKED_UP || proto->Bonding == BIND_QUEST_ITEM || proto->Bonding == BIND_WHEN_USE) return false; if (proto->Quality < ITEM_QUALITY_NORMAL) return false; if ((proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON) && proto->Quality >= ITEM_QUALITY_RARE) return true; if (proto->Class == ITEM_CLASS_TRADE_GOODS || proto->Class == ITEM_CLASS_CONSUMABLE) return true; return false; } }; class RandomItemGuildTaskRewardPredicate : public RandomItemPredicate { public: RandomItemGuildTaskRewardPredicate(bool equip, bool rare) : equip(equip), rare(rare) {} bool Apply(ItemTemplate const* proto) override { if (proto->Bonding == BIND_WHEN_PICKED_UP || proto->Bonding == BIND_QUEST_ITEM || proto->Bonding == BIND_WHEN_USE) return false; if (proto->Class == ITEM_CLASS_QUEST) return false; if (equip) { uint32 desiredQuality = rare ? ITEM_QUALITY_RARE : ITEM_QUALITY_UNCOMMON; if (proto->Quality < desiredQuality || proto->Quality >= ITEM_QUALITY_EPIC) return false; if (proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON) return true; } else { uint32 desiredQuality = rare ? ITEM_QUALITY_UNCOMMON : ITEM_QUALITY_NORMAL; if (proto->Quality < desiredQuality || proto->Quality >= ITEM_QUALITY_RARE) return false; if (proto->Class == ITEM_CLASS_TRADE_GOODS || proto->Class == ITEM_CLASS_CONSUMABLE) return true; } return false; } private: bool equip; bool rare; }; RandomItemMgr::RandomItemMgr() { predicates[RANDOM_ITEM_GUILD_TASK] = new RandomItemGuildTaskPredicate(); predicates[RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_GREEN] = new RandomItemGuildTaskRewardPredicate(true, false); predicates[RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_BLUE] = new RandomItemGuildTaskRewardPredicate(true, true); predicates[RANDOM_ITEM_GUILD_TASK_REWARD_TRADE] = new RandomItemGuildTaskRewardPredicate(false, false); predicates[RANDOM_ITEM_GUILD_TASK_REWARD_TRADE_RARE] = new RandomItemGuildTaskRewardPredicate(false, true); viableSlots[EQUIPMENT_SLOT_HEAD].insert(INVTYPE_HEAD); viableSlots[EQUIPMENT_SLOT_NECK].insert(INVTYPE_NECK); viableSlots[EQUIPMENT_SLOT_SHOULDERS].insert(INVTYPE_SHOULDERS); viableSlots[EQUIPMENT_SLOT_BODY].insert(INVTYPE_BODY); viableSlots[EQUIPMENT_SLOT_CHEST].insert(INVTYPE_CHEST); viableSlots[EQUIPMENT_SLOT_CHEST].insert(INVTYPE_ROBE); viableSlots[EQUIPMENT_SLOT_WAIST].insert(INVTYPE_WAIST); viableSlots[EQUIPMENT_SLOT_LEGS].insert(INVTYPE_LEGS); viableSlots[EQUIPMENT_SLOT_FEET].insert(INVTYPE_FEET); viableSlots[EQUIPMENT_SLOT_WRISTS].insert(INVTYPE_WRISTS); viableSlots[EQUIPMENT_SLOT_HANDS].insert(INVTYPE_HANDS); viableSlots[EQUIPMENT_SLOT_FINGER1].insert(INVTYPE_FINGER); viableSlots[EQUIPMENT_SLOT_FINGER2].insert(INVTYPE_FINGER); viableSlots[EQUIPMENT_SLOT_TRINKET1].insert(INVTYPE_TRINKET); viableSlots[EQUIPMENT_SLOT_TRINKET2].insert(INVTYPE_TRINKET); viableSlots[EQUIPMENT_SLOT_MAINHAND].insert(INVTYPE_WEAPON); viableSlots[EQUIPMENT_SLOT_MAINHAND].insert(INVTYPE_2HWEAPON); viableSlots[EQUIPMENT_SLOT_MAINHAND].insert(INVTYPE_WEAPONMAINHAND); viableSlots[EQUIPMENT_SLOT_OFFHAND].insert(INVTYPE_WEAPON); viableSlots[EQUIPMENT_SLOT_OFFHAND].insert(INVTYPE_2HWEAPON); viableSlots[EQUIPMENT_SLOT_OFFHAND].insert(INVTYPE_SHIELD); viableSlots[EQUIPMENT_SLOT_OFFHAND].insert(INVTYPE_WEAPONMAINHAND); viableSlots[EQUIPMENT_SLOT_OFFHAND].insert(INVTYPE_HOLDABLE); viableSlots[EQUIPMENT_SLOT_RANGED].insert(INVTYPE_RANGED); viableSlots[EQUIPMENT_SLOT_RANGED].insert(INVTYPE_THROWN); viableSlots[EQUIPMENT_SLOT_RANGED].insert(INVTYPE_RANGEDRIGHT); viableSlots[EQUIPMENT_SLOT_RANGED].insert(INVTYPE_RELIC); viableSlots[EQUIPMENT_SLOT_TABARD].insert(INVTYPE_TABARD); viableSlots[EQUIPMENT_SLOT_BACK].insert(INVTYPE_CLOAK); weightStatLink["sta"] = ITEM_MOD_STAMINA; weightStatLink["str"] = ITEM_MOD_STRENGTH; weightStatLink["agi"] = ITEM_MOD_AGILITY; weightStatLink["int"] = ITEM_MOD_INTELLECT; weightStatLink["spi"] = ITEM_MOD_SPIRIT; weightStatLink["splpwr"] = ITEM_MOD_SPELL_POWER; weightStatLink["atkpwr"] = ITEM_MOD_ATTACK_POWER; weightStatLink["exprtng"] = ITEM_MOD_EXPERTISE_RATING; weightStatLink["critstrkrtng"] = ITEM_MOD_CRIT_RATING; weightStatLink["hitrtng"] = ITEM_MOD_HIT_RATING; weightStatLink["hastertng"] = ITEM_MOD_HASTE_RATING; weightStatLink["armorpenrtng"] = ITEM_MOD_ARMOR_PENETRATION_RATING; weightStatLink["defrtng"] = ITEM_MOD_DEFENSE_SKILL_RATING; weightStatLink["dodgertng"] = ITEM_MOD_DODGE_RATING; weightStatLink["block"] = ITEM_MOD_BLOCK_VALUE; weightStatLink["blockrtng"] = ITEM_MOD_BLOCK_RATING; weightStatLink["parryrtng"] = ITEM_MOD_PARRY_RATING; weightStatLink["manargn"] = ITEM_MOD_MANA_REGENERATION; weightRatingLink["exprtng"] = CR_EXPERTISE; weightRatingLink["critstrkrtng"] = CR_CRIT_MELEE; weightRatingLink["hitrtng"] = CR_HIT_MELEE; weightRatingLink["hastertng"] = CR_HASTE_MELEE; weightRatingLink["armorpenrtng"] = CR_ARMOR_PENETRATION; weightRatingLink["defrtng"] = CR_DEFENSE_SKILL; weightRatingLink["dodgertng"] = CR_DODGE; weightRatingLink["blockrtng"] = CR_BLOCK; weightRatingLink["parryrtng"] = CR_PARRY; } void RandomItemMgr::Init() { BuildItemInfoCache(); // BuildEquipCache(); BuildEquipCacheNew(); BuildAmmoCache(); BuildPotionCache(); BuildFoodCache(); BuildTradeCache(); } void RandomItemMgr::InitAfterAhBot() { BuildRandomItemCache(); // BuildRarityCache(); } RandomItemMgr::~RandomItemMgr() { for (std::map::iterator i = predicates.begin(); i != predicates.end(); ++i) delete i->second; predicates.clear(); } bool RandomItemMgr::HandleConsoleCommand(ChatHandler* handler, char const* args) { if (!args || !*args) { LOG_ERROR("playerbots", "Usage: rnditem"); return false; } return false; } RandomItemList RandomItemMgr::Query(uint32 level, RandomItemType type, RandomItemPredicate* predicate) { RandomItemList& list = randomItemCache[(level - 1) / 10][type]; RandomItemList result; for (RandomItemList::iterator i = list.begin(); i != list.end(); ++i) { uint32 itemId = *i; ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (!proto) continue; if (predicate && !predicate->Apply(proto)) continue; result.push_back(itemId); } return result; } void RandomItemMgr::BuildRandomItemCache() { if (PreparedQueryResult result = PlayerbotsDatabase.Query(PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RNDITEM_CACHE))) { LOG_INFO("server.loading", "Loading random item cache"); uint32 count = 0; do { Field* fields = result->Fetch(); uint32 level = fields[0].Get(); uint32 type = fields[1].Get(); uint32 itemId = fields[2].Get(); RandomItemType rit = (RandomItemType)type; randomItemCache[level][rit].push_back(itemId); ++count; } while (result->NextRow()); LOG_INFO("server.loading", "Equipment cache loaded from {} records", count); } else { ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); LOG_INFO("server.loading", "Building random item cache from {} items", itemTemplates->size()); for (auto const& itr : *itemTemplates) { ItemTemplate const* proto = &itr.second; if (!proto) continue; if (proto->Duration & 0x80000000) continue; if (strstri(proto->Name1.c_str(), "qa") || strstri(proto->Name1.c_str(), "test") || strstri(proto->Name1.c_str(), "deprecated")) continue; if (!proto->ItemLevel) continue; if (!proto->SellPrice) continue; uint32 level = proto->ItemLevel; for (uint32 type = RANDOM_ITEM_GUILD_TASK; type <= RANDOM_ITEM_GUILD_TASK_REWARD_TRADE_RARE; type++) { RandomItemType rit = (RandomItemType)type; if (predicates[rit] && !predicates[rit]->Apply(proto)) continue; randomItemCache[level / 10][rit].push_back(itr.first); PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RNDITEM_CACHE); stmt->SetData(0, level / 10); stmt->SetData(1, type); stmt->SetData(2, itr.first); PlayerbotsDatabase.Execute(stmt); } } uint32 maxLevel = sPlayerbotAIConfig->randomBotMaxLevel; if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); for (uint32 level = 0; level < maxLevel / 10; level++) { for (uint32 type = RANDOM_ITEM_GUILD_TASK; type <= RANDOM_ITEM_GUILD_TASK_REWARD_TRADE_RARE; type++) { RandomItemList list = randomItemCache[level][(RandomItemType)type]; LOG_INFO("playerbots", " Level {}..{} Type {} - {} random items cached", level * 10, level * 10 + 9, type, list.size()); for (RandomItemList::iterator i = list.begin(); i != list.end(); ++i) { uint32 itemId = *i; ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (!proto) continue; LOG_DEBUG("playerbots", " [{}] {}", itemId, proto->Name1.c_str()); } } } } } uint32 RandomItemMgr::GetRandomItem(uint32 level, RandomItemType type, RandomItemPredicate* predicate) { RandomItemList const& list = Query(level, type, predicate); if (list.empty()) return 0; uint32 index = urand(0, list.size() - 1); uint32 itemId = list[index]; return itemId; } bool RandomItemMgr::CanEquipItem(BotEquipKey key, ItemTemplate const* proto) { if (proto->Duration & 0x80000000) return false; if (proto->Quality != key.quality) return false; if (proto->Bonding == BIND_QUEST_ITEM || proto->Bonding == BIND_WHEN_USE) return false; if (proto->Class == ITEM_CLASS_CONTAINER) return true; std::set slots = viableSlots[(EquipmentSlots)key.slot]; if (slots.find((InventoryType)proto->InventoryType) == slots.end()) return false; uint32 requiredLevel = proto->RequiredLevel; if (!requiredLevel) { requiredLevel = GetMinLevelFromCache(proto->ItemId); } if (!requiredLevel) requiredLevel = key.level; uint32 level = key.level; uint32 delta = 2; if (level < 15) delta = 15; else if (level < 40) delta = 10; // urand(5, 10); else if (level < 60) delta = 6; // urand(3, 7); else if (level < 70) delta = 9; // urand(2, 5); else if (level < 80) delta = 9; // urand(2, 4); else if (level == 80) delta = 9; // urand(2, 4); if (key.quality > ITEM_QUALITY_NORMAL && (requiredLevel > level || requiredLevel < level - delta)) return false; // for (uint32 gap = 60; gap <= 80; gap += 10) // { // if (level > gap && requiredLevel <= gap) // return false; // } return true; } bool RandomItemMgr::CanEquipItemNew(ItemTemplate const* proto) { if (proto->Duration & 0x80000000) return false; if (proto->Bonding == BIND_QUEST_ITEM || proto->Bonding == BIND_WHEN_USE) return false; if (proto->Class == ITEM_CLASS_CONTAINER) return false; bool properSlot = false; for (std::map >::iterator i = viableSlots.begin(); i != viableSlots.end(); ++i) { std::set const& slots = viableSlots[(EquipmentSlots)i->first]; if (slots.find((InventoryType)proto->InventoryType) != slots.end()) properSlot = true; } return properSlot; } void RandomItemMgr::AddItemStats(uint32 mod, uint8& sp, uint8& ap, uint8& tank) { switch (mod) { case ITEM_MOD_HEALTH: case ITEM_MOD_STAMINA: case ITEM_MOD_MANA: case ITEM_MOD_INTELLECT: case ITEM_MOD_SPIRIT: ++sp; break; } switch (mod) { case ITEM_MOD_AGILITY: case ITEM_MOD_STRENGTH: case ITEM_MOD_HEALTH: case ITEM_MOD_STAMINA: ++tank; break; } switch (mod) { case ITEM_MOD_HEALTH: case ITEM_MOD_STAMINA: case ITEM_MOD_AGILITY: case ITEM_MOD_STRENGTH: ++ap; break; } } bool RandomItemMgr::CheckItemStats(uint8 clazz, uint8 sp, uint8 ap, uint8 tank) { switch (clazz) { case CLASS_PRIEST: case CLASS_MAGE: case CLASS_WARLOCK: if (!sp || ap > sp || tank > sp) return false; break; case CLASS_PALADIN: case CLASS_WARRIOR: if ((!ap && !tank) || sp > ap || sp > tank) return false; break; case CLASS_HUNTER: case CLASS_ROGUE: if (!ap || sp > ap || sp > tank) return false; break; } return sp || ap || tank; } std::vector RandomItemMgr::GetCachedEquipments(uint32 requiredLevel, uint32 inventoryType) { return equipCacheNew[requiredLevel][inventoryType]; } bool RandomItemMgr::ShouldEquipArmorForSpec(uint8 playerclass, uint8 spec, ItemTemplate const* proto) { if (proto->InventoryType == INVTYPE_TABARD) return true; if (!m_weightScales[playerclass][spec].info.id) return false; std::unordered_set resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH}; switch (playerclass) { case CLASS_WARRIOR: { if (proto->InventoryType == INVTYPE_HOLDABLE) return false; if (m_weightScales[playerclass][spec].info.name == "arms" || m_weightScales[playerclass][spec].info.name == "fury") { resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_LEATHER, ITEM_SUBCLASS_ARMOR_MAIL, ITEM_SUBCLASS_ARMOR_PLATE}; } else resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_MAIL, ITEM_SUBCLASS_ARMOR_PLATE}; break; } case CLASS_DEATH_KNIGHT: { if (proto->InventoryType == INVTYPE_HOLDABLE) return false; resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_SIGIL, ITEM_SUBCLASS_ARMOR_PLATE}; } case CLASS_PALADIN: { if (m_weightScales[playerclass][spec].info.name != "holy" && proto->InventoryType == INVTYPE_HOLDABLE) return false; if (m_weightScales[playerclass][spec].info.name != "holy") resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_MAIL, ITEM_SUBCLASS_ARMOR_PLATE, ITEM_SUBCLASS_ARMOR_LIBRAM}; else resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH, ITEM_SUBCLASS_ARMOR_LEATHER, ITEM_SUBCLASS_ARMOR_MAIL, ITEM_SUBCLASS_ARMOR_PLATE, ITEM_SUBCLASS_ARMOR_LIBRAM}; break; } case CLASS_HUNTER: { if (proto->InventoryType == INVTYPE_HOLDABLE) return false; resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH, ITEM_SUBCLASS_ARMOR_LEATHER, ITEM_SUBCLASS_ARMOR_MAIL}; break; } case CLASS_ROGUE: { if (proto->InventoryType == INVTYPE_HOLDABLE) return false; resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH, ITEM_SUBCLASS_ARMOR_LEATHER}; break; } case CLASS_PRIEST: { resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH}; break; } case CLASS_SHAMAN: { if (m_weightScales[playerclass][spec].info.name == "enhance" && proto->InventoryType == INVTYPE_HOLDABLE) return false; resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_TOTEM, ITEM_SUBCLASS_ARMOR_CLOTH, ITEM_SUBCLASS_ARMOR_LEATHER, ITEM_SUBCLASS_ARMOR_MAIL}; break; } case CLASS_MAGE: case CLASS_WARLOCK: { resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH}; break; } case CLASS_DRUID: { if ((m_weightScales[playerclass][spec].info.name == "feraltank" || m_weightScales[playerclass][spec].info.name == "feraldps") && proto->InventoryType == INVTYPE_HOLDABLE) return false; resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_IDOL, ITEM_SUBCLASS_ARMOR_CLOTH, ITEM_SUBCLASS_ARMOR_LEATHER}; break; } } return resultArmorSubClass.find(proto->SubClass) != resultArmorSubClass.end(); } bool RandomItemMgr::ShouldEquipWeaponForSpec(uint8 playerclass, uint8 spec, ItemTemplate const* proto) { EquipmentSlots slot_mh = EQUIPMENT_SLOT_START; EquipmentSlots slot_oh = EQUIPMENT_SLOT_START; EquipmentSlots slot_rh = EQUIPMENT_SLOT_START; for (std::map >::iterator i = viableSlots.begin(); i != viableSlots.end(); ++i) { std::set slots = viableSlots[(EquipmentSlots)i->first]; if (slots.find((InventoryType)proto->InventoryType) != slots.end()) { if (i->first == EQUIPMENT_SLOT_MAINHAND) slot_mh = i->first; if (i->first == EQUIPMENT_SLOT_OFFHAND) slot_oh = i->first; if (i->first == EQUIPMENT_SLOT_RANGED) slot_rh = i->first; } } if (slot_mh == EQUIPMENT_SLOT_START && slot_oh == EQUIPMENT_SLOT_START && slot_rh == EQUIPMENT_SLOT_START) return false; if (!m_weightScales[playerclass][spec].info.id) return false; std::unordered_set mh_weapons; std::unordered_set oh_weapons; std::unordered_set r_weapons; switch (playerclass) { case CLASS_WARRIOR: { if (m_weightScales[playerclass][spec].info.name == "prot") { mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD, ITEM_SUBCLASS_WEAPON_AXE, ITEM_SUBCLASS_WEAPON_MACE, ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_FIST}; oh_weapons = {ITEM_SUBCLASS_ARMOR_SHIELD}; r_weapons = {ITEM_SUBCLASS_WEAPON_BOW, ITEM_SUBCLASS_WEAPON_CROSSBOW, ITEM_SUBCLASS_WEAPON_GUN}; } else { mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD2, ITEM_SUBCLASS_WEAPON_AXE2, ITEM_SUBCLASS_WEAPON_MACE2, ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_POLEARM}; r_weapons = {ITEM_SUBCLASS_WEAPON_BOW, ITEM_SUBCLASS_WEAPON_CROSSBOW, ITEM_SUBCLASS_WEAPON_GUN}; } break; } case CLASS_PALADIN: { if (m_weightScales[playerclass][spec].info.name == "prot") { mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD, ITEM_SUBCLASS_WEAPON_AXE, ITEM_SUBCLASS_WEAPON_MACE}; oh_weapons = {ITEM_SUBCLASS_ARMOR_SHIELD}; r_weapons = {ITEM_SUBCLASS_ARMOR_LIBRAM}; } else if (m_weightScales[playerclass][spec].info.name == "holy") { mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD, ITEM_SUBCLASS_WEAPON_AXE, ITEM_SUBCLASS_WEAPON_MACE}; oh_weapons = {ITEM_SUBCLASS_ARMOR_SHIELD, ITEM_SUBCLASS_ARMOR_MISC}; r_weapons = {ITEM_SUBCLASS_ARMOR_LIBRAM}; } else { mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD2, ITEM_SUBCLASS_WEAPON_AXE2, ITEM_SUBCLASS_WEAPON_MACE2, ITEM_SUBCLASS_WEAPON_POLEARM}; r_weapons = {ITEM_SUBCLASS_ARMOR_LIBRAM}; } break; } case CLASS_HUNTER: { mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD2, ITEM_SUBCLASS_WEAPON_AXE2, ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_POLEARM}; r_weapons = {ITEM_SUBCLASS_WEAPON_BOW, ITEM_SUBCLASS_WEAPON_CROSSBOW, ITEM_SUBCLASS_WEAPON_GUN}; break; } case CLASS_ROGUE: { mh_weapons = {ITEM_SUBCLASS_WEAPON_DAGGER}; oh_weapons = {ITEM_SUBCLASS_WEAPON_DAGGER}; r_weapons = {ITEM_SUBCLASS_WEAPON_BOW, ITEM_SUBCLASS_WEAPON_CROSSBOW, ITEM_SUBCLASS_WEAPON_GUN}; break; } case CLASS_PRIEST: { mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_MACE}; oh_weapons = {ITEM_SUBCLASS_ARMOR_MISC}; r_weapons = {ITEM_SUBCLASS_WEAPON_WAND}; break; } case CLASS_SHAMAN: { if (m_weightScales[playerclass][spec].info.name == "resto") { mh_weapons = {ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_AXE, ITEM_SUBCLASS_WEAPON_MACE, ITEM_SUBCLASS_WEAPON_FIST}; oh_weapons = {ITEM_SUBCLASS_ARMOR_MISC, ITEM_SUBCLASS_ARMOR_SHIELD}; r_weapons = {ITEM_SUBCLASS_ARMOR_TOTEM}; } else if (m_weightScales[playerclass][spec].info.name == "enhance") { mh_weapons = {ITEM_SUBCLASS_WEAPON_MACE2, ITEM_SUBCLASS_WEAPON_AXE2, ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_AXE, ITEM_SUBCLASS_WEAPON_MACE, ITEM_SUBCLASS_WEAPON_FIST}; oh_weapons = {ITEM_SUBCLASS_ARMOR_SHIELD}; r_weapons = {ITEM_SUBCLASS_ARMOR_TOTEM}; } else { mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF}; r_weapons = {ITEM_SUBCLASS_ARMOR_TOTEM}; } break; } case CLASS_MAGE: case CLASS_WARLOCK: { mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_SWORD}; oh_weapons = {ITEM_SUBCLASS_ARMOR_MISC}; r_weapons = {ITEM_SUBCLASS_WEAPON_WAND}; break; } case CLASS_DRUID: { if (m_weightScales[playerclass][spec].info.name == "feraltank") { mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_MACE2}; r_weapons = {ITEM_SUBCLASS_ARMOR_IDOL}; } else if (m_weightScales[playerclass][spec].info.name == "resto") { mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_FIST, ITEM_SUBCLASS_WEAPON_MACE}; oh_weapons = {ITEM_SUBCLASS_ARMOR_MISC}; r_weapons = {ITEM_SUBCLASS_ARMOR_IDOL}; } else if (m_weightScales[playerclass][spec].info.name == "feraldps") { mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_MACE2}; r_weapons = {ITEM_SUBCLASS_ARMOR_IDOL}; } else { mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF}; r_weapons = {ITEM_SUBCLASS_ARMOR_IDOL}; } break; } } if (slot_mh == EQUIPMENT_SLOT_MAINHAND) { return mh_weapons.find(proto->SubClass) != mh_weapons.end(); } if (slot_oh == EQUIPMENT_SLOT_OFFHAND) { return oh_weapons.find(proto->SubClass) != oh_weapons.end(); } if (slot_rh == EQUIPMENT_SLOT_RANGED) { return r_weapons.find(proto->SubClass) != r_weapons.end(); } return false; } bool RandomItemMgr::CanEquipArmor(uint8 clazz, uint32 level, ItemTemplate const* proto) { if (proto->InventoryType == INVTYPE_TABARD) return true; if ((clazz == CLASS_WARRIOR || clazz == CLASS_PALADIN || clazz == CLASS_SHAMAN) && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) return true; if ((clazz == CLASS_WARRIOR || clazz == CLASS_PALADIN) && level >= 40) { if (proto->SubClass != ITEM_SUBCLASS_ARMOR_PLATE && proto->InventoryType != INVTYPE_CLOAK) return false; } if (((clazz == CLASS_WARRIOR || clazz == CLASS_PALADIN) && level < 40) || ((clazz == CLASS_HUNTER || clazz == CLASS_SHAMAN) && level >= 40)) { if (proto->SubClass != ITEM_SUBCLASS_ARMOR_MAIL && proto->InventoryType != INVTYPE_CLOAK) return false; } if (((clazz == CLASS_HUNTER || clazz == CLASS_SHAMAN) && level < 40) || (clazz == CLASS_DRUID || clazz == CLASS_ROGUE)) { if (proto->SubClass != ITEM_SUBCLASS_ARMOR_LEATHER && proto->InventoryType != INVTYPE_CLOAK) return false; } if (proto->Quality <= ITEM_QUALITY_NORMAL) return true; uint8 sp = 0, ap = 0, tank = 0; for (uint8 j = 0; j < MAX_ITEM_PROTO_STATS; ++j) { // for ItemStatValue != 0 if (!proto->ItemStat[j].ItemStatValue) continue; AddItemStats(proto->ItemStat[j].ItemStatType, sp, ap, tank); } return CheckItemStats(clazz, sp, ap, tank); } bool RandomItemMgr::CanEquipWeapon(uint8 clazz, ItemTemplate const* proto) { switch (clazz) { case CLASS_PRIEST: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_WAND && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER) return false; break; case CLASS_MAGE: case CLASS_WARLOCK: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_WAND && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD) return false; break; case CLASS_WARRIOR: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) return false; break; case CLASS_PALADIN: case CLASS_DEATH_KNIGHT: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD) return false; break; case CLASS_SHAMAN: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) return false; break; case CLASS_DRUID: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM) return false; break; case CLASS_HUNTER: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM) return false; break; case CLASS_ROGUE: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE) return false; break; } return true; } void RandomItemMgr::BuildItemInfoCache() { //uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); //not used, line marked for removal. // load weightscales LOG_INFO("playerbots", "Loading weightscales info"); uint32 counter = 1; uint32 totalcount = 0; uint32 statcount = 0; uint32 curClass = CLASS_WARRIOR; PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_WEIGHTSCALES); if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt)) { do { Field* fields = result->Fetch(); uint32 id = fields[0].Get(); std::string name = fields[1].Get(); uint8 clazz = fields[2].Get(); if (clazz != curClass) { counter = 1; curClass = clazz; } WeightScale scale; scale.info.id = id; scale.info.name = name; m_weightScales[clazz][counter] = std::move(scale); counter++; totalcount++; } while (result->NextRow()); LOG_INFO("playerbots", "Loaded {} weightscale class specs", totalcount); } stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_WEIGHTSCALE_DATA); if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt)) { do { Field* fields = result->Fetch(); uint32 id = fields[0].Get(); std::string field = fields[1].Get(); uint32 weight = fields[2].Get(); WeightScaleStat stat; stat.stat = field; stat.weight = weight; for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls) { for (uint32 spec = 1; spec < 5; ++spec) { if (m_weightScales[cls][spec].info.id == id) m_weightScales[cls][spec].stats.push_back(std::move(stat)); } } statcount++; } while (result->NextRow()); LOG_INFO("playerbots", "Loaded {} weightscale stat weights", statcount); } if (m_weightScales[1].empty()) { LOG_ERROR("playerbots", "Error loading item weight scales"); return; } // vendor items LOG_INFO("playerbots", "Loading vendor item list..."); std::set vendorItems; vendorItems.clear(); if (QueryResult result = WorldDatabase.Query("SELECT item FROM npc_vendor")) { do { Field* fields = result->Fetch(); int32 entry = fields[0].Get(); if (entry <= 0) continue; vendorItems.insert(entry); } while (result->NextRow()); } LOG_INFO("playerbots", "Loaded {} vendor items...", vendorItems.size()); // calculate drop source LOG_INFO("playerbots", "Loading loot templates..."); DropMap* dropMap = new DropMap; if (CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates()) { for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr) { uint32 sEntry = itr->first; if (LootTemplateAccess const* lTemplateA = DropMapValue::GetLootTemplate(ObjectGuid::Create(sEntry, uint32(1)), LOOT_CORPSE)) for (auto const& lItem : lTemplateA->Entries) dropMap->insert(std::make_pair(lItem->itemid, sEntry)); } } if (GameObjectTemplateContainer const* gameobjects = sObjectMgr->GetGameObjectTemplates()) { for (auto const& itr : *gameobjects) { uint32 sEntry = itr.first; if (LootTemplateAccess const* lTemplateA = DropMapValue::GetLootTemplate( ObjectGuid::Create(sEntry, uint32(1)), LOOT_CORPSE)) for (auto const& lItem : lTemplateA->Entries) dropMap->insert(std::make_pair(lItem->itemid, sEntry)); } } LOG_INFO("playerbots", "Loaded {} loot templates...", dropMap->size()); ItemTemplateContainer const* itemTemplate = sObjectMgr->GetItemTemplateStore(); LOG_INFO("playerbots", "Calculating stat weights for {} items...", itemTemplate->size()); PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction(); for (auto const& itr : *itemTemplate) { ItemTemplate const* proto = &itr.second; if (!proto) continue; // skip test items if (strstr(proto->Name1.c_str(), "(Test)") || strstr(proto->Name1.c_str(), "(TEST)") || strstr(proto->Name1.c_str(), "(test)") || strstr(proto->Name1.c_str(), "(JEFFTEST)") || strstr(proto->Name1.c_str(), "Test ") || strstr(proto->Name1.c_str(), "Test") || strstr(proto->Name1.c_str(), "TEST") || strstr(proto->Name1.c_str(), "TEST ") || strstr(proto->Name1.c_str(), " TEST") || strstr(proto->Name1.c_str(), "2200 ") || strstr(proto->Name1.c_str(), "Deprecated ") || strstr(proto->Name1.c_str(), "Unused ") || strstr(proto->Name1.c_str(), "Monster ") || strstr(proto->Name1.c_str(), "[PH]") || strstr(proto->Name1.c_str(), "(OLD)") || strstr(proto->Name1.c_str(), "QR") || strstr(proto->Name1.c_str(), "zzOLD")) { itemForTest.insert(proto->ItemId); continue; } if (proto->Flags & ITEM_FLAG_DEPRECATED) { itemForTest.insert(proto->ItemId); continue; } // skip items with rank/rep requirements /*if (proto->RequiredHonorRank > 0 || proto->RequiredSkillRank > 0 || proto->RequiredCityRank > 0 || proto->RequiredReputationRank > 0) continue;*/ // if (proto->RequiredHonorRank > 0 || proto->RequiredSkillRank > 0 || proto->RequiredCityRank > 0) // continue; // // skip random enchant items // if (proto->RandomProperty) // continue; // // skip heirloom items // if (proto->Quality == ITEM_QUALITY_HEIRLOOM) // continue; // // check possible equip slots // EquipmentSlots slot = EQUIPMENT_SLOT_START; // for (std::map >::iterator i = viableSlots.begin(); // i != viableSlots.end(); ++i) // { // std::set slots = viableSlots[(EquipmentSlots)i->first]; // if (slots.find((InventoryType)proto->InventoryType) != slots.end()) // slot = i->first; // } // if (slot == EQUIPMENT_SLOT_START) // continue; // Init Item cache // ItemInfoEntry cacheInfo; // for (uint8 clazz = CLASS_WARRIOR; clazz < MAX_CLASSES; ++clazz) // { // // skip nonexistent classes // if (!((1 << (clazz - 1)) & CLASSMASK_ALL_PLAYABLE) || !sChrClassesStore.LookupEntry(clazz)) // continue; // // skip wrong classes // if ((proto->AllowableClass & (1 << (clazz - 1))) == 0) // continue; // for (uint32 spec = 1; spec < 5; ++spec) // { // if (!m_weightScales[clazz][spec].info.id) // continue; // // check possible armor for spec // if (m_weightScales) // if (proto->Class == ITEM_CLASS_ARMOR && ( // slot == EQUIPMENT_SLOT_HEAD || // slot == EQUIPMENT_SLOT_SHOULDERS || // slot == EQUIPMENT_SLOT_CHEST || // slot == EQUIPMENT_SLOT_WAIST || // slot == EQUIPMENT_SLOT_LEGS || // slot == EQUIPMENT_SLOT_FEET || // slot == EQUIPMENT_SLOT_WRISTS || // slot == EQUIPMENT_SLOT_HANDS) && // !ShouldEquipArmorForSpec(clazz, spec, proto)) // continue; // // check possible weapon for spec // if ((proto->Class == ITEM_CLASS_WEAPON || (proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD || // (proto->SubClass == ITEM_SUBCLASS_ARMOR_MISC && proto->InventoryType == INVTYPE_HOLDABLE))) && // !ShouldEquipWeaponForSpec(clazz, spec, proto)) // continue; // StatWeight statWeight; // statWeight.id = m_weightScales[clazz][spec].info.id; // uint32 statW = CalculateStatWeight(clazz, spec, proto); // // set stat weight = 1 for items that can be equipped but have no proper stats // statWeight.weight = statW ? statW : 1; // //statWeight.weight = statW; // // save item statWeight into ItemCache // cacheInfo.weights[statWeight.id] = statWeight.weight; // LOG_DEBUG("playerbots", "Item: {}, weight: {}, class: {}, spec: {}", proto->ItemId, // statWeight.weight, clazz, m_weightScales[clazz][spec].info.name); // } // } // cacheInfo.team = TEAM_NEUTRAL; // // check faction // if (proto->Flags2 & ITEM_FLAG2_FACTION_HORDE) // cacheInfo.team = TEAM_HORDE; // if (proto->Flags2 & ITEM_FLAG2_FACTION_ALLIANCE) // cacheInfo.team = TEAM_ALLIANCE; // if (cacheInfo.team == TEAM_NEUTRAL && proto->AllowableRace > 1 && proto->AllowableRace < 8388607) // { // if (FactionEntry const* faction = sFactionStore.LookupEntry(HORDE)) // if ((proto->AllowableRace & faction->BaseRepRaceMask[0]) != 0) // cacheInfo.team = TEAM_HORDE; // if (FactionEntry const* faction = sFactionStore.LookupEntry(ALLIANCE)) // if ((proto->AllowableRace & faction->BaseRepRaceMask[0]) != 0) // cacheInfo.team = TEAM_ALLIANCE; // } // if (cacheInfo.team < TEAM_NEUTRAL) // LOG_DEBUG("playerbots", "Item: {}, team (item): {}", proto->ItemId, cacheInfo.team == TEAM_ALLIANCE ? // "Alliance" : "Horde"); // // check min level // if (proto->RequiredLevel) // cacheInfo.minLevel = proto->RequiredLevel; // // check item source // if (proto->Flags & ITEM_FLAG_NO_DISENCHANT) // { // cacheInfo.source = ITEM_SOURCE_PVP; // LOG_DEBUG("playerbots", "Item: {}, source: PvP Reward", proto->ItemId); // } // // check quests // if (cacheInfo.source == ITEM_SOURCE_NONE) // { // std::vector questIds = GetQuestIdsForItem(proto->ItemId); // if (questIds.size()) // { // bool isAlly = false; // bool isHorde = false; // for (std::vector::iterator i = questIds.begin(); i != questIds.end(); ++i) // { // Quest const* quest = sObjectMgr->GetQuestTemplate(*i); // if (quest) // { // cacheInfo.source = ITEM_SOURCE_QUEST; // cacheInfo.sourceId = *i; // if (!cacheInfo.minLevel) // cacheInfo.minLevel = quest->GetMinLevel(); // // check quest team // if (cacheInfo.team == TEAM_NEUTRAL) // { // uint32 reqRace = quest->GetAllowableRaces(); // if (reqRace) // { // if ((reqRace & RACEMASK_ALLIANCE) != 0) // isAlly = true; // else if ((reqRace & RACEMASK_HORDE) != 0) // isHorde = true; // } // } // } // } // if (isAlly && isHorde) // cacheInfo.team = TEAM_NEUTRAL; // else if (isAlly) // cacheInfo.team = TEAM_ALLIANCE; // else if (isHorde) // cacheInfo.team = TEAM_HORDE; // LOG_DEBUG("playerbots", "Item: {}, team (quest): {}", proto->ItemId, cacheInfo.team == TEAM_ALLIANCE // ? "Alliance" : cacheInfo.team == TEAM_HORDE ? "Horde" : "Both"); LOG_DEBUG("playerbots", "Item: {}, // source: quest {}, minlevel: {}", proto->ItemId, cacheInfo.sourceId, cacheInfo.minLevel); // } // } // if (cacheInfo.minLevel) // LOG_DEBUG("playerbots", "Item: {}, minlevel: {}", proto->ItemId, cacheInfo.minLevel); // // check vendors // if (cacheInfo.source == ITEM_SOURCE_NONE) // { // for (std::set::iterator i = vendorItems.begin(); i != vendorItems.end(); ++i) // { // if (proto->ItemId == *i) // { // cacheInfo.source = ITEM_SOURCE_VENDOR; // LOG_DEBUG("playerbots", "Item: {} source: vendor", proto->ItemId); // break; // } // } // } // // check drops // std::vector creatures; // std::vector gameobjects; // auto range = dropMap->equal_range(itr.first); // for (auto iter = range.first; iter != range.second; ++iter) // { // if (iter->second > 0) // creatures.push_back(iter->second); // else // gameobjects.push_back(abs(iter->second)); // } // // check creature drop // if (cacheInfo.source == ITEM_SOURCE_NONE) // { // if (creatures.size()) // { // if (creatures.size() == 1) // { // cacheInfo.source = ITEM_SOURCE_DROP; // cacheInfo.sourceId = creatures.front(); // LOG_DEBUG("playerbots", "Item: {}, source: creature drop, ID: {}", proto->ItemId, // creatures.front()); // } // else // { // cacheInfo.source = ITEM_SOURCE_DROP; // LOG_DEBUG("playerbots", "Item: {}, source: creatures drop, number: {}", proto->ItemId, // creatures.size()); // } // } // } // // check gameobject drop // if (cacheInfo.source == ITEM_SOURCE_NONE || (cacheInfo.source == ITEM_SOURCE_DROP && !cacheInfo.sourceId)) // { // if (gameobjects.size()) // { // if (gameobjects.size() == 1) // { // cacheInfo.source = ITEM_SOURCE_DROP; // cacheInfo.sourceId = gameobjects.front(); // LOG_INFO("playerbots", "Item: {}, source: gameobject, ID: {}", proto->ItemId, // gameobjects.front()); // } // else // { // cacheInfo.source = ITEM_SOURCE_DROP; // LOG_INFO("playerbots", "Item: {}, source: gameobjects, number: {}", proto->ItemId, // gameobjects.size()); // } // } // } // // check faction // if (proto->RequiredReputationFaction > 0 && proto->RequiredReputationFaction != 35 && // proto->RequiredReputationRank < 15) // { // cacheInfo.repFaction = proto->RequiredReputationFaction; // cacheInfo.repRank = proto->RequiredReputationRank; // } // cacheInfo.quality = proto->Quality; // cacheInfo.itemId = proto->ItemId; // cacheInfo.slot = slot; // // save cache // PlayerbotsDatabasePreparedStatement* stmt = // PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_DEL_EQUIP_CACHE_NEW); stmt->SetData(0, proto->ItemId); // trans->Append(stmt); // stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_EQUIP_CACHE_NEW); // stmt->SetData(0, cacheInfo.itemId); // stmt->SetData(1, cacheInfo.quality); // stmt->SetData(2, cacheInfo.slot); // stmt->SetData(3, cacheInfo.source); // stmt->SetData(4, cacheInfo.sourceId); // stmt->SetData(5, cacheInfo.team); // stmt->SetData(6, cacheInfo.repFaction); // stmt->SetData(7, cacheInfo.repRank); // stmt->SetData(8, cacheInfo.minLevel); // for (uint8 i = 1; i <= MAX_STAT_SCALES; ++i) // { // if (cacheInfo.weights[i]) // stmt->SetData(8 + i, cacheInfo.weights[i]); // else // stmt->SetData(8 + i, 0); // } // trans->Append(stmt); // itemInfoCache[cacheInfo.itemId] = std::move(cacheInfo); } PlayerbotsDatabase.CommitTransaction(trans); } uint32 RandomItemMgr::CalculateStatWeight(uint8 playerclass, uint8 spec, ItemTemplate const* proto) { uint32 statWeight = 0; bool isCasterItem = false; bool isAttackItem = false; bool noCaster = (Classes)playerclass == CLASS_WARRIOR || (Classes)playerclass == CLASS_ROGUE || (Classes)playerclass == CLASS_DEATH_KNIGHT || (Classes)playerclass == CLASS_HUNTER; uint32 spellPower = 0; uint32 spellHeal = 0; uint32 attackPower = 0; bool hasInt = false; bool hasMana = !((Classes)playerclass == CLASS_WARRIOR || (Classes)playerclass == CLASS_ROGUE || (Classes)playerclass == CLASS_DEATH_KNIGHT); if (proto->SubClass == ITEM_SUBCLASS_ARMOR_LIBRAM || proto->SubClass == ITEM_SUBCLASS_ARMOR_IDOL || proto->SubClass == ITEM_SUBCLASS_ARMOR_TOTEM || proto->SubClass == ITEM_SUBCLASS_ARMOR_SIGIL) return (uint32)(proto->Quality + proto->ItemLevel); // check basic item stats int32 basicStatsWeight = 0; for (uint8 j = 0; j < MAX_ITEM_PROTO_STATS; ++j) { uint32 statType; int32 val; std::string weightName; // if (j >= proto->StatsCount) // continue; statType = proto->ItemStat[j].ItemStatType; val = proto->ItemStat[j].ItemStatValue; if (val == 0) continue; for (std::map::iterator i = weightStatLink.begin(); i != weightStatLink.end(); ++i) { uint32 modd = i->second; if (modd == statType) { weightName = i->first; break; } } if (weightName.empty()) continue; uint32 singleStat = CalculateSingleStatWeight(playerclass, spec, weightName, val); basicStatsWeight += singleStat; if (val) { if (weightName == "int" && !noCaster) isCasterItem = true; if (weightName == "int") hasInt = true; if (weightName == "splpwr") isCasterItem = true; if (weightName == "str") isAttackItem = true; if (weightName == "agi") isAttackItem = true; if (weightName == "atkpwr") isAttackItem = true; } } // check armor & block statWeight += CalculateSingleStatWeight(playerclass, spec, "armor", proto->Armor); statWeight += CalculateSingleStatWeight(playerclass, spec, "block", proto->Block); // check weapon dps if (proto->IsWeaponVellum()) { //WeaponAttackType attType = BASE_ATTACK; //not used, line marked for removal. uint32 dps = 0; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; i++) { if (proto->Damage[i].DamageMax == 0) break; dps = (proto->Damage[i].DamageMin + proto->Damage[i].DamageMax) / (float)(proto->Delay / 1000.0f) / 2; if (dps) { if (proto->IsRangedWeapon()) statWeight += CalculateSingleStatWeight(playerclass, spec, "rgddps", dps); else statWeight += CalculateSingleStatWeight(playerclass, spec, "mledps", dps); } } } // check item spells for (const auto& spellData : proto->Spells) { // no spell if (!spellData.SpellId) continue; // apply only at-equip spells if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_EQUIP) continue; // check if it is valid spell SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellData.SpellId); if (!spellInfo) continue; uint32 spellDamage = 0; uint32 spellHealing = 0; for (uint32 j = 0; j < MAX_SPELL_EFFECTS; j++) { if (spellInfo->Effects[j].Effect == SPELL_EFFECT_APPLY_AURA && spellInfo->Effects[j].BasePoints) { // spell damage // SPELL_AURA_MOD_DAMAGE_DONE if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_DAMAGE_DONE) { spellDamage = spellInfo->Effects[j].BasePoints + 1; } // spell healing // SPELL_AURA_MOD_HEALING_DONE if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_HEALING_DONE) { spellHealing = spellInfo->Effects[j].BasePoints + 1; } // check spell power if (spellDamage && spellDamage == spellHealing) { isCasterItem = true; spellPower += CalculateSingleStatWeight(playerclass, spec, "splpwr", spellDamage); } // spell hit rating (pre tbc) // SPELL_AURA_MOD_SPELL_HIT_CHANCE if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_SPELL_HIT_CHANCE) { statWeight += CalculateSingleStatWeight(playerclass, spec, "spellhitrtng", spellInfo->Effects[j].BasePoints + 1); } // spell crit rating (pre tbc) // SPELL_AURA_MOD_SPELL_CRIT_CHANCE if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_SPELL_CRIT_CHANCE) { statWeight += CalculateSingleStatWeight(playerclass, spec, "spellcritstrkrtng", spellInfo->Effects[j].BasePoints + 1); } // spell penetration // SPELL_AURA_MOD_TARGET_RESISTANCE if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_TARGET_RESISTANCE) { // check if magic type if (spellInfo->Effects[j].MiscValue == SPELL_SCHOOL_MASK_SPELL) statWeight += CalculateSingleStatWeight(playerclass, spec, "spellpenrtng", abs(spellInfo->Effects[j].BasePoints + 1)); } // check attack power if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_ATTACK_POWER) { isAttackItem = true; attackPower += CalculateSingleStatWeight(playerclass, spec, "atkpwr", spellInfo->Effects[j].BasePoints + 1); } // check ranged ap // SPELL_AURA_MOD_RANGED_ATTACK_POWER if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_RANGED_ATTACK_POWER) { isAttackItem = true; attackPower += CalculateSingleStatWeight(playerclass, spec, "atkpwr", spellInfo->Effects[j].BasePoints + 1); } // check block if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_SHIELD_BLOCKVALUE) { statWeight += CalculateSingleStatWeight(playerclass, spec, "block", spellInfo->Effects[j].BasePoints + 1); } // block chance // SPELL_AURA_MOD_BLOCK_PERCENT if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_BLOCK_PERCENT) { statWeight += CalculateSingleStatWeight(playerclass, spec, "blockrtng", spellInfo->Effects[j].BasePoints + 1); } // armor penetration // SPELL_AURA_MOD_TARGET_RESISTANCE if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_TARGET_RESISTANCE) { // check if physical type if (spellInfo->Effects[j].MiscValue == SPELL_SCHOOL_MASK_NORMAL) statWeight += CalculateSingleStatWeight(playerclass, spec, "armorpenrtng", abs(spellInfo->Effects[j].BasePoints + 1)); } // hit rating (pre tbc) // SPELL_AURA_MOD_HIT_CHANCE if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_HIT_CHANCE) { statWeight += CalculateSingleStatWeight(playerclass, spec, "hitrtng", spellInfo->Effects[j].BasePoints + 1); } // crit rating (pre tbc) // SPELL_AURA_MOD_HIT_CHANCE if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_CRIT_PCT) { statWeight += CalculateSingleStatWeight(playerclass, spec, "critstrkrtng", spellInfo->Effects[j].BasePoints + 1); } // check defense SPELL_AURA_MOD_SKILL // check block // if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_SKILL) //{ // statWeight += CalculateSingleStatWeight(playerclass, spec, "block", // spellInfo->Effects[j].BasePoints + 1); //} // ratings // enum CombatRating if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_RATING) { for (uint32 rating = 0; rating < MAX_COMBAT_RATING; ++rating) { if (spellInfo->Effects[j].MiscValue & (1 << rating)) { int32 val = spellInfo->Effects[j].BasePoints + 1; std::string weightName; for (std::map::iterator i = weightRatingLink.begin(); i != weightRatingLink.end(); ++i) { uint32 modd = i->second; if (modd == rating) { weightName = i->first; break; } } if (weightName.empty()) continue; statWeight += CalculateSingleStatWeight(playerclass, spec, weightName, val); } } } // mana regen // SPELL_AURA_MOD_POWER_REGEN if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_POWER_REGEN) { statWeight += CalculateSingleStatWeight(playerclass, spec, "manargn", spellInfo->Effects[j].BasePoints + 1); } } } } // check for caster item if (isCasterItem || hasInt) { if ((!hasMana || noCaster) && spellPower) return 0; if (!hasMana && hasInt) return 0; if ((!hasMana && noCaster && playerclass != CLASS_PALADIN) && spellPower > attackPower) return 0; bool playerCaster = false; for (std::vector::iterator i = m_weightScales[playerclass][spec].stats.begin(); i != m_weightScales[playerclass][spec].stats.end(); ++i) { if (i->stat == "splpwr" || i->stat == "int" || i->stat == "manargn") { playerCaster = true; } } if (!playerCaster) return 0; } // check for caster item if (isAttackItem) { if (hasMana && !noCaster && !(hasInt || spellPower)) return 0; // if (!noCaster && attackPower) // return 0; bool playerAttacker = false; for (std::vector::iterator i = m_weightScales[playerclass][spec].stats.begin(); i != m_weightScales[playerclass][spec].stats.end(); ++i) { if (i->stat == "str" || i->stat == "agi" || i->stat == "atkpwr" || i->stat == "mledps" || i->stat == "rgddps") { playerAttacker = true; } } if (!playerAttacker) return 0; } statWeight += spellPower; statWeight += spellHeal; statWeight += attackPower; // handle negative stats if (basicStatsWeight < 0 && (abs(basicStatsWeight) >= statWeight)) statWeight = 0; else statWeight += basicStatsWeight; return statWeight; } uint32 RandomItemMgr::CalculateSingleStatWeight(uint8 playerclass, uint8 spec, std::string stat, uint32 value) { uint32 statWeight = 0; for (std::vector::iterator i = m_weightScales[playerclass][spec].stats.begin(); i != m_weightScales[playerclass][spec].stats.end(); ++i) { if (stat == i->stat) { statWeight = i->weight * value; // if (statWeight) // LOG_INFO("playerbots", "stat: {}, val: {}, weight: {}, total: {}, class: {}, spec: {}", // stat, value, i->weight, statWeight, playerclass, m_weightScales[playerclass][spec].info.name); return statWeight; } } return statWeight; } uint32 RandomItemMgr::GetQuestIdForItem(uint32 itemId) { bool isQuest = false; uint32 questId = 0; ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates(); for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i) { Quest const* quest = i->second; uint32 rewItemCount = quest->GetRewItemsCount(); for (uint32 i = 0; i < rewItemCount; ++i) { if (!quest->RewardItemId[i]) continue; if (quest->RewardItemId[i] == itemId) { isQuest = true; questId = quest->GetQuestId(); break; } } uint32 rewChocieItemCount = quest->GetRewChoiceItemsCount(); for (uint32 i = 0; i < rewChocieItemCount; ++i) { if (!quest->RewardChoiceItemId[i]) continue; if (quest->RewardChoiceItemId[i] == itemId) { isQuest = true; questId = quest->GetQuestId(); break; } } if (isQuest) break; } return questId; } std::vector RandomItemMgr::GetQuestIdsForItem(uint32 itemId) { std::vector questIds; ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates(); for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i) { Quest const* quest = i->second; uint32 rewItemCount = quest->GetRewItemsCount(); for (uint32 i = 0; i < rewItemCount; ++i) { if (!quest->RewardItemId[i]) continue; if (quest->RewardItemId[i] == itemId) { questIds.push_back(quest->GetQuestId()); break; } } uint32 rewChocieItemCount = quest->GetRewChoiceItemsCount(); for (uint32 i = 0; i < rewChocieItemCount; ++i) { if (!quest->RewardChoiceItemId[i]) continue; if (quest->RewardChoiceItemId[i] == itemId) { questIds.push_back(quest->GetQuestId()); break; } } } return std::move(questIds); } uint32 RandomItemMgr::GetUpgrade(Player* player, std::string spec, uint8 slot, uint32 quality, uint32 itemId) { if (!player) return 0; // get old item statWeight uint32 oldStatWeight = 0; uint32 specId = 0; uint32 closestUpgrade = 0; uint32 closestUpgradeWeight = 0; std::vector classspecs; for (uint32 specNum = 1; specNum < 5; ++specNum) { if (!m_weightScales[player->getClass()][specNum].info.id) continue; classspecs.push_back(m_weightScales[player->getClass()][specNum].info.id); if (m_weightScales[player->getClass()][specNum].info.name == spec) specId = m_weightScales[player->getClass()][specNum].info.id; } if (!specId) return 0; if (itemId && itemInfoCache.find(itemId) != itemInfoCache.end()) { oldStatWeight = itemInfoCache[itemId].weights[specId]; if (oldStatWeight) LOG_INFO("playerbots", "Old Item: {}, weight: {}", itemId, oldStatWeight); else LOG_INFO("playerbots", "Old item has no stat weight"); } for (std::map::iterator i = itemInfoCache.begin(); i != itemInfoCache.end(); ++i) { ItemInfoEntry& info = i->second; // skip useless items if (info.weights[specId] == 0) continue; // skip higher lvl if (info.minLevel > player->GetLevel()) continue; // skip too low level if (info.minLevel < (player->GetLevel() - 10)) continue; // skip wrong team if (info.team != TEAM_NEUTRAL && info.team != player->GetTeamId()) continue; // skip wrong slot if ((EquipmentSlots)info.slot != (EquipmentSlots)slot) continue; // skip higher quality if (quality && info.quality != quality) continue; // skip worse items if (info.weights[specId] <= oldStatWeight) continue; // skip items that only fit in slot, but not stats if (!itemId && info.weights[specId] == 1 && player->GetLevel() > 40) continue; // skip quest items if (info.source == ITEM_SOURCE_QUEST) { if (player->GetQuestRewardStatus(info.sourceId) != QUEST_STATUS_COMPLETE) continue; } // skip no stats trinkets if (info.weights[specId] == 1 && info.slot == EQUIPMENT_SLOT_NECK || info.slot == EQUIPMENT_SLOT_TRINKET1 || info.slot == EQUIPMENT_SLOT_TRINKET2 || info.slot == EQUIPMENT_SLOT_FINGER1 || info.slot == EQUIPMENT_SLOT_FINGER2) continue; // check if item stat score is the best among class specs uint32 bestSpecId = 0; uint32 bestSpecScore = 0; for (std::vector::iterator i = classspecs.begin(); i != classspecs.end(); ++i) { if (info.weights[*i] > bestSpecScore) { bestSpecId = *i; bestSpecScore = info.weights[specId]; } } if (bestSpecId && bestSpecId != specId && player->GetLevel() > 40) return 0; if (!closestUpgrade) { closestUpgrade = info.itemId; closestUpgradeWeight = info.weights[specId]; } // pick closest upgrade if (info.weights[specId] < closestUpgradeWeight) { closestUpgrade = info.itemId; closestUpgradeWeight = info.weights[specId]; } } if (closestUpgrade) LOG_INFO("playerbots", "New Item: {}, weight: {}", closestUpgrade, closestUpgradeWeight); return closestUpgrade; } std::vector RandomItemMgr::GetUpgradeList(Player* player, std::string spec, uint8 slot, uint32 quality, uint32 itemId, uint32 amount) { std::vector listItems; if (!player) return std::move(listItems); // get old item statWeight uint32 oldStatWeight = 0; uint32 specId = 0; uint32 closestUpgrade = 0; uint32 closestUpgradeWeight = 0; std::vector classspecs; for (uint32 specNum = 1; specNum < 5; ++specNum) { if (!m_weightScales[player->getClass()][specNum].info.id) continue; classspecs.push_back(m_weightScales[player->getClass()][specNum].info.id); if (m_weightScales[player->getClass()][specNum].info.name == spec) specId = m_weightScales[player->getClass()][specNum].info.id; } if (!specId) return std::move(listItems); if (itemId && itemInfoCache.find(itemId) != itemInfoCache.end()) { oldStatWeight = itemInfoCache[itemId].weights[specId]; if (oldStatWeight) LOG_INFO("playerbots", "Old Item: {}, weight: {}", itemId, oldStatWeight); else LOG_INFO("playerbots", "Old item has no stat weight"); } for (std::map::iterator i = itemInfoCache.begin(); i != itemInfoCache.end(); ++i) { ItemInfoEntry& info = i->second; // skip useless items if (info.weights[specId] == 0) continue; // skip higher lvl if (info.minLevel > player->GetLevel()) continue; // skip too low level if ((int32)info.minLevel < (int32)(player->GetLevel() - 20)) continue; // skip wrong team if (info.team != TEAM_NEUTRAL && info.team != player->GetTeamId()) continue; // skip wrong slot if ((EquipmentSlots)info.slot != (EquipmentSlots)slot) continue; // skip higher quality if (quality && info.quality != quality) continue; // skip worse items if (info.weights[specId] <= oldStatWeight) continue; // skip items that only fit in slot, but not stats if (!itemId && info.weights[specId] == 1 && player->GetLevel() > 40) continue; // skip quest items if (info.source == ITEM_SOURCE_QUEST) { if (player->GetQuestRewardStatus(info.sourceId) != QUEST_STATUS_COMPLETE) continue; } // skip no stats trinkets if (info.weights[specId] < 2 && (info.slot == EQUIPMENT_SLOT_NECK || info.slot == EQUIPMENT_SLOT_TRINKET1 || info.slot == EQUIPMENT_SLOT_TRINKET2 || info.slot == EQUIPMENT_SLOT_FINGER1 || info.slot == EQUIPMENT_SLOT_FINGER2)) continue; // if (player->GetLevel() >= 40) //{ // // check if item stat score is the best among class specs // uint32 bestSpecId = 0; // uint32 bestSpecScore = 0; // for (vector::iterator i = classspecs.begin(); i != classspecs.end(); ++i) // { // if (info->weights[*i] > bestSpecScore) // { // bestSpecId = *i; // bestSpecScore = info->weights[specId]; // } // } // if (bestSpecId && bestSpecId != specId) // continue; //} listItems.push_back(info.itemId); // continue; // pick closest upgrade if (info.weights[specId] > closestUpgradeWeight) { closestUpgrade = info.itemId; closestUpgradeWeight = info.weights[specId]; } } if (listItems.size()) LOG_INFO("playerbots", "New Items: {}, Old item:%d, New items max: {}", listItems.size(), oldStatWeight, closestUpgradeWeight); return std::move(listItems); } bool RandomItemMgr::HasStatWeight(uint32 itemId) { auto itr = itemInfoCache.find(itemId); return itr != itemInfoCache.end(); } uint32 RandomItemMgr::GetMinLevelFromCache(uint32 itemId) { auto itr = itemInfoCache.find(itemId); if (itr == itemInfoCache.end()) return 0; return itr->second.minLevel; } uint32 RandomItemMgr::GetStatWeight(Player* player, uint32 itemId) { if (!player || !itemId) return 0; auto itr = itemInfoCache.find(itemId); if (itr == itemInfoCache.end()) return 0; uint32 statWeight = 0; uint32 specId = 0; std::vector classspecs; std::string const& specName = AiFactory::GetPlayerSpecName(player); if (specName.empty()) return 0; for (uint32 specNum = 1; specNum < 5; ++specNum) { if (!m_weightScales[player->getClass()][specNum].info.id) continue; classspecs.push_back(m_weightScales[player->getClass()][specNum].info.id); if (m_weightScales[player->getClass()][specNum].info.name == specName) specId = m_weightScales[player->getClass()][specNum].info.id; if (m_weightScales[player->getClass()][specNum].info.name == specName) { specId = specNum; break; } } if (!specId) return 0; statWeight = itr->second.weights[specId]; return statWeight; } uint32 RandomItemMgr::GetLiveStatWeight(Player* player, uint32 itemId) { if (!player || !itemId) return 0; auto itr = itemInfoCache.find(itemId); if (itr == itemInfoCache.end()) return 0; uint32 statWeight = 0; uint32 specId = 0; std::vector classspecs; std::string const& specName = AiFactory::GetPlayerSpecName(player); if (specName.empty()) return 0; for (uint32 specNum = 1; specNum < 5; ++specNum) { if (!m_weightScales[player->getClass()][specNum].info.id) continue; // for bestSpec check // classspecs.push_back(m_weightScales[player->getClass()][specNum].info.id); if (m_weightScales[player->getClass()][specNum].info.name == specName) specId = m_weightScales[player->getClass()][specNum].info.id; } if (!specId) return 0; statWeight = itr->second.weights[specId]; // skip higher lvl if (itr->second.minLevel > player->GetLevel()) return 0; // skip too low level // if ((int32)info->minLevel < (int32)(player->GetLevel() - 20)) // return 0; // skip wrong team if (itr->second.team != TEAM_NEUTRAL && itr->second.team != player->GetTeamId()) return 0; // skip quest items if (itr->second.source == ITEM_SOURCE_QUEST && itr->second.sourceId) { if (player->GetQuestRewardStatus(itr->second.sourceId) != QUEST_STATUS_COMPLETE) return 0; } // skip pvp items if (itr->second.source == ITEM_SOURCE_PVP) { if (!player->GetHonorPoints() && !player->GetArenaPoints()) return 0; } // skip no stats trinkets if (itr->second.weights[specId] == 1 && (itr->second.slot == EQUIPMENT_SLOT_NECK || itr->second.slot == EQUIPMENT_SLOT_TRINKET1 || itr->second.slot == EQUIPMENT_SLOT_TRINKET2 || itr->second.slot == EQUIPMENT_SLOT_FINGER1 || itr->second.slot == EQUIPMENT_SLOT_FINGER2)) return 0; // skip items that only fit in slot, but not stats if (!itemId && itr->second.weights[specId] == 1 && player->GetLevel() > 20) return 0; // check if item stat score is the best among class specs /*uint32 bestSpecId = 0; uint32 bestSpecScore = 0; for (vector::iterator i = classspecs.begin(); i != classspecs.end(); ++i) { if (itemCache[itemId]->weights[*i] > bestSpecScore && itemCache[itemId]->weights[*i] > 1) { bestSpecId = *i; bestSpecScore = itemCache[itemId]->weights[specId]; } } if (bestSpecId && bestSpecId != specId && player->GetLevel() >= 60) return 0;*/ return statWeight; } void RandomItemMgr::BuildEquipCache() { uint32 maxLevel = sPlayerbotAIConfig->randomBotMaxLevel; if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_EQUIP_CACHE); if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt)) { LOG_INFO("server.loading", "Loading equipment cache for {} classes, {} levels, {} slots, {} quality from {} items", MAX_CLASSES, maxLevel, EQUIPMENT_SLOT_END, ITEM_QUALITY_ARTIFACT, itemTemplates->size()); uint32 count = 0; do { Field* fields = result->Fetch(); uint8 clazz = fields[0].Get(); uint32 level = fields[1].Get(); uint8 slot = fields[2].Get(); uint32 quality = fields[3].Get(); uint32 itemId = fields[4].Get(); BotEquipKey key(level, clazz, slot, quality); equipCache[key].push_back(itemId); ++count; } while (result->NextRow()); LOG_INFO("playerbots", "Equipment cache loaded from {} records", count); } else { uint64 total = MAX_CLASSES * maxLevel * EQUIPMENT_SLOT_END * ITEM_QUALITY_ARTIFACT; LOG_INFO("server.loading", "Building equipment cache for {} classes, {} levels, {} slots, {} quality from {} items ({} total)", MAX_CLASSES, maxLevel, EQUIPMENT_SLOT_END, ITEM_QUALITY_ARTIFACT, itemTemplates->size(), total); for (uint8 class_ = CLASS_WARRIOR; class_ < MAX_CLASSES; ++class_) { // skip nonexistent classes if (!((1 << (class_ - 1)) & CLASSMASK_ALL_PLAYABLE) || !sChrClassesStore.LookupEntry(class_)) continue; for (uint32 level = 1; level <= maxLevel; ++level) { for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) { for (uint32 quality = ITEM_QUALITY_POOR; quality <= ITEM_QUALITY_ARTIFACT; ++quality) { BotEquipKey key(level, class_, slot, quality); RandomItemList items; for (auto const& itr : *itemTemplates) { ItemTemplate const* proto = &itr.second; if (!proto) continue; if (proto->Class != ITEM_CLASS_WEAPON && proto->Class != ITEM_CLASS_ARMOR && proto->Class != ITEM_CLASS_CONTAINER && proto->Class != ITEM_CLASS_PROJECTILE) continue; if (!CanEquipItem(key, proto)) continue; if (proto->Class == ITEM_CLASS_ARMOR && (slot == EQUIPMENT_SLOT_HEAD || slot == EQUIPMENT_SLOT_SHOULDERS || slot == EQUIPMENT_SLOT_CHEST || slot == EQUIPMENT_SLOT_WAIST || slot == EQUIPMENT_SLOT_LEGS || slot == EQUIPMENT_SLOT_FEET || slot == EQUIPMENT_SLOT_WRISTS || slot == EQUIPMENT_SLOT_HANDS) && !CanEquipArmor(key.clazz, key.level, proto)) continue; if (proto->Class == ITEM_CLASS_WEAPON && !CanEquipWeapon(key.clazz, proto)) continue; if (slot == EQUIPMENT_SLOT_OFFHAND && key.clazz == CLASS_ROGUE && proto->Class != ITEM_CLASS_WEAPON) continue; items.push_back(itr.first); PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_EQUIP_CACHE); stmt->SetData(0, class_); stmt->SetData(1, level); stmt->SetData(2, slot); stmt->SetData(3, quality); stmt->SetData(4, proto->ItemId); PlayerbotsDatabase.Execute(stmt); } equipCache[key] = items; LOG_DEBUG("playerbots", "Equipment cache for class: {}, level {}, slot {}, quality {}: {} items", class_, level, slot, quality, items.size()); } } } } LOG_INFO("server.loading", "Equipment cache saved to DB"); } } void RandomItemMgr::BuildEquipCacheNew() { LOG_INFO("playerbots", "Loading equipments cache..."); std::unordered_set questItemIds; ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates(); for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i) { //uint32 questId = i->first; //not used in this scope, line marked for removal. Quest const* quest = i->second; if (quest->IsRepeatable()) continue; if (quest->GetQuestLevel() <= 0) continue; if (quest->GetRequiredClasses()) continue; for (int j = 0; j < quest->GetRewChoiceItemsCount(); j++) if (uint32 itemId = quest->RewardChoiceItemId[j]) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (proto->Class != ITEM_CLASS_WEAPON && proto->Class != ITEM_CLASS_ARMOR) continue; int requiredLevel = std::max((int)proto->RequiredLevel, quest->GetQuestLevel()); equipCacheNew[requiredLevel][proto->InventoryType].push_back(itemId); questItemIds.insert(itemId); } for (int j = 0; j < quest->GetRewItemsCount(); j++) if (uint32 itemId = quest->RewardItemId[j]) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (proto->Class != ITEM_CLASS_WEAPON && proto->Class != ITEM_CLASS_ARMOR) continue; int requiredLevel = std::max((int)proto->RequiredLevel, quest->GetQuestLevel()); equipCacheNew[requiredLevel][proto->InventoryType].push_back(itemId); questItemIds.insert(itemId); } } ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); for (auto const& itr : *itemTemplates) { ItemTemplate const* proto = &itr.second; if (!proto) continue; uint32 itemId = proto->ItemId; if (questItemIds.find(itemId) != questItemIds.end()) continue; if (IsTestItem(itemId)) { continue; } if (itemId == 22784) { // Sunwell Orb continue; } equipCacheNew[proto->RequiredLevel][proto->InventoryType].push_back(itemId); } } RandomItemList RandomItemMgr::Query(uint32 level, uint8 clazz, uint8 slot, uint32 quality) { // return equipCache[key]; BotEquipKey key(level, clazz, slot, quality); RandomItemList items; ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); for (auto const& itr : *itemTemplates) { ItemTemplate const* proto = &itr.second; if (!proto) continue; if (proto->Class != ITEM_CLASS_WEAPON && proto->Class != ITEM_CLASS_ARMOR && proto->Class != ITEM_CLASS_CONTAINER && proto->Class != ITEM_CLASS_PROJECTILE) continue; if (!CanEquipItem(key, proto)) continue; if (proto->Class == ITEM_CLASS_ARMOR && (slot == EQUIPMENT_SLOT_HEAD || slot == EQUIPMENT_SLOT_SHOULDERS || slot == EQUIPMENT_SLOT_CHEST || slot == EQUIPMENT_SLOT_WAIST || slot == EQUIPMENT_SLOT_LEGS || slot == EQUIPMENT_SLOT_FEET || slot == EQUIPMENT_SLOT_WRISTS || slot == EQUIPMENT_SLOT_HANDS) && !CanEquipArmor(key.clazz, key.level, proto)) continue; if (proto->Class == ITEM_CLASS_WEAPON && !CanEquipWeapon(key.clazz, proto)) continue; if (slot == EQUIPMENT_SLOT_OFFHAND && key.clazz == CLASS_ROGUE && proto->Class != ITEM_CLASS_WEAPON) continue; items.push_back(itr.first); } return items; } void RandomItemMgr::BuildAmmoCache() { uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); LOG_INFO("server.loading", "Building ammo cache for {} levels", maxLevel); uint32 counter = 0; for (uint32 level = 1; level <= maxLevel; level += 1) { for (uint32 subClass = ITEM_SUBCLASS_ARROW; subClass <= ITEM_SUBCLASS_BULLET; subClass++) { QueryResult results = WorldDatabase.Query( "SELECT entry FROM item_template WHERE class = {} AND subclass = {} AND RequiredLevel <= {} AND duration = 0 " "AND (Flags & 16) = 0 AND dmg_min1 != 0 AND RequiredLevel != 0 " "ORDER BY stackable DESC, ItemLevel DESC", ITEM_CLASS_PROJECTILE, subClass, level); if (!results) continue; do { Field* fields = results->Fetch(); uint32 entry = fields[0].Get(); ammoCache[level][subClass].push_back(entry); ++counter; } while (results->NextRow()); } } LOG_INFO("server.loading", "Cached {} ammo", counter); // TEST } std::vector RandomItemMgr::GetAmmo(uint32 level, uint32 subClass) { return ammoCache[level][subClass]; } void RandomItemMgr::BuildPotionCache() { uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); LOG_INFO("playerbots", "Building potion cache for {} levels", maxLevel); ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); uint32 counter = 0; for (uint32 level = 1; level <= maxLevel; level++) { uint32 effects[] = {SPELL_EFFECT_HEAL, SPELL_EFFECT_ENERGIZE}; for (uint8 i = 0; i < 2; ++i) { uint32 effect = effects[i]; for (auto const& itr : *itemTemplates) { ItemTemplate const* proto = &itr.second; if (!proto) continue; if (proto->Class != ITEM_CLASS_CONSUMABLE || (proto->SubClass != ITEM_SUBCLASS_POTION && proto->SubClass != ITEM_SUBCLASS_FLASK) || proto->Bonding != NO_BIND) continue; uint32 requiredLevel = proto->RequiredLevel; if (requiredLevel > level || (level > 13 && requiredLevel < level - 13)) continue; if (proto->RequiredSkill) continue; if (proto->Area || proto->Map || proto->RequiredCityRank || proto->RequiredHonorRank) continue; if (proto->Duration & 0x80000000) continue; if (proto->AllowableClass != -1) continue; bool hybrid = false; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[0].SpellId); if (!spellInfo) continue; // do not accept hybrid potion for (uint8 i = 1; i < 3; i++) { if (spellInfo->Effects[i].Effect != 0) { hybrid = true; break; } } if (hybrid) continue; if (spellInfo->Effects[0].Effect == effect) potionCache[level][effect].push_back(itr.first); } } } for (uint32 level = 1; level <= maxLevel; level++) { uint32 effects[] = {SPELL_EFFECT_HEAL, SPELL_EFFECT_ENERGIZE}; for (uint8 i = 0; i < 2; ++i) { uint32 effect = effects[i]; uint32 size = potionCache[level][effect].size(); counter += size; } } LOG_INFO("playerbots", "Cached {} potions", counter); } void RandomItemMgr::BuildFoodCache() { uint32 maxLevel = sPlayerbotAIConfig->randomBotMaxLevel; if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); LOG_INFO("server.loading", "Building food cache for {} levels", maxLevel); ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); uint32 counter = 0; for (uint32 level = 1; level <= maxLevel + 1; level += 10) { uint32 categories[] = {11, 59}; for (int i = 0; i < 2; ++i) { uint32 category = categories[i]; for (auto const& itr : *itemTemplates) { ItemTemplate const* proto = &itr.second; if (!proto) continue; if (proto->Class != ITEM_CLASS_CONSUMABLE || (proto->SubClass != ITEM_SUBCLASS_FOOD && proto->SubClass != ITEM_SUBCLASS_CONSUMABLE) || (proto->Spells[0].SpellCategory != category) || proto->Bonding != NO_BIND) continue; if (proto->RequiredLevel && (proto->RequiredLevel > level || proto->RequiredLevel < level - 10)) continue; if (proto->RequiredSkill) continue; if (proto->Area || proto->Map || proto->RequiredCityRank || proto->RequiredHonorRank) continue; if (proto->Duration & 0x80000000) continue; foodCache[level / 10][category].push_back(itr.first); } } } for (uint32 level = 1; level <= maxLevel + 1; level += 10) { uint32 categories[] = {11, 59}; for (uint8 i = 0; i < 2; ++i) { uint32 category = categories[i]; uint32 size = foodCache[level / 10][category].size(); ++counter; LOG_DEBUG("server.loading", "Food cache for level={}, category={}: {} items", level, category, size); } } LOG_INFO("server.loading", "Cached {} types of food", counter); } uint32 RandomItemMgr::GetRandomPotion(uint32 level, uint32 effect) { const std::vector &potions = potionCache[level][effect]; if (potions.empty()) return 0; return potions[urand(0, potions.size() - 1)]; } uint32 RandomItemMgr::GetFood(uint32 level, uint32 category) { std::initializer_list items; std::vector food; if (category == 11) { if (level < 5) items = {787, 117, 4540, 2680}; else if (level < 15) items = {2287, 4592, 4541, 21072}; else if (level < 25) items = {3770, 16170, 4542, 20074}; else if (level < 35) items = {4594, 3771, 1707, 4457}; else if (level < 45) items = {4599, 4601, 21552, 17222 /*21030, 16168 */}; else if (level < 55) items = {8950, 8952, 8957, 21023 /*21033, 21031 */}; else if (level < 65) items = {29292, 27859, 30458, 27662}; else if (level < 75) items = {29450, 29451, 29452}; else items = {35947}; } if (category == 59) { if (level < 5) items = {159, 117}; else if (level < 15) items = {1179, 21072}; else if (level < 25) items = {1205}; else if (level < 35) items = {1708}; else if (level < 45) items = {1645}; else if (level < 55) items = {8766}; else if (level < 65) items = {28399}; else if (level < 75) items = {27860}; else items = {33445}; } food.insert(food.end(), items); if (food.empty()) return 0; return food[urand(0, food.size() - 1)]; } uint32 RandomItemMgr::GetRandomFood(uint32 level, uint32 category) { std::vector food = foodCache[(level - 1) / 10][category]; if (food.empty()) return 0; return food[urand(0, food.size() - 1)]; } void RandomItemMgr::BuildTradeCache() { uint32 maxLevel = sPlayerbotAIConfig->randomBotMaxLevel; if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); LOG_INFO("server.loading", "Building trade cache for {} levels", maxLevel); ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); uint32 counter = 0; for (uint32 level = 1; level <= maxLevel + 1; level += 10) { for (auto const& itr : *itemTemplates) { ItemTemplate const* proto = &itr.second; if (!proto) continue; if (proto->Class != ITEM_CLASS_TRADE_GOODS || proto->Bonding != NO_BIND) continue; if (proto->ItemLevel < level) continue; if (proto->RequiredLevel && (proto->RequiredLevel > level || proto->RequiredLevel < level - 10)) continue; if (proto->RequiredSkill) continue; tradeCache[level / 10].push_back(itr.first); } } for (uint32 level = 1; level <= maxLevel + 1; level += 10) { uint32 size = tradeCache[level / 10].size(); LOG_DEBUG("server.loading", "Trade cache for level={}: {} items", level, size); ++counter; } LOG_INFO("server.loading", "Cached {} trade items", counter); // TEST } uint32 RandomItemMgr::GetRandomTrade(uint32 level) { std::vector trade = tradeCache[(level - 1) / 10]; if (trade.empty()) return 0; return trade[urand(0, trade.size() - 1)]; } void RandomItemMgr::BuildRarityCache() { if (PreparedQueryResult result = PlayerbotsDatabase.Query(PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RARITY_CACHE))) { LOG_INFO("playerbots", "Loading item rarity cache"); uint32 count = 0; do { Field* fields = result->Fetch(); uint32 itemId = fields[0].Get(); float rarity = fields[1].Get(); rarityCache[itemId] = rarity; ++count; } while (result->NextRow()); LOG_INFO("playerbots", "Item rarity cache loaded from {} records", count); } else { ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); LOG_INFO("playerbots", "Building item rarity cache from {} items", itemTemplates->size()); for (auto const& itr : *itemTemplates) { ItemTemplate const* proto = &itr.second; if (!proto) continue; if (proto->Duration & 0x80000000) continue; if (proto->Quality == ITEM_QUALITY_POOR) continue; if (strstri(proto->Name1.c_str(), "qa") || strstri(proto->Name1.c_str(), "test") || strstri(proto->Name1.c_str(), "deprecated")) continue; if (!proto->ItemLevel) continue; QueryResult results = WorldDatabase.Query( "SELECT MAX(q.chance) FROM ( " // "-- Creature " "SELECT " "AVG ( " " CASE " " WHEN lt.groupid = 0 THEN lt.ChanceOrQuestChance " " WHEN lt.ChanceOrQuestChance > 0 THEN lt.ChanceOrQuestChance " " ELSE " " IFNULL(100 - (SELECT SUM(ChanceOrQuestChance) FROM creature_loot_template lt1 WHERE lt1.groupid = " "lt.groupid AND lt1.entry = lt.entry AND lt1.ChanceOrQuestChance > 0), 100) " " / (SELECT COUNT(*) FROM creature_loot_template lt1 WHERE lt1.groupid = lt.groupid AND lt1.entry = " "lt.entry AND lt1.ChanceOrQuestChance = 0) " " END " ") chance, 'creature' type " "FROM creature_loot_template lt " "JOIN creature_template ct ON ct.LootId = lt.entry " "JOIN creature c ON c.id1 = ct.entry " "WHERE lt.item = {} " "union all " // "-- Gameobject " "SELECT " "AVG ( " " CASE " " WHEN lt.groupid = 0 THEN lt.ChanceOrQuestChance " " WHEN lt.ChanceOrQuestChance > 0 THEN lt.ChanceOrQuestChance " " ELSE " " IFNULL(100 - (SELECT SUM(ChanceOrQuestChance) FROM gameobject_loot_template lt1 WHERE lt1.groupid " "= lt.groupid AND lt1.entry = lt.entry AND lt1.ChanceOrQuestChance > 0), 100) " " / (SELECT COUNT(*) FROM gameobject_loot_template lt1 WHERE lt1.groupid = lt.groupid AND lt1.entry " "= lt.entry AND lt1.ChanceOrQuestChance = 0) " " END " ") chance, 'gameobject' type " "FROM gameobject_loot_template lt " "JOIN gameobject_template ct ON ct.data1 = lt.entry " "JOIN gameobject c ON c.id1 = ct.entry " "WHERE lt.item = {} " "union all " // "-- Disenchant " "SELECT " "AVG ( " " CASE " " WHEN lt.groupid = 0 THEN lt.ChanceOrQuestChance " " WHEN lt.ChanceOrQuestChance > 0 THEN lt.ChanceOrQuestChance " " ELSE " " IFNULL(100 - (SELECT SUM(ChanceOrQuestChance) FROM disenchant_loot_template lt1 WHERE lt1.groupid " "= lt.groupid AND lt1.entry = lt.entry AND lt1.ChanceOrQuestChance > 0), 100) " " / (SELECT COUNT(*) FROM disenchant_loot_template lt1 WHERE lt1.groupid = lt.groupid AND lt1.entry " "= lt.entry AND lt1.ChanceOrQuestChance = 0) " " END " ") chance, 'disenchant' type " "FROM disenchant_loot_template lt " "JOIN item_template ct ON ct.DisenchantID = lt.entry " "WHERE lt.item = {} " "union all " // "-- Fishing " "SELECT " "AVG ( " " CASE " " WHEN lt.groupid = 0 THEN lt.ChanceOrQuestChance " " WHEN lt.ChanceOrQuestChance > 0 THEN lt.ChanceOrQuestChance " " ELSE " " IFNULL(100 - (SELECT SUM(ChanceOrQuestChance) FROM fishing_loot_template lt1 WHERE lt1.groupid = " "lt.groupid AND lt1.entry = lt.entry AND lt1.ChanceOrQuestChance > 0), 100) " " / (SELECT COUNT(*) FROM fishing_loot_template lt1 WHERE lt1.groupid = lt.groupid AND lt1.entry = " "lt.entry AND lt1.ChanceOrQuestChance = 0) " " END " ") chance, 'fishing' type " "FROM fishing_loot_template lt " "WHERE lt.item = {} " "union all " // "-- Skinning " "SELECT " "AVG ( " " CASE " " WHEN lt.groupid = 0 THEN lt.ChanceOrQuestChance " " WHEN lt.ChanceOrQuestChance > 0 THEN lt.ChanceOrQuestChance " " ELSE " " IFNULL(100 - (SELECT SUM(ChanceOrQuestChance) FROM skinning_loot_template lt1 WHERE lt1.groupid = " "lt.groupid AND lt1.entry = lt.entry AND lt1.ChanceOrQuestChance > 0), 100) " " * IFNULL((SELECT 1/COUNT(*) FROM skinning_loot_template lt1 WHERE lt1.groupid = lt.groupid AND " "lt1.entry = lt.entry AND lt1.ChanceOrQuestChance = 0), 1) " " END " ") chance, 'skinning' type " "FROM skinning_loot_template lt " "JOIN creature_template ct ON ct.SkinningLootId = lt.entry " "JOIN creature c ON c.id1 = ct.entry " "WHERE lt.item = {}) q; ", itr.first, itr.first, itr.first, itr.first, itr.first); if (results) { Field* fields = results->Fetch(); float rarity = fields[0].Get(); if (rarity > 0.01) { rarityCache[itr.first] = rarity; PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RARITY_CACHE); stmt->SetData(0, itr.first); stmt->SetData(1, rarity); PlayerbotsDatabase.Execute(stmt); } } } LOG_INFO("playerbots", "Item rarity cache built from {} items", itemTemplates->size()); } } float RandomItemMgr::GetItemRarity(uint32 itemId) { return rarityCache[itemId]; } inline bool IsCraftedBySpellInfo(ItemTemplate const* proto, SpellInfo const* spellInfo) { for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x) { if (spellInfo->Reagent[x] <= 0) { continue; } if (proto->ItemId == spellInfo->Reagent[x]) { return true; } } for (uint8 i = 0; i < 3; ++i) { if (spellInfo->Effects[i].Effect == SPELL_EFFECT_CREATE_ITEM) { if (spellInfo->Effects[i].ItemType == proto->ItemId) { return true; } } } return false; } inline bool IsCraftedBySpell(ItemTemplate const* proto, uint32 spellId) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) return false; return IsCraftedBySpellInfo(proto, spellInfo); } inline bool IsCraftedBy(ItemTemplate const* proto, uint32 spellId) { if (IsCraftedBySpell(proto, spellId)) return true; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) return false; for (uint32 effect = 0; effect < 3; ++effect) { uint32 craftId = spellInfo->Effects[effect].TriggerSpell; SpellInfo const* craftSpellInfo = sSpellMgr->GetSpellInfo(craftId); if (!craftSpellInfo) continue; if (IsCraftedBySpellInfo(proto, craftSpellInfo)) return true; } return false; } inline bool ContainsInternal(ItemTemplate const* proto, uint32 skillId) { for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j) { SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j); if (!skillLine || skillLine->ID != skillId) continue; if (IsCraftedBy(proto, skillLine->Spell)) return true; } CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates(); for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr) { if (itr->second.trainer_type != TRAINER_TYPE_TRADESKILLS) continue; uint32 trainerId = itr->second.Entry; TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId); if (!trainer_spells) continue; for (TrainerSpellMap::const_iterator iter = trainer_spells->spellList.begin(); iter != trainer_spells->spellList.end(); ++iter) { TrainerSpell const* tSpell = &iter->second; if (!tSpell || tSpell->reqSkill != skillId) continue; if (IsCraftedBy(proto, tSpell->spell)) return true; } } std::vector const* itemTemplates = sObjectMgr->GetItemTemplateStoreFast(); for (ItemTemplate const* recipe : *itemTemplates) { if (!recipe) continue; if (recipe->Class == ITEM_CLASS_RECIPE && ((recipe->SubClass == ITEM_SUBCLASS_LEATHERWORKING_PATTERN && skillId == SKILL_LEATHERWORKING) || (recipe->SubClass == ITEM_SUBCLASS_TAILORING_PATTERN && skillId == SKILL_TAILORING) || (recipe->SubClass == ITEM_SUBCLASS_ENGINEERING_SCHEMATIC && skillId == SKILL_ENGINEERING) || (recipe->SubClass == ITEM_SUBCLASS_BLACKSMITHING && skillId == SKILL_BLACKSMITHING) || (recipe->SubClass == ITEM_SUBCLASS_COOKING_RECIPE && skillId == SKILL_COOKING) || (recipe->SubClass == ITEM_SUBCLASS_ALCHEMY_RECIPE && skillId == SKILL_ALCHEMY) || (recipe->SubClass == ITEM_SUBCLASS_FIRST_AID_MANUAL && skillId == SKILL_FIRST_AID) || (recipe->SubClass == ITEM_SUBCLASS_ENCHANTING_FORMULA && skillId == SKILL_ENCHANTING) || (recipe->SubClass == ITEM_SUBCLASS_JEWELCRAFTING_RECIPE && skillId == SKILL_JEWELCRAFTING) || (recipe->SubClass == ITEM_SUBCLASS_FISHING_MANUAL && skillId == SKILL_FISHING))) { for (uint32 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { if (IsCraftedBy(proto, recipe->Spells[i].SpellId)) return true; } } } return false; } bool RandomItemMgr::IsUsedBySkill(ItemTemplate const* proto, uint32 skillId) { if (itemCache.find(proto->ItemId) != itemCache.end()) return true; switch (proto->Class) { case ITEM_CLASS_TRADE_GOODS: case ITEM_CLASS_MISC: case ITEM_CLASS_REAGENT: case ITEM_CLASS_GEM: break; default: return false; } bool contains = ContainsInternal(proto, skillId); if (contains) itemCache.insert(proto->ItemId); return contains; }