Added config, prevent buyerbot from buying overpriced vendor items, refactored advancedPricingMultiplier out of calculateItemValue()

This commit is contained in:
zeb
2025-09-17 11:26:23 -04:00
parent a20ff266b4
commit 1b169974ff
4 changed files with 155 additions and 101 deletions

View File

@@ -276,6 +276,111 @@ void AuctionHouseBot::calculateItemValue(ItemTemplate const* itemProto, uint64&
float classQualityPriceMultiplier = PriceMultiplierCategoryQuality[itemProto->Class][itemProto->Quality];
float advancedPricingMultiplier = getAdvancedPricingMultiplier(itemProto);
// Grab the minimum prices
uint64 PriceMinimumCenterBase = 1000;
auto it = PriceMinimumCenterBaseOverridesByItemID.find(itemProto->ItemId);
if (it != PriceMinimumCenterBaseOverridesByItemID.end())
PriceMinimumCenterBase = it->second;
else
{
switch (itemProto->Class)
{
case ITEM_CLASS_CONSUMABLE: PriceMinimumCenterBase = PriceMinimumCenterBaseConsumable; break;
case ITEM_CLASS_CONTAINER: PriceMinimumCenterBase = PriceMinimumCenterBaseContainer; break;
case ITEM_CLASS_WEAPON: PriceMinimumCenterBase = PriceMinimumCenterBaseWeapon; break;
case ITEM_CLASS_GEM: PriceMinimumCenterBase = PriceMinimumCenterBaseGem; break;
case ITEM_CLASS_REAGENT: PriceMinimumCenterBase = PriceMinimumCenterBaseReagent; break;
case ITEM_CLASS_ARMOR: PriceMinimumCenterBase = PriceMinimumCenterBaseArmor; break;
case ITEM_CLASS_PROJECTILE: PriceMinimumCenterBase = PriceMinimumCenterBaseProjectile; break;
case ITEM_CLASS_TRADE_GOODS: PriceMinimumCenterBase = PriceMinimumCenterBaseTradeGood; break;
case ITEM_CLASS_GENERIC: PriceMinimumCenterBase = PriceMinimumCenterBaseGeneric; break;
case ITEM_CLASS_RECIPE: PriceMinimumCenterBase = PriceMinimumCenterBaseRecipe; break;
case ITEM_CLASS_QUIVER: PriceMinimumCenterBase = PriceMinimumCenterBaseQuiver; break;
case ITEM_CLASS_QUEST: PriceMinimumCenterBase = PriceMinimumCenterBaseQuest; break;
case ITEM_CLASS_KEY: PriceMinimumCenterBase = PriceMinimumCenterBaseKey; break;
case ITEM_CLASS_MISC: PriceMinimumCenterBase = PriceMinimumCenterBaseMisc; break;
case ITEM_CLASS_GLYPH: PriceMinimumCenterBase = PriceMinimumCenterBaseGlyph; break;
default: break;
}
}
// Set the minimum price
if (outBuyoutPrice < PriceMinimumCenterBase)
outBuyoutPrice = urand(PriceMinimumCenterBase * (1.0f - BuyoutVariationReducePercent), PriceMinimumCenterBase * (1.0f + BuyoutVariationAddPercent));
else
outBuyoutPrice = urand(outBuyoutPrice * (1.0f - BuyoutVariationReducePercent), outBuyoutPrice * (1.0f + BuyoutVariationAddPercent));
// Ensure no multipliers are zero or negative
if (classPriceMultiplier <= 0.0f)
classPriceMultiplier = 1.0f;
if (qualityPriceMultplier <= 0.0f)
qualityPriceMultplier = 1.0f;
if (classQualityPriceMultiplier <= 0.0f)
classQualityPriceMultiplier = 1.0f;
if (advancedPricingMultiplier <= 0.0f)
advancedPricingMultiplier = 1.0f;
// Grab any item level price multipliers
float itemLevelPriceMultplier = 0.0f;
switch (itemProto->Class)
{
case ITEM_CLASS_CONSUMABLE: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryConsumable; break;
case ITEM_CLASS_CONTAINER: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryContainer; break;
case ITEM_CLASS_WEAPON: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryWeapon; break;
case ITEM_CLASS_GEM: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryGem; break;
case ITEM_CLASS_REAGENT: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryReagent; break;
case ITEM_CLASS_ARMOR: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryArmor; break;
case ITEM_CLASS_PROJECTILE: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryProjectile; break;
case ITEM_CLASS_TRADE_GOODS: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryTradeGood; break;
case ITEM_CLASS_GENERIC: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryGeneric; break;
case ITEM_CLASS_RECIPE: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryRecipe; break;
case ITEM_CLASS_QUIVER: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryQuiver; break;
case ITEM_CLASS_QUEST: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryQuest; break;
case ITEM_CLASS_KEY: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryKey; break;
case ITEM_CLASS_MISC: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryMisc; break;
case ITEM_CLASS_GLYPH: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryGlyph; break;
default: break;
}
// Multiply the price based on multipliers
outBuyoutPrice *= qualityPriceMultplier;
outBuyoutPrice *= classPriceMultiplier;
outBuyoutPrice *= classQualityPriceMultiplier;
outBuyoutPrice *= static_cast<float>(advancedPricingMultiplier);
// Only apply item level multiplier if set, and no advanced pricing has been enabled
if (itemLevelPriceMultplier > 0.0f && itemProto->ItemLevel > 0 && advancedPricingMultiplier == 1.0f)
outBuyoutPrice *= itemProto->ItemLevel * itemLevelPriceMultplier;
// If a vendor sells this item, make the price at least that high
if (itemProto->SellPrice > outBuyoutPrice)
outBuyoutPrice = itemProto->SellPrice;
// Avoid price overflows
if (outBuyoutPrice > MaxBuyoutPriceInCopper)
outBuyoutPrice = MaxBuyoutPriceInCopper;
// Calculate a bid price based on a variance against buyout price
float sellVarianceBidPriceTopPercent = 1.0f - BidVariationHighReducePercent;
float sellVarianceBidPriceBottomPercent = 1.0f - BidVariationLowReducePercent;
outBidPrice = urand(sellVarianceBidPriceBottomPercent * outBuyoutPrice, sellVarianceBidPriceTopPercent * outBuyoutPrice);
// If variance brought price below sell price, bring it back up to avoid making money off vendoring AH items
if (outBuyoutPrice < itemProto->SellPrice)
{
float minLowPriceAddVariancePercent = 1.0f + BuyoutBelowVendorVariationAddPercent;
outBuyoutPrice = urand(itemProto->SellPrice, minLowPriceAddVariancePercent * itemProto->SellPrice);
}
// Bid price can never be below sell price
if (outBidPrice < itemProto->SellPrice)
outBidPrice = itemProto->SellPrice;
}
float AuctionHouseBot::getAdvancedPricingMultiplier(ItemTemplate const* itemProto)
{
/* "ADVANCED" SUBCLASS PRICE MULTIPLIER FORMULA NOTES
1. multiplierHelper = log(1 + b * ItemLevel)
@@ -390,105 +495,7 @@ void AuctionHouseBot::calculateItemValue(ItemTemplate const* itemProto, uint64&
}
}
// Grab the minimum prices
uint64 PriceMinimumCenterBase = 1000;
auto it = PriceMinimumCenterBaseOverridesByItemID.find(itemProto->ItemId);
if (it != PriceMinimumCenterBaseOverridesByItemID.end())
PriceMinimumCenterBase = it->second;
else
{
switch (itemProto->Class)
{
case ITEM_CLASS_CONSUMABLE: PriceMinimumCenterBase = PriceMinimumCenterBaseConsumable; break;
case ITEM_CLASS_CONTAINER: PriceMinimumCenterBase = PriceMinimumCenterBaseContainer; break;
case ITEM_CLASS_WEAPON: PriceMinimumCenterBase = PriceMinimumCenterBaseWeapon; break;
case ITEM_CLASS_GEM: PriceMinimumCenterBase = PriceMinimumCenterBaseGem; break;
case ITEM_CLASS_REAGENT: PriceMinimumCenterBase = PriceMinimumCenterBaseReagent; break;
case ITEM_CLASS_ARMOR: PriceMinimumCenterBase = PriceMinimumCenterBaseArmor; break;
case ITEM_CLASS_PROJECTILE: PriceMinimumCenterBase = PriceMinimumCenterBaseProjectile; break;
case ITEM_CLASS_TRADE_GOODS: PriceMinimumCenterBase = PriceMinimumCenterBaseTradeGood; break;
case ITEM_CLASS_GENERIC: PriceMinimumCenterBase = PriceMinimumCenterBaseGeneric; break;
case ITEM_CLASS_RECIPE: PriceMinimumCenterBase = PriceMinimumCenterBaseRecipe; break;
case ITEM_CLASS_QUIVER: PriceMinimumCenterBase = PriceMinimumCenterBaseQuiver; break;
case ITEM_CLASS_QUEST: PriceMinimumCenterBase = PriceMinimumCenterBaseQuest; break;
case ITEM_CLASS_KEY: PriceMinimumCenterBase = PriceMinimumCenterBaseKey; break;
case ITEM_CLASS_MISC: PriceMinimumCenterBase = PriceMinimumCenterBaseMisc; break;
case ITEM_CLASS_GLYPH: PriceMinimumCenterBase = PriceMinimumCenterBaseGlyph; break;
default: break;
}
}
// Set the minimum price
if (outBuyoutPrice < PriceMinimumCenterBase)
outBuyoutPrice = urand(PriceMinimumCenterBase * (1.0f - BuyoutVariationReducePercent), PriceMinimumCenterBase * (1.0f + BuyoutVariationAddPercent));
else
outBuyoutPrice = urand(outBuyoutPrice * (1.0f - BuyoutVariationReducePercent), outBuyoutPrice * (1.0f + BuyoutVariationAddPercent));
// Ensure no multipliers are zero or negative
if (classPriceMultiplier <= 0.0f)
classPriceMultiplier = 1.0f;
if (qualityPriceMultplier <= 0.0f)
qualityPriceMultplier = 1.0f;
if (classQualityPriceMultiplier <= 0.0f)
classQualityPriceMultiplier = 1.0f;
if (advancedPricingMultiplier <= 0.0f)
advancedPricingMultiplier = 1.0f;
// Grab any item level price multipliers
float itemLevelPriceMultplier = 0.0f;
switch (itemProto->Class)
{
case ITEM_CLASS_CONSUMABLE: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryConsumable; break;
case ITEM_CLASS_CONTAINER: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryContainer; break;
case ITEM_CLASS_WEAPON: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryWeapon; break;
case ITEM_CLASS_GEM: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryGem; break;
case ITEM_CLASS_REAGENT: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryReagent; break;
case ITEM_CLASS_ARMOR: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryArmor; break;
case ITEM_CLASS_PROJECTILE: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryProjectile; break;
case ITEM_CLASS_TRADE_GOODS: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryTradeGood; break;
case ITEM_CLASS_GENERIC: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryGeneric; break;
case ITEM_CLASS_RECIPE: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryRecipe; break;
case ITEM_CLASS_QUIVER: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryQuiver; break;
case ITEM_CLASS_QUEST: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryQuest; break;
case ITEM_CLASS_KEY: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryKey; break;
case ITEM_CLASS_MISC: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryMisc; break;
case ITEM_CLASS_GLYPH: itemLevelPriceMultplier = PriceMultiplierItemLevelCategoryGlyph; break;
default: break;
}
// Multiply the price based on multipliers
outBuyoutPrice *= qualityPriceMultplier;
outBuyoutPrice *= classPriceMultiplier;
outBuyoutPrice *= classQualityPriceMultiplier;
outBuyoutPrice *= static_cast<float>(advancedPricingMultiplier);
// Only apply item level multiplier if set, and no advanced pricing has been enabled
if (itemLevelPriceMultplier > 0.0f && itemProto->ItemLevel > 0 && advancedPricingMultiplier == 1.0f)
outBuyoutPrice *= itemProto->ItemLevel * itemLevelPriceMultplier;
// If a vendor sells this item, make the price at least that high
if (itemProto->SellPrice > outBuyoutPrice)
outBuyoutPrice = itemProto->SellPrice;
// Avoid price overflows
if (outBuyoutPrice > MaxBuyoutPriceInCopper)
outBuyoutPrice = MaxBuyoutPriceInCopper;
// Calculate a bid price based on a variance against buyout price
float sellVarianceBidPriceTopPercent = 1.0f - BidVariationHighReducePercent;
float sellVarianceBidPriceBottomPercent = 1.0f - BidVariationLowReducePercent;
outBidPrice = urand(sellVarianceBidPriceBottomPercent * outBuyoutPrice, sellVarianceBidPriceTopPercent * outBuyoutPrice);
// If variance brought price below sell price, bring it back up to avoid making money off vendoring AH items
if (outBuyoutPrice < itemProto->SellPrice)
{
float minLowPriceAddVariancePercent = 1.0f + BuyoutBelowVendorVariationAddPercent;
outBuyoutPrice = urand(itemProto->SellPrice, minLowPriceAddVariancePercent * itemProto->SellPrice);
}
// Bid price can never be below sell price
if (outBidPrice < itemProto->SellPrice)
outBidPrice = itemProto->SellPrice;
return static_cast<float>(advancedPricingMultiplier);
}
void AuctionHouseBot::populatetemClassSeedListForItemClass(uint32 itemClass, uint32 itemClassSeedWeight)
@@ -1043,9 +1050,19 @@ void AuctionHouseBot::addNewAuctionBuyerBotBid(Player* AHBplayer, AHBConfig *con
if (auction->buyout != 0 && auction->buyout < willingToPayForStackPrice)
doBuyout = true;
else if (auction->startbid < willingToPayForStackPrice && auction->GetAuctionOutBid() < willingToPayForStackPrice)
else if (auction->startbid < willingToPayForStackPrice && (auction->startbid + auction->GetAuctionOutBid()) < willingToPayForStackPrice)
doBid = true;
// Check that the item isn't listed above Vendor sell price
if (PreventOverpayingForVendorItems)
{
if (doBuyout && auction->buyout >= vendorItemsPrices[prototype->ItemId]){
doBuyout = false;
}
if (doBid && (auction->startbid + auction->GetAuctionOutBid()) >= vendorItemsPrices[prototype->ItemId])
doBid = false;
}
if (debug_Out)
{
LOG_INFO("module", "-------------------------------------------------");
@@ -1223,6 +1240,9 @@ void AuctionHouseBot::InitializeConfiguration()
// Buyer Bot
BuyingBotBuyCandidatesPerBuyCycle = sConfigMgr->GetOption<uint32>("AuctionHouseBot.Buyer.BuyCandidatesPerBuyCycle", 1);
BuyingBotAcceptablePriceModifier = sConfigMgr->GetOption<float>("AuctionHouseBot.Buyer.AcceptablePriceModifier", 1);
PreventOverpayingForVendorItems = sConfigMgr->GetOption<bool>("AuctionHouseBot.Buyer.PreventOverpayingForVendorItems", true);
if (PreventOverpayingForVendorItems)
populateVendorItemsPrices();
// Stack Ratios
RandomStackRatioConsumable = GetRandomStackValue("AuctionHouseBot.ListingStack.RandomRatio.Consumable", 50);
@@ -1592,3 +1612,24 @@ const char* AuctionHouseBot::GetCategoryName(ItemClass category)
default: return "Unknown";
}
}
void AuctionHouseBot::populateVendorItemsPrices()
{
// Load vendor items' prices into a vector for fast lookup
QueryResult r = WorldDatabase.Query("SELECT MAX(entry) FROM item_template");
Field* f = r->Fetch();
uint32_t numItems = f[0].Get<uint32>();
vendorItemsPrices = std::vector<uint32>(numItems, UINT32_MAX);
QueryResult result = WorldDatabase.Query("SELECT v.entry, MIN(v.SellPrice) AS SellPrice FROM item_template v JOIN npc_vendor p ON v.entry = p.item GROUP BY v.entry");
if (result)
{
do
{
Field* pFields = result->Fetch();
uint32_t itemID = pFields[0].Get<uint32>();
uint32_t itemPrice = pFields[1].Get<uint32>();
vendorItemsPrices[itemID] = itemPrice;
} while (result->NextRow());
}
}

View File

@@ -132,6 +132,7 @@ private:
uint32 ListingExpireTimeInSecondsMin;
uint32 ListingExpireTimeInSecondsMax;
float BuyingBotAcceptablePriceModifier;
std::vector<uint32> vendorItemsPrices;
std::string AHCharactersGUIDsForQuery;
uint32 ItemsPerCycle;
bool DisabledItemTextFilter;
@@ -263,6 +264,7 @@ private:
uint32 ListedItemIDMin;
uint32 ListedItemIDMax;
std::set<uint32> ListedItemIDExceptionItems;
bool PreventOverpayingForVendorItems;
AHBConfig AllianceConfig;
AHBConfig HordeConfig;
@@ -272,6 +274,7 @@ private:
uint32 getStackSizeForItem(ItemTemplate const* itemProto) const;
void calculateItemValue(ItemTemplate const* itemProto, uint64& outBidPrice, uint64& outBuyoutPrice);
float getAdvancedPricingMultiplier(ItemTemplate const* itemProto);
void populatetemClassSeedListForItemClass(uint32 itemClass, uint32 itemClassSeedWeight);
void populateItemClassProportionList();
ItemTemplate const* getProducedItemFromRecipe(ItemTemplate const* recipeItemTemplate);
@@ -279,6 +282,7 @@ private:
int getRandomValidItemClassForNewListing();
void addNewAuctions(Player* AHBplayer, AHBConfig *config);
void addNewAuctionBuyerBotBid(Player* AHBplayer, AHBConfig *config);
void populateVendorItemsPrices();
AuctionHouseBot();