diff --git a/src/strategy/actions/EquipAction.cpp b/src/strategy/actions/EquipAction.cpp index 2fec22a4..19a39661 100644 --- a/src/strategy/actions/EquipAction.cpp +++ b/src/strategy/actions/EquipAction.cpp @@ -65,182 +65,206 @@ 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 << "moving " << chat->FormatItem(oldMHProto) << " to offhand"; + botAI->TellMaster(moveMsg); + } + + std::ostringstream out; + out << "equipping " << chat->FormatItem(itemProto) << " in main hand as an upgrade"; + 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 as an upgrade"; + 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); + // 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) + }; - bool isWeapon = (itemProto->Class == ITEM_CLASS_WEAPON); - bool have2HWeapon = false; - bool isValidTGWeapon = false; - - if (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON) + if (equippedItems[0]) { - isValidTGWeapon = (itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || - itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 || - itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2); - } - - // Check if we currently have a 2H weapon in main hand - { - Item* currentWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); - have2HWeapon = currentWeapon && currentWeapon->GetTemplate()->InventoryType == INVTYPE_2HWEAPON; - } - - bool canDualWieldOrTG = (bot->CanDualWield() || (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON)); - - // Run best-weapon logic only if it's a weapon and can dual wield or Titan Grip. - // Remove conditions that previously restricted running this logic based on dstSlot. - // We'll always check if this new weapon is better, and then decide final slot. - if (isWeapon && canDualWieldOrTG) - { - Item* mainHandItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); - Item* offHandItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); - - StatsWeightCalculator calculator(bot); - calculator.SetItemSetBonus(false); - calculator.SetOverflowPenalty(false); - - 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; - - bool canGoMain = (itemProto->InventoryType == INVTYPE_WEAPON || - itemProto->InventoryType == INVTYPE_WEAPONMAINHAND || - (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON)); - - bool canTGOff = false; - if (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON) + if (equippedItems[1]) { - canTGOff = isValidTGWeapon; - } + // Both slots are full - pick the worst item to replace + StatsWeightCalculator calc(bot); + calc.SetItemSetBonus(false); + calc.SetOverflowPenalty(false); - bool canGoOff = (itemProto->InventoryType == INVTYPE_WEAPON || - itemProto->InventoryType == INVTYPE_WEAPONOFFHAND || - canTGOff); + float firstItemScore = calc.CalculateItem(equippedItems[0]->GetTemplate()->ItemId); + float secondItemScore = calc.CalculateItem(equippedItems[1]->GetTemplate()->ItemId); - bool mainHandCanGoOff = false; - if (mainHandItem) - { - const ItemTemplate* mhProto = mainHandItem->GetTemplate(); - bool mhIsValidTG = (bot->CanTitanGrip() && mhProto->InventoryType == INVTYPE_2HWEAPON && - (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)); - } - - // First priority: If new weapon is better than main hand and can be equipped in main hand, - // do that. - if (canGoMain && newItemScore > mainHandScore && - ((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) || (bot->CanTitanGrip() && isValidTGWeapon))) - { - // Equip new weapon in main hand + // If the second slot is worse, place the new item there + if (firstItemScore > secondItemScore) { - WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2); - ObjectGuid newItemGuid = item->GetGUID(); - eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_MAINHAND); - bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket); - } - - // If we had a main hand item, consider moving it to offhand if beneficial - if (mainHandItem && mainHandCanGoOff && - (!offHandItem || mainHandScore > offHandScore)) - { - WorldPacket offhandPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2); - ObjectGuid oldMHGuid = mainHandItem->GetGUID(); - offhandPacket << oldMHGuid << uint8(EQUIPMENT_SLOT_OFFHAND); - bot->GetSession()->HandleAutoEquipItemSlotOpcode(offhandPacket); - } - - std::ostringstream out; - out << "equipping " << chat->FormatItem(itemProto) << " in main hand as an upgrade"; - botAI->TellMaster(out); - return; - } - // If not better than main hand, check if it's 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 as an upgrade"; - botAI->TellMaster(out); - return; - } - else - { - // No improvement, do nothing - return; - } - } - - // 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 && 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) - }; - - 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 equippedItemScore[2] = { - calculator.CalculateItem(equippedItems[0]->GetTemplate()->ItemId), - 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 in second slot, equip there 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; @@ -253,6 +277,7 @@ void EquipAction::EquipItem(Item* item) botAI->TellMaster(out); } + bool EquipUpgradesAction::Execute(Event event) { if (!sPlayerbotAIConfig->autoEquipUpgradeLoot && !sRandomPlayerbotMgr->IsRandomBot(bot))