From 2ec5628d95d94726f285992bc2b88a937e8ad0ed Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Thu, 7 Nov 2024 22:59:35 +1100 Subject: [PATCH 1/3] Handle bot gear upgrades for multi-slot items Switch to using opcode "CMSG_AUTOEQUIP_ITEM_SLOT" to equip items to specific slots, rather than "right clicking" item upgrades. Fixes an issue with rings, trinkets and offhand weapons where the bot would only ever upgrade their first slot. Also evaluate the above item types for equipping in both slots rather than just comparing to the first item. --- src/strategy/actions/EquipAction.cpp | 66 +++++++++-- src/strategy/values/ItemUsageValue.cpp | 154 ++++++++++++++----------- 2 files changed, 140 insertions(+), 80 deletions(-) diff --git a/src/strategy/actions/EquipAction.cpp b/src/strategy/actions/EquipAction.cpp index 85591cf8..b74e6c3c 100644 --- a/src/strategy/actions/EquipAction.cpp +++ b/src/strategy/actions/EquipAction.cpp @@ -9,6 +9,7 @@ #include "ItemCountValue.h" #include "ItemUsageValue.h" #include "Playerbots.h" +#include "StatsWeightCalculator.h" bool EquipAction::Execute(Event event) { @@ -62,16 +63,17 @@ void EquipAction::EquipItem(Item* item) { uint8 bagIndex = item->GetBagSlot(); uint8 slot = item->GetSlot(); - uint32 itemId = item->GetTemplate()->ItemId; + const ItemTemplate* itemProto = item->GetTemplate(); + uint32 itemId = itemProto->ItemId; - if (item->GetTemplate()->InventoryType == INVTYPE_AMMO) + if (itemProto->InventoryType == INVTYPE_AMMO) { bot->SetAmmo(itemId); } else { - bool equipedBag = false; - if (item->GetTemplate()->Class == ITEM_CLASS_CONTAINER) + bool equippedBag = false; + if (itemProto->Class == ITEM_CLASS_CONTAINER) { Bag* pBag = (Bag*)&item; uint8 newBagSlot = GetSmallestBagSlot(); @@ -80,20 +82,64 @@ void EquipAction::EquipItem(Item* item) uint16 src = ((bagIndex << 8) | slot); uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | newBagSlot); bot->SwapItem(src, dst); - equipedBag = true; + equippedBag = true; } } - if (!equipedBag) + if (!equippedBag) { - WorldPacket packet(CMSG_AUTOEQUIP_ITEM, 2); - packet << bagIndex << slot; - bot->GetSession()->HandleAutoEquipItemOpcode(packet); + uint8 dstSlot = botAI->FindEquipSlot(item->GetTemplate(), NULL_SLOT, true); + if (dstSlot == EQUIPMENT_SLOT_FINGER1 || + dstSlot == EQUIPMENT_SLOT_TRINKET1 || + dstSlot == EQUIPMENT_SLOT_MAINHAND) + { + Item* const equippedItems[2] = { + bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot), + bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot + 1) + }; + + if (equippedItems[0]) + { + if (equippedItems[1]) + { + // Both slots are full - determine worst item to replace + StatsWeightCalculator calculator(bot); + calculator.SetItemSetBonus(false); + calculator.SetOverflowPenalty(false); + + // float newItemScore = calculator.CalculateItem(itemId); + float equippedItemScore[2] = { + equippedItemScore[0] = calculator.CalculateItem(equippedItems[0]->GetTemplate()->ItemId), + equippedItemScore[1] = calculator.CalculateItem(equippedItems[1]->GetTemplate()->ItemId) + }; + + // Second item is worse than first, equip candidate item in second slot + if (equippedItemScore[0] > equippedItemScore[1]) + { + dstSlot++; + } + } + else // No item equipped in slot 2, equip in that slot instead of replacing first item + { + dstSlot++; + } + } + } + + WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2); + ObjectGuid itemguid = item->GetGUID(); + + packet << itemguid << dstSlot; + bot->GetSession()->HandleAutoEquipItemSlotOpcode(packet); + + // WorldPacket packet(CMSG_AUTOEQUIP_ITEM, 2); + // packet << bagIndex << slot; + // bot->GetSession()->HandleAutoEquipItemOpcode(packet); } } std::ostringstream out; - out << "equipping " << chat->FormatItem(item->GetTemplate()); + out << "equipping " << chat->FormatItem(itemProto); botAI->TellMaster(out); } diff --git a/src/strategy/values/ItemUsageValue.cpp b/src/strategy/values/ItemUsageValue.cpp index 9c6adbf9..0855f707 100644 --- a/src/strategy/values/ItemUsageValue.cpp +++ b/src/strategy/values/ItemUsageValue.cpp @@ -205,102 +205,116 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto) !sRandomItemMgr->CanEquipArmor(bot->getClass(), bot->GetLevel(), itemProto)) shouldEquip = false; - Item* oldItem = bot->GetItemByPos(dest); - - // No item equiped - if (!oldItem) + uint8 possibleSlots = 1; + uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); + if (dstSlot == EQUIPMENT_SLOT_FINGER1 || + dstSlot == EQUIPMENT_SLOT_TRINKET1 || + dstSlot == EQUIPMENT_SLOT_MAINHAND) { - if (shouldEquip) - return ITEM_USAGE_EQUIP; - else - { - return ITEM_USAGE_BAD_EQUIP; - } + possibleSlots = 2; } - ItemTemplate const* oldItemProto = oldItem->GetTemplate(); - float oldScore = calculator.CalculateItem(oldItemProto->ItemId); - if (oldItem) + for (uint8 i = 0; i < possibleSlots; i++) { - // uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); - if (itemScore || oldScore) + bool shouldEquipInSlot = shouldEquip; + Item* oldItem = bot->GetItemByPos(dest + i); + + // No item equipped + if (!oldItem) { - shouldEquip = itemScore > oldScore * sPlayerbotAIConfig->equipUpgradeThreshold; + if (shouldEquipInSlot) + return ITEM_USAGE_EQUIP; + else + { + return ITEM_USAGE_BAD_EQUIP; + } } - } - // Bigger quiver - if (itemProto->Class == ITEM_CLASS_QUIVER) - { - if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots) + ItemTemplate const* oldItemProto = oldItem->GetTemplate(); + float oldScore = calculator.CalculateItem(oldItemProto->ItemId); + if (oldItem) { - return ITEM_USAGE_EQUIP; + // uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); + if (itemScore || oldScore) + { + shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig->equipUpgradeThreshold; + } } - else + + // Bigger quiver + if (itemProto->Class == ITEM_CLASS_QUIVER) { - return ITEM_USAGE_NONE; + 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; + 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; + 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); + // 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; + // 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 && (shouldEquip || !existingShouldEquip) && isBetter) - { - switch (itemProto->Class) + 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) { - case ITEM_CLASS_ARMOR: - if (oldItemProto->SubClass <= itemProto->SubClass) + 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 (shouldEquip) - return ITEM_USAGE_REPLACE; + else if (shouldEquipInSlot) + return ITEM_USAGE_EQUIP; else return ITEM_USAGE_BAD_EQUIP; - - break; } - default: - { - if (itemIsBroken && !oldItemIsBroken) - return ITEM_USAGE_BROKEN_EQUIP; - else if (shouldEquip) - 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; } - - // 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; } From 33f4ef63b6b08aa3e88792e549414626b0691c0a Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Thu, 7 Nov 2024 23:10:13 +1100 Subject: [PATCH 2/3] Update EquipAction.cpp --- src/strategy/actions/EquipAction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategy/actions/EquipAction.cpp b/src/strategy/actions/EquipAction.cpp index b74e6c3c..f4437e2c 100644 --- a/src/strategy/actions/EquipAction.cpp +++ b/src/strategy/actions/EquipAction.cpp @@ -88,7 +88,7 @@ void EquipAction::EquipItem(Item* item) if (!equippedBag) { - uint8 dstSlot = botAI->FindEquipSlot(item->GetTemplate(), NULL_SLOT, true); + uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1 || dstSlot == EQUIPMENT_SLOT_MAINHAND) From 71b7844fef52475bd6959e7c3ab3adba6dfe62d4 Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Fri, 8 Nov 2024 20:35:31 +1100 Subject: [PATCH 3/3] Offhand equip bugfix Prevent caster bots and others who cannot dual-wield from attempting to equip weapons in their offhand --- src/strategy/actions/EquipAction.cpp | 14 +++++++++++++- src/strategy/values/ItemUsageValue.cpp | 21 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/strategy/actions/EquipAction.cpp b/src/strategy/actions/EquipAction.cpp index f4437e2c..f9258d77 100644 --- a/src/strategy/actions/EquipAction.cpp +++ b/src/strategy/actions/EquipAction.cpp @@ -89,9 +89,21 @@ void EquipAction::EquipItem(Item* item) if (!equippedBag) { uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); + 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; + isValidTGWeapon = itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || + itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 || + itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2; + } + if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1 || - dstSlot == EQUIPMENT_SLOT_MAINHAND) + (dstSlot == EQUIPMENT_SLOT_MAINHAND && bot->CanDualWield() && + ((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) || (bot->CanTitanGrip() && isValidTGWeapon)))) { Item* const equippedItems[2] = { bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot), diff --git a/src/strategy/values/ItemUsageValue.cpp b/src/strategy/values/ItemUsageValue.cpp index 0855f707..459340c3 100644 --- a/src/strategy/values/ItemUsageValue.cpp +++ b/src/strategy/values/ItemUsageValue.cpp @@ -207,13 +207,28 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto) uint8 possibleSlots = 1; uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); - if (dstSlot == EQUIPMENT_SLOT_FINGER1 || - dstSlot == EQUIPMENT_SLOT_TRINKET1 || - dstSlot == EQUIPMENT_SLOT_MAINHAND) + 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; + isValidTGWeapon = itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || + itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 || + itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2; + + if (bot->CanDualWield() && ((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) || (bot->CanTitanGrip() && isValidTGWeapon))) + { + possibleSlots = 2; + } + } + for (uint8 i = 0; i < possibleSlots; i++) { bool shouldEquipInSlot = shouldEquip;