Refactor of EquipActions (#1994)

#PR Description 

The root cause of issue #1987 was the AI Value item usage becoming a
very expensive call when bots gained professions accidentally.

My original approach was to eliminate it entirely, but after inputs and
testing I decided to introduce a more focused Ai value "Item upgrade"
that only checks equipment and ammo inheriting directly from item usage,
so the logic is unified between them.

Upgrades are now only assessed when receiving an item that can be
equipped.

Additionally, I noticed that winning loot rolls did not trigger the
upgrade action, so I added a new package handler for that.


Performance needs to be re-evaluated, but I expect a reduction in calls
and in the cost of each call.

I tested with bots and selfbot in deadmines and ahadowfang keep.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
This commit is contained in:
Keleborn
2026-02-08 03:41:33 -08:00
committed by GitHub
parent 8585f10f48
commit 3db2a5a193
10 changed files with 176 additions and 149 deletions

View File

@@ -19,19 +19,9 @@
ItemUsage ItemUsageValue::Calculate()
{
uint32 itemId = 0;
uint32 randomPropertyId = 0;
size_t pos = qualifier.find(",");
if (pos != std::string::npos)
{
itemId = atoi(qualifier.substr(0, pos).c_str());
randomPropertyId = atoi(qualifier.substr(pos + 1).c_str());
}
else
{
itemId = atoi(qualifier.c_str());
}
ParsedItemUsage const parsed = GetItemIdFromQualifier();
uint32 itemId = parsed.itemId;
uint32 randomPropertyId = parsed.randomPropertyId;
if (!itemId)
return ITEM_USAGE_NONE;
@@ -142,96 +132,30 @@ ItemUsage ItemUsageValue::Calculate()
// If the loot is from an item in the bots bags, ignore syncQuestWithPlayer
if (isLootFromItem && botNeedsItemForQuest)
{
return ITEM_USAGE_QUEST;
}
// If the bot is NOT acting alone and the master needs this quest item, defer to the master
if (!isSelfBot && masterNeedsItemForQuest)
{
return ITEM_USAGE_NONE;
}
// If the bot itself needs the item for a quest, allow looting
if (botNeedsItemForQuest)
{
return ITEM_USAGE_QUEST;
}
if (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK)
{
if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_ROGUE || bot->getClass() == CLASS_WARRIOR)
{
Item* rangedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED);
uint32 requiredSubClass = 0;
if (rangedWeapon)
{
switch (rangedWeapon->GetTemplate()->SubClass)
{
case ITEM_SUBCLASS_WEAPON_GUN:
requiredSubClass = ITEM_SUBCLASS_BULLET;
break;
case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
requiredSubClass = ITEM_SUBCLASS_ARROW;
break;
}
}
// Ensure the item is the correct ammo type for the equipped ranged weapon
if (proto->SubClass == requiredSubClass)
{
float ammoCount = BetterStacks(proto, "ammo");
float requiredAmmo = (bot->getClass() == CLASS_HUNTER) ? 8 : 2; // Hunters get 8 stacks, others 2
uint32 currentAmmoId = bot->GetUInt32Value(PLAYER_AMMO_ID);
// Check if the bot has an ammo type assigned
if (currentAmmoId == 0)
{
return ITEM_USAGE_EQUIP; // Equip the ammo if no ammo
}
// Compare new ammo vs current equipped ammo
ItemTemplate const* currentAmmoProto = sObjectMgr->GetItemTemplate(currentAmmoId);
if (currentAmmoProto)
{
uint32 currentAmmoDPS = (currentAmmoProto->Damage[0].DamageMin + currentAmmoProto->Damage[0].DamageMax) * 1000 / 2;
uint32 newAmmoDPS = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2;
if (newAmmoDPS > currentAmmoDPS) // New ammo meets upgrade condition
{
return ITEM_USAGE_EQUIP;
}
if (newAmmoDPS < currentAmmoDPS) // New ammo is worse
{
return ITEM_USAGE_NONE;
}
}
// Ensure we have enough ammo in the inventory
if (ammoCount < requiredAmmo)
{
ammoCount += CurrentStacks(proto);
if (ammoCount < requiredAmmo) // Buy ammo to reach the proper supply
return ITEM_USAGE_AMMO;
else if (ammoCount < requiredAmmo + 1)
return ITEM_USAGE_KEEP; // Keep the ammo if we don't have too much.
}
}
}
ItemUsage ammoUsage = QueryItemUsageForAmmo(proto);
if (ammoUsage != ITEM_USAGE_NONE)
return ammoUsage;
}
// Need to add something like free bagspace or item value.
if (proto->SellPrice > 0)
{
if (proto->Quality >= ITEM_QUALITY_NORMAL && !isSoulbound)
{
return ITEM_USAGE_AH;
}
else
{
return ITEM_USAGE_VENDOR;
}
}
return ITEM_USAGE_NONE;
@@ -480,6 +404,80 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
return ITEM_USAGE_NONE;
}
ItemUsage ItemUsageValue::QueryItemUsageForAmmo(ItemTemplate const* proto)
{
if (bot->getClass() != CLASS_HUNTER || bot->getClass() != CLASS_ROGUE || bot->getClass() != CLASS_WARRIOR)
return ITEM_USAGE_NONE;
Item* rangedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED);
uint32 requiredSubClass = 0;
if (rangedWeapon)
{
switch (rangedWeapon->GetTemplate()->SubClass)
{
case ITEM_SUBCLASS_WEAPON_GUN:
requiredSubClass = ITEM_SUBCLASS_BULLET;
break;
case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
requiredSubClass = ITEM_SUBCLASS_ARROW;
break;
}
}
// Ensure the item is the correct ammo type for the equipped ranged weapon
if (proto->SubClass == requiredSubClass)
{
float ammoCount = BetterStacks(proto, "ammo");
float requiredAmmo = (bot->getClass() == CLASS_HUNTER) ? 8 : 2; // Hunters get 8 stacks, others 2
uint32 currentAmmoId = bot->GetUInt32Value(PLAYER_AMMO_ID);
// Check if the bot has an ammo type assigned
if (currentAmmoId == 0)
return ITEM_USAGE_EQUIP; // Equip the ammo if no ammo
// Compare new ammo vs current equipped ammo
ItemTemplate const* currentAmmoProto = sObjectMgr->GetItemTemplate(currentAmmoId);
if (currentAmmoProto)
{
uint32 currentAmmoDPS = (currentAmmoProto->Damage[0].DamageMin + currentAmmoProto->Damage[0].DamageMax) * 1000 / 2;
uint32 newAmmoDPS = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2;
if (newAmmoDPS > currentAmmoDPS) // New ammo meets upgrade condition
return ITEM_USAGE_EQUIP;
if (newAmmoDPS < currentAmmoDPS) // New ammo is worse
return ITEM_USAGE_NONE;
}
// Ensure we have enough ammo in the inventory
if (ammoCount < requiredAmmo)
{
ammoCount += CurrentStacks(proto);
if (ammoCount < requiredAmmo) // Buy ammo to reach the proper supply
return ITEM_USAGE_AMMO;
else if (ammoCount < requiredAmmo + 1)
return ITEM_USAGE_KEEP; // Keep the ammo if we don't have too much.
}
}
return ITEM_USAGE_NONE;
}
ParsedItemUsage ItemUsageValue::GetItemIdFromQualifier()
{
ParsedItemUsage parsed;
size_t const pos = qualifier.find(",");
if (pos != std::string::npos)
{
parsed.itemId = atoi(qualifier.substr(0, pos).c_str());
parsed.randomPropertyId = atoi(qualifier.substr(pos + 1).c_str());
return parsed;
}
else
parsed.itemId = atoi(qualifier.c_str());
return parsed;
}
// Return smaltest bag size equipped
uint32 ItemUsageValue::GetSmallestBagSize()
{
@@ -913,3 +911,25 @@ std::string const ItemUsageValue::GetConsumableType(ItemTemplate const* proto, b
return "";
}
ItemUsage ItemUpgradeValue::Calculate()
{
ParsedItemUsage parsed = GetItemIdFromQualifier();
uint32 itemId = parsed.itemId;
uint32 randomPropertyId = parsed.randomPropertyId;
if (!itemId)
return ITEM_USAGE_NONE;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
return ITEM_USAGE_NONE;
ItemUsage equip = QueryItemUsageForEquip(proto, randomPropertyId);
if (equip != ITEM_USAGE_NONE)
return equip;
if (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK)
return QueryItemUsageForAmmo(proto);
return ITEM_USAGE_NONE;
}