diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 6dff36e4..0c4ce732 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -28,6 +28,7 @@ #include "PlayerbotMgr.h" #include "PointMovementGenerator.h" #include "PositionValue.h" +#include "ScriptMgr.h" #include "ServerFacade.h" #include "SharedDefines.h" #include "SocialMgr.h" @@ -4387,4 +4388,311 @@ bool PlayerbotAI::EqualLowercaseName(std::string s1, std::string s2) } } return true; +} + +InventoryResult PlayerbotAI::CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading) const +{ + dest = 0; + if (pItem) + { + LOG_DEBUG("entities.player.items", "STORAGE: CanEquipItem slot = {}, item = {}, count = {}", slot, pItem->GetEntry(), pItem->GetCount()); + ItemTemplate const* pProto = pItem->GetTemplate(); + if (pProto) + { + if (!sScriptMgr->CanEquipItem(bot, slot, dest, pItem, swap, not_loading)) + return EQUIP_ERR_CANT_DO_RIGHT_NOW; + + // item used + if (pItem->m_lootGenerated) + return EQUIP_ERR_ALREADY_LOOTED; + + if (pItem->IsBindedNotWith(bot)) + return EQUIP_ERR_DONT_OWN_THAT_ITEM; + + // check count of items (skip for auto move for same player from bank) + InventoryResult res = bot->CanTakeMoreSimilarItems(pItem); + if (res != EQUIP_ERR_OK) + return res; + + ScalingStatDistributionEntry const* ssd = pProto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(pProto->ScalingStatDistribution) : 0; + // check allowed level (extend range to upper values if MaxLevel more or equal max player level, this let GM set high level with 1...max range items) + if (ssd && ssd->MaxLevel < DEFAULT_MAX_LEVEL && ssd->MaxLevel < bot->GetLevel()) + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + + uint8 eslot = FindEquipSlot(pProto, slot, swap); + if (eslot == NULL_SLOT) + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + + // Xinef: dont allow to equip items on disarmed slot + if (!bot->CanUseAttackType(bot->GetAttackBySlot(eslot))) + return EQUIP_ERR_NOT_WHILE_DISARMED; + + res = bot->CanUseItem(pItem, not_loading); + if (res != EQUIP_ERR_OK) + return res; + + if (!swap && bot->GetItemByPos(INVENTORY_SLOT_BAG_0, eslot)) + return EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE; + + // if we are swapping 2 equiped items, CanEquipUniqueItem check + // should ignore the item we are trying to swap, and not the + // destination item. CanEquipUniqueItem should ignore destination + // item only when we are swapping weapon from bag + uint8 ignore = uint8(NULL_SLOT); + switch (eslot) + { + case EQUIPMENT_SLOT_MAINHAND: + ignore = EQUIPMENT_SLOT_OFFHAND; + break; + case EQUIPMENT_SLOT_OFFHAND: + ignore = EQUIPMENT_SLOT_MAINHAND; + break; + case EQUIPMENT_SLOT_FINGER1: + ignore = EQUIPMENT_SLOT_FINGER2; + break; + case EQUIPMENT_SLOT_FINGER2: + ignore = EQUIPMENT_SLOT_FINGER1; + break; + case EQUIPMENT_SLOT_TRINKET1: + ignore = EQUIPMENT_SLOT_TRINKET2; + break; + case EQUIPMENT_SLOT_TRINKET2: + ignore = EQUIPMENT_SLOT_TRINKET1; + break; + } + + if (ignore == uint8(NULL_SLOT) || pItem != bot->GetItemByPos(INVENTORY_SLOT_BAG_0, ignore)) + ignore = eslot; + + InventoryResult res2 = bot->CanEquipUniqueItem(pItem, swap ? ignore : uint8(NULL_SLOT)); + if (res2 != EQUIP_ERR_OK) + return res2; + + // check unique-equipped special item classes + if (pProto->Class == ITEM_CLASS_QUIVER) + for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Item* pBag = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pBag != pItem) + if (ItemTemplate const* pBagProto = pBag->GetTemplate()) + if (pBagProto->Class == pProto->Class && (!swap || pBag->GetSlot() != eslot)) + return (pBagProto->SubClass == ITEM_SUBCLASS_AMMO_POUCH) + ? EQUIP_ERR_CAN_EQUIP_ONLY1_AMMOPOUCH + : EQUIP_ERR_CAN_EQUIP_ONLY1_QUIVER; + + uint32 type = pProto->InventoryType; + + if (eslot == EQUIPMENT_SLOT_OFFHAND) + { + // Do not allow polearm to be equipped in the offhand (rare case for the only 1h polearm 41750) + // xinef: same for fishing poles + if (type == INVTYPE_WEAPON && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || pProto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE)) + return EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT; + + else if (type == INVTYPE_WEAPON || type == INVTYPE_WEAPONOFFHAND) + { + if (!bot->CanDualWield()) + return EQUIP_ERR_CANT_DUAL_WIELD; + } + else if (type == INVTYPE_2HWEAPON) + { + if (!bot->CanDualWield() || !bot->CanTitanGrip()) + return EQUIP_ERR_CANT_DUAL_WIELD; + } + + if (bot->IsTwoHandUsed()) + return EQUIP_ERR_CANT_EQUIP_WITH_TWOHANDED; + } + + // equip two-hand weapon case (with possible unequip 2 items) + if (type == INVTYPE_2HWEAPON) + { + if (eslot == EQUIPMENT_SLOT_OFFHAND) + { + if (!bot->CanTitanGrip()) + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + } + else if (eslot != EQUIPMENT_SLOT_MAINHAND) + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + + if (!bot->CanTitanGrip()) + { + // offhand item must can be stored in inventory for offhand item and it also must be unequipped + Item* offItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + ItemPosCountVec off_dest; + if (offItem && (!not_loading || + bot->CanUnequipItem(uint16(INVENTORY_SLOT_BAG_0) << 8 | EQUIPMENT_SLOT_OFFHAND, false) != EQUIP_ERR_OK || + bot->CanStoreItem(NULL_BAG, NULL_SLOT, off_dest, offItem, false) != EQUIP_ERR_OK)) + return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_INVENTORY_FULL; + } + } + dest = ((INVENTORY_SLOT_BAG_0 << 8) | eslot); + return EQUIP_ERR_OK; + } + } + + return !swap ? EQUIP_ERR_ITEM_NOT_FOUND : EQUIP_ERR_ITEMS_CANT_BE_SWAPPED; +} + +uint8 PlayerbotAI::FindEquipSlot(ItemTemplate const* proto, uint32 slot, bool swap) const +{ + uint8 slots[4]; + slots[0] = NULL_SLOT; + slots[1] = NULL_SLOT; + slots[2] = NULL_SLOT; + slots[3] = NULL_SLOT; + switch (proto->InventoryType) + { + case INVTYPE_HEAD: + slots[0] = EQUIPMENT_SLOT_HEAD; + break; + case INVTYPE_NECK: + slots[0] = EQUIPMENT_SLOT_NECK; + break; + case INVTYPE_SHOULDERS: + slots[0] = EQUIPMENT_SLOT_SHOULDERS; + break; + case INVTYPE_BODY: + slots[0] = EQUIPMENT_SLOT_BODY; + break; + case INVTYPE_CHEST: + case INVTYPE_ROBE: + slots[0] = EQUIPMENT_SLOT_CHEST; + break; + case INVTYPE_WAIST: + slots[0] = EQUIPMENT_SLOT_WAIST; + break; + case INVTYPE_LEGS: + slots[0] = EQUIPMENT_SLOT_LEGS; + break; + case INVTYPE_FEET: + slots[0] = EQUIPMENT_SLOT_FEET; + break; + case INVTYPE_WRISTS: + slots[0] = EQUIPMENT_SLOT_WRISTS; + break; + case INVTYPE_HANDS: + slots[0] = EQUIPMENT_SLOT_HANDS; + break; + case INVTYPE_FINGER: + slots[0] = EQUIPMENT_SLOT_FINGER1; + slots[1] = EQUIPMENT_SLOT_FINGER2; + break; + case INVTYPE_TRINKET: + slots[0] = EQUIPMENT_SLOT_TRINKET1; + slots[1] = EQUIPMENT_SLOT_TRINKET2; + break; + case INVTYPE_CLOAK: + slots[0] = EQUIPMENT_SLOT_BACK; + break; + case INVTYPE_WEAPON: + { + slots[0] = EQUIPMENT_SLOT_MAINHAND; + + // suggest offhand slot only if know dual wielding + // (this will be replace mainhand weapon at auto equip instead unwonted "you don't known dual wielding" ... + if (bot->CanDualWield()) + slots[1] = EQUIPMENT_SLOT_OFFHAND; + break; + } + case INVTYPE_SHIELD: + case INVTYPE_WEAPONOFFHAND: + case INVTYPE_HOLDABLE: + slots[0] = EQUIPMENT_SLOT_OFFHAND; + break; + case INVTYPE_RANGED: + case INVTYPE_RANGEDRIGHT: + case INVTYPE_THROWN: + slots[0] = EQUIPMENT_SLOT_RANGED; + break; + case INVTYPE_2HWEAPON: + slots[0] = EQUIPMENT_SLOT_MAINHAND; + if (Item* mhWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND)) + { + if (ItemTemplate const* mhWeaponProto = mhWeapon->GetTemplate()) + { + if (mhWeaponProto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || mhWeaponProto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) + { + bot->AutoUnequipOffhandIfNeed(true); + break; + } + } + } + + if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND)) + { + if (proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) + { + break; + } + } + if (bot->CanDualWield() && bot->CanTitanGrip() && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_FISHING_POLE) + slots[1] = EQUIPMENT_SLOT_OFFHAND; + break; + case INVTYPE_TABARD: + slots[0] = EQUIPMENT_SLOT_TABARD; + break; + case INVTYPE_WEAPONMAINHAND: + slots[0] = EQUIPMENT_SLOT_MAINHAND; + break; + case INVTYPE_BAG: + slots[0] = INVENTORY_SLOT_BAG_START + 0; + slots[1] = INVENTORY_SLOT_BAG_START + 1; + slots[2] = INVENTORY_SLOT_BAG_START + 2; + slots[3] = INVENTORY_SLOT_BAG_START + 3; + break; + case INVTYPE_RELIC: + { + switch (proto->SubClass) + { + case ITEM_SUBCLASS_ARMOR_LIBRAM: + if (bot->IsClass(CLASS_PALADIN, CLASS_CONTEXT_EQUIP_RELIC)) + slots[0] = EQUIPMENT_SLOT_RANGED; + break; + case ITEM_SUBCLASS_ARMOR_IDOL: + if (bot->IsClass(CLASS_DRUID, CLASS_CONTEXT_EQUIP_RELIC)) + slots[0] = EQUIPMENT_SLOT_RANGED; + break; + case ITEM_SUBCLASS_ARMOR_TOTEM: + if (bot->IsClass(CLASS_SHAMAN, CLASS_CONTEXT_EQUIP_RELIC)) + slots[0] = EQUIPMENT_SLOT_RANGED; + break; + case ITEM_SUBCLASS_ARMOR_MISC: + if (bot->IsClass(CLASS_WARLOCK, CLASS_CONTEXT_EQUIP_RELIC)) + slots[0] = EQUIPMENT_SLOT_RANGED; + break; + case ITEM_SUBCLASS_ARMOR_SIGIL: + if (bot->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_EQUIP_RELIC)) + slots[0] = EQUIPMENT_SLOT_RANGED; + break; + } + break; + } + default: + return NULL_SLOT; + } + + if (slot != NULL_SLOT) + { + if (swap || !bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + for (uint8 i = 0; i < 4; ++i) + if (slots[i] == slot) + return slot; + } + else + { + // search free slot at first + for (uint8 i = 0; i < 4; ++i) + if (slots[i] != NULL_SLOT && !bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slots[i])) + // in case 2hand equipped weapon (without titan grip) offhand slot empty but not free + if (slots[i] != EQUIPMENT_SLOT_OFFHAND || !bot->IsTwoHandUsed()) + return slots[i]; + + // if not found free and can swap return first appropriate from used + for (uint8 i = 0; i < 4; ++i) + if (slots[i] != NULL_SLOT && swap) + return slots[i]; + } + + // no free position + return NULL_SLOT; } \ No newline at end of file diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 698bfae4..d5fec1c9 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -10,6 +10,7 @@ #include "ChatFilter.h" #include "Common.h" #include "Event.h" +#include "Item.h" #include "PlayerbotAIBase.h" #include "PlayerbotAIConfig.h" #include "PlayerbotSecurity.h" @@ -451,6 +452,8 @@ class PlayerbotAI : public PlayerbotAIBase bool IsInRealGuild(); static std::vector dispel_whitelist; bool EqualLowercaseName(std::string s1, std::string s2); + InventoryResult CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading = true) const; + uint8 FindEquipSlot(ItemTemplate const* proto, uint32 slot, bool swap) const; private: static void _fillGearScoreData(Player* player, Item* item, std::vector* gearScore, uint32& twoHandScore, bool mixed = false); bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); diff --git a/src/strategy/values/ItemUsageValue.cpp b/src/strategy/values/ItemUsageValue.cpp index 4a447705..8c05803a 100644 --- a/src/strategy/values/ItemUsageValue.cpp +++ b/src/strategy/values/ItemUsageValue.cpp @@ -160,7 +160,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto) return ITEM_USAGE_NONE; uint16 dest; - InventoryResult result = bot->CanEquipItem(NULL_SLOT, dest, pItem, true, false); + InventoryResult result = botAI->CanEquipItem(NULL_SLOT, dest, pItem, true, false); pItem->RemoveFromUpdateQueueOf(bot); delete pItem;