mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Hunter ammo equip bug and other issues with BuyAction.cpp (#986)
Resolves #947 Equip logic was failing as projectiles were never returning ITEM_USAGE_EQUIP in ItemUsageValue.cpp, added two cases where equip is returned: If no ammo is currently set If new ammo has higher DPS than old/currently equipped ammo While testing this using "b [itemlink]" and "b vendor" to purchase arrows I noticed some issues with BuyAction.cpp and have resolved them: Bots will now perform the "equip upgrades" action for any bought item that has an equip usage When using "b vendor" to buy all useful items from vendors within interaction distance, it now sorts the list of available items by calculated item score and buys the highest scoring item (if it is higher than the currently equipped item) for each slot. It should not buy multiple items for the same slot anymore, saving gold/emblems/etc. "b vendor" will now only attempt to buy 1 of each item. Consumable and projectile item types can be bought up to 10 times per execution as long as it is still useful to buy the item in each iteration of the for loop. All items were following this behaviour previously and since the equip command was only given after the for loop it would buy 10 of an item before triggering it wasn't useful to buy more. And finally, resolved issues where a bot runs out of ammo mid-fight: Re-enabled combat and non-combat "no ammo" strategies to perform "equip upgrades" action. Modified GenericTriggers.cpp; AmmoCountTrigger::IsActive to return true when the bot has ammo but it is not equipped yet.
This commit is contained in:
@@ -4867,6 +4867,40 @@ Item* PlayerbotAI::FindPoison() const
|
||||
{ return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == 6; });
|
||||
}
|
||||
|
||||
// Find Ammo
|
||||
Item* PlayerbotAI::FindAmmo() const
|
||||
{
|
||||
// Get equipped ranged weapon
|
||||
if (Item* rangedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
|
||||
{
|
||||
uint32 weaponSubClass = rangedWeapon->GetTemplate()->SubClass;
|
||||
uint32 requiredAmmoType = 0;
|
||||
|
||||
// Determine the correct ammo type based on the weapon
|
||||
switch (weaponSubClass)
|
||||
{
|
||||
case ITEM_SUBCLASS_WEAPON_GUN:
|
||||
requiredAmmoType = ITEM_SUBCLASS_BULLET;
|
||||
break;
|
||||
case ITEM_SUBCLASS_WEAPON_BOW:
|
||||
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
|
||||
requiredAmmoType = ITEM_SUBCLASS_ARROW;
|
||||
break;
|
||||
default:
|
||||
return nullptr; // Not a ranged weapon that requires ammo
|
||||
}
|
||||
|
||||
// Search inventory for the correct ammo type
|
||||
return FindItemInInventory([requiredAmmoType](ItemTemplate const* pItemProto) -> bool
|
||||
{
|
||||
return pItemProto->Class == ITEM_CLASS_PROJECTILE &&
|
||||
pItemProto->SubClass == requiredAmmoType;
|
||||
});
|
||||
}
|
||||
|
||||
return nullptr; // No ranged weapon equipped
|
||||
}
|
||||
|
||||
// Find Consumable
|
||||
Item* PlayerbotAI::FindConsumable(uint32 displayId) const
|
||||
{
|
||||
|
||||
@@ -462,6 +462,7 @@ public:
|
||||
bool PlayEmote(uint32 emote);
|
||||
void Ping(float x, float y);
|
||||
Item* FindPoison() const;
|
||||
Item* FindAmmo() const;
|
||||
Item* FindBandage() const;
|
||||
Item* FindConsumable(uint32 displayId) const;
|
||||
Item* FindStoneFor(Item* weapon) const;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "ItemUsageValue.h"
|
||||
#include "ItemVisitors.h"
|
||||
#include "Playerbots.h"
|
||||
#include "StatsWeightCalculator.h"
|
||||
|
||||
bool BuyAction::Execute(Event event)
|
||||
{
|
||||
@@ -61,30 +62,87 @@ bool BuyAction::Execute(Event event)
|
||||
if (m_items_sorted.empty())
|
||||
continue;
|
||||
|
||||
std::sort(m_items_sorted.begin(), m_items_sorted.end(),
|
||||
[](VendorItem* i, VendorItem* j) {
|
||||
return sObjectMgr->GetItemTemplate(i->item)->ItemLevel >
|
||||
sObjectMgr->GetItemTemplate(j->item)->ItemLevel;
|
||||
});
|
||||
StatsWeightCalculator calculator(bot);
|
||||
calculator.SetItemSetBonus(false);
|
||||
calculator.SetOverflowPenalty(false);
|
||||
|
||||
std::sort(m_items_sorted.begin(), m_items_sorted.end(),
|
||||
[&calculator](VendorItem* i, VendorItem* j)
|
||||
{
|
||||
ItemTemplate const* item1 = sObjectMgr->GetItemTemplate(i->item);
|
||||
ItemTemplate const* item2 = sObjectMgr->GetItemTemplate(j->item);
|
||||
|
||||
if (!item1 || !item2)
|
||||
return false;
|
||||
|
||||
float score1 = calculator.CalculateItem(item1->ItemId);
|
||||
float score2 = calculator.CalculateItem(item2->ItemId);
|
||||
|
||||
// Fallback to itemlevel if either score is 0
|
||||
if (score1 == 0 || score2 == 0)
|
||||
{
|
||||
score1 = item1->ItemLevel;
|
||||
score2 = item2->ItemLevel;
|
||||
}
|
||||
return score1 > score2; // Sort in descending order (highest score first)
|
||||
});
|
||||
|
||||
std::unordered_map<uint32, float> bestPurchasedItemScore; // Track best item score per InventoryType
|
||||
|
||||
for (auto& tItem : m_items_sorted)
|
||||
{
|
||||
for (uint32 i = 0; i < 10; i++) // Buy 10 times or until no longer usefull/possible
|
||||
uint32 maxPurchases = 1; // Default to buying once
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(tItem->item);
|
||||
if (!proto)
|
||||
continue;
|
||||
|
||||
if (proto->Class == ITEM_CLASS_CONSUMABLE || proto->Class == ITEM_CLASS_PROJECTILE)
|
||||
{
|
||||
maxPurchases = 10; // Allow up to 10 purchases if it's a consumable or projectile
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < maxPurchases; i++)
|
||||
{
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", tItem->item);
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(tItem->item);
|
||||
|
||||
uint32 invType = proto->InventoryType;
|
||||
|
||||
// Calculate item score
|
||||
float newScore = calculator.CalculateItem(proto->ItemId);
|
||||
|
||||
// Skip if we already bought a better item for this slot
|
||||
if (bestPurchasedItemScore.find(invType) != bestPurchasedItemScore.end() &&
|
||||
bestPurchasedItemScore[invType] > newScore)
|
||||
{
|
||||
break; // Skip lower-scoring items
|
||||
}
|
||||
|
||||
// Check the bot's currently equipped item for this slot
|
||||
uint8 dstSlot = botAI->FindEquipSlot(proto, NULL_SLOT, true);
|
||||
Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot);
|
||||
|
||||
float oldScore = 0.0f;
|
||||
if (oldItem)
|
||||
{
|
||||
ItemTemplate const* oldItemProto = oldItem->GetTemplate();
|
||||
if (oldItemProto)
|
||||
oldScore = calculator.CalculateItem(oldItemProto->ItemId);
|
||||
}
|
||||
|
||||
// Skip if the bot already has a better or equal item equipped
|
||||
if (oldScore > newScore)
|
||||
break;
|
||||
|
||||
uint32 price = proto->BuyPrice;
|
||||
|
||||
// reputation discount
|
||||
price = uint32(floor(price * bot->GetReputationPriceDiscount(pCreature)));
|
||||
|
||||
NeedMoneyFor needMoneyFor = NeedMoneyFor::none;
|
||||
|
||||
switch (usage)
|
||||
{
|
||||
case ITEM_USAGE_REPLACE:
|
||||
case ITEM_USAGE_EQUIP:
|
||||
case ITEM_USAGE_BAD_EQUIP:
|
||||
case ITEM_USAGE_BROKEN_EQUIP:
|
||||
needMoneyFor = NeedMoneyFor::gear;
|
||||
break;
|
||||
case ITEM_USAGE_AMMO:
|
||||
@@ -112,11 +170,12 @@ bool BuyAction::Execute(Event event)
|
||||
if (!BuyItem(tItems, vendorguid, proto))
|
||||
break;
|
||||
|
||||
if (usage == ITEM_USAGE_REPLACE ||
|
||||
usage == ITEM_USAGE_EQUIP) // Equip upgrades and stop buying this time.
|
||||
// Store the best item score per InventoryType
|
||||
bestPurchasedItemScore[invType] = newScore;
|
||||
|
||||
if (needMoneyFor == NeedMoneyFor::gear)
|
||||
{
|
||||
botAI->DoSpecificAction("equip upgrades");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +199,15 @@ bool BuyAction::Execute(Event event)
|
||||
std::ostringstream out;
|
||||
out << "Nobody sells " << ChatHelper::FormatItem(proto) << " nearby";
|
||||
botAI->TellMaster(out.str());
|
||||
continue;
|
||||
}
|
||||
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemId);
|
||||
if (usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_EQUIP ||
|
||||
usage == ITEM_USAGE_BAD_EQUIP || usage == ITEM_USAGE_BROKEN_EQUIP)
|
||||
{
|
||||
botAI->DoSpecificAction("equip upgrades");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ void GenericHunterNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tri
|
||||
new TriggerNode("low ammo", NextAction::array(0, new NextAction("say::low ammo", ACTION_NORMAL), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("no track", NextAction::array(0, new NextAction("track humanoids", ACTION_NORMAL), nullptr)));
|
||||
triggers.push_back(new TriggerNode("no ammo",
|
||||
NextAction::array(0, new NextAction("equip upgrades", ACTION_HIGH + 1), nullptr)));
|
||||
// triggers.push_back(new TriggerNode("no ammo", NextAction::array(0, new NextAction("switch to melee",
|
||||
// ACTION_NORMAL + 1), new NextAction("say::no ammo", ACTION_NORMAL), nullptr))); triggers.push_back(new
|
||||
// TriggerNode("has ammo", NextAction::array(0, new NextAction("switch to ranged", ACTION_NORMAL), nullptr)));
|
||||
|
||||
@@ -102,8 +102,8 @@ void GenericHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
new TriggerNode("medium threat", NextAction::array(0, new NextAction("feign death", 35.0f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("hunters pet medium health",
|
||||
NextAction::array(0, new NextAction("mend pet", ACTION_HIGH + 2), nullptr)));
|
||||
// triggers.push_back(new TriggerNode("no ammo", NextAction::array(0, new NextAction("switch to melee", ACTION_HIGH
|
||||
// + 1), new NextAction("say::no ammo", ACTION_HIGH), nullptr)));
|
||||
triggers.push_back(new TriggerNode("no ammo",
|
||||
NextAction::array(0, new NextAction("equip upgrades", ACTION_HIGH + 9), nullptr)));
|
||||
triggers.push_back(new TriggerNode("aspect of the viper",
|
||||
NextAction::array(0, new NextAction("aspect of the viper", ACTION_HIGH), NULL)));
|
||||
triggers.push_back(new TriggerNode("enemy too close for auto shot",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "BattlegroundWS.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "GameTime.h"
|
||||
#include "ItemVisitors.h"
|
||||
#include "LastSpellCastValue.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
@@ -629,4 +630,15 @@ bool IsFallingFarTrigger::IsActive() { return bot->HasUnitMovementFlag(MOVEMENTF
|
||||
|
||||
bool HasAreaDebuffTrigger::IsActive() { return AI_VALUE2(bool, "has area debuff", "self target"); }
|
||||
|
||||
Value<Unit*>* BuffOnMainTankTrigger::GetTargetValue() { return context->GetValue<Unit*>("main tank", spell); }
|
||||
Value<Unit*>* BuffOnMainTankTrigger::GetTargetValue() { return context->GetValue<Unit*>("main tank", spell); }
|
||||
|
||||
bool AmmoCountTrigger::IsActive()
|
||||
{
|
||||
if (bot->GetUInt32Value(PLAYER_AMMO_ID) != 0)
|
||||
return ItemCountTrigger::IsActive(); // Ammo already equipped
|
||||
|
||||
if (botAI->FindAmmo())
|
||||
return true; // Found ammo in inventory but not equipped
|
||||
|
||||
return ItemCountTrigger::IsActive();
|
||||
}
|
||||
|
||||
@@ -596,6 +596,7 @@ public:
|
||||
: ItemCountTrigger(botAI, item, count, interval)
|
||||
{
|
||||
}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class HasAuraTrigger : public Trigger
|
||||
|
||||
@@ -64,8 +64,9 @@ ItemUsage ItemUsageValue::Calculate()
|
||||
|
||||
if (proto->Class == ITEM_CLASS_KEY)
|
||||
return ITEM_USAGE_USE;
|
||||
|
||||
if (proto->Class == ITEM_CLASS_CONSUMABLE)
|
||||
|
||||
if (proto->Class == ITEM_CLASS_CONSUMABLE &&
|
||||
(proto->MaxCount == 0 || AI_VALUE2(uint32, "item count", proto->Name1) < proto->MaxCount))
|
||||
{
|
||||
std::string const foodType = GetConsumableType(proto, bot->GetPower(POWER_MANA));
|
||||
|
||||
@@ -103,39 +104,67 @@ ItemUsage ItemUsageValue::Calculate()
|
||||
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)
|
||||
{
|
||||
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
|
||||
Item* rangedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED);
|
||||
uint32 requiredSubClass = 0;
|
||||
|
||||
if (rangedWeapon)
|
||||
{
|
||||
uint32 subClass = 0;
|
||||
switch (pItem->GetTemplate()->SubClass)
|
||||
switch (rangedWeapon->GetTemplate()->SubClass)
|
||||
{
|
||||
case ITEM_SUBCLASS_WEAPON_GUN:
|
||||
subClass = ITEM_SUBCLASS_BULLET;
|
||||
requiredSubClass = ITEM_SUBCLASS_BULLET;
|
||||
break;
|
||||
case ITEM_SUBCLASS_WEAPON_BOW:
|
||||
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
|
||||
subClass = ITEM_SUBCLASS_ARROW;
|
||||
requiredSubClass = ITEM_SUBCLASS_ARROW;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (proto->SubClass == subClass)
|
||||
// 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)
|
||||
{
|
||||
float ammo = BetterStacks(proto, "ammo");
|
||||
float needAmmo = (bot->getClass() == CLASS_HUNTER) ? 8 : 2;
|
||||
|
||||
if (ammo < needAmmo) // We already have enough of the current ammo.
|
||||
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
|
||||
{
|
||||
ammo += CurrentStacks(proto);
|
||||
|
||||
if (ammo < needAmmo) // Buy ammo to get to the proper supply
|
||||
return ITEM_USAGE_AMMO;
|
||||
else if (ammo < needAmmo + 1)
|
||||
return ITEM_USAGE_KEEP; // Keep the ammo until we have too much.
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need to add something like free bagspace or item value.
|
||||
if (proto->SellPrice > 0)
|
||||
|
||||
Reference in New Issue
Block a user