Skip to content

Commit

Permalink
Merge pull request liyunfan1223#166 from liyunfan1223/fix_offhand
Browse files Browse the repository at this point in the history
Fix occasionally unequip offhand
  • Loading branch information
liyunfan1223 authored Mar 28, 2024
2 parents f03e12c + 4047fe4 commit e6295e6
Show file tree
Hide file tree
Showing 3 changed files with 312 additions and 1 deletion.
308 changes: 308 additions & 0 deletions src/PlayerbotAI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "PlayerbotMgr.h"
#include "PointMovementGenerator.h"
#include "PositionValue.h"
#include "ScriptMgr.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
#include "SocialMgr.h"
Expand Down Expand Up @@ -4387,4 +4388,311 @@ bool PlayerbotAI::EqualLowercaseName(std::string s1, std::string s2)
}
}
return true;
}

InventoryResult PlayerbotAI::CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading) const
{
dest = 0;
if (pItem)
{
LOG_DEBUG("entities.player.items", "STORAGE: CanEquipItem slot = {}, item = {}, count = {}", slot, pItem->GetEntry(), pItem->GetCount());
ItemTemplate const* pProto = pItem->GetTemplate();
if (pProto)
{
if (!sScriptMgr->CanEquipItem(bot, slot, dest, pItem, swap, not_loading))
return EQUIP_ERR_CANT_DO_RIGHT_NOW;

// item used
if (pItem->m_lootGenerated)
return EQUIP_ERR_ALREADY_LOOTED;

if (pItem->IsBindedNotWith(bot))
return EQUIP_ERR_DONT_OWN_THAT_ITEM;

// check count of items (skip for auto move for same player from bank)
InventoryResult res = bot->CanTakeMoreSimilarItems(pItem);
if (res != EQUIP_ERR_OK)
return res;

ScalingStatDistributionEntry const* ssd = pProto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(pProto->ScalingStatDistribution) : 0;
// check allowed level (extend range to upper values if MaxLevel more or equal max player level, this let GM set high level with 1...max range items)
if (ssd && ssd->MaxLevel < DEFAULT_MAX_LEVEL && ssd->MaxLevel < bot->GetLevel())
return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;

uint8 eslot = FindEquipSlot(pProto, slot, swap);
if (eslot == NULL_SLOT)
return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;

// Xinef: dont allow to equip items on disarmed slot
if (!bot->CanUseAttackType(bot->GetAttackBySlot(eslot)))
return EQUIP_ERR_NOT_WHILE_DISARMED;

res = bot->CanUseItem(pItem, not_loading);
if (res != EQUIP_ERR_OK)
return res;

if (!swap && bot->GetItemByPos(INVENTORY_SLOT_BAG_0, eslot))
return EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE;

// if we are swapping 2 equiped items, CanEquipUniqueItem check
// should ignore the item we are trying to swap, and not the
// destination item. CanEquipUniqueItem should ignore destination
// item only when we are swapping weapon from bag
uint8 ignore = uint8(NULL_SLOT);
switch (eslot)
{
case EQUIPMENT_SLOT_MAINHAND:
ignore = EQUIPMENT_SLOT_OFFHAND;
break;
case EQUIPMENT_SLOT_OFFHAND:
ignore = EQUIPMENT_SLOT_MAINHAND;
break;
case EQUIPMENT_SLOT_FINGER1:
ignore = EQUIPMENT_SLOT_FINGER2;
break;
case EQUIPMENT_SLOT_FINGER2:
ignore = EQUIPMENT_SLOT_FINGER1;
break;
case EQUIPMENT_SLOT_TRINKET1:
ignore = EQUIPMENT_SLOT_TRINKET2;
break;
case EQUIPMENT_SLOT_TRINKET2:
ignore = EQUIPMENT_SLOT_TRINKET1;
break;
}

if (ignore == uint8(NULL_SLOT) || pItem != bot->GetItemByPos(INVENTORY_SLOT_BAG_0, ignore))
ignore = eslot;

InventoryResult res2 = bot->CanEquipUniqueItem(pItem, swap ? ignore : uint8(NULL_SLOT));
if (res2 != EQUIP_ERR_OK)
return res2;

// check unique-equipped special item classes
if (pProto->Class == ITEM_CLASS_QUIVER)
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
if (Item* pBag = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
if (pBag != pItem)
if (ItemTemplate const* pBagProto = pBag->GetTemplate())
if (pBagProto->Class == pProto->Class && (!swap || pBag->GetSlot() != eslot))
return (pBagProto->SubClass == ITEM_SUBCLASS_AMMO_POUCH)
? EQUIP_ERR_CAN_EQUIP_ONLY1_AMMOPOUCH
: EQUIP_ERR_CAN_EQUIP_ONLY1_QUIVER;

uint32 type = pProto->InventoryType;

if (eslot == EQUIPMENT_SLOT_OFFHAND)
{
// Do not allow polearm to be equipped in the offhand (rare case for the only 1h polearm 41750)
// xinef: same for fishing poles
if (type == INVTYPE_WEAPON && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || pProto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE))
return EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT;

else if (type == INVTYPE_WEAPON || type == INVTYPE_WEAPONOFFHAND)
{
if (!bot->CanDualWield())
return EQUIP_ERR_CANT_DUAL_WIELD;
}
else if (type == INVTYPE_2HWEAPON)
{
if (!bot->CanDualWield() || !bot->CanTitanGrip())
return EQUIP_ERR_CANT_DUAL_WIELD;
}

if (bot->IsTwoHandUsed())
return EQUIP_ERR_CANT_EQUIP_WITH_TWOHANDED;
}

// equip two-hand weapon case (with possible unequip 2 items)
if (type == INVTYPE_2HWEAPON)
{
if (eslot == EQUIPMENT_SLOT_OFFHAND)
{
if (!bot->CanTitanGrip())
return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
}
else if (eslot != EQUIPMENT_SLOT_MAINHAND)
return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;

if (!bot->CanTitanGrip())
{
// offhand item must can be stored in inventory for offhand item and it also must be unequipped
Item* offItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
ItemPosCountVec off_dest;
if (offItem && (!not_loading ||
bot->CanUnequipItem(uint16(INVENTORY_SLOT_BAG_0) << 8 | EQUIPMENT_SLOT_OFFHAND, false) != EQUIP_ERR_OK ||
bot->CanStoreItem(NULL_BAG, NULL_SLOT, off_dest, offItem, false) != EQUIP_ERR_OK))
return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_INVENTORY_FULL;
}
}
dest = ((INVENTORY_SLOT_BAG_0 << 8) | eslot);
return EQUIP_ERR_OK;
}
}

return !swap ? EQUIP_ERR_ITEM_NOT_FOUND : EQUIP_ERR_ITEMS_CANT_BE_SWAPPED;
}

