mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 17:09:08 +00:00
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.
267 lines
9.5 KiB
C++
267 lines
9.5 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 "BuyAction.h"
|
|
|
|
#include "BudgetValues.h"
|
|
#include "Event.h"
|
|
#include "ItemCountValue.h"
|
|
#include "ItemUsageValue.h"
|
|
#include "ItemVisitors.h"
|
|
#include "Playerbots.h"
|
|
#include "StatsWeightCalculator.h"
|
|
|
|
bool BuyAction::Execute(Event event)
|
|
{
|
|
bool buyUseful = false;
|
|
ItemIds itemIds;
|
|
std::string const link = event.getParam();
|
|
|
|
if (link == "vendor")
|
|
buyUseful = true;
|
|
else
|
|
{
|
|
itemIds = chat->parseItems(link);
|
|
}
|
|
|
|
GuidVector vendors = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
|
|
|
bool vendored = false;
|
|
bool result = false;
|
|
for (GuidVector::iterator i = vendors.begin(); i != vendors.end(); ++i)
|
|
{
|
|
ObjectGuid vendorguid = *i;
|
|
Creature* pCreature = bot->GetNPCIfCanInteractWith(vendorguid, UNIT_NPC_FLAG_VENDOR);
|
|
if (!pCreature)
|
|
continue;
|
|
|
|
vendored = true;
|
|
|
|
if (buyUseful)
|
|
{
|
|
// Items are evaluated from high-level to low level.
|
|
// For each item the bot checks again if an item is usefull.
|
|
// Bot will buy until no usefull items are left.
|
|
|
|
VendorItemData const* tItems = pCreature->GetVendorItems();
|
|
if (!tItems)
|
|
continue;
|
|
|
|
VendorItemList m_items_sorted = tItems->m_items;
|
|
|
|
m_items_sorted.erase(std::remove_if(m_items_sorted.begin(), m_items_sorted.end(),
|
|
[](VendorItem* i)
|
|
{
|
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(i->item);
|
|
return !proto;
|
|
}),
|
|
m_items_sorted.end());
|
|
|
|
if (m_items_sorted.empty())
|
|
continue;
|
|
|
|
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)
|
|
{
|
|
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);
|
|
|
|
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;
|
|
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:
|
|
needMoneyFor = NeedMoneyFor::ammo;
|
|
break;
|
|
case ITEM_USAGE_QUEST:
|
|
needMoneyFor = NeedMoneyFor::anything;
|
|
break;
|
|
case ITEM_USAGE_USE:
|
|
needMoneyFor = NeedMoneyFor::consumables;
|
|
break;
|
|
case ITEM_USAGE_SKILL:
|
|
needMoneyFor = NeedMoneyFor::tradeskill;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (needMoneyFor == NeedMoneyFor::none)
|
|
break;
|
|
|
|
if (AI_VALUE2(uint32, "free money for", uint32(needMoneyFor)) < price)
|
|
break;
|
|
|
|
if (!BuyItem(tItems, vendorguid, proto))
|
|
break;
|
|
|
|
// Store the best item score per InventoryType
|
|
bestPurchasedItemScore[invType] = newScore;
|
|
|
|
if (needMoneyFor == NeedMoneyFor::gear)
|
|
{
|
|
botAI->DoSpecificAction("equip upgrades");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (itemIds.empty())
|
|
return false;
|
|
|
|
for (ItemIds::iterator i = itemIds.begin(); i != itemIds.end(); i++)
|
|
{
|
|
uint32 itemId = *i;
|
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
|
if (!proto)
|
|
continue;
|
|
|
|
result |= BuyItem(pCreature->GetVendorItems(), vendorguid, proto);
|
|
|
|
if (!result)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!vendored)
|
|
{
|
|
botAI->TellError("There are no vendors nearby");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
|
|
{
|
|
uint32 oldCount = AI_VALUE2(uint32, "item count", proto->Name1);
|
|
|
|
if (!tItems)
|
|
return false;
|
|
|
|
uint32 itemId = proto->ItemId;
|
|
for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++)
|
|
{
|
|
if (tItems->GetItem(slot)->item == itemId)
|
|
{
|
|
uint32 botMoney = bot->GetMoney();
|
|
if (botAI->HasCheat(BotCheatMask::gold))
|
|
{
|
|
bot->SetMoney(10000000);
|
|
}
|
|
|
|
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
|
|
|
if (botAI->HasCheat(BotCheatMask::gold))
|
|
{
|
|
bot->SetMoney(botMoney);
|
|
}
|
|
|
|
if (oldCount <
|
|
AI_VALUE2(
|
|
uint32, "item count",
|
|
proto->Name1)) // BuyItem Always returns false (unless unique) so we have to check the item counts.
|
|
{
|
|
std::ostringstream out;
|
|
out << "Buying " << ChatHelper::FormatItem(proto);
|
|
botAI->TellMaster(out.str());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|