mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 09:07:19 +00:00
Description of Changes:
Implemented logic to ensure the strongest weapon is always placed in the main hand for dual-wielding or Titan Grip-capable bots.
When equipping a new weapon, the code now compares the new weapon’s score with the currently equipped main-hand and off-hand weapons.
If the new weapon is the strongest, it goes into the main hand. The previous main-hand weapon may be moved to the off-hand if it is allowed (e.g., not a main-hand-only weapon) and provides a performance improvement.
Titan Grip conditions are accounted for, allowing valid two-handed weapons (2H axes, maces, swords) to be placed in the off-hand as well.
315 lines
13 KiB
C++
315 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, 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<Item*> 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;
|
|
|
|
if (itemProto->InventoryType == INVTYPE_AMMO)
|
|
{
|
|
bot->SetAmmo(itemId);
|
|
}
|
|
else
|
|
{
|
|
bool equippedBag = false;
|
|
if (itemProto->Class == ITEM_CLASS_CONTAINER)
|
|
{
|
|
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 (!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;
|
|
}
|
|
|
|
// New logic: Ensure strongest weapon is in main hand for dual wield/Titan Grip scenarios
|
|
bool isWeapon = (itemProto->Class == ITEM_CLASS_WEAPON);
|
|
bool canDualWieldOrTG = (bot->CanDualWield() || (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON));
|
|
if (isWeapon && canDualWieldOrTG && dstSlot == EQUIPMENT_SLOT_MAINHAND &&
|
|
((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) || (bot->CanTitanGrip() && isValidTGWeapon)))
|
|
{
|
|
// Compare current mainhand and offhand weapons to the new item
|
|
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;
|
|
|
|
// Determine if new weapon is best
|
|
bool newIsBest = (newItemScore > mainHandScore && newItemScore > offHandScore);
|
|
bool betterThanOff = (newItemScore > offHandScore) && !newIsBest;
|
|
|
|
// Check that the new item can go main hand
|
|
bool canGoMain = (itemProto->InventoryType == INVTYPE_WEAPON ||
|
|
itemProto->InventoryType == INVTYPE_WEAPONMAINHAND ||
|
|
(bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON));
|
|
|
|
// Check Titan Grip offhand eligibility
|
|
bool canTGOff = false;
|
|
if (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON)
|
|
{
|
|
// Titan Grip allows 2H axes, maces, swords in offhand
|
|
canTGOff = (isValidTGWeapon);
|
|
}
|
|
|
|
// Check that the new item can go off hand
|
|
bool canGoOff = (itemProto->InventoryType == INVTYPE_WEAPON ||
|
|
itemProto->InventoryType == INVTYPE_WEAPONOFFHAND ||
|
|
canTGOff);
|
|
|
|
// Check what the main hand item can do if we move it
|
|
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));
|
|
}
|
|
|
|
// If new weapon is best of all three, put it in main hand
|
|
if (newIsBest && canGoMain)
|
|
{
|
|
// 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);
|
|
}
|
|
|
|
// If there was a main hand item, try to move it to offhand if it improves offhand
|
|
if (mainHandItem && mainHandCanGoOff)
|
|
{
|
|
// Only move if it's better than the current offhand or offhand is empty
|
|
if (!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) << " as the best weapon in main hand";
|
|
botAI->TellMaster(out);
|
|
return;
|
|
}
|
|
else if (betterThanOff && canGoOff)
|
|
{
|
|
// Equip the new weapon 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
|
|
{
|
|
// Not an improvement or can't place it properly, do nothing
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Existing logic below - do not remove or modify existing comments
|
|
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 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(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<uint32, uint32>::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 {} <{}> auto equips 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;
|
|
}
|
|
|
|
bool EquipUpgradeAction::Execute(Event event)
|
|
{
|
|
ListItemsVisitor visitor;
|
|
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
|
|
|
|
ItemIds items;
|
|
for (std::map<uint32, uint32>::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)
|
|
{
|
|
items.insert(i->first);
|
|
}
|
|
}
|
|
EquipItems(items);
|
|
return true;
|
|
}
|