uint8 PlayerbotAI::FindEquipSlot(ItemTemplate const* proto, uint32 slot, bool swap) const
{
uint8 slots[4];
slots[0] = NULL_SLOT;
slots[1] = NULL_SLOT;
slots[2] = NULL_SLOT;
slots[3] = NULL_SLOT;
switch (proto->InventoryType)
{
case INVTYPE_HEAD:
slots[0] = EQUIPMENT_SLOT_HEAD;
break;
case INVTYPE_NECK:
slots[0] = EQUIPMENT_SLOT_NECK;
break;
case INVTYPE_SHOULDERS:
slots[0] = EQUIPMENT_SLOT_SHOULDERS;
break;
case INVTYPE_BODY:
slots[0] = EQUIPMENT_SLOT_BODY;
break;
case INVTYPE_CHEST:
case INVTYPE_ROBE:
slots[0] = EQUIPMENT_SLOT_CHEST;
break;
case INVTYPE_WAIST:
slots[0] = EQUIPMENT_SLOT_WAIST;
break;
case INVTYPE_LEGS:
slots[0] = EQUIPMENT_SLOT_LEGS;
break;
case INVTYPE_FEET:
slots[0] = EQUIPMENT_SLOT_FEET;
break;
case INVTYPE_WRISTS:
slots[0] = EQUIPMENT_SLOT_WRISTS;
break;
case INVTYPE_HANDS:
slots[0] = EQUIPMENT_SLOT_HANDS;
break;
case INVTYPE_FINGER:
slots[0] = EQUIPMENT_SLOT_FINGER1;
slots[1] = EQUIPMENT_SLOT_FINGER2;
break;
case INVTYPE_TRINKET:
slots[0] = EQUIPMENT_SLOT_TRINKET1;
slots[1] = EQUIPMENT_SLOT_TRINKET2;
break;
case INVTYPE_CLOAK:
slots[0] = EQUIPMENT_SLOT_BACK;
break;
case INVTYPE_WEAPON:
{
slots[0] = EQUIPMENT_SLOT_MAINHAND;

// suggest offhand slot only if know dual wielding
// (this will be replace mainhand weapon at auto equip instead unwonted "you don't known dual wielding" ...
if (bot->CanDualWield())
slots[1] = EQUIPMENT_SLOT_OFFHAND;
break;
}
case INVTYPE_SHIELD:
case INVTYPE_WEAPONOFFHAND:
case INVTYPE_HOLDABLE:
slots[0] = EQUIPMENT_SLOT_OFFHAND;
break;
case INVTYPE_RANGED:
case INVTYPE_RANGEDRIGHT:
case INVTYPE_THROWN:
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
case INVTYPE_2HWEAPON:
slots[0] = EQUIPMENT_SLOT_MAINHAND;
if (Item* mhWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND))
{
if (ItemTemplate const* mhWeaponProto = mhWeapon->GetTemplate())
{
if (mhWeaponProto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || mhWeaponProto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF)
{
bot->AutoUnequipOffhandIfNeed(true);
break;
}
}
}

if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND))
{
if (proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF)
{
break;
}
}
if (bot->CanDualWield() && bot->CanTitanGrip() && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_FISHING_POLE)
slots[1] = EQUIPMENT_SLOT_OFFHAND;
break;
case INVTYPE_TABARD:
slots[0] = EQUIPMENT_SLOT_TABARD;
break;
case INVTYPE_WEAPONMAINHAND:
slots[0] = EQUIPMENT_SLOT_MAINHAND;
break;
case INVTYPE_BAG:
slots[0] = INVENTORY_SLOT_BAG_START + 0;
slots[1] = INVENTORY_SLOT_BAG_START + 1;
slots[2] = INVENTORY_SLOT_BAG_START + 2;
slots[3] = INVENTORY_SLOT_BAG_START + 3;
break;
case INVTYPE_RELIC:
{
switch (proto->SubClass)
{
case ITEM_SUBCLASS_ARMOR_LIBRAM:
if (bot->IsClass(CLASS_PALADIN, CLASS_CONTEXT_EQUIP_RELIC))
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
case ITEM_SUBCLASS_ARMOR_IDOL:
if (bot->IsClass(CLASS_DRUID, CLASS_CONTEXT_EQUIP_RELIC))
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
case ITEM_SUBCLASS_ARMOR_TOTEM:
if (bot->IsClass(CLASS_SHAMAN, CLASS_CONTEXT_EQUIP_RELIC))
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
case ITEM_SUBCLASS_ARMOR_MISC:
if (bot->IsClass(CLASS_WARLOCK, CLASS_CONTEXT_EQUIP_RELIC))
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
case ITEM_SUBCLASS_ARMOR_SIGIL:
if (bot->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_EQUIP_RELIC))
slots[0] = EQUIPMENT_SLOT_RANGED;
break;
}
break;
}
default:
return NULL_SLOT;
}

