Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 59 additions & 17 deletions src/AuctionHouseBot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,16 +262,25 @@ void AuctionHouseBot::Buy(Player* AHBplayer, AHBConfig* config, WorldSession* se
return;
}

// Retrieve items not owned by the bot and not bought/bidded on by the bot
std::string botGUIDsStr = JoinGUIDs(config->GetBotGUIDs());
// Retrieve items not owned by the bot. Auctions the bot already leads stay
// included so it can still escalate to a buyout instead of abandoning them.
// The exclusion list comes from the resolved bot characters (gBotsId), not
// the raw config GUID list: in account-only mode the latter is empty and
// would render an invalid "NOT IN ()" clause.
if (gBotsId.empty())
{
return;
}

std::string botGUIDsStr = JoinGUIDs(std::vector<uint32>(gBotsId.begin(), gBotsId.end()));
uint32 auctionHouseID = config->GetAHID();

Comment thread
icemansparks marked this conversation as resolved.
if (config->DebugOutBuyer)
{
LOG_INFO("module", "AHBot [{}]: Querying auction house {} for items not owned by bots", _id, auctionHouseID);
}

QueryResult ahContentQueryResult = CharacterDatabase.Query("SELECT id FROM auctionhouse WHERE houseid = {} AND itemowner NOT IN ({}) AND buyguid NOT IN ({})", auctionHouseID, botGUIDsStr, botGUIDsStr);
QueryResult ahContentQueryResult = CharacterDatabase.Query("SELECT id FROM auctionhouse WHERE houseid = {} AND itemowner NOT IN ({})", auctionHouseID, botGUIDsStr);

if (!ahContentQueryResult || ahContentQueryResult->GetRowCount() == 0)
{
Expand All @@ -280,14 +289,19 @@ void AuctionHouseBot::Buy(Player* AHBplayer, AHBConfig* config, WorldSession* se

// Fetches content of selected AH to look for possible bids
AuctionHouseObject* auctionHouseObject = sAuctionMgr->GetAuctionsMap(config->GetAHFID());
std::set<uint32> auctionsGuidsToConsider;
std::vector<uint32> auctionsGuidsToConsider;

do
{
uint32 auctionGuid = ahContentQueryResult->Fetch()->Get<uint32>();
auctionsGuidsToConsider.insert(auctionGuid);
auctionsGuidsToConsider.push_back(auctionGuid);
} while (ahContentQueryResult->NextRow());

// Randomized order: a fixed lowest-ID-first scan lets permanently
// overpriced auctions occupy the head of the queue and starve newer
// listings out of consideration entirely.
std::shuffle(auctionsGuidsToConsider.begin(), auctionsGuidsToConsider.end(), std::mt19937(std::random_device()()));

if (config->DebugOutBuyer)
{
LOG_INFO("module", "AHBot [{}]: Found {} possible bids", _id, auctionsGuidsToConsider.size());
Expand Down Expand Up @@ -323,21 +337,25 @@ void AuctionHouseBot::Buy(Player* AHBplayer, AHBConfig* config, WorldSession* se
LOG_INFO("module", "AHBot [{}]: Considering {} auctions per interval to bid on.", _id, bidsPerInterval);
}

for (uint32 count = 1; count <= bidsPerInterval && !auctionsGuidsToConsider.empty(); ++count)
// Only successful operations (bid or buyout) consume the per-interval
// budget; skipped auctions used to eat it and could starve the whole run.
// Evaluations are still bounded so an AH with a huge number of player
// auctions cannot make a skip-heavy cycle scan all of them; the shuffle
// keeps the sample fair across cycles, so this reintroduces no starvation.
uint32 opsDone = 0;
uint32 evaluated = 0;
uint32 const maxEvaluations = bidsPerInterval * 100;

for (uint32 auctionID : auctionsGuidsToConsider)
{
if (auctionsGuidsToConsider.empty()) {
return;
if (opsDone >= bidsPerInterval || evaluated >= maxEvaluations)
{
break;
Comment thread
icemansparks marked this conversation as resolved.
}

std::set<uint32>::iterator it = auctionsGuidsToConsider.begin();
std::advance(it, 0);
uint32 auctionID = *it;
AuctionEntry* auction = auctionHouseObject->GetAuction(auctionID);
evaluated++;

//
// Prevent to bid again on the same auction
//
auctionsGuidsToConsider.erase(it);
AuctionEntry* auction = auctionHouseObject->GetAuction(auctionID);

if (!auction)
{
Expand Down Expand Up @@ -388,6 +406,16 @@ void AuctionHouseBot::Buy(Player* AHBplayer, AHBConfig* config, WorldSession* se
}

uint64 maxPrice = (avgPrice + ( avgPrice - minPrice ));

// A degenerate override (minPrice == avgPrice) collapses the cap to the
// exact average while the seller lists at avg +-10%, so ordinary player
// undercuts of the bot's own listings would never be bought. Guarantee
// some headroom above the average.
if (avgPrice > 0 && maxPrice < avgPrice * 115 / 100)
{
maxPrice = avgPrice * 115 / 100;
}

uint64 SellPriceValue = maxPrice > 0 ? maxPrice : prototype->SellPrice;
uint64 BuyPriceValue = avgPrice > 0 ? avgPrice : prototype->BuyPrice;

Expand Down Expand Up @@ -504,6 +532,16 @@ void AuctionHouseBot::Buy(Player* AHBplayer, AHBConfig* config, WorldSession* se
continue;
}

// If the bot already leads this auction it must never outbid itself;
// the only remaining move is escalating to a buyout when that still
// fits the cap, otherwise the auction just runs out at the current bid.
bool botAlreadyLeads = auction->bidder && gBotsId.find(auction->bidder.GetCounter()) != gBotsId.end();

if (botAlreadyLeads && (auction->buyout == 0 || auction->buyout > maximumBid))
{
continue;
}

// Calculate our bid: step up from the current price by a small percentage,
// rather than leaping anywhere up to our maximum acceptable price.
double bidValue = currentPrice + (static_cast<double>(currentPrice) * urand(config->GetBuyerBidIncrementMinPct(), config->GetBuyerBidIncrementMaxPct()) / 100.0);
Expand Down Expand Up @@ -539,7 +577,7 @@ void AuctionHouseBot::Buy(Player* AHBplayer, AHBConfig* config, WorldSession* se
// Check whether we do normal bid, or buyout
//

if ((bidPrice < auction->buyout) || (auction->buyout == 0))
if (!botAlreadyLeads && ((bidPrice < auction->buyout) || (auction->buyout == 0)))
{
//
// Perform a new bid on the auction
Expand Down Expand Up @@ -571,6 +609,8 @@ void AuctionHouseBot::Buy(Player* AHBplayer, AHBConfig* config, WorldSession* se
{
LOG_INFO("module", "AHBot [{}]: New bid, itemid={}, ah={}, auctionId={} item={}, start={}, current={}, buyout={}", _id, prototype->ItemId, auction->GetHouseId(), auction->Id, auction->item_template, auction->startbid, currentPrice, auction->buyout);
}

opsDone++;
}
else
{
Expand Down Expand Up @@ -608,6 +648,8 @@ void AuctionHouseBot::Buy(Player* AHBplayer, AHBConfig* config, WorldSession* se
{
LOG_INFO("module", "AHBot [{}]: Bought , itemid={}, ah={}, item={}, start={}, current={}, buyout={}", _id, prototype->ItemId, AuctionHouseId(auction->GetHouseId()), auction->item_template, auction->startbid, currentPrice, auction->buyout);
}

opsDone++;
}
}

Expand Down
Loading