Handle bot gear upgrades for multi-slot items (#695)

* 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.

* Update EquipAction.cpp
This commit is contained in:
Bobblybook
2024-11-08 03:18:59 +11:00
committed by GitHub
parent 7fa1ab36a3
commit 2d13373a8d
2 changed files with 140 additions and 80 deletions

View File

@@ -9,6 +9,7 @@
#include "ItemCountValue.h" #include "ItemCountValue.h"
#include "ItemUsageValue.h" #include "ItemUsageValue.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "StatsWeightCalculator.h"
bool EquipAction::Execute(Event event) bool EquipAction::Execute(Event event)
{ {
@@ -62,16 +63,17 @@ void EquipAction::EquipItem(Item* item)
{ {
uint8 bagIndex = item->GetBagSlot(); uint8 bagIndex = item->GetBagSlot();
uint8 slot = item->GetSlot(); 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); bot->SetAmmo(itemId);
} }
else else
{ {
bool equipedBag = false; bool equippedBag = false;
if (item->GetTemplate()->Class == ITEM_CLASS_CONTAINER) if (itemProto->Class == ITEM_CLASS_CONTAINER)
{ {
Bag* pBag = (Bag*)&item; Bag* pBag = (Bag*)&item;
uint8 newBagSlot = GetSmallestBagSlot(); uint8 newBagSlot = GetSmallestBagSlot();
@@ -80,20 +82,64 @@ void EquipAction::EquipItem(Item* item)
uint16 src = ((bagIndex << 8) | slot); uint16 src = ((bagIndex << 8) | slot);
uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | newBagSlot); uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | newBagSlot);
bot->SwapItem(src, dst); bot->SwapItem(src, dst);
equipedBag = true; equippedBag = true;
} }
} }
if (!equipedBag) if (!equippedBag)
{ {
WorldPacket packet(CMSG_AUTOEQUIP_ITEM, 2); uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true);
packet << bagIndex << slot; if (dstSlot == EQUIPMENT_SLOT_FINGER1 ||
bot->GetSession()->HandleAutoEquipItemOpcode(packet); 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; std::ostringstream out;
out << "equipping " << chat->FormatItem(item->GetTemplate()); out << "equipping " << chat->FormatItem(itemProto);
botAI->TellMaster(out); botAI->TellMaster(out);
} }

View File

@@ -205,12 +205,24 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
!sRandomItemMgr->CanEquipArmor(bot->getClass(), bot->GetLevel(), itemProto)) !sRandomItemMgr->CanEquipArmor(bot->getClass(), bot->GetLevel(), itemProto))
shouldEquip = false; shouldEquip = false;
Item* oldItem = bot->GetItemByPos(dest); uint8 possibleSlots = 1;
uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true);
if (dstSlot == EQUIPMENT_SLOT_FINGER1 ||
dstSlot == EQUIPMENT_SLOT_TRINKET1 ||
dstSlot == EQUIPMENT_SLOT_MAINHAND)
{
possibleSlots = 2;
}
// No item equiped for (uint8 i = 0; i < possibleSlots; i++)
{
bool shouldEquipInSlot = shouldEquip;
Item* oldItem = bot->GetItemByPos(dest + i);
// No item equipped
if (!oldItem) if (!oldItem)
{ {
if (shouldEquip) if (shouldEquipInSlot)
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
else else
{ {
@@ -225,7 +237,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
// uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); // uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId);
if (itemScore || oldScore) if (itemScore || oldScore)
{ {
shouldEquip = itemScore > oldScore * sPlayerbotAIConfig->equipUpgradeThreshold; shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig->equipUpgradeThreshold;
} }
} }
@@ -269,16 +281,18 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
bool oldItemIsBroken = bool oldItemIsBroken =
oldItem->GetUInt32Value(ITEM_FIELD_DURABILITY) == 0 && oldItem->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0; oldItem->GetUInt32Value(ITEM_FIELD_DURABILITY) == 0 && oldItem->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0;
if (itemProto->ItemId != oldItemProto->ItemId && (shouldEquip || !existingShouldEquip) && isBetter) if (itemProto->ItemId != oldItemProto->ItemId && (shouldEquipInSlot || !existingShouldEquip) && isBetter)
{ {
switch (itemProto->Class) switch (itemProto->Class)
{ {
case ITEM_CLASS_ARMOR: case ITEM_CLASS_ARMOR:
if (oldItemProto->SubClass <= itemProto->SubClass) 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) if (itemIsBroken && !oldItemIsBroken)
return ITEM_USAGE_BROKEN_EQUIP; return ITEM_USAGE_BROKEN_EQUIP;
else if (shouldEquip) else if (shouldEquipInSlot)
return ITEM_USAGE_REPLACE; return ITEM_USAGE_REPLACE;
else else
return ITEM_USAGE_BAD_EQUIP; return ITEM_USAGE_BAD_EQUIP;
@@ -289,7 +303,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
{ {
if (itemIsBroken && !oldItemIsBroken) if (itemIsBroken && !oldItemIsBroken)
return ITEM_USAGE_BROKEN_EQUIP; return ITEM_USAGE_BROKEN_EQUIP;
else if (shouldEquip) else if (shouldEquipInSlot)
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
else else
return ITEM_USAGE_BAD_EQUIP; return ITEM_USAGE_BAD_EQUIP;
@@ -300,7 +314,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
// Item is not better but current item is broken and new one is not. // Item is not better but current item is broken and new one is not.
if (oldItemIsBroken && !itemIsBroken) if (oldItemIsBroken && !itemIsBroken)
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
}
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
} }