if (slot != NULL_SLOT)
{
if (swap || !bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
for (uint8 i = 0; i < 4; ++i)
if (slots[i] == slot)
return slot;
}
else
{
// search free slot at first
for (uint8 i = 0; i < 4; ++i)
if (slots[i] != NULL_SLOT && !bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slots[i]))
// in case 2hand equipped weapon (without titan grip) offhand slot empty but not free
if (slots[i] != EQUIPMENT_SLOT_OFFHAND || !bot->IsTwoHandUsed())
return slots[i];

// if not found free and can swap return first appropriate from used
for (uint8 i = 0; i < 4; ++i)
if (slots[i] != NULL_SLOT && swap)
return slots[i];
}

// no free position
return NULL_SLOT;
}
3 changes: 3 additions & 0 deletions src/PlayerbotAI.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "ChatFilter.h"
#include "Common.h"
#include "Event.h"
#include "Item.h"
#include "PlayerbotAIBase.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotSecurity.h"
Expand Down Expand Up @@ -451,6 +452,8 @@ class PlayerbotAI : public PlayerbotAIBase
bool IsInRealGuild();
static std::vector<std::string> dispel_whitelist;
bool EqualLowercaseName(std::string s1, std::string s2);
InventoryResult CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading = true) const;
uint8 FindEquipSlot(ItemTemplate const* proto, uint32 slot, bool swap) const;
private:
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore, bool mixed = false);
bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
Expand Down
2 changes: 1 addition & 1 deletion src/strategy/values/ItemUsageValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
return ITEM_USAGE_NONE;

uint16 dest;
InventoryResult result = bot->CanEquipItem(NULL_SLOT, dest, pItem, true, false);
InventoryResult result = botAI->CanEquipItem(NULL_SLOT, dest, pItem, true, false);
pItem->RemoveFromUpdateQueueOf(bot);
delete pItem;

Expand Down

0 comments on commit e6295e6

Please sign in to comment.