/* * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it * and/or modify it under version 2 of the License, or (at your option), any later version. */ #include "EquipAction.h" #include "Event.h" #include "ItemCountValue.h" #include "ItemUsageValue.h" #include "Playerbots.h" #include "StatsWeightCalculator.h" bool EquipAction::Execute(Event event) { std::string const text = event.getParam(); ItemIds ids = chat->parseItems(text); EquipItems(ids); return true; } void EquipAction::EquipItems(ItemIds ids) { for (ItemIds::iterator i = ids.begin(); i != ids.end(); i++) { FindItemByIdVisitor visitor(*i); EquipItem(&visitor); } } // Return bagslot with smalest bag. uint8 EquipAction::GetSmallestBagSlot() { int8 curBag = 0; uint32 curSlots = 0; for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) { const Bag* const pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); if (pBag) { if (curBag > 0 && curSlots < pBag->GetBagSize()) continue; curBag = bag; curSlots = pBag->GetBagSize(); } else return bag; } return curBag; } void EquipAction::EquipItem(FindItemVisitor* visitor) { IterateItems(visitor); std::vector items = visitor->GetResult(); if (!items.empty()) EquipItem(*items.begin()); } void EquipAction::EquipItem(Item* item) { uint8 bagIndex = item->GetBagSlot(); uint8 slot = item->GetSlot(); const ItemTemplate* itemProto = item->GetTemplate(); uint32 itemId = itemProto->ItemId; uint8 invType = itemProto->InventoryType; // Ensure only the correct classes attempt to equip relics if (itemProto->SubClass == ITEM_SUBCLASS_ARMOR_IDOL && bot->getClass() != CLASS_DRUID) return; if (itemProto->SubClass == ITEM_SUBCLASS_ARMOR_SIGIL && bot->getClass() != CLASS_DEATH_KNIGHT) return; if (itemProto->SubClass == ITEM_SUBCLASS_ARMOR_LIBRAM && bot->getClass() != CLASS_PALADIN) return; if (itemProto->SubClass == ITEM_SUBCLASS_ARMOR_TOTEM && bot->getClass() != CLASS_SHAMAN) return; // Handle ammunition separately if (invType == INVTYPE_AMMO) { bot->SetAmmo(itemId); std::ostringstream out; out << "equipping " << chat->FormatItem(itemProto); botAI->TellMaster(out); return; } // Handle bags first bool equippedBag = false; if (itemProto->Class == ITEM_CLASS_CONTAINER) { // Attempt to equip as a bag 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) { // Ranged weapons aren't handled by the rest of the weapon equip logic // Handle them early here to avoid issues. if (invType == INVTYPE_RANGED || invType == INVTYPE_THROWN || invType == INVTYPE_RANGEDRIGHT) { WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2); ObjectGuid itemguid = item->GetGUID(); packet << itemguid << uint8(EQUIPMENT_SLOT_RANGED); bot->GetSession()->HandleAutoEquipItemSlotOpcode(packet); std::ostringstream out; out << "Equipping " << chat->FormatItem(itemProto) << " in ranged slot"; botAI->TellMaster(out); return; } 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)); bool canDualWieldOrTG = (canDualWield || 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 || 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) { 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) || (isTwoHander && !canTitanGrip) || (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 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)))) { // 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[1]) { // Both slots are full - pick the worst item to replace StatsWeightCalculator calc(bot); calc.SetItemSetBonus(false); calc.SetOverflowPenalty(false); 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); } } std::ostringstream out; out << "Equipping " << chat->FormatItem(itemProto); botAI->TellMaster(out); } bool EquipUpgradesAction::Execute(Event event) { if (!sPlayerbotAIConfig->autoEquipUpgradeLoot && !sRandomPlayerbotMgr->IsRandomBot(bot)) return false; if (event.GetSource() == "trade status") { WorldPacket p(event.getPacket()); p.rpos(0); uint32 status; p >> status; if (status != TRADE_STATUS_TRADE_ACCEPT) return false; } ListItemsVisitor visitor; IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); ItemIds items; for (std::map::iterator i = visitor.items.begin(); i != visitor.items.end(); ++i) { ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", i->first); if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP) { // LOG_INFO("playerbots", "Bot {} <{}> EquipUpgradesAction {} ({})", bot->GetGUID().ToString().c_str(), // bot->GetName().c_str(), i->first, usage == 1 ? "no item in slot" : usage == 2 ? "replace" : usage == 3 ? // "wrong item but empty slot" : ""); items.insert(i->first); } } EquipItems(items); return true; } bool EquipUpgradeAction::Execute(Event /*event*/) { ListItemsVisitor visitor; IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); ItemIds items; for (std::map::iterator i = visitor.items.begin(); i != visitor.items.end(); ++i) { ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", i->first); if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP) { // LOG_INFO("playerbots", "Bot {} <{}> EquipUpgradeAction item {} ({})", bot->GetGUID().ToString().c_str(), // bot->GetName().c_str(), i->first, usage == 1 ? "no item in slot" : usage == 2 ? "replace" : usage == 3 ? // "wrong item but empty slot" : ""); items.insert(i->first); } } EquipItems(items); return true; }