mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-02-25 21:05:54 +00:00
# Pull Request Brighton caught a mistake I made changeing the action registry, so the correct action was no longer triggering. I cleaned that up, and renamed the action. ## How to Test the Changes - This was tested by adding logging to both equip actions. But to test this without that, the best way to verify the fix is to stop alts from auto upgrading via config. Then they should correctly follow the configured behavior. ## Complexity & Impact Does this change add new decision branches? - - [x ] No - - [ ] Yes (**explain below**) Does this change increase per-bot or per-tick processing? - - [x] No - - [ ] Yes (**describe and justify impact**) Could this logic scale poorly under load? - - [x] No - - [ ] Yes (**explain why**) --- ## Defaults & Configuration Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) If this introduces more advanced or AI-heavy logic: - - [x] Lightweight mode remains the default - - [ ] More complex behavior is optional and thereby configurable --- ## AI Assistance Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change? - - [x] No - - [ ] Yes (**explain below**) If yes, please specify: - AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.) - Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation) - Which parts of the change were influenced or generated - Whether the result was manually reviewed and adapted AI assistance is allowed, but all submitted code must be fully understood, reviewed, and owned by the contributor. Any AI-influenced changes must be verified against existing CORE and PB logic. We expect contributors to be honest about what they do and do not understand. --- ## Final Checklist - - [x] Stability is not compromised - - [x] Performance impact is understood, tested, and acceptable - - [x] Added logic complexity is justified and explained - - [x] Documentation updated if needed --- ## Notes for Reviewers Anything that significantly improves realism at the cost of stability or performance should be carefully discussed before merging.
261 lines
9.2 KiB
C++
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 packet action");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 packet action");
|
|
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;
|
|
}
|