Merge remote-tracking branch 'upstream/master' into addl-category-configs

This commit is contained in:
zeb
2025-09-10 11:03:26 -04:00
4 changed files with 217 additions and 129 deletions

View File

@@ -20,7 +20,7 @@ Requires an AzerothCore version that is caught up to at least change set 3f46e05
## Usage
Edit the module configuration and add a character GUID ID to the "AuctionHouseBot.GUIDs" variable, which is sourced from your character table in the character database. These names will be visable in the auction house, so pick good names. Find the configuration as named mod_ahbot.conf.dist / mod_ahbot.conf. IMPORTANT: If you are using a bot mod (like playerbots), then ensure you use regular non-bot characters for your auctionhouse character(s)
Edit the module configuration and add a character GUID ID to the "AuctionHouseBot.GUIDs" variable, which is sourced from your character table in the character database. These names will be visable in the auction house, so pick good names. IMPORTANT: If you are using a bot mod (like playerbots), then ensure you use regular non-bot characters for your auctionhouse character(s)
Notes:
- These accounts do not need any security level and can be a player accounts.
@@ -28,6 +28,15 @@ Notes:
- Important! By default, most player crafted items (except glyphs, bolts, maybe a few other things) are disabled from showing up in the auction house in order to encourage player crafting on lower pop servers. If you want different behavior, alter the config variable "AuctionHouseBot.DisabledCraftedItemIDs" by deleting IDs you wish to show up. Note that fish are also disabled to encourage fishing, and that's also managed by disabled lists.
- It takes a few hours for the auction house to fully populate, as only 75 items gets added by default every 'tick'. You can change this in the config with the AuctionHouseBot.ItemsPerCycle variable.
## Buying Bot Behavior
1. **Determining Items to Buy:** Every minute the buyer bot will select (BuyCanditatesPerBuyCycle) items currently up for auction which are listed by players as potential purchase items.
2. **Price Willing to Pay:** The buyer bot will use the same item price calculation the seller bot uses, including the random +/- 25%, and that calculated price is then multiplied by (AcceptablePriceModifier) which then becomes the price the buyer will be willing to spend.
3. **Buying it:** If the price calculated is higher than the buy out price, then the bot will buy it out. If not, it will test to see if it has been bidded on by a bot and if not, if the bid price is below the price it is willing to pay. If so, it will bid (outbid, so just over current bid).
The above behavior is replicated on each enabled auction house. If left to default settings, 1 item in each auction house will attempt to be bought from, if the price calculation seems favorable. Note that any item buy attempt, even items above buying price, consumes a buy candidate. That means that too many overpriced items can drown out potential sales.
## Credits
- NathanHandley: Created this rewrite of the one that was ported to AzerothCore

File diff suppressed because one or more lines are too long

View File

@@ -21,6 +21,7 @@
#include "ObjectMgr.h"
#include "AuctionHouseMgr.h"
#include "AuctionHouseBot.h"
#include "AuctionHouseSearcher.h"
#include "Config.h"
#include "Player.h"
#include "WorldSession.h"
@@ -34,17 +35,91 @@
using namespace std;
AuctionHouseBot::AuctionHouseBot()
AuctionHouseBot::AuctionHouseBot() :
debug_Out(false),
debug_Out_Filters(false),
SellingBotEnabled(false),
BuyingBotEnabled(false),
CyclesBetweenBuyOrSell(1),
BuyingBotBuyCanditatesPerBuyCycle(1),
BuyingBotAcceptablePriceModifier(1),
AHCharactersGUIDsForQuery(""),
ItemsPerCycle(75),
DisabledItemTextFilter(true),
ListedItemLevelRestrictedEnabled(false),
ListedItemLevelMax(999),
ListedItemLevelMin(0),
RandomStackRatioConsumable(1),
RandomStackRatioContainer(1),
RandomStackRatioWeapon(1),
RandomStackRatioGem(1),
RandomStackRatioArmor(1),
RandomStackRatioReagent(1),
RandomStackRatioProjectile(1),
RandomStackRatioTradeGood(1),
RandomStackRatioGeneric(1),
RandomStackRatioRecipe(1),
RandomStackRatioQuiver(1),
RandomStackRatioQuest(1),
RandomStackRatioKey(1),
RandomStackRatioMisc(1),
RandomStackRatioGlyph(1),
ListProportionConsumable(1),
ListProportionContainer(1),
ListProportionWeapon(1),
ListProportionGem(1),
ListProportionArmor(1),
ListProportionReagent(1),
ListProportionProjectile(1),
ListProportionTradeGood(1),
ListProportionGeneric(1),
ListProportionRecipe(1),
ListProportionQuiver(1),
ListProportionQuest(1),
ListProportionKey(1),
ListProportionMisc(1),
ListProportionGlyph(1),
PriceMultiplierCategoryConsumable(1),
PriceMultiplierCategoryContainer(1),
PriceMultiplierCategoryWeapon(1),
PriceMultiplierCategoryGem(1),
PriceMultiplierCategoryArmor(1),
PriceMultiplierCategoryReagent(1),
PriceMultiplierCategoryProjectile(1),
PriceMultiplierCategoryTradeGood(1),
PriceMultiplierCategoryGeneric(1),
PriceMultiplierCategoryRecipe(1),
PriceMultiplierCategoryQuiver(1),
PriceMultiplierCategoryQuest(1),
PriceMultiplierCategoryKey(1),
PriceMultiplierCategoryMisc(1),
PriceMultiplierCategoryGlyph(1),
PriceMultiplierQualityPoor(1),
PriceMultiplierQualityNormal(1),
PriceMultiplierQualityUncommon(1),
PriceMultiplierQualityRare(1),
PriceMultiplierQualityEpic(1),
PriceMultiplierQualityLegendary(1),
PriceMultiplierQualityArtifact(1),
PriceMultiplierQualityHeirloom(1),
PriceMinimumCenterBaseConsumable(1),
PriceMinimumCenterBaseContainer(1),
PriceMinimumCenterBaseWeapon(1),
PriceMinimumCenterBaseGem(1),
PriceMinimumCenterBaseArmor(1),
PriceMinimumCenterBaseReagent(1),
PriceMinimumCenterBaseProjectile(1),
PriceMinimumCenterBaseTradeGood(1),
PriceMinimumCenterBaseGeneric(1),
PriceMinimumCenterBaseRecipe(1),
PriceMinimumCenterBaseQuiver(1),
PriceMinimumCenterBaseQuest(1),
PriceMinimumCenterBaseKey(1),
PriceMinimumCenterBaseMisc(1),
PriceMinimumCenterBaseGlyph(1),
ItemLevelPriceMultiplier(1),
LastCycleCount(0)
{
debug_Out = false;
debug_Out_Filters = false;
AHBSeller = false;
AHBBuyer = false;
_lastrun_a = time(NULL);
_lastrun_h = time(NULL);
_lastrun_n = time(NULL);
AllianceConfig = AHBConfig(2);
HordeConfig = AHBConfig(6);
NeutralConfig = AHBConfig(7);
@@ -518,7 +593,7 @@ void AuctionHouseBot::populateItemCandidateList()
void AuctionHouseBot::addNewAuctions(Player* AHBplayer, AHBConfig *config)
{
if (!AHBSeller)
if (!SellingBotEnabled)
{
if (debug_Out)
LOG_INFO("module", "AHSeller: Disabled");
@@ -654,7 +729,7 @@ void AuctionHouseBot::addNewAuctions(Player* AHBplayer, AHBConfig *config)
void AuctionHouseBot::addNewAuctionBuyerBotBid(Player* AHBplayer, AHBConfig *config)
{
if (!AHBBuyer)
if (!BuyingBotEnabled)
{
if (debug_Out)
LOG_ERROR("module", "AHBuyer: Disabled");
@@ -679,13 +754,13 @@ void AuctionHouseBot::addNewAuctionBuyerBotBid(Player* AHBplayer, AHBConfig *con
possibleBids.push_back(tmpdata);
}while (result->NextRow());
for (uint32 count = 1; count <= config->GetBidsPerInterval(); ++count)
for (uint32 count = 1; count <= BuyingBotBuyCanditatesPerBuyCycle; ++count)
{
// Do we have anything to bid? If not, stop here.
if (possibleBids.empty())
{
//if (debug_Out) sLog->outError( "AHBuyer: I have no items to bid on.");
count = config->GetBidsPerInterval();
count = BuyingBotBuyCanditatesPerBuyCycle;
continue;
}
@@ -719,29 +794,19 @@ void AuctionHouseBot::addNewAuctionBuyerBotBid(Player* AHBplayer, AHBConfig *con
uint64 willingToSpendPerItemPrice = 0;
uint64 discardBidPrice = 0;
calculateItemValue(prototype, discardBidPrice, willingToSpendPerItemPrice);
uint64 willingToSpendForStackPrice = willingToSpendPerItemPrice * pItem->GetCount();
willingToSpendPerItemPrice = (uint64)((float)willingToSpendPerItemPrice * BuyingBotAcceptablePriceModifier);
// Buy it if the price is greater than buy out, bid if the price is greater than current bid, otherwise skip
uint64 willingToPayForStackPrice = willingToSpendPerItemPrice * pItem->GetCount();
uint64 bidAmount = 0;
// Determine if it's a bid, buyout, or skip
bool doBuyout = false;
bool doBid = false;
uint32 minBidPrice = 0;
if (auction->buyout != 0 && willingToSpendForStackPrice >= auction->buyout)
doBuyout = true;
else
{
if (auction->bid >= auction->startbid)
minBidPrice = auction->GetAuctionOutBid();
else
minBidPrice = auction->startbid;
if (minBidPrice <= willingToSpendForStackPrice)
{
if (auction->buyout != 0 && minBidPrice >= auction->buyout)
doBuyout = true;
else
doBid = true;
}
}
if (auction->buyout != 0 && auction->buyout < willingToPayForStackPrice)
doBuyout = true;
else if (auction->startbid < willingToPayForStackPrice && auction->GetAuctionOutBid() < willingToPayForStackPrice)
doBid = true;
if (doBuyout == true || doBid == true)
{
@@ -757,8 +822,7 @@ void AuctionHouseBot::addNewAuctionBuyerBotBid(Player* AHBplayer, AHBConfig *con
LOG_INFO("module", "AHBuyer: Buyout: {}", auction->buyout);
LOG_INFO("module", "AHBuyer: Deposit: {}", auction->deposit);
LOG_INFO("module", "AHBuyer: Expire Time: {}", uint32(auction->expire_time));
LOG_INFO("module", "AHBuyer: Willing To Spend For Stack Price: {}", willingToSpendForStackPrice);
LOG_INFO("module", "AHBuyer: Minimum Bid Price: {}", minBidPrice);
LOG_INFO("module", "AHBuyer: Willing To Pay For Stack Price: {}", willingToPayForStackPrice);
LOG_INFO("module", "AHBuyer: Item GUID: {}", auction->item_guid.ToString());
LOG_INFO("module", "AHBuyer: Item Template: {}", auction->item_template);
LOG_INFO("module", "AHBuyer: Item Info:");
@@ -774,37 +838,37 @@ void AuctionHouseBot::addNewAuctionBuyerBotBid(Player* AHBplayer, AHBConfig *con
if (doBid)
{
// Return money of prior bidder
auto trans = CharacterDatabase.BeginTransaction();
// Perform outbid
uint32 bidAmount = 0;
if (auction->bid == 0)
bidAmount = auction->startbid;
else
bidAmount = auction->GetAuctionOutBid();
if (auction->bidder)
{
if (auction->bidder == AHBplayer->GetGUID())
{
//pl->ModifyMoney(-int32(price - auction->bid));
}
else
{
// mail to last bidder and return money
auto trans = CharacterDatabase.BeginTransaction();
sAuctionMgr->SendAuctionOutbiddedMail(auction, minBidPrice, AHBplayer, trans);
CharacterDatabase.CommitTransaction(trans);
//pl->ModifyMoney(-int32(price));
}
}
sAuctionMgr->SendAuctionOutbiddedMail(auction, bidAmount, AHBplayer, trans);
auction->bidder = AHBplayer->GetGUID();
auction->bid = minBidPrice;
auction->bid = bidAmount;
// Saving auction into database
CharacterDatabase.Execute("UPDATE auctionhouse SET buyguid = '{}',lastbid = '{}' WHERE id = '{}'", auction->bidder.GetCounter(), auction->bid, auction->Id);
sAuctionMgr->GetAuctionHouseSearcher()->UpdateBid(auction);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_BID);
stmt->SetData(0, auction->bidder.GetCounter());
stmt->SetData(1, auction->bid);
stmt->SetData(2, auction->Id);
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
}
else if (doBuyout)
{
auto trans = CharacterDatabase.BeginTransaction();
//buyout
if ((auction->bidder) && (AHBplayer->GetGUID() != auction->bidder))
{
sAuctionMgr->SendAuctionOutbiddedMail(auction, auction->buyout, AHBplayer, trans);
}
auction->bidder = AHBplayer->GetGUID();
auction->bid = auction->buyout;
@@ -823,11 +887,17 @@ void AuctionHouseBot::addNewAuctionBuyerBotBid(Player* AHBplayer, AHBConfig *con
void AuctionHouseBot::Update()
{
if ((AHBSeller == false) && (AHBBuyer == false))
if ((SellingBotEnabled == false) && (BuyingBotEnabled == false))
return;
if (AHCharacters.size() == 0)
return;
time_t _newrun = time(NULL);
// Only update if the update cycle has been hit
LastCycleCount++;
if (LastCycleCount < CyclesBetweenBuyOrSell)
return;
LastCycleCount = 0;
// Randomly select the bot to load, and load it
uint32 botIndex = urand(0, AHCharacters.size() - 1);
@@ -842,26 +912,17 @@ void AuctionHouseBot::Update()
if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
{
addNewAuctions(&_AHBplayer, &AllianceConfig);
if (((_newrun - _lastrun_a) >= (AllianceConfig.GetBiddingInterval() * MINUTE)) && (AllianceConfig.GetBidsPerInterval() > 0))
{
if (BuyingBotBuyCanditatesPerBuyCycle > 0)
addNewAuctionBuyerBotBid(&_AHBplayer, &AllianceConfig);
_lastrun_a = _newrun;
}
addNewAuctions(&_AHBplayer, &HordeConfig);
if (((_newrun - _lastrun_h) >= (HordeConfig.GetBiddingInterval() * MINUTE)) && (HordeConfig.GetBidsPerInterval() > 0))
{
if (BuyingBotBuyCanditatesPerBuyCycle > 0)
addNewAuctionBuyerBotBid(&_AHBplayer, &HordeConfig);
_lastrun_h = _newrun;
}
}
addNewAuctions(&_AHBplayer, &NeutralConfig);
if (((_newrun - _lastrun_n) >= (NeutralConfig.GetBiddingInterval() * MINUTE)) && (NeutralConfig.GetBidsPerInterval() > 0))
{
if (BuyingBotBuyCanditatesPerBuyCycle > 0)
addNewAuctionBuyerBotBid(&_AHBplayer, &NeutralConfig);
_lastrun_n = _newrun;
}
ObjectAccessor::RemoveObject(&_AHBplayer);
}
@@ -877,16 +938,21 @@ void AuctionHouseBot::InitializeConfiguration()
{
debug_Out = sConfigMgr->GetOption<bool>("AuctionHouseBot.DEBUG", false);
debug_Out_Filters = sConfigMgr->GetOption<bool>("AuctionHouseBot.DEBUG_FILTERS", false);
SellingBotEnabled = sConfigMgr->GetOption<bool>("AuctionHouseBot.EnableSeller", false);
AHBSeller = sConfigMgr->GetOption<bool>("AuctionHouseBot.EnableSeller", false);
AHBBuyer = sConfigMgr->GetOption<bool>("AuctionHouseBot.EnableBuyer", false);
if (AHBSeller == false && AHBBuyer == false)
// Buyer Bot
BuyingBotEnabled = sConfigMgr->GetOption<bool>("AuctionHouseBot.Buyer.Enabled", false);
CyclesBetweenBuyOrSell = sConfigMgr->GetOption<uint32>("AuctionHouseBot.AuctionHouseManagerCyclesBetweenBuyOrSell", 1);
BuyingBotBuyCanditatesPerBuyCycle = sConfigMgr->GetOption<uint32>("AuctionHouseBot.Buyer.BuyCanditatesPerBuyCycle", 1);
BuyingBotAcceptablePriceModifier = sConfigMgr->GetOption<float>("AuctionHouseBot.Buyer.AcceptablePriceModifier", 1);
if (SellingBotEnabled == false && BuyingBotEnabled == false)
return;
string charString = sConfigMgr->GetOption<std::string>("AuctionHouseBot.GUIDs", "0");
if (charString == "0")
{
AHBBuyer = false;
AHBSeller = false;
BuyingBotEnabled = false;
SellingBotEnabled = false;
LOG_INFO("module", "AuctionHouseBot: AuctionHouseBot.GUIDs is '0' so this module will be disabled");
return;
}
@@ -1001,18 +1067,12 @@ void AuctionHouseBot::InitializeConfiguration()
{
AllianceConfig.SetMinItems(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Alliance.MinItems", 15000));
AllianceConfig.SetMaxItems(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Alliance.MaxItems", 15000));
AllianceConfig.SetBiddingInterval(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Alliance.BidInterval", 1));
AllianceConfig.SetBidsPerInterval(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Alliance.BidsPerInterval", 1));
HordeConfig.SetMinItems(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Horde.MinItems", 15000));
HordeConfig.SetMaxItems(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Horde.MaxItems", 15000));
HordeConfig.SetBiddingInterval(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Horde.BidInterval", 1));
HordeConfig.SetBidsPerInterval(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Horde.BidsPerInterval", 1));
}
NeutralConfig.SetMinItems(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Neutral.MinItems", 15000));
NeutralConfig.SetMaxItems(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Neutral.MaxItems", 15000));
NeutralConfig.SetBiddingInterval(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Neutral.BidInterval", 1));
NeutralConfig.SetBidsPerInterval(sConfigMgr->GetOption<uint32>("AuctionHouseBot.Neutral.BidsPerInterval", 1));
}
uint32 AuctionHouseBot::GetRandomStackValue(std::string configKeyString, uint32 defaultValue)

View File

@@ -42,9 +42,6 @@ private:
uint32 minItems;
uint32 maxItems;
uint32 buyerBiddingInterval;
uint32 buyerBidsPerInterval;
public:
AHBConfig(uint32 ahid)
{
@@ -99,24 +96,6 @@ public:
{
return maxItems;
}
void SetBiddingInterval(uint32 value)
{
buyerBiddingInterval = value;
}
uint32 GetBiddingInterval()
{
return buyerBiddingInterval;
}
void SetBidsPerInterval(uint32 value)
{
buyerBidsPerInterval = value;
}
uint32 GetBidsPerInterval()
{
return buyerBidsPerInterval;
}
~AHBConfig()
{
}
@@ -142,8 +121,12 @@ private:
bool debug_Out;
bool debug_Out_Filters;
bool AHBSeller;
bool AHBBuyer;
bool SellingBotEnabled;
bool BuyingBotEnabled;
int CyclesBetweenBuyOrSell;
int BuyingBotBuyCanditatesPerBuyCycle;
float BuyingBotAcceptablePriceModifier;
std::string AHCharactersGUIDsForQuery;
uint32 ItemsPerCycle;
@@ -229,14 +212,11 @@ private:
std::unordered_map<uint32, uint64> PriceMinimumCenterBaseOverridesByItemID;
float ItemLevelPriceMultiplier;
AHBConfig AllianceConfig;
AHBConfig HordeConfig;
AHBConfig NeutralConfig;
time_t _lastrun_a;
time_t _lastrun_h;
time_t _lastrun_n;
int LastCycleCount;
inline uint32 minValue(uint32 a, uint32 b) { return a <= b ? a : b; };
uint32 getStackSizeForItem(ItemTemplate const* itemProto) const;