diff --git a/src/strategy/actions/EquipAction.cpp b/src/strategy/actions/EquipAction.cpp index f9258d77..9ea2a797 100644 --- a/src/strategy/actions/EquipAction.cpp +++ b/src/strategy/actions/EquipAction.cpp @@ -65,88 +65,210 @@ void EquipAction::EquipItem(Item* item) uint8 slot = item->GetSlot(); const ItemTemplate* itemProto = item->GetTemplate(); uint32 itemId = itemProto->ItemId; + uint8 invType = itemProto->InventoryType; - if (itemProto->InventoryType == INVTYPE_AMMO) + // Handle ammunition separately + if (invType == INVTYPE_AMMO) { bot->SetAmmo(itemId); + std::ostringstream out; + out << "equipping " << chat->FormatItem(itemProto); + botAI->TellMaster(out); + return; } - else + + // Handle bags first + bool equippedBag = false; + if (itemProto->Class == ITEM_CLASS_CONTAINER) { - bool equippedBag = false; - if (itemProto->Class == ITEM_CLASS_CONTAINER) + // Attempt to equip as a bag + Bag* pBag = reinterpret_cast(item); + uint8 newBagSlot = GetSmallestBagSlot(); + if (newBagSlot > 0) { - Bag* pBag = (Bag*)&item; - uint8 newBagSlot = GetSmallestBagSlot(); - if (newBagSlot > 0) + uint16 src = ((bagIndex << 8) | slot); + uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | newBagSlot); + bot->SwapItem(src, dst); + equippedBag = true; + } + } + + // If we didn't equip as a bag, try to equip as gear + if (!equippedBag) + { + uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); + + // Check if the item is a weapon and whether the bot can dual wield or use Titan Grip + bool isWeapon = (itemProto->Class == ITEM_CLASS_WEAPON); + bool canTitanGrip = bot->CanTitanGrip(); + bool canDualWield = bot->CanDualWield(); + + bool isTwoHander = (invType == INVTYPE_2HWEAPON); + bool isValidTGWeapon = false; + if (canTitanGrip && isTwoHander) + { + // Titan Grip-valid 2H weapon subclasses: Axe2, Mace2, Sword2 + isValidTGWeapon = (itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || + itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 || + itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2); + } + + // Check if the main hand currently has a 2H weapon equipped + Item* currentMHItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + bool have2HWeaponEquipped = (currentMHItem && currentMHItem->GetTemplate()->InventoryType == INVTYPE_2HWEAPON); + + bool canDualWieldOrTG = (canDualWield || (canTitanGrip && isTwoHander)); + + // If this is a weapon and we can dual wield or Titan Grip, check if we can improve main/off-hand setup + if (isWeapon && canDualWieldOrTG) + { + // Fetch current main hand and offhand items + Item* mainHandItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + Item* offHandItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + + // Set up the stats calculator once and reuse results for performance + StatsWeightCalculator calculator(bot); + calculator.SetItemSetBonus(false); + calculator.SetOverflowPenalty(false); + + // Calculate item scores once and store them + float newItemScore = calculator.CalculateItem(itemId); + float mainHandScore = mainHandItem ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId) : 0.0f; + float offHandScore = offHandItem ? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId) : 0.0f; + + // Determine where this weapon can go + bool canGoMain = (invType == INVTYPE_WEAPON || + invType == INVTYPE_WEAPONMAINHAND || + (canTitanGrip && isTwoHander)); + + bool canTGOff = false; + if (canTitanGrip && isTwoHander && isValidTGWeapon) + canTGOff = true; + + bool canGoOff = (invType == INVTYPE_WEAPON || + invType == INVTYPE_WEAPONOFFHAND || + canTGOff); + + // Check if the main hand item can go to offhand if needed + bool mainHandCanGoOff = false; + if (mainHandItem) { - uint16 src = ((bagIndex << 8) | slot); - uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | newBagSlot); - bot->SwapItem(src, dst); - equippedBag = true; + const ItemTemplate* mhProto = mainHandItem->GetTemplate(); + bool mhIsValidTG = false; + if (canTitanGrip && mhProto->InventoryType == INVTYPE_2HWEAPON) + { + mhIsValidTG = (mhProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || + mhProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 || + mhProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2); + } + + mainHandCanGoOff = (mhProto->InventoryType == INVTYPE_WEAPON || + mhProto->InventoryType == INVTYPE_WEAPONOFFHAND || + (mhProto->InventoryType == INVTYPE_2HWEAPON && mhIsValidTG)); + } + + // Priority 1: Replace main hand if the new weapon is strictly better + // and if conditions allow (e.g. no conflicting 2H logic) + bool betterThanMH = (newItemScore > mainHandScore); + bool mhConditionOK = ((invType != INVTYPE_2HWEAPON && !have2HWeaponEquipped) || + (canTitanGrip && isValidTGWeapon)); + + if (canGoMain && betterThanMH && mhConditionOK) + { + // Equip new weapon in main hand + { + WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2); + ObjectGuid newItemGuid = item->GetGUID(); + eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_MAINHAND); + bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket); + } + + // Try moving old main hand weapon to offhand if beneficial + if (mainHandItem && mainHandCanGoOff && (!offHandItem || mainHandScore > offHandScore)) + { + const ItemTemplate* oldMHProto = mainHandItem->GetTemplate(); + + WorldPacket offhandPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2); + ObjectGuid oldMHGuid = mainHandItem->GetGUID(); + offhandPacket << oldMHGuid << uint8(EQUIPMENT_SLOT_OFFHAND); + bot->GetSession()->HandleAutoEquipItemSlotOpcode(offhandPacket); + + std::ostringstream moveMsg; + moveMsg << "Main hand upgrade found. Moving " << chat->FormatItem(oldMHProto) << " to offhand"; + botAI->TellMaster(moveMsg); + } + + std::ostringstream out; + out << "Equipping " << chat->FormatItem(itemProto) << " in main hand"; + botAI->TellMaster(out); + return; + } + + // Priority 2: If not better than main hand, check if better than offhand + else if (canGoOff && newItemScore > offHandScore) + { + // Equip in offhand + WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2); + ObjectGuid newItemGuid = item->GetGUID(); + eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_OFFHAND); + bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket); + + std::ostringstream out; + out << "Equipping " << chat->FormatItem(itemProto) << " in offhand"; + botAI->TellMaster(out); + return; + } + else + { + // No improvement, do nothing + return; } } - if (!equippedBag) + // If not a special dual-wield/TG scenario or no improvement found, fall back to original logic + if (dstSlot == EQUIPMENT_SLOT_FINGER1 || + dstSlot == EQUIPMENT_SLOT_TRINKET1 || + (dstSlot == EQUIPMENT_SLOT_MAINHAND && canDualWield && + ((invType != INVTYPE_2HWEAPON && !have2HWeaponEquipped) || (canTitanGrip && isValidTGWeapon)))) { - 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 && bot->CanDualWield() && - ((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) || (bot->CanTitanGrip() && isValidTGWeapon)))) - { - Item* const equippedItems[2] = { - bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot), - bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot + 1) - }; + // Handle ring/trinket dual-slot logic + Item* const equippedItems[2] = { + bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot), + bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot + 1) + }; - if (equippedItems[0]) + if (equippedItems[0]) + { + if (equippedItems[1]) { - 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) - }; + // Both slots are full - pick the worst item to replace + StatsWeightCalculator calc(bot); + calc.SetItemSetBonus(false); + calc.SetOverflowPenalty(false); - // 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 + float firstItemScore = calc.CalculateItem(equippedItems[0]->GetTemplate()->ItemId); + float secondItemScore = calc.CalculateItem(equippedItems[1]->GetTemplate()->ItemId); + + // If the second slot is worse, place the new item there + if (firstItemScore > secondItemScore) { dstSlot++; } } + else + { + // Second slot empty, use it + dstSlot++; + } } + } + // Equip the item in the chosen slot + { 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); } } @@ -155,6 +277,7 @@ void EquipAction::EquipItem(Item* item) botAI->TellMaster(out); } + bool EquipUpgradesAction::Execute(Event event) { if (!sPlayerbotAIConfig->autoEquipUpgradeLoot && !sRandomPlayerbotMgr->IsRandomBot(bot)) diff --git a/src/strategy/values/ItemUsageValue.cpp b/src/strategy/values/ItemUsageValue.cpp index 5c5e38e1..fbf9ccda 100644 --- a/src/strategy/values/ItemUsageValue.cpp +++ b/src/strategy/values/ItemUsageValue.cpp @@ -256,20 +256,42 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto) // 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))) + + // 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;