Files
mod-playerbots/src/strategy/actions/EquipAction.cpp
Type1Error f365b79e96 fix(#1276) Prevent bots from equipping relics intended for other classes (#1277)
Fixes #1276

This change adds logic to detect if an item is a relic and ensures it can only be equipped by bots of the corresponding class:

- Idols are restricted to druids
- Librams are restricted to paladins
- Sigils are restricted to death knights
- Totems are restricted to shamans

Fixes a bug where bots would attempt to equip relics regardless of class restrictions.

Additionally, this commit resolves two unrelated build warnings found in the same file.
2025-05-08 23:37:33 +02:00

366 lines
14 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;
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<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 {} <{}> 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<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 {} <{}> 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;
}