diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/game/AuctionHouseBot/AuctionHouseBot.cpp | 18 | ||||
-rw-r--r-- | src/server/game/AuctionHouseBot/AuctionHouseBot.h | 18 | ||||
-rw-r--r-- | src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp | 569 | ||||
-rw-r--r-- | src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h | 31 | ||||
-rw-r--r-- | src/server/worldserver/worldserver.conf.dist | 59 |
5 files changed, 371 insertions, 324 deletions
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp b/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp index 7e8ccb4bb23..a04e4091778 100644 --- a/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp +++ b/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp @@ -204,14 +204,24 @@ void AuctionBotConfig::GetConfigFromFile() SetConfig(CONFIG_AHBOT_MINTIME, "AuctionHouseBot.MinTime", 1); SetConfig(CONFIG_AHBOT_MAXTIME, "AuctionHouseBot.MaxTime", 72); - SetConfigMinMax(CONFIG_AHBOT_BUYER_CHANCE_RATIO_ALLIANCE, "AuctionHouseBot.Buyer.Alliance.Chance.Ratio", 3, 1, 100); - SetConfigMinMax(CONFIG_AHBOT_BUYER_CHANCE_RATIO_HORDE, "AuctionHouseBot.Buyer.Horde.Chance.Ratio", 3, 1, 100); - SetConfigMinMax(CONFIG_AHBOT_BUYER_CHANCE_RATIO_NEUTRAL, "AuctionHouseBot.Buyer.Neutral.Chance.Ratio", 3, 1, 100); SetConfigMinMax(CONFIG_AHBOT_BUYER_RECHECK_INTERVAL, "AuctionHouseBot.Buyer.Recheck.Interval", 20, 1, DAY / MINUTE); + SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_GRAY, "AuctionHouseBot.Buyer.Baseprice.Gray", 3504); + SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_WHITE, "AuctionHouseBot.Buyer.Baseprice.White", 5429); + SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_GREEN, "AuctionHouseBot.Buyer.Baseprice.Green", 21752); + SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_BLUE, "AuctionHouseBot.Buyer.Baseprice.Blue", 36463); + SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_PURPLE, "AuctionHouseBot.Buyer.Baseprice.Purple", 87124); + SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_ORANGE, "AuctionHouseBot.Buyer.Baseprice.Orange", 214347); + SetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_YELLOW, "AuctionHouseBot.Buyer.Baseprice.Yellow", 407406); + SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GRAY, "AuctionHouseBot.Buyer.ChanceMultiplier.Gray", 100); + SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_WHITE, "AuctionHouseBot.Buyer.ChanceMultiplier.White", 100); + SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GREEN, "AuctionHouseBot.Buyer.ChanceMultiplier.Green", 100); + SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_BLUE, "AuctionHouseBot.Buyer.ChanceMultiplier.Blue", 100); + SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_PURPLE, "AuctionHouseBot.Buyer.ChanceMultiplier.Purple", 100); + SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_ORANGE, "AuctionHouseBot.Buyer.ChanceMultiplier.Orange", 100); + SetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_YELLOW, "AuctionHouseBot.Buyer.ChanceMultiplier.Yellow", 100); SetConfig(CONFIG_AHBOT_SELLER_ENABLED, "AuctionHouseBot.Seller.Enabled", false); SetConfig(CONFIG_AHBOT_BUYER_ENABLED, "AuctionHouseBot.Buyer.Enabled", false); - SetConfig(CONFIG_AHBOT_BUYPRICE_BUYER, "AuctionHouseBot.Buyer.Buyprice", true); SetConfig(CONFIG_AHBOT_CLASS_MISC_MOUNT_MIN_REQ_LEVEL, "AuctionHouseBot.Class.Misc.Mount.ReqLevel.Min", 0); SetConfig(CONFIG_AHBOT_CLASS_MISC_MOUNT_MAX_REQ_LEVEL, "AuctionHouseBot.Class.Misc.Mount.ReqLevel.Max", 0); diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBot.h b/src/server/game/AuctionHouseBot/AuctionHouseBot.h index 964262579e6..29460ba13d7 100644 --- a/src/server/game/AuctionHouseBot/AuctionHouseBot.h +++ b/src/server/game/AuctionHouseBot/AuctionHouseBot.h @@ -110,10 +110,21 @@ enum AuctionBotConfigUInt32Values CONFIG_AHBOT_CLASS_PERMANENT_PRICE_RATIO, CONFIG_AHBOT_CLASS_MISC_PRICE_RATIO, CONFIG_AHBOT_CLASS_GLYPH_PRICE_RATIO, - CONFIG_AHBOT_BUYER_CHANCE_RATIO_ALLIANCE, - CONFIG_AHBOT_BUYER_CHANCE_RATIO_HORDE, - CONFIG_AHBOT_BUYER_CHANCE_RATIO_NEUTRAL, CONFIG_AHBOT_BUYER_RECHECK_INTERVAL, + CONFIG_AHBOT_BUYER_BASEPRICE_GRAY, + CONFIG_AHBOT_BUYER_BASEPRICE_WHITE, + CONFIG_AHBOT_BUYER_BASEPRICE_GREEN, + CONFIG_AHBOT_BUYER_BASEPRICE_BLUE, + CONFIG_AHBOT_BUYER_BASEPRICE_PURPLE, + CONFIG_AHBOT_BUYER_BASEPRICE_ORANGE, + CONFIG_AHBOT_BUYER_BASEPRICE_YELLOW, + CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GRAY, + CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_WHITE, + CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GREEN, + CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_BLUE, + CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_PURPLE, + CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_ORANGE, + CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_YELLOW, CONFIG_AHBOT_CLASS_MISC_MOUNT_MIN_REQ_LEVEL, CONFIG_AHBOT_CLASS_MISC_MOUNT_MAX_REQ_LEVEL, CONFIG_AHBOT_CLASS_MISC_MOUNT_MIN_SKILL_RANK, @@ -143,7 +154,6 @@ enum AuctionBotConfigBoolValues CONFIG_AHBOT_BIND_USE, CONFIG_AHBOT_BIND_QUEST, CONFIG_AHBOT_BUYPRICE_SELLER, - CONFIG_AHBOT_BUYPRICE_BUYER, CONFIG_AHBOT_SELLER_ENABLED, CONFIG_AHBOT_BUYER_ENABLED, CONFIG_AHBOT_LOCKBOX_ENABLED, diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp index af60316cbec..bd5defe2bc3 100644 --- a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp +++ b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp @@ -20,7 +20,7 @@ #include "ItemPrototype.h" #include "AuctionHouseBotBuyer.h" -AuctionBotBuyer::AuctionBotBuyer(): _checkInterval(20) +AuctionBotBuyer::AuctionBotBuyer() : _checkInterval(20 * MINUTE) { // Define faction for our main data class. for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i) @@ -48,391 +48,394 @@ bool AuctionBotBuyer::Initialize() if (!activeHouse) return false; - //load Check interval + // load Check interval _checkInterval = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_RECHECK_INTERVAL) * MINUTE; - TC_LOG_DEBUG("ahbot", "AHBot buyer interval between 2 check = %u", _checkInterval); + TC_LOG_DEBUG("ahbot", "AHBot buyer interval is %u minutes", _checkInterval / MINUTE); return true; } -void AuctionBotBuyer::LoadBuyerValues(BuyerConfiguration& config) +void AuctionBotBuyer::LoadConfig() { - uint32 factionChance; - - switch (config.GetHouseType()) + for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i) { - case AUCTION_HOUSE_ALLIANCE: - config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ALLIANCE_PRICE_RATIO) + 50; - factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_ALLIANCE); - break; - case AUCTION_HOUSE_HORDE: - config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_HORDE_PRICE_RATIO) + 50; - factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_HORDE); - break; - default: - config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_NEUTRAL_PRICE_RATIO) + 50; - factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_NEUTRAL); - break; + _houseConfig[i].BuyerEnabled = sAuctionBotConfig->GetConfigBuyerEnabled(AuctionHouseType(i)); + if (_houseConfig[i].BuyerEnabled) + LoadBuyerValues(_houseConfig[i]); } +} + +void AuctionBotBuyer::LoadBuyerValues(BuyerConfiguration& config) +{ - config.FactionChance = 5000 * factionChance; } -void AuctionBotBuyer::LoadConfig() +// Makes an AHbot buyer cycle for AH type if necessary +bool AuctionBotBuyer::Update(AuctionHouseType houseType) { - for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i) + if (!sAuctionBotConfig->GetConfigBuyerEnabled(houseType)) + return false; + + TC_LOG_DEBUG("ahbot", "AHBot: %s buying ...", AuctionBotConfig::GetHouseTypeName(houseType)); + + BuyerConfiguration& config = _houseConfig[houseType]; + uint32 eligibleItems = GetItemInformation(config); + if (eligibleItems) { - _houseConfig[i].BuyerEnabled = sAuctionBotConfig->GetConfigBuyerEnabled(AuctionHouseType(i)); - if (_houseConfig[i].BuyerEnabled) - LoadBuyerValues(_houseConfig[i]); + // Prepare list of items to bid or buy - remove old items + PrepareListOfEntry(config); + // Process buying and bidding items + BuyAndBidItems(config); } + + return true; } -uint32 AuctionBotBuyer::GetBuyableEntry(BuyerConfiguration& config) +// Collects information about item counts and minimum prices to SameItemInfo and updates EligibleItems - a list with new items eligible for bot to buy and bid +// Returns count of items in AH that were eligible for being bought or bidded on by ahbot buyer (EligibleItems size) +uint32 AuctionBotBuyer::GetItemInformation(BuyerConfiguration& config) { config.SameItemInfo.clear(); - uint32 count = 0; time_t now = time(nullptr); + uint32 count = 0; AuctionHouseObject* house = sAuctionMgr->GetAuctionsMap(config.GetHouseType()); for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = house->GetAuctionsBegin(); itr != house->GetAuctionsEnd(); ++itr) { AuctionEntry* entry = itr->second; Item* item = sAuctionMgr->GetAItem(entry->itemGUIDLow); - if (item) + if (!item) + continue; + + BuyerItemInfo& itemInfo = config.SameItemInfo[item->GetEntry()]; + + // Update item entry's count and total bid prices + // This can be used later to determine the prices and chances to bid + uint32 itemBidPrice = entry->startbid / item->GetCount(); + itemInfo.TotalBidPrice = itemInfo.TotalBidPrice + itemBidPrice; + itemInfo.BidItemCount++; + + // Set minimum bid price + if (!itemInfo.MinBidPrice) + itemInfo.MinBidPrice = itemBidPrice; + else + itemBidPrice = std::min(itemInfo.MinBidPrice, itemBidPrice); + + // Set minimum buyout price if item has buyout + if (entry->buyout) { - ItemTemplate const * prototype = item->GetTemplate(); - if (prototype) - { - ++config.SameItemInfo[item->GetEntry()].ItemCount; // Structure constructor will make sure Element are correctly initialized if entry is created here. - config.SameItemInfo[item->GetEntry()].BuyPrice = config.SameItemInfo[item->GetEntry()].BuyPrice + (itr->second->buyout / item->GetCount()); - config.SameItemInfo[item->GetEntry()].BidPrice = config.SameItemInfo[item->GetEntry()].BidPrice + (itr->second->startbid / item->GetCount()); - if (itr->second->buyout != 0) - { - if (itr->second->buyout / item->GetCount() < config.SameItemInfo[item->GetEntry()].MinBuyPrice) - config.SameItemInfo[item->GetEntry()].MinBuyPrice = itr->second->buyout / item->GetCount(); - else if (config.SameItemInfo[item->GetEntry()].MinBuyPrice == 0) - config.SameItemInfo[item->GetEntry()].MinBuyPrice = itr->second->buyout / item->GetCount(); - } - if (itr->second->startbid / item->GetCount() < config.SameItemInfo[item->GetEntry()].MinBidPrice) - config.SameItemInfo[item->GetEntry()].MinBidPrice = itr->second->startbid / item->GetCount(); - else if (config.SameItemInfo[item->GetEntry()].MinBidPrice == 0) - config.SameItemInfo[item->GetEntry()].MinBidPrice = itr->second->startbid / item->GetCount(); - - if (!entry->owner) - { - - if (entry->bid != 0 && entry->bidder) // Add bid by player - { - config.CheckedEntry[entry->Id].LastExist = now; - config.CheckedEntry[entry->Id].AuctionId = entry->Id; - ++count; - } - } - else - { - if (entry->bid != 0) - { - if (entry->bidder) - { - config.CheckedEntry[entry->Id].LastExist = now; - config.CheckedEntry[entry->Id].AuctionId = entry->Id; - ++count; - } - } - else - { - config.CheckedEntry[entry->Id].LastExist = now; - config.CheckedEntry[entry->Id].AuctionId = entry->Id; - ++count; - } - } - } + // Update item entry's count and total buyout prices + // This can be used later to determine the prices and chances to buyout + uint32 itemBuyPrice = entry->buyout / item->GetCount(); + itemInfo.TotalBuyPrice = itemInfo.TotalBuyPrice + itemBuyPrice; + itemInfo.BuyItemCount++; + + if (!itemInfo.MinBuyPrice) + itemInfo.MinBuyPrice = itemBuyPrice; + else + itemInfo.MinBuyPrice = std::min(itemInfo.MinBuyPrice, itemBuyPrice); + } + + // Add/update to EligibleItems if: + // has a bid by player or + // has no bids and not owned by bot + if ((entry->bid && entry->bidder) || (entry->owner && !entry->bid)) + { + config.EligibleItems[entry->Id].LastExist = now; + config.EligibleItems[entry->Id].AuctionId = entry->Id; + ++count; } } - TC_LOG_DEBUG("ahbot", "AHBot: %u items added to buyable vector for ah type: %u", count, config.GetHouseType()); + TC_LOG_DEBUG("ahbot", "AHBot: %u items added to buyable/biddable vector for ah type: %u", count, config.GetHouseType()); TC_LOG_DEBUG("ahbot", "AHBot: SameItemInfo size = %u", (uint32)config.SameItemInfo.size()); return count; } -void AuctionBotBuyer::PrepareListOfEntry(BuyerConfiguration& config) +// ahInfo can be NULL +bool AuctionBotBuyer::RollBuyChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 /*bidPrice*/) { - time_t now = time(nullptr) - 5; + if (!auction->buyout) + return false; - for (CheckEntryMap::iterator itr = config.CheckedEntry.begin(); itr != config.CheckedEntry.end();) - { - if (itr->second.LastExist < (now - 5)) - config.CheckedEntry.erase(itr++); - else - ++itr; - } + uint32 itemBuyPrice = auction->buyout / item->GetCount(); + uint32 itemPrice = item->GetTemplate()->SellPrice ? item->GetTemplate()->SellPrice : GetVendorPrice(item->GetTemplate()->Quality); + // The AH cut needs to be added to the price, but we dont want a 100% chance to buy if the price is exactly AH default + itemPrice *= 1.4f; - TC_LOG_DEBUG("ahbot", "AHBot: CheckedEntry size = %u", (uint32)config.CheckedEntry.size()); -} + // This value is between 0 and 100 and is used directly as the chance to buy or bid + // Value equal or above 100 means 100% chance and value below 0 means 0% chance + float chance = 100 / sqrt(itemBuyPrice / float(itemPrice)); -bool AuctionBotBuyer::IsBuyableEntry(uint32 buyoutPrice, double inGameBuyPrice, uint32 maxBuyablePrice, uint32 minBuyPrice, uint32 maxChance, uint32 chanceRatio) -{ - double ratio = 0; - uint32 chance = 0; + // If a player has bidded on item, have fifth of normal chance + if (auction->bidder) + chance = chance / 5; - if (buyoutPrice <= minBuyPrice) + if (ahInfo) { - if (buyoutPrice <= maxBuyablePrice) - chance = maxChance; - else - { + float avgBuyPrice = ahInfo->TotalBuyPrice / float(ahInfo->BuyItemCount); - if (buyoutPrice > 0 && maxBuyablePrice > 0) - { - ratio = double(buyoutPrice) / double(maxBuyablePrice); - if (ratio < 10) - chance = maxChance - (ratio * maxChance / 10); - else - chance = 1; - } - } - } - else if (buyoutPrice <= inGameBuyPrice) - { - if (buyoutPrice <= maxBuyablePrice) - chance = maxChance / 5; - else - { + TC_LOG_DEBUG("ahbot", "AHBot: buyout average: %.1f items with buyout: %u", avgBuyPrice, ahInfo->BuyItemCount); - if (buyoutPrice > 0 && maxBuyablePrice > 0) - { - ratio = double(buyoutPrice) / double(maxBuyablePrice); - if (ratio < 10) - chance = (maxChance / 5) - (ratio * maxChance / 50); - else - chance = 1; - } - } - } - else if (buyoutPrice <= maxBuyablePrice) - chance = maxChance / 10; - else - { - if (buyoutPrice > 0 && maxBuyablePrice > 0) + // If there are more than 5 items on AH of this entry, try weigh in the average buyout price + if (ahInfo->BuyItemCount > 5) { - ratio = double(buyoutPrice) / double(maxBuyablePrice); - if (ratio < 10) - chance = (maxChance / 5) - (ratio* maxChance / 50); - else - chance = 0; + chance *= 1 / sqrt(itemBuyPrice / avgBuyPrice); } - else - chance = 0; } - if (urand(1, chanceRatio) <= chance) - { - TC_LOG_DEBUG("ahbot", "AHBot: WIN BUY! Chance = %u, num = %u.", chance, chanceRatio); - return true; - } - else - { - TC_LOG_DEBUG("ahbot", "AHBot: LOOSE BUY! Chance = %u, num = %u.", chance, chanceRatio); - return false; - } + // Add config weigh in for quality + chance *= GetChanceMultiplier(item->GetTemplate()->Quality) / 100.0f; + + float rand = frand(0, 100); + bool win = rand <= chance; + TC_LOG_DEBUG("ahbot", "AHBot: %s BUY! chance = %.2f, price = %u, buyprice = %u.", win ? "WIN" : "LOSE", chance, itemPrice, itemBuyPrice); + return win; } -bool AuctionBotBuyer::IsBidableEntry(uint32 bidPrice, double inGameBuyPrice, double maxBidablePrice, uint32 minBidPrice, uint32 maxChance, uint32 chanceRatio) +// ahInfo can be NULL +bool AuctionBotBuyer::RollBidChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 bidPrice) { - double ratio = 0; - uint32 chance = 0; + uint32 itemBidPrice = bidPrice / item->GetCount(); + uint32 itemPrice = item->GetTemplate()->SellPrice ? item->GetTemplate()->SellPrice : GetVendorPrice(item->GetTemplate()->Quality); + // The AH cut needs to be added to the price, but we dont want a 100% chance to buy if the price is exactly AH default + itemPrice *= 1.4f; - if (bidPrice <= minBidPrice) - { - if (inGameBuyPrice != 0 && bidPrice < inGameBuyPrice - (inGameBuyPrice / 30)) - chance = maxChance; - else - { - if (bidPrice < maxBidablePrice) - { - ratio = maxBidablePrice / bidPrice; - if (ratio < 3) - chance = maxChance / 500 * ratio; - else - chance = maxChance / 500; - } - } - } - else if (bidPrice < (inGameBuyPrice - (inGameBuyPrice / 30))) - chance = (maxChance / 10); - else + // This value is between 0 and 100 and is used directly as the chance to buy or bid + // Value equal or above 100 means 100% chance and value below 0 means 0% chance + float chance = 100 / sqrt(itemBidPrice / float(itemPrice)); + + if (ahInfo) { - if (bidPrice < maxBidablePrice) + float avgBidPrice = ahInfo->TotalBidPrice / float(ahInfo->BidItemCount); + + TC_LOG_DEBUG("ahbot", "AHBot: Bid average: %.1f biddable item count: %u", avgBidPrice, ahInfo->BidItemCount); + + // If there are more than 5 items on AH of this entry, try weigh in the average bid price + if (ahInfo->BidItemCount >= 5) { - ratio = maxBidablePrice / bidPrice; - if (ratio < 4) - chance = maxChance / 1000 * ratio; - else - chance = maxChance / 1000; + chance *= 1 / sqrt(itemBidPrice / avgBidPrice); } } - if (urand(1, chanceRatio) <= chance) - { - TC_LOG_DEBUG("ahbot", "AHBot: WIN BID! Chance = %u, num = %u.", chance, chanceRatio); - return true; - } - else - { - TC_LOG_DEBUG("ahbot", "AHBot: LOOSE BID! Chance = %u, num = %u.", chance, chanceRatio); - return false; - } -} + // If a player has bidded on item, have fifth of normal chance + if (auction->bidder) + chance = chance / 5; -void AuctionBotBuyer::PlaceBidToEntry(AuctionEntry* auction, uint32 bidPrice) -{ - TC_LOG_DEBUG("ahbot", "AHBot: Bid placed to entry %u, %.2fg", auction->Id, float(bidPrice) / 10000.0f); - auction->bid = bidPrice; + // Add config weigh in for quality + chance *= GetChanceMultiplier(item->GetTemplate()->Quality) / 100.0f; + + float rand = frand(0, 100); + bool win = rand <= chance; + TC_LOG_DEBUG("ahbot", "AHBot: %s BID! chance = %.2f, price = %u, bidprice = %u.", win ? "WIN" : "LOSE", chance, itemPrice, itemBidPrice); + return win; } -void AuctionBotBuyer::BuyEntry(AuctionEntry* auction) +// Removes items from EligibleItems that we shouldnt buy or bid on +// The last existed time on them should be older than now +void AuctionBotBuyer::PrepareListOfEntry(BuyerConfiguration& config) { - TC_LOG_DEBUG("ahbot", "AHBot: Entry %u bought at %.2fg", auction->Id, float(auction->buyout) / 10000.0f); - auction->bid = auction->buyout; + // now - 5 seconds to leave out all old entries but keep the ones just updated a moment ago + time_t now = time(nullptr) - 5; + + for (CheckEntryMap::iterator itr = config.EligibleItems.begin(); itr != config.EligibleItems.end();) + { + if (itr->second.LastExist < now) + config.EligibleItems.erase(itr++); + else + ++itr; + } + + TC_LOG_DEBUG("ahbot", "AHBot: EligibleItems size = %u", (uint32)config.EligibleItems.size()); } -void AuctionBotBuyer::AddNewAuctionBuyerBotBid(BuyerConfiguration& config) +// Tries to bid and buy items based on their prices and chances set in configs +void AuctionBotBuyer::BuyAndBidItems(BuyerConfiguration& config) { + time_t now = time(nullptr); AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(config.GetHouseType()); + CheckEntryMap& items = config.EligibleItems; - PrepareListOfEntry(config); - - time_t now = time(nullptr); - uint32 buyCycles; - if (config.CheckedEntry.size() > sAuctionBotConfig->GetItemPerCycleBoost()) + // Max amount of items to buy or bid + uint32 cycles = sAuctionBotConfig->GetItemPerCycleNormal(); + if (items.size() > sAuctionBotConfig->GetItemPerCycleBoost()) { - buyCycles = sAuctionBotConfig->GetItemPerCycleBoost(); + // set more cycles if there is a huge influx of items + cycles = sAuctionBotConfig->GetItemPerCycleBoost(); TC_LOG_DEBUG("ahbot", "AHBot: Boost value used for Buyer! (if this happens often adjust both ItemsPerCycle in worldserver.conf)"); } - else - buyCycles = sAuctionBotConfig->GetItemPerCycleNormal(); - for (CheckEntryMap::iterator itr = config.CheckedEntry.begin(); itr != config.CheckedEntry.end();) + // Process items eligible to be bidded or bought + CheckEntryMap::iterator itr = items.begin(); + while (cycles && itr != items.end()) { AuctionEntry* auction = auctionHouse->GetAuction(itr->second.AuctionId); - if (!auction) // is auction not active now + if (!auction) { - TC_LOG_DEBUG("ahbot", "AHBot: Entry %u doesn't exists, perhaps bought already?", - itr->second.AuctionId); - - config.CheckedEntry.erase(itr++); + TC_LOG_DEBUG("ahbot", "AHBot: Entry %u doesn't exists, perhaps bought already?", itr->second.AuctionId); + items.erase(itr++); continue; } - if (itr->second.LastChecked != 0 && (now - itr->second.LastChecked) <= _checkInterval) + // Check if the item has been checked once before + // If it has been checked and it was recently, skip it + if (itr->second.LastChecked && (now - itr->second.LastChecked) <= _checkInterval) { TC_LOG_DEBUG("ahbot", "AHBot: In time interval wait for entry %u!", auction->Id); ++itr; continue; } - if (buyCycles == 0) - break; - - uint32 maxChance = 5000; - Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow); - if (!item) // auction item not accessible, possible auction in payment pending mode + if (!item) { - config.CheckedEntry.erase(itr++); + // auction item not accessible, possible auction in payment pending mode + items.erase(itr++); continue; } - ItemTemplate const* prototype = item->GetTemplate(); - - uint32 basePrice = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYPRICE_BUYER) ? prototype->BuyPrice : prototype->SellPrice; - basePrice *= item->GetCount(); - - uint32 maxBuyablePrice = (basePrice * config.BuyerPriceRatio) / 100; - BuyerItemInfoMap::iterator sameItemItr = config.SameItemInfo.find(item->GetEntry()); - uint32 buyoutPrice = auction->buyout / item->GetCount(); - + // price to bid if bidding uint32 bidPrice; - uint32 bidPriceByItem; - uint32 minBidPrice; - uint32 minBuyPrice; if (auction->bid >= auction->startbid) { - bidPrice = auction->GetAuctionOutBid(); - bidPriceByItem = auction->bid / item->GetCount(); + // get bid price to outbid previous bidder + bidPrice = auction->bid + auction->GetAuctionOutBid(); } else { + // no previous bidders - use starting bid bidPrice = auction->startbid; - bidPriceByItem = auction->startbid / item->GetCount(); } - double inGameBuyPrice; - double inGameBidPrice; - if (sameItemItr == config.SameItemInfo.end()) - { - inGameBuyPrice = 0; - inGameBidPrice = 0; - minBidPrice = 0; - minBuyPrice = 0; - } - else - { - if (sameItemItr->second.ItemCount == 1) - maxBuyablePrice = maxBuyablePrice * 5; // if only one item exist can be bought if the price is high too. - inGameBuyPrice = sameItemItr->second.BuyPrice / sameItemItr->second.ItemCount; - inGameBidPrice = sameItemItr->second.BidPrice / sameItemItr->second.ItemCount; - minBidPrice = sameItemItr->second.MinBidPrice; - minBuyPrice = sameItemItr->second.MinBuyPrice; - } + const BuyerItemInfo* ahInfo = nullptr; + BuyerItemInfoMap::const_iterator sameItemItr = config.SameItemInfo.find(item->GetEntry()); + if (sameItemItr != config.SameItemInfo.end()) + ahInfo = &sameItemItr->second; - uint32 maxBidablePrice = maxBuyablePrice - (maxBuyablePrice / 30); // Max Bidable price defined to 70% of max buyable price + TC_LOG_DEBUG("ahbot", "AHBot: Rolling for AHentry %u:", auction->Id); - TC_LOG_DEBUG("ahbot", "AHBot: Auction added with data:"); - TC_LOG_DEBUG("ahbot", "AHBot: MaxPrice of Entry %u is %.1fg.", itr->second.AuctionId, double(maxBuyablePrice) / 10000.0); - TC_LOG_DEBUG("ahbot", "AHBot: GamePrice buy=%.1fg, bid=%.1fg.", inGameBuyPrice / 10000, inGameBidPrice / 10000); - TC_LOG_DEBUG("ahbot", "AHBot: Minimal price see in AH Buy=%ug, Bid=%ug.", - minBuyPrice / 10000, minBidPrice / 10000); - TC_LOG_DEBUG("ahbot", "AHBot: Actual Entry price, Buy=%ug, Bid=%ug.", buyoutPrice / 10000, bidPrice / 10000); + // Roll buy and bid chances + bool successBuy = RollBuyChance(ahInfo, item, auction, bidPrice); + bool successBid = RollBidChance(ahInfo, item, auction, bidPrice); - if (!auction->owner) // Original auction owner - maxChance = maxChance / 5; // if Owner is AHBot this mean player placed bid on this auction. We divide by 5 chance for AhBuyer to place bid on it. (This make more challenge than ignore entry) - if (auction->buyout != 0) // Is the item directly buyable? - { - if (IsBuyableEntry(buyoutPrice, inGameBuyPrice, maxBuyablePrice, minBuyPrice, maxChance, config.FactionChance)) - { - if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance / 2, config.FactionChance)) - { - if (urand(0, 5) == 0) - PlaceBidToEntry(auction, bidPrice); - else - BuyEntry(auction); - } - else - BuyEntry(auction); - } - else if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance / 2, config.FactionChance)) - PlaceBidToEntry(auction, bidPrice); - } - else if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance, config.FactionChance)) - PlaceBidToEntry(auction, bidPrice); + // If roll bidding succesfully and bid price is above buyout -> buyout + // If roll for buying was successful but not for bid, buyout directly + // If roll bidding was also successful, buy the entry with 20% chance + // - Better bid than buy since the item is bought by bot if no player bids after + // Otherwise bid if roll for bid was successful + if ((auction->buyout && successBid && bidPrice >= auction->buyout) || + (successBuy && (!successBid || urand(1, 5) == 1))) + BuyEntry(auction, auctionHouse); // buyout + else if (successBid) + PlaceBidToEntry(auction, bidPrice); // bid itr->second.LastChecked = now; - --buyCycles; - + --cycles; ++itr; } + + // Clear not needed entries + config.SameItemInfo.clear(); } -bool AuctionBotBuyer::Update(AuctionHouseType houseType) +uint32 AuctionBotBuyer::GetVendorPrice(uint32 quality) +{ + switch (quality) + { + case ITEM_QUALITY_POOR: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_GRAY); + case ITEM_QUALITY_NORMAL: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_WHITE); + case ITEM_QUALITY_UNCOMMON: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_GREEN); + case ITEM_QUALITY_RARE: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_BLUE); + case ITEM_QUALITY_EPIC: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_PURPLE); + case ITEM_QUALITY_LEGENDARY: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_ORANGE); + case ITEM_QUALITY_ARTIFACT: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_BASEPRICE_YELLOW); + default: + return 1 * SILVER; + } +} + +uint32 AuctionBotBuyer::GetChanceMultiplier(uint32 quality) { - if (sAuctionBotConfig->GetConfigBuyerEnabled(houseType)) + switch (quality) { - TC_LOG_DEBUG("ahbot", "AHBot: %s buying ...", AuctionBotConfig::GetHouseTypeName(houseType)); - if (GetBuyableEntry(_houseConfig[houseType]) > 0) - AddNewAuctionBuyerBotBid(_houseConfig[houseType]); - return true; + case ITEM_QUALITY_POOR: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GRAY); + case ITEM_QUALITY_NORMAL: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_WHITE); + case ITEM_QUALITY_UNCOMMON: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GREEN); + case ITEM_QUALITY_RARE: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_BLUE); + case ITEM_QUALITY_EPIC: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_PURPLE); + case ITEM_QUALITY_LEGENDARY: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_ORANGE); + case ITEM_QUALITY_ARTIFACT: + return sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_YELLOW); + default: + return 100; } +} + +// Buys the auction and does necessary actions to complete the buyout +void AuctionBotBuyer::BuyEntry(AuctionEntry* auction, AuctionHouseObject* auctionHouse) +{ + TC_LOG_DEBUG("ahbot", "AHBot: Entry %u bought at %.2fg", auction->Id, float(auction->buyout) / GOLD); + + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + + // Send mail to previous bidder if any + if (auction->bidder) + sAuctionMgr->SendAuctionOutbiddedMail(auction, auction->buyout, NULL, trans); + + // Set bot as bidder and set new bid amount + auction->bidder = 0; + auction->bid = auction->buyout; + + // Mails must be under transaction control too to prevent data loss + sAuctionMgr->SendAuctionSalePendingMail(auction, trans); + sAuctionMgr->SendAuctionSuccessfulMail(auction, trans); + sAuctionMgr->SendAuctionWonMail(auction, trans); + + // Delete auction from DB + auction->DeleteFromDB(trans); + + // Remove auction item and auction from memory + sAuctionMgr->RemoveAItem(auction->itemGUIDLow); + auctionHouse->RemoveAuction(auction); + + // Run SQLs + CharacterDatabase.CommitTransaction(trans); +} + +// Bids on the auction and does the necessary actions for bidding +void AuctionBotBuyer::PlaceBidToEntry(AuctionEntry* auction, uint32 bidPrice) +{ + TC_LOG_DEBUG("ahbot", "AHBot: Bid placed to entry %u, %.2fg", auction->Id, float(bidPrice) / GOLD); + + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + + // Send mail to previous bidder if any + if (auction->bidder) + sAuctionMgr->SendAuctionOutbiddedMail(auction, bidPrice, NULL, trans); + + // Set bot as bidder and set new bid amount + auction->bidder = 0; + auction->bid = bidPrice; + + // Update auction to DB + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_BID); + stmt->setUInt32(0, auction->bidder); + stmt->setUInt32(1, auction->bid); + stmt->setUInt32(2, auction->Id); + trans->Append(stmt); - return false; + // Run SQLs + CharacterDatabase.CommitTransaction(trans); } diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h index bd8d6e46f0e..c030731cb40 100644 --- a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h +++ b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h @@ -24,7 +24,7 @@ struct BuyerAuctionEval { - BuyerAuctionEval(): AuctionId(0), LastChecked(0), LastExist(0) {} + BuyerAuctionEval() : AuctionId(0), LastChecked(0), LastExist(0) { } uint32 AuctionId; time_t LastChecked; @@ -33,13 +33,14 @@ struct BuyerAuctionEval struct BuyerItemInfo { - BuyerItemInfo(): ItemCount(0), BuyPrice(0), BidPrice(0), MinBuyPrice(0), MinBidPrice(0) {} + BuyerItemInfo() : BidItemCount(0), BuyItemCount(0), MinBuyPrice(0), MinBidPrice(0), TotalBuyPrice(0), TotalBidPrice(0) { } - uint32 ItemCount; - double BuyPrice; - double BidPrice; + uint32 BidItemCount; + uint32 BuyItemCount; uint32 MinBuyPrice; uint32 MinBidPrice; + double TotalBuyPrice; + double TotalBidPrice; }; typedef std::map<uint32, BuyerItemInfo> BuyerItemInfoMap; @@ -47,7 +48,7 @@ typedef std::map<uint32, BuyerAuctionEval> CheckEntryMap; struct BuyerConfiguration { - BuyerConfiguration(): FactionChance(3), BuyerEnabled(false), BuyerPriceRatio(100), _houseType(AUCTION_HOUSE_NEUTRAL) {} + BuyerConfiguration() : BuyerEnabled(false), _houseType(AUCTION_HOUSE_NEUTRAL) { } void Initialize(AuctionHouseType houseType) { @@ -57,10 +58,8 @@ struct BuyerConfiguration AuctionHouseType GetHouseType() const { return _houseType; } BuyerItemInfoMap SameItemInfo; - CheckEntryMap CheckedEntry; - uint32 FactionChance; + CheckEntryMap EligibleItems; bool BuyerEnabled; - uint32 BuyerPriceRatio; private: AuctionHouseType _houseType; @@ -78,19 +77,23 @@ public: bool Update(AuctionHouseType houseType) override; void LoadConfig(); - void AddNewAuctionBuyerBotBid(BuyerConfiguration& config); + void BuyAndBidItems(BuyerConfiguration& config); private: uint32 _checkInterval; BuyerConfiguration _houseConfig[MAX_AUCTION_HOUSE_TYPE]; void LoadBuyerValues(BuyerConfiguration& config); - bool IsBuyableEntry(uint32 buyoutPrice, double inGameBuyPrice, uint32 maxBuyablePrice, uint32 minBuyPrice, uint32 maxChance, uint32 chanceRatio); - bool IsBidableEntry(uint32 bidPrice, double inGameBuyPrice, double maxBidablePrice, uint32 minBidPrice, uint32 maxChance, uint32 chanceRatio); + + // ahInfo can be NULL + bool RollBuyChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 bidPrice); + bool RollBidChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 bidPrice); void PlaceBidToEntry(AuctionEntry* auction, uint32 bidPrice); - void BuyEntry(AuctionEntry* auction); + void BuyEntry(AuctionEntry* auction, AuctionHouseObject* auctionHouse); void PrepareListOfEntry(BuyerConfiguration& config); - uint32 GetBuyableEntry(BuyerConfiguration& config); + uint32 GetItemInformation(BuyerConfiguration& config); + uint32 GetVendorPrice(uint32 quality); + uint32 GetChanceMultiplier(uint32 quality); }; #endif diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index eaef902addc..010c73779bc 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -3029,33 +3029,54 @@ AuctionHouseBot.Buyer.Horde.Enabled = 0 AuctionHouseBot.Buyer.Neutral.Enabled = 0 # -# AuctionHouseBot.BuyPrice.Buyer -# Description: Should the Buyer use BuyPrice or SellPrice to determine Bid Prices -# Default: 1 - (use BuyPrice) -# 0 - (use SellPrice) - -AuctionHouseBot.Buyer.Buyprice = 1 +# AuctionHouseBot.Buyer.Baseprice.QUALITY +# Description: Base sellprices in copper for non priced items for each quality. +# The default values are based on average item prices of each quality. +# Defaults: Gray 3504 +# White 5429 +# Green 21752 +# Blue 36463 +# Purple 87124 +# Orange 214347 +# Yellow 407406 + +AuctionHouseBot.Buyer.Baseprice.Gray = 3504 +AuctionHouseBot.Buyer.Baseprice.White = 5429 +AuctionHouseBot.Buyer.Baseprice.Green = 21752 +AuctionHouseBot.Buyer.Baseprice.Blue = 36463 +AuctionHouseBot.Buyer.Baseprice.Purple = 87124 +AuctionHouseBot.Buyer.Baseprice.Orange = 214347 +AuctionHouseBot.Buyer.Baseprice.Yellow = 407406 + +# +# AuctionHouseBot.Buyer.ChanceMultiplier.QUALITY +# Description: Multipliers for the buy/bid chances for each quality. 100 means the chance is 100% of the original, +# 1 would mean 1 % of the original and 200 would mean 200% of the original chance. +# Defaults: Gray 100 +# White 100 +# Green 100 +# Blue 100 +# Purple 100 +# Orange 100 +# Yellow 100 + +AuctionHouseBot.Buyer.ChanceMultiplier.Gray = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.White = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.Green = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.Blue = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.Purple = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.Orange = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.Yellow = 100 # # AuctionHouseBot.Buyer.Recheck.Interval -# Description: This specifies the time interval (in minutes) between two evaluations of the same selled item. -# The lesser this value is, the more chances you give for item to be bought by ahbot. +# Description: This specifies the time interval (in minutes) between two evaluations of the same sold item. +# The smaller this value is, the more chances you give for an item to be bought by ahbot. # Default: 20 (20min.) AuctionHouseBot.Buyer.Recheck.Interval = 20 # -# AuctionHouseBot.Buyer.Alliance.Chance.Ratio -# Description: Chance ratio for the buyer to buy an item. Higher the value, lesser the probability -# Example: 3 (1 out of 3 change, that is, 33%). -# Default: 3 -# - -AuctionHouseBot.Buyer.Alliance.Chance.Ratio = 3 -AuctionHouseBot.Buyer.Horde.Chance.Ratio = 3 -AuctionHouseBot.Buyer.Neutral.Chance.Ratio = 3 - -# ################################################################################################### ################################################################################################### |