Files
mod-playerbots/src/strategy/actions/BuyAction.cpp
bashermens 6be860c967 [Stability] Various crash fixes based on Regrad fixes and crashlogs. (#1928)
These contains various fixes, fixes that have history worked one in past
more then once as person as group, aswell @Wishmaster117. But due
various reasons we had to drop them due priority or simply timewise.
These fixes have recollected again by @Regrad based on his crash logs.

Most crash logs we have, i am talking 30+ of them, to many to post here.
@Regrad running a larger server 100+ real players with bots, which means
he will walk into issues that most of us wont or are extremely difficult
to reproduce.

@Regrad used LLM to solve them based on crash log and mentioned his
server crashes almost disappeared, instead of redoing every single PR
and pull them apart. I tried to keep his bunch of changes together as
whole, reviewed them, some redone, verified again etc etc. This is not
how would normally do this. But since i want @Regrad being able to
confirm, we need this in a package as a whole. Pulling them apart in the
current situation is simply to much, to complicated in the verification
process.

So this PR is open and in my opinion has priority above others, but
@Regrad is only person who can give the green light for the
mod-playerbot changes for now.

I, we spend huge amount of time into these issues over last couple of
months. I will put other PR's on hold for abit.

---------

Signed-off-by: Engardium <regradius@gmail.com>
Co-authored-by: Engardium <regradius@gmail.com>
2025-12-24 13:24:29 +01:00

261 lines
9.2 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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)
{
if (!tItems || !proto)
return false;
uint32 itemId = proto->ItemId;
uint32 oldCount = bot->GetItemCount(itemId, false);
for (uint32 slot = 0; slot < tItems->GetItemCount(); ++slot)
{
if (tItems->GetItem(slot)->item != itemId)
continue;
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);
uint32 newCount = bot->GetItemCount(itemId, false);
if (newCount > oldCount)
{
std::ostringstream out;
out << "Buying " << ChatHelper::FormatItem(proto);
botAI->TellMaster(out.str());
return true;
}
return false;
}
return false;
}