/* * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it * and/or modify it under version 2 of the License, or (at your option), any later version. */ #include "ItemUsageValue.h" #include "AiFactory.h" #include "ChatHelper.h" #include "GuildTaskMgr.h" #include "LootObjectStack.h" #include "PlayerbotAIConfig.h" #include "PlayerbotFactory.h" #include "Playerbots.h" #include "RandomItemMgr.h" #include "ServerFacade.h" #include "StatsWeightCalculator.h" ItemUsage ItemUsageValue::Calculate() { uint32 itemId = atoi(qualifier.c_str()); if (!itemId) return ITEM_USAGE_NONE; ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (!proto) return ITEM_USAGE_NONE; if (botAI->HasActivePlayerMaster()) { if (IsItemUsefulForSkill(proto) || IsItemNeededForSkill(proto)) return ITEM_USAGE_SKILL; } else { bool needItem = false; if (IsItemNeededForSkill(proto)) needItem = true; else { bool lowBagSpace = AI_VALUE(uint8, "bag space") > 50; if (proto->Class == ITEM_CLASS_TRADE_GOODS || proto->Class == ITEM_CLASS_MISC || proto->Class == ITEM_CLASS_REAGENT) needItem = IsItemNeededForUsefullSpell(proto, lowBagSpace); else if (proto->Class == ITEM_CLASS_RECIPE) { if (bot->HasSpell(proto->Spells[2].SpellId)) needItem = false; else needItem = bot->CanUseItem(proto) == EQUIP_ERR_OK; } } if (needItem) { float stacks = CurrentStacks(proto); if (stacks < 1) return ITEM_USAGE_SKILL; // Buy more. if (stacks < 2) return ITEM_USAGE_KEEP; // Keep current amount. } } if (proto->Class == ITEM_CLASS_KEY) return ITEM_USAGE_USE; if (proto->Class == ITEM_CLASS_CONSUMABLE && (proto->MaxCount == 0 || AI_VALUE2(uint32, "item count", proto->Name1) < proto->MaxCount)) { std::string const foodType = GetConsumableType(proto, bot->GetPower(POWER_MANA)); if (!foodType.empty() && bot->CanUseItem(proto) == EQUIP_ERR_OK) { float stacks = BetterStacks(proto, foodType); if (stacks < 2) { stacks += CurrentStacks(proto); if (stacks < 2) return ITEM_USAGE_USE; // Buy some to get to 2 stacks else if (stacks < 3) // Keep the item if less than 3 stacks return ITEM_USAGE_KEEP; } } } if (bot->GetGuildId() && sGuildTaskMgr->IsGuildTaskItem(itemId, bot->GetGuildId())) return ITEM_USAGE_GUILD_TASK; ItemUsage equip = QueryItemUsageForEquip(proto); if (equip != ITEM_USAGE_NONE) return equip; // Get item instance to check if it's soulbound Item* item = bot->GetItemByEntry(proto->ItemId); bool isSoulbound = item && item->IsSoulBound(); if ((proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON) && botAI->HasSkill(SKILL_ENCHANTING) && proto->Quality >= ITEM_QUALITY_UNCOMMON) { // Retrieve the bot's Enchanting skill level uint32 enchantingSkill = bot->GetSkillValue(SKILL_ENCHANTING); // Check if the bot has a high enough skill to disenchant this item if (proto->RequiredDisenchantSkill > 0 && enchantingSkill < proto->RequiredDisenchantSkill) return ITEM_USAGE_NONE; // Not skilled enough to disenchant // BoE (Bind on Equip) items should NOT be disenchanted unless they are already bound if (proto->Bonding == BIND_WHEN_PICKED_UP || (proto->Bonding == BIND_WHEN_EQUIPPED && isSoulbound)) return ITEM_USAGE_DISENCHANT; } Player* master = botAI->GetMaster(); bool isSelfBot = (master == bot); bool botNeedsItemForQuest = IsItemUsefulForQuest(bot, proto); bool masterNeedsItemForQuest = master && sPlayerbotAIConfig->syncQuestWithPlayer && IsItemUsefulForQuest(master, proto); // Identify the source of loot LootObject lootObject = AI_VALUE(LootObject, "loot target"); // Get GUID of loot source ObjectGuid lootGuid = lootObject.guid; // Check if loot source is an item bool isLootFromItem = lootGuid.IsItem(); // If the loot is from an item in the bot’s bags, ignore syncQuestWithPlayer if (isLootFromItem && botNeedsItemForQuest) { return ITEM_USAGE_QUEST; } // If the bot is NOT acting alone and the master needs this quest item, defer to the master if (!isSelfBot && masterNeedsItemForQuest) { return ITEM_USAGE_NONE; } // If the bot itself needs the item for a quest, allow looting if (botNeedsItemForQuest) { return ITEM_USAGE_QUEST; } if (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK) { if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_ROGUE || bot->getClass() == CLASS_WARRIOR) { Item* rangedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED); uint32 requiredSubClass = 0; if (rangedWeapon) { switch (rangedWeapon->GetTemplate()->SubClass) { case ITEM_SUBCLASS_WEAPON_GUN: requiredSubClass = ITEM_SUBCLASS_BULLET; break; case ITEM_SUBCLASS_WEAPON_BOW: case ITEM_SUBCLASS_WEAPON_CROSSBOW: requiredSubClass = ITEM_SUBCLASS_ARROW; break; } } // Ensure the item is the correct ammo type for the equipped ranged weapon if (proto->SubClass == requiredSubClass) { float ammoCount = BetterStacks(proto, "ammo"); float requiredAmmo = (bot->getClass() == CLASS_HUNTER) ? 8 : 2; // Hunters get 8 stacks, others 2 uint32 currentAmmoId = bot->GetUInt32Value(PLAYER_AMMO_ID); // Check if the bot has an ammo type assigned if (currentAmmoId == 0) { return ITEM_USAGE_EQUIP; // Equip the ammo if no ammo } // Compare new ammo vs current equipped ammo ItemTemplate const* currentAmmoProto = sObjectMgr->GetItemTemplate(currentAmmoId); if (currentAmmoProto) { uint32 currentAmmoDPS = (currentAmmoProto->Damage[0].DamageMin + currentAmmoProto->Damage[0].DamageMax) * 1000 / 2; uint32 newAmmoDPS = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2; if (newAmmoDPS > currentAmmoDPS) // New ammo meets upgrade condition { return ITEM_USAGE_EQUIP; } if (newAmmoDPS < currentAmmoDPS) // New ammo is worse { return ITEM_USAGE_NONE; } } // Ensure we have enough ammo in the inventory if (ammoCount < requiredAmmo) { ammoCount += CurrentStacks(proto); if (ammoCount < requiredAmmo) // Buy ammo to reach the proper supply return ITEM_USAGE_AMMO; else if (ammoCount < requiredAmmo + 1) return ITEM_USAGE_KEEP; // Keep the ammo if we don't have too much. } } } } // Need to add something like free bagspace or item value. if (proto->SellPrice > 0) { if (proto->Quality >= ITEM_QUALITY_NORMAL && !isSoulbound) { return ITEM_USAGE_AH; } else { return ITEM_USAGE_VENDOR; } } return ITEM_USAGE_NONE; } ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto) { if (bot->CanUseItem(itemProto) != EQUIP_ERR_OK) return ITEM_USAGE_NONE; if (itemProto->InventoryType == INVTYPE_NON_EQUIP) return ITEM_USAGE_NONE; Item* pItem = Item::CreateItem(itemProto->ItemId, 1, bot, false, 0, true); if (!pItem) return ITEM_USAGE_NONE; uint16 dest; InventoryResult result = botAI->CanEquipItem(NULL_SLOT, dest, pItem, true, true); pItem->RemoveFromUpdateQueueOf(bot); delete pItem; if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) { return ITEM_USAGE_NONE; } // Check is unique items are equipped or not bool needToCheckUnique = false; if (result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) { needToCheckUnique = true; } else if (itemProto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE) { needToCheckUnique = true; } if (needToCheckUnique) { // Count the total number of the item (equipped + in bags) uint32 totalItemCount = bot->GetItemCount(itemProto->ItemId, true); // Count the number of the item in bags only uint32 bagItemCount = bot->GetItemCount(itemProto->ItemId, false); // Determine if the unique item is already equipped bool isEquipped = (totalItemCount > bagItemCount); if (isEquipped) { return ITEM_USAGE_NONE; // Item is already equipped } // If not equipped, continue processing } if (itemProto->Class == ITEM_CLASS_QUIVER) if (bot->getClass() != CLASS_HUNTER) return ITEM_USAGE_NONE; if (itemProto->Class == ITEM_CLASS_CONTAINER) { if (itemProto->SubClass != ITEM_SUBCLASS_CONTAINER) return ITEM_USAGE_NONE; // Todo add logic for non-bag containers. We want to look at professions/class and // only replace if non-bag is larger than bag. if (GetSmallestBagSize() >= itemProto->ContainerSlots) return ITEM_USAGE_NONE; return ITEM_USAGE_EQUIP; } bool shouldEquip = false; // uint32 statWeight = sRandomItemMgr->GetLiveStatWeight(bot, itemProto->ItemId); StatsWeightCalculator calculator(bot); calculator.SetItemSetBonus(false); calculator.SetOverflowPenalty(false); float itemScore = calculator.CalculateItem(itemProto->ItemId); if (itemScore) shouldEquip = true; if (itemProto->Class == ITEM_CLASS_WEAPON && !sRandomItemMgr->CanEquipWeapon(bot->getClass(), itemProto)) shouldEquip = false; if (itemProto->Class == ITEM_CLASS_ARMOR && !sRandomItemMgr->CanEquipArmor(bot->getClass(), bot->GetLevel(), itemProto)) shouldEquip = false; uint8 possibleSlots = 1; uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); // Check if dest wasn't set correctly by CanEquipItem and use FindEquipSlot instead // This occurs with unique items that are already in the bots bags when CanEquipItem is called if (dest == 0) { if (dstSlot != NULL_SLOT) { // Construct dest from dstSlot dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot; } } if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1) { possibleSlots = 2; } // Check weapon case separately to keep things a bit cleaner bool have2HWeapon = false; bool isValidTGWeapon = false; if (dstSlot == EQUIPMENT_SLOT_MAINHAND) { Item* currentWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); have2HWeapon = currentWeapon && currentWeapon->GetTemplate()->InventoryType == INVTYPE_2HWEAPON; // Determine if the new weapon is a valid Titan Grip weapon isValidTGWeapon = (itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 || itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2); // If the bot can Titan Grip, ignore any 2H weapon that isn't a 2H sword, mace, or axe. if (bot->CanTitanGrip()) { // If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all. if (itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon) { return ITEM_USAGE_NONE; } } // Now handle the logic for equipping and possible offhand slots // If the bot can Dual Wield and: // - The weapon is not 2H and we currently don't have a 2H weapon equipped // OR // - The bot can Titan Grip and it is a valid TG weapon // Then we can consider the offhand slot as well. if (bot->CanDualWield() && ((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) || (bot->CanTitanGrip() && isValidTGWeapon))) { possibleSlots = 2; } } for (uint8 i = 0; i < possibleSlots; i++) { bool shouldEquipInSlot = shouldEquip; Item* oldItem = bot->GetItemByPos(dest + i); // No item equipped if (!oldItem) { if (shouldEquipInSlot) return ITEM_USAGE_EQUIP; else { return ITEM_USAGE_BAD_EQUIP; } } ItemTemplate const* oldItemProto = oldItem->GetTemplate(); float oldScore = calculator.CalculateItem(oldItemProto->ItemId); if (oldItem) { // uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); if (itemScore || oldScore) { shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig->equipUpgradeThreshold; } } // Bigger quiver if (itemProto->Class == ITEM_CLASS_QUIVER) { if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots) { return ITEM_USAGE_EQUIP; } else { return ITEM_USAGE_NONE; } } bool existingShouldEquip = true; if (oldItemProto->Class == ITEM_CLASS_WEAPON && !sRandomItemMgr->CanEquipWeapon(bot->getClass(), oldItemProto)) existingShouldEquip = false; if (oldItemProto->Class == ITEM_CLASS_ARMOR && !sRandomItemMgr->CanEquipArmor(bot->getClass(), bot->GetLevel(), oldItemProto)) existingShouldEquip = false; // uint32 oldItemPower = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); // uint32 newItemPower = sRandomItemMgr->GetLiveStatWeight(bot, itemProto->ItemId); // Compare items based on item level, quality or itemId. bool isBetter = false; if (itemScore > oldScore) isBetter = true; // else if (newItemPower == oldScore && itemProto->Quality > oldItemProto->Quality) // isBetter = true; // else if (newItemPower == oldScore && itemProto->Quality == oldItemProto->Quality && itemProto->ItemId > // oldItemProto->ItemId) // isBetter = true; Item* item = CurrentItem(itemProto); bool itemIsBroken = item && item->GetUInt32Value(ITEM_FIELD_DURABILITY) == 0 && item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0; bool oldItemIsBroken = oldItem->GetUInt32Value(ITEM_FIELD_DURABILITY) == 0 && oldItem->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0; if (itemProto->ItemId != oldItemProto->ItemId && (shouldEquipInSlot || !existingShouldEquip) && isBetter) { switch (itemProto->Class) { case ITEM_CLASS_ARMOR: if (oldItemProto->SubClass <= itemProto->SubClass) { // Need to add some logic to check second slot before returning, but as it happens, all three of these // return vals will result in an attempted equip action so it wouldn't have much effect currently if (itemIsBroken && !oldItemIsBroken) return ITEM_USAGE_BROKEN_EQUIP; else if (shouldEquipInSlot) return ITEM_USAGE_REPLACE; else return ITEM_USAGE_BAD_EQUIP; break; } default: { if (itemIsBroken && !oldItemIsBroken) return ITEM_USAGE_BROKEN_EQUIP; else if (shouldEquipInSlot) return ITEM_USAGE_EQUIP; else return ITEM_USAGE_BAD_EQUIP; } } } // Item is not better but current item is broken and new one is not. if (oldItemIsBroken && !itemIsBroken) return ITEM_USAGE_EQUIP; } return ITEM_USAGE_NONE; } // Return smaltest bag size equipped uint32 ItemUsageValue::GetSmallestBagSize() { int8 curSlot = 0; uint32 curSlots = 0; for (uint8 bag = INVENTORY_SLOT_BAG_START + 1; bag < INVENTORY_SLOT_BAG_END; ++bag) { if (Bag const* pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag)) { if (curSlot > 0 && curSlots < pBag->GetBagSize()) continue; curSlot = pBag->GetSlot(); curSlots = pBag->GetBagSize(); } else return 0; } return curSlots; } bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* proto) { PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); if (!botAI) return false; for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) { uint32 entry = player->GetQuestSlotQuestId(slot); Quest const* quest = sObjectMgr->GetQuestTemplate(entry); if (!quest) continue; // Check if the item itself is needed for the quest for (uint8 i = 0; i < 4; i++) { if (quest->RequiredItemId[i] == proto->ItemId) { if (AI_VALUE2(uint32, "item count", proto->Name1) >= quest->RequiredItemCount[i]) continue; return true; // Item is directly required for a quest } } // Check if the item has spells that create a required quest item for (uint8 i = 0; i < MAX_ITEM_SPELLS; i++) { uint32 spellId = proto->Spells[i].SpellId; if (!spellId) continue; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) continue; for (uint8 effectIndex = 0; effectIndex < MAX_SPELL_EFFECTS; effectIndex++) { if (spellInfo->Effects[effectIndex].Effect == SPELL_EFFECT_CREATE_ITEM) { uint32 createdItemId = spellInfo->Effects[effectIndex].ItemType; // Check if the created item is required for a quest for (uint8 j = 0; j < 4; j++) { if (quest->RequiredItemId[j] == createdItemId) { if (AI_VALUE2(uint32, "item count", createdItemId) >= quest->RequiredItemCount[j]) continue; return true; // Item is useful because it creates a required quest item } } } } } } return false; // Item is not useful for any active quests } bool ItemUsageValue::IsItemNeededForSkill(ItemTemplate const* proto) { switch (proto->ItemId) { case 756: // Tunnel Pick return botAI->HasSkill(SKILL_MINING); case 778: // Kobold Excavation Pick return botAI->HasSkill(SKILL_MINING); case 1819: // Gouging Pick return botAI->HasSkill(SKILL_MINING); case 1893: // Miner's Revenge return botAI->HasSkill(SKILL_MINING); case 1959: // Cold Iron Pick return botAI->HasSkill(SKILL_MINING); case 2901: // Mining Pick return botAI->HasSkill(SKILL_MINING); case 9465: // Digmaster 5000 return botAI->HasSkill(SKILL_MINING); case 20723: // Brann's Trusty Pick return botAI->HasSkill(SKILL_MINING); case 40772: // Gnomish Army Knife return botAI->HasSkill(SKILL_MINING) || botAI->HasSkill(SKILL_ENGINEERING) || botAI->HasSkill(SKILL_BLACKSMITHING) || botAI->HasSkill(SKILL_COOKING) || botAI->HasSkill(SKILL_SKINNING); case 40892: // Hammer Pick return botAI->HasSkill(SKILL_MINING) || botAI->HasSkill(SKILL_BLACKSMITHING); case 40893: // Bladed Pickaxe return botAI->HasSkill(SKILL_MINING) || botAI->HasSkill(SKILL_SKINNING); case 5956: // Blacksmith Hammer return botAI->HasSkill(SKILL_BLACKSMITHING) || botAI->HasSkill(SKILL_ENGINEERING); case 6219: // Arclight Spanner return botAI->HasSkill(SKILL_ENGINEERING); case 6218: // Runed copper rod return botAI->HasSkill(SKILL_ENCHANTING); case 6339: // Runed silver rod return botAI->HasSkill(SKILL_ENCHANTING); case 11130: // Runed golden rod return botAI->HasSkill(SKILL_ENCHANTING); case 11145: // Runed truesilver rod return botAI->HasSkill(SKILL_ENCHANTING); case 16207: // Runed Arcanite Rod return botAI->HasSkill(SKILL_ENCHANTING); case 7005: // Skinning Knife return botAI->HasSkill(SKILL_SKINNING); case 12709: return botAI->HasSkill(SKILL_SKINNING); case 19901: return botAI->HasSkill(SKILL_SKINNING); case 4471: // Flint and Tinder return botAI->HasSkill(SKILL_COOKING); case 4470: // Simple Wood return botAI->HasSkill(SKILL_COOKING); case 6256: // Fishing Rod return botAI->HasSkill(SKILL_FISHING); } return false; } bool ItemUsageValue::IsItemUsefulForSkill(ItemTemplate const* proto) { switch (proto->Class) { case ITEM_CLASS_TRADE_GOODS: case ITEM_CLASS_MISC: case ITEM_CLASS_REAGENT: case ITEM_CLASS_GEM: { if (botAI->HasSkill(SKILL_TAILORING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_TAILORING)) return true; if (botAI->HasSkill(SKILL_LEATHERWORKING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_LEATHERWORKING)) return true; if (botAI->HasSkill(SKILL_ENGINEERING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_ENGINEERING)) return true; if (botAI->HasSkill(SKILL_BLACKSMITHING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_BLACKSMITHING)) return true; if (botAI->HasSkill(SKILL_ALCHEMY) && RandomItemMgr::IsUsedBySkill(proto, SKILL_ALCHEMY)) return true; if (botAI->HasSkill(SKILL_ENCHANTING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_ENCHANTING)) return true; if (botAI->HasSkill(SKILL_FISHING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_FISHING)) return true; if (botAI->HasSkill(SKILL_FIRST_AID) && RandomItemMgr::IsUsedBySkill(proto, SKILL_FIRST_AID)) return true; if (botAI->HasSkill(SKILL_COOKING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_COOKING)) return true; if (botAI->HasSkill(SKILL_JEWELCRAFTING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_JEWELCRAFTING)) return true; if (botAI->HasSkill(SKILL_MINING) && (RandomItemMgr::IsUsedBySkill(proto, SKILL_MINING) || RandomItemMgr::IsUsedBySkill(proto, SKILL_BLACKSMITHING) || RandomItemMgr::IsUsedBySkill(proto, SKILL_JEWELCRAFTING) || RandomItemMgr::IsUsedBySkill(proto, SKILL_ENGINEERING))) return true; if (botAI->HasSkill(SKILL_SKINNING) && (RandomItemMgr::IsUsedBySkill(proto, SKILL_SKINNING) || RandomItemMgr::IsUsedBySkill(proto, SKILL_LEATHERWORKING))) return true; if (botAI->HasSkill(SKILL_HERBALISM) && (RandomItemMgr::IsUsedBySkill(proto, SKILL_HERBALISM) || RandomItemMgr::IsUsedBySkill(proto, SKILL_ALCHEMY))) return true; return false; } case ITEM_CLASS_RECIPE: { if (bot->HasSpell(proto->Spells[2].SpellId)) break; switch (proto->SubClass) { case ITEM_SUBCLASS_LEATHERWORKING_PATTERN: return botAI->HasSkill(SKILL_LEATHERWORKING); case ITEM_SUBCLASS_TAILORING_PATTERN: return botAI->HasSkill(SKILL_TAILORING); case ITEM_SUBCLASS_ENGINEERING_SCHEMATIC: return botAI->HasSkill(SKILL_ENGINEERING); case ITEM_SUBCLASS_BLACKSMITHING: return botAI->HasSkill(SKILL_BLACKSMITHING); case ITEM_SUBCLASS_COOKING_RECIPE: return botAI->HasSkill(SKILL_COOKING); case ITEM_SUBCLASS_ALCHEMY_RECIPE: return botAI->HasSkill(SKILL_ALCHEMY); case ITEM_SUBCLASS_FIRST_AID_MANUAL: return botAI->HasSkill(SKILL_FIRST_AID); case ITEM_SUBCLASS_ENCHANTING_FORMULA: return botAI->HasSkill(SKILL_ENCHANTING); case ITEM_SUBCLASS_FISHING_MANUAL: return botAI->HasSkill(SKILL_FISHING); } } } return false; } bool ItemUsageValue::IsItemNeededForUsefullSpell(ItemTemplate const* proto, bool checkAllReagents) { for (auto spellId : SpellsUsingItem(proto->ItemId, bot)) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) continue; if (checkAllReagents && !HasItemsNeededForSpell(spellId, proto)) continue; if (SpellGivesSkillUp(spellId, bot)) return true; uint32 newItemId = spellInfo->Effects[EFFECT_0].ItemType; if (newItemId && newItemId != proto->ItemId) { ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", newItemId); if (usage != ITEM_USAGE_REPLACE && usage != ITEM_USAGE_EQUIP && usage != ITEM_USAGE_AMMO && usage != ITEM_USAGE_QUEST && usage != ITEM_USAGE_SKILL && usage != ITEM_USAGE_USE) continue; return true; } } return false; } bool ItemUsageValue::HasItemsNeededForSpell(uint32 spellId, ItemTemplate const* proto) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) return false; for (uint8 i = 0; i < MAX_SPELL_REAGENTS; i++) if (spellInfo->ReagentCount[i] > 0 && spellInfo->Reagent[i]) { if (proto && proto->ItemId == spellInfo->Reagent[i] && spellInfo->ReagentCount[i] == 1) // If we only need 1 item then current item does not need to be // checked since we are looting/buying or already have it. continue; ItemTemplate const* reqProto = sObjectMgr->GetItemTemplate(spellInfo->Reagent[i]); uint32 count = AI_VALUE2(uint32, "item count", reqProto->Name1); if (count < spellInfo->ReagentCount[i]) return false; } return true; } Item* ItemUsageValue::CurrentItem(ItemTemplate const* proto) { Item* bestItem = nullptr; std::vector found = AI_VALUE2(std::vector, "inventory items", chat->FormatItem(proto)); for (auto item : found) { if (bestItem && item->GetUInt32Value(ITEM_FIELD_DURABILITY) < bestItem->GetUInt32Value(ITEM_FIELD_DURABILITY)) continue; if (bestItem && item->GetCount() < bestItem->GetCount()) continue; bestItem = item; } return bestItem; } float ItemUsageValue::CurrentStacks(ItemTemplate const* proto) { uint32 maxStack = proto->GetMaxStackSize(); std::vector found = AI_VALUE2(std::vector, "inventory items", chat->FormatItem(proto)); float itemCount = 0; for (auto stack : found) { itemCount += stack->GetCount(); } return itemCount / maxStack; } float ItemUsageValue::BetterStacks(ItemTemplate const* proto, std::string const itemType) { std::vector items = AI_VALUE2(std::vector, "inventory items", itemType); float stacks = 0; for (auto& otherItem : items) { ItemTemplate const* otherProto = otherItem->GetTemplate(); if (otherProto->Class != proto->Class || otherProto->SubClass != proto->SubClass) continue; if (otherProto->ItemLevel < proto->ItemLevel) continue; if (otherProto->ItemId == proto->ItemId) continue; stacks += CurrentStacks(otherProto); } return stacks; } std::vector ItemUsageValue::SpellsUsingItem(uint32 itemId, Player* bot) { std::vector retSpells; PlayerSpellMap const& spellMap = bot->GetSpellMap(); for (auto& spell : spellMap) { uint32 spellId = spell.first; if (spell.second->State == PLAYERSPELL_REMOVED || !spell.second->Active) continue; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) continue; if (spellInfo->IsPassive()) continue; if (spellInfo->Effects[EFFECT_0].Effect != SPELL_EFFECT_CREATE_ITEM) continue; for (uint8 i = 0; i < MAX_SPELL_REAGENTS; i++) if (spellInfo->ReagentCount[i] > 0 && spellInfo->Reagent[i] == itemId) retSpells.push_back(spellId); } return retSpells; } inline int32 SkillGainChance(uint32 SkillValue, uint32 GrayLevel, uint32 GreenLevel, uint32 YellowLevel) { if (SkillValue >= GrayLevel) return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_GREY) * 10; if (SkillValue >= GreenLevel) return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_GREEN) * 10; if (SkillValue >= YellowLevel) return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_YELLOW) * 10; return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_ORANGE) * 10; } bool ItemUsageValue::SpellGivesSkillUp(uint32 spellId, Player* bot) { SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId); for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx) { SkillLineAbilityEntry const* skill = _spell_idx->second; if (skill->SkillLine) { uint32 SkillValue = bot->GetPureSkillValue(skill->SkillLine); uint32 craft_skill_gain = sWorld->getIntConfig(CONFIG_SKILL_GAIN_CRAFTING); if (SkillGainChance(SkillValue, skill->TrivialSkillLineRankHigh, (skill->TrivialSkillLineRankHigh + skill->TrivialSkillLineRankLow) / 2, skill->TrivialSkillLineRankLow) > 0) return true; } } return false; } std::string const ItemUsageValue::GetConsumableType(ItemTemplate const* proto, bool hasMana) { std::string const foodType = ""; if ((proto->SubClass == ITEM_SUBCLASS_CONSUMABLE || proto->SubClass == ITEM_SUBCLASS_FOOD)) { if (proto->Spells[0].SpellCategory == 11) return "food"; else if (proto->Spells[0].SpellCategory == 59 && hasMana) return "drink"; } if (proto->SubClass == ITEM_SUBCLASS_POTION || proto->SubClass == ITEM_SUBCLASS_FLASK) { for (int j = 0; j < MAX_ITEM_PROTO_SPELLS; j++) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[j].SpellId); if (spellInfo) for (int i = 0; i < 3; i++) { if (spellInfo->Effects[i].Effect == SPELL_EFFECT_ENERGIZE && hasMana) return "mana potion"; if (spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL) return "healing potion"; } } } if (proto->SubClass == ITEM_SUBCLASS_BANDAGE) { return "bandage"; } return ""; }