From 4920a5d6543ca7dabe6190a11bc82a67cc96b0d7 Mon Sep 17 00:00:00 2001 From: Tecc Date: Mon, 24 Aug 2026 07:46:56 +0000 Subject: [PATCH] feat(seller): restock sold out items in batches instead of one stack per cycle GetItemsToSell() put every price override item that was absent from the auction house into the priority block exactly once, so a sold out item came back at one stack per cycle at best, and only if it won the shuffle against every other missing item within that cycle's ItemsPerCycle budget. The priority block now queues an item as many times as it is short of its target count - the per-item mod_auctionhousebot_countOverride value, else the global DuplicatesCount, else one stack, matching the cap Sell() already enforces per listing. Items still partially stocked are topped up too, after the sold out ones. AuctionHouseBot.RestockBatchSize (default 5) bounds how many stacks of a single item one cycle may queue; 1 keeps the old behaviour. --- conf/mod_ahbot.conf.dist | 10 ++++++ src/AuctionHouseBot.cpp | 66 ++++++++++++++++++++++++++++++++--- src/AuctionHouseBot.h | 2 +- src/AuctionHouseBotConfig.cpp | 3 ++ src/AuctionHouseBotConfig.h | 1 + 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/conf/mod_ahbot.conf.dist b/conf/mod_ahbot.conf.dist index f79faca..3df71be 100644 --- a/conf/mod_ahbot.conf.dist +++ b/conf/mod_ahbot.conf.dist @@ -85,6 +85,15 @@ # If set to zero then no limits are set in place. # Default 0 # +# AuctionHouseBot.RestockBatchSize +# The maximum amount of stacks of the same item the seller queues in a single +# cycle while restocking it up to its target count. The target count is taken +# from the "mod_auctionhousebot_countOverride" table, or from +# AuctionHouseBot.DuplicatesCount when the item has no row there. +# Without this, a sold out item comes back one stack per cycle only. +# Set to 1 to restore that behaviour. +# Default 5 +# # AuctionHouseBot.DivisibleStacks # Sell items in stack sizes which depends on the maximum amount per stack. # For example, an item with max stack size 20 will be sold in 5, 10 15 and 20 stacks, and not at random. @@ -121,6 +130,7 @@ AuctionHouseBot.ItemsPerCycle = 200 AuctionHouseBot.ConsiderOnlyBotAuctions = 0 AuctionHouseBot.DuplicatesCount = 0 +AuctionHouseBot.RestockBatchSize = 5 AuctionHouseBot.DivisibleStacks = 0 AuctionHouseBot.ElapsingTimeClass = 1 AuctionHouseBot.MinItems = 10000 diff --git a/src/AuctionHouseBot.cpp b/src/AuctionHouseBot.cpp index 4f729ca..602346c 100644 --- a/src/AuctionHouseBot.cpp +++ b/src/AuctionHouseBot.cpp @@ -821,7 +821,7 @@ void AuctionHouseBot::Sell(Player* AHBplayer, AHBConfig* config) std::vector itemCounts(14, 0); // Get prioritized item IDs - std::vector itemsToSell = GetItemsToSell(config, AHBplayer->GetGUID(), itemsInAH); + std::vector itemsToSell = GetItemsToSell(config, AHBplayer->GetGUID(), itemsInAH, botItemCounts); // Loop variables uint32 nbSold = 0; // Tracing counter @@ -1247,7 +1247,7 @@ void AuctionHouseBot::Sell(Player* AHBplayer, AHBConfig* config) // Get Prioritized ItemIDs // ============================================================================= -std::vector AuctionHouseBot::GetItemsToSell(AHBConfig* config, ObjectGuid /* botGuid */, const std::unordered_set& itemsInAH) +std::vector AuctionHouseBot::GetItemsToSell(AHBConfig* config, ObjectGuid /* botGuid */, const std::unordered_set& itemsInAH, const std::unordered_map& botItemCounts) { //std::vector prioritizedItemIDs; std::vector allItemIDs; @@ -1286,17 +1286,74 @@ std::vector AuctionHouseBot::GetItemsToSell(AHBConfig* config, ObjectGui std::vector yellowItemsBin(config->YellowItemsBin.begin(), config->YellowItemsBin.end()); - // 1. Items with price overrides that are not listed by the bot yet (randomized order) + // How many stacks of an item are missing from its target count. The target is + // the per-item countOverride, else the global DuplicatesCount, else 1 stack - + // the same precedence the listing cap in Sell() applies. Capped by + // RestockBatchSize so a single item cannot eat the whole ItemsPerCycle budget. + auto missingStacks = [&](uint32 itemID) -> uint32 + { + uint32 target = config->GetCountOverrideForItem(itemID); + + if (target == 0) + { + target = config->DuplicatesCount; + } + + if (target == 0) + { + target = 1; + } + + auto listed = botItemCounts.find(itemID); + uint32 current = (listed != botItemCounts.end()) ? listed->second : 0; + + if (current >= target) + { + return 0; + } + + uint32 batch = config->RestockBatchSize > 0 ? config->RestockBatchSize : 1; + + return std::min(target - current, batch); + }; + + // Queue every stack an item is short of, keeping the copies adjacent so a + // sold out item is restocked as a batch instead of one stack per cycle. + auto appendMissingStacks = [&](const std::vector& items) + { + for (auto const& itemID : items) + { + allItemIDs.insert(allItemIDs.end(), missingStacks(itemID), itemID); + } + }; + + // 1. Items with price overrides below their target count (randomized order), + // sold out ones first, then the ones still partially stocked std::vector itemsWithOverridesNotListed; + std::vector itemsWithOverridesBelowTarget; + for (const auto& [itemID, _] : config->itemPriceOverrides) { + if (missingStacks(itemID) == 0) + { + continue; + } + if (itemsInAH.find(itemID) == itemsInAH.end()) { itemsWithOverridesNotListed.push_back(itemID); } + else + { + itemsWithOverridesBelowTarget.push_back(itemID); + } } + std::shuffle(itemsWithOverridesNotListed.begin(), itemsWithOverridesNotListed.end(), std::mt19937(std::random_device()())); - allItemIDs.insert(allItemIDs.end(), itemsWithOverridesNotListed.begin(), itemsWithOverridesNotListed.end()); + std::shuffle(itemsWithOverridesBelowTarget.begin(), itemsWithOverridesBelowTarget.end(), std::mt19937(std::random_device()())); + + appendMissingStacks(itemsWithOverridesNotListed); + appendMissingStacks(itemsWithOverridesBelowTarget); // 2. Items for which price overrides do exist (randomized order) std::vector itemsWithOverrides; @@ -1339,6 +1396,7 @@ std::vector AuctionHouseBot::GetItemsToSell(AHBConfig* config, ObjectGui // Log the number of items to sell if(config->TraceSeller) { + LOG_INFO("module", "AHBot [{}]: restocking {} sold out and {} understocked price override items", _id, itemsWithOverridesNotListed.size(), itemsWithOverridesBelowTarget.size()); LOG_INFO("module", "AHBot [{}]: GetItemsToSell returning {} items", _id, allItemIDs.size()); } diff --git a/src/AuctionHouseBot.h b/src/AuctionHouseBot.h index 3dec118..0364762 100644 --- a/src/AuctionHouseBot.h +++ b/src/AuctionHouseBot.h @@ -102,7 +102,7 @@ class AuctionHouseBot ObjectGuid::LowType GetAHBplayerGUID() { return _id; }; - std::vector GetItemsToSell(AHBConfig* config, ObjectGuid botGuid, const std::unordered_set& itemsInAH); + std::vector GetItemsToSell(AHBConfig* config, ObjectGuid botGuid, const std::unordered_set& itemsInAH, const std::unordered_map& botItemCounts); bool IsItemListedByBot(uint32 itemID, uint32 ahID, ObjectGuid botGuid); bool IsItemInAuctionHouse(uint32 itemID, uint32 ahID); std::vector GetAllItemIDs(uint32 ahID); diff --git a/src/AuctionHouseBotConfig.cpp b/src/AuctionHouseBotConfig.cpp index 480f8cc..e70c97b 100644 --- a/src/AuctionHouseBotConfig.cpp +++ b/src/AuctionHouseBotConfig.cpp @@ -222,6 +222,7 @@ AHBConfig::AHBConfig(uint32 ahid, AHBConfig* conf) Bind_When_Use = conf->Bind_When_Use; Bind_Quest_Item = conf->Bind_Quest_Item; DuplicatesCount = conf->DuplicatesCount; + RestockBatchSize = conf->RestockBatchSize; ElapsingTimeClass = conf->ElapsingTimeClass; DivisibleStacks = conf->DivisibleStacks; DisablePermEnchant = conf->DisablePermEnchant; @@ -532,6 +533,7 @@ void AHBConfig::Reset() Bind_When_Use = false; Bind_Quest_Item = false; DuplicatesCount = 0; + RestockBatchSize = 5; ElapsingTimeClass = 1; DivisibleStacks = false; @@ -2069,6 +2071,7 @@ void AHBConfig::InitializeFromFile() SellAtMarketPrice = sConfigMgr->GetOption ("AuctionHouseBot.UseMarketPriceForSeller", false); MarketResetThreshold = sConfigMgr->GetOption("AuctionHouseBot.MarketResetThreshold" , 25); DuplicatesCount = sConfigMgr->GetOption("AuctionHouseBot.DuplicatesCount" , 0); + RestockBatchSize = sConfigMgr->GetOption("AuctionHouseBot.RestockBatchSize" , 5); DivisibleStacks = sConfigMgr->GetOption ("AuctionHouseBot.DivisibleStacks" , false); ElapsingTimeClass = sConfigMgr->GetOption("AuctionHouseBot.ElapsingTimeClass" , 1); ConsiderOnlyBotAuctions = sConfigMgr->GetOption ("AuctionHouseBot.ConsiderOnlyBotAuctions", false); diff --git a/src/AuctionHouseBotConfig.h b/src/AuctionHouseBotConfig.h index de55433..ff97546 100644 --- a/src/AuctionHouseBotConfig.h +++ b/src/AuctionHouseBotConfig.h @@ -203,6 +203,7 @@ class AHBConfig bool Bind_Quest_Item; uint32 DuplicatesCount; + uint32 RestockBatchSize; uint32 ElapsingTimeClass; bool DivisibleStacks;