TrinityCore
AuctionHouseBotBuyer.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
19#include "DatabaseEnv.h"
20#include "GameTime.h"
21#include "Item.h"
22#include "ItemTemplate.h"
23#include "Log.h"
24#include "Random.h"
25
27{
28 // Define faction for our main data class.
29 for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i)
31}
32
34{
35}
36
38{
39 LoadConfig();
40
41 bool activeHouse = false;
42 for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i)
43 {
44 if (_houseConfig[i].BuyerEnabled)
45 {
46 activeHouse = true;
47 break;
48 }
49 }
50
51 if (!activeHouse)
52 return false;
53
54 // load Check interval
56 TC_LOG_DEBUG("ahbot", "AHBot buyer interval is {} minutes", _checkInterval / MINUTE);
57 return true;
58}
59
61{
62 for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i)
63 {
64 _houseConfig[i].BuyerEnabled = sAuctionBotConfig->GetConfigBuyerEnabled(AuctionHouseType(i));
65 if (_houseConfig[i].BuyerEnabled)
67 }
68}
69
71{
72
73}
74
75// Makes an AHbot buyer cycle for AH type if necessary
77{
78 if (!sAuctionBotConfig->GetConfigBuyerEnabled(houseType))
79 return false;
80
81 TC_LOG_DEBUG("ahbot", "AHBot: {} buying ...", AuctionBotConfig::GetHouseTypeName(houseType));
82
83 BuyerConfiguration& config = _houseConfig[houseType];
84 uint32 eligibleItems = GetItemInformation(config);
85 if (eligibleItems)
86 {
87 // Prepare list of items to bid or buy - remove old items
88 PrepareListOfEntry(config);
89 // Process buying and bidding items
90 BuyAndBidItems(config);
91 }
92
93 return true;
94}
95
96// 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
97// Returns count of items in AH that were eligible for being bought or bidded on by ahbot buyer (EligibleItems size)
99{
100 config.SameItemInfo.clear();
101 time_t now = GameTime::GetGameTime();
102 uint32 count = 0;
103
104 AuctionHouseObject* house = sAuctionMgr->GetAuctionsById(sAuctionBotConfig->GetAuctionHouseId(config.GetHouseType()));
105 for (auto itr = house->GetAuctionsBegin(); itr != house->GetAuctionsEnd(); ++itr)
106 {
107 AuctionPosting* entry = &itr->second;
108
109 if (entry->IsCommodity())
110 continue; // skip commodities, there can be thousands of items in a single auction. TODO: partial buys?
111
112 if (entry->Owner.IsEmpty() || sAuctionBotConfig->IsBotChar(entry->Owner))
113 continue; // Skip auctions owned by AHBot
114
115 BuyerItemInfo& itemInfo = config.SameItemInfo[entry->Bucket->Key.ItemId];
116
117 // Update item entry's count and total bid prices
118 // This can be used later to determine the prices and chances to bid
119 if (entry->MinBid)
120 {
121 uint32 itemBidPrice = entry->MinBid;
122 itemInfo.TotalBidPrice = itemInfo.TotalBidPrice + itemBidPrice;
123 itemInfo.BidItemCount++;
124
125 // Set minimum bid price
126 if (!itemInfo.MinBidPrice)
127 itemInfo.MinBidPrice = itemBidPrice;
128 else
129 itemInfo.MinBidPrice = std::min(itemInfo.MinBidPrice, itemBidPrice);
130 }
131
132 // Set minimum buyout price if item has buyout
133 if (entry->BuyoutOrUnitPrice)
134 {
135 // Update item entry's count and total buyout prices
136 // This can be used later to determine the prices and chances to buyout
137 uint32 itemBuyPrice = entry->BuyoutOrUnitPrice;
138 itemInfo.TotalBuyPrice = itemInfo.TotalBuyPrice + itemBuyPrice;
139 itemInfo.BuyItemCount++;
140
141 if (!itemInfo.MinBuyPrice)
142 itemInfo.MinBuyPrice = itemBuyPrice;
143 else
144 itemInfo.MinBuyPrice = std::min(itemInfo.MinBuyPrice, itemBuyPrice);
145 }
146
147 // Add/update EligibleItems if:
148 // * no bid
149 // * bid from player
150 if (!entry->BidAmount || !entry->Bidder.IsEmpty())
151 {
152 config.EligibleItems[entry->Id].LastExist = now;
153 config.EligibleItems[entry->Id].AuctionId = entry->Id;
154 ++count;
155 }
156 }
157
158 TC_LOG_DEBUG("ahbot", "AHBot: {} items added to buyable/biddable vector for ah type: {}", count, config.GetHouseType());
159 TC_LOG_DEBUG("ahbot", "AHBot: SameItemInfo size = {}", (uint32)config.SameItemInfo.size());
160 return count;
161}
162
163// ahInfo can be NULL
165{
166 if (!auction->BuyoutOrUnitPrice)
167 return false;
168
169 Item const* item = auction->Items[0];
170 float itemBuyPrice = float(auction->BuyoutOrUnitPrice);
171 float itemPrice;
172 if (uint32 itemSellPrice = item->GetSellPrice(item->GetTemplate(), item->GetQuality(), item->GetItemLevel(item->GetTemplate(), *item->GetBonus(), 0, 0, 0, 0, 0, false, 0)))
173 itemPrice = float(itemSellPrice);
174 else
175 itemPrice = float(GetVendorPrice(item->GetQuality()));
176
177 // 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
178 itemPrice *= 1.4f;
179
180 // This value is between 0 and 100 and is used directly as the chance to buy or bid
181 // Value equal or above 100 means 100% chance and value below 0 means 0% chance
182 float chance = std::min(100.f, std::pow(100.f, 1.f + (1.f - itemBuyPrice / itemPrice) / sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_FACTOR)));
183
184 // If a player has bidded on item, have fifth of normal chance
185 if (!auction->Bidder.IsEmpty())
186 chance = chance / 5.f;
187
188 if (ahInfo)
189 {
190 float avgBuyPrice = ahInfo->TotalBuyPrice / float(ahInfo->BuyItemCount);
191
192 TC_LOG_DEBUG("ahbot", "AHBot: buyout average: {:.1f} items with buyout: {}", avgBuyPrice, ahInfo->BuyItemCount);
193
194 // If there are more than 5 items on AH of this entry, try weigh in the average buyout price
195 if (ahInfo->BuyItemCount > 5)
196 chance *= 1.f / std::sqrt(itemBuyPrice / avgBuyPrice);
197 }
198
199 // Add config weigh in for quality
200 chance *= GetChanceMultiplier(item->GetTemplate()->GetQuality()) / 100.0f;
201
202 float rand = frand(0.f, 100.f);
203 bool win = rand <= chance;
204 TC_LOG_DEBUG("ahbot", "AHBot: {} BUY! chance = {:.2f}, price = {}, buyprice = {}.", win ? "WIN" : "LOSE", chance, uint32(itemPrice), uint32(itemBuyPrice));
205 return win;
206}
207
208// ahInfo can be NULL
209bool AuctionBotBuyer::RollBidChance(BuyerItemInfo const* ahInfo, AuctionPosting const* auction, uint32 bidPrice)
210{
211 if (!auction->MinBid)
212 return false;
213
214 Item const* item = auction->Items[0];
215 float itemBidPrice = float(bidPrice);
216 float itemPrice;
217 if (uint32 itemSellPrice = item->GetSellPrice(item->GetTemplate(), item->GetQuality(), item->GetItemLevel(item->GetTemplate(), *item->GetBonus(), 0, 0, 0, 0, 0, false, 0)))
218 itemPrice = float(itemSellPrice);
219 else
220 itemPrice = float(GetVendorPrice(item->GetQuality()));
221
222 // 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
223 itemPrice *= 1.4f;
224
225 // This value is between 0 and 100 and is used directly as the chance to buy or bid
226 // Value equal or above 100 means 100% chance and value below 0 means 0% chance
227 float chance = std::min(100.f, std::pow(100.f, 1.f + (1.f - itemBidPrice / itemPrice) / sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_FACTOR)));
228
229 if (ahInfo)
230 {
231 float avgBidPrice = ahInfo->TotalBidPrice / float(ahInfo->BidItemCount);
232
233 TC_LOG_DEBUG("ahbot", "AHBot: Bid average: {:.1f} biddable item count: {}", avgBidPrice, ahInfo->BidItemCount);
234
235 // If there are more than 5 items on AH of this entry, try weigh in the average bid price
236 if (ahInfo->BidItemCount >= 5)
237 chance *= 1.f / std::sqrt(itemBidPrice / avgBidPrice);
238 }
239
240 // If a player has bidded on item, have fifth of normal chance
241 if (!auction->Bidder.IsEmpty() && !sAuctionBotConfig->IsBotChar(auction->Bidder))
242 chance = chance / 5.f;
243
244 // Add config weigh in for quality
245 chance *= GetChanceMultiplier(item->GetTemplate()->GetQuality()) / 100.0f;
246
247 float rand = frand(0.f, 100.f);
248 bool win = rand <= chance;
249 TC_LOG_DEBUG("ahbot", "AHBot: {} BID! chance = {:.2f}, price = {}, bidprice = {}.", win ? "WIN" : "LOSE", chance, uint32(itemPrice), uint32(itemBidPrice));
250 return win;
251}
252
253// Removes items from EligibleItems that we shouldnt buy or bid on
254// The last existed time on them should be older than now
256{
257 // now - 5 seconds to leave out all old entries but keep the ones just updated a moment ago
258 time_t now = GameTime::GetGameTime() - 5;
259
260 for (CheckEntryMap::iterator itr = config.EligibleItems.begin(); itr != config.EligibleItems.end();)
261 {
262 if (itr->second.LastExist < now)
263 config.EligibleItems.erase(itr++);
264 else
265 ++itr;
266 }
267
268 TC_LOG_DEBUG("ahbot", "AHBot: EligibleItems size = {}", (uint32)config.EligibleItems.size());
269}
270
271// Tries to bid and buy items based on their prices and chances set in configs
273{
274 time_t now = GameTime::GetGameTime();
275 AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsById(sAuctionBotConfig->GetAuctionHouseId(config.GetHouseType()));
276 CheckEntryMap& items = config.EligibleItems;
277
278 // Max amount of items to buy or bid
279 uint32 cycles = sAuctionBotConfig->GetItemPerCycleNormal();
280 if (items.size() > sAuctionBotConfig->GetItemPerCycleBoost())
281 {
282 // set more cycles if there is a huge influx of items
283 cycles = sAuctionBotConfig->GetItemPerCycleBoost();
284 TC_LOG_DEBUG("ahbot", "AHBot: Boost value used for Buyer! (if this happens often adjust both ItemsPerCycle in worldserver.conf)");
285 }
286
287 // Process items eligible to be bidded or bought
288 CheckEntryMap::iterator itr = items.begin();
289 while (cycles && itr != items.end())
290 {
291 AuctionPosting* auction = auctionHouse->GetAuction(itr->second.AuctionId);
292 if (!auction)
293 {
294 TC_LOG_DEBUG("ahbot", "AHBot: Entry {} doesn't exists, perhaps bought already?", itr->second.AuctionId);
295 items.erase(itr++);
296 continue;
297 }
298
299 // Check if the item has been checked once before
300 // If it has been checked and it was recently, skip it
301 if (itr->second.LastChecked && (now - itr->second.LastChecked) <= _checkInterval)
302 {
303 TC_LOG_DEBUG("ahbot", "AHBot: In time interval wait for entry {}!", auction->Id);
304 ++itr;
305 continue;
306 }
307
308 // price to bid if bidding
309 uint64 bidPrice;
310 if (auction->BidAmount)
311 {
312 // get bid price to outbid previous bidder
313 bidPrice = auction->BidAmount + auction->CalculateMinIncrement();
314 }
315 else
316 {
317 // no previous bidders - use starting bid
318 bidPrice = auction->MinBid;
319 }
320
321 BuyerItemInfo const* ahInfo = nullptr;
322 BuyerItemInfoMap::const_iterator sameItemItr = config.SameItemInfo.find(auction->Bucket->Key.ItemId);
323 if (sameItemItr != config.SameItemInfo.end())
324 ahInfo = &sameItemItr->second;
325
326 TC_LOG_DEBUG("ahbot", "AHBot: Rolling for AHentry {}:", auction->Id);
327
328 // Roll buy and bid chances
329 bool successBuy = RollBuyChance(ahInfo, auction);
330 bool successBid = RollBidChance(ahInfo, auction, bidPrice);
331
332 // If roll bidding succesfully and bid price is above buyout -> buyout
333 // If roll for buying was successful but not for bid, buyout directly
334 // If roll bidding was also successful, buy the entry with 20% chance
335 // - Better bid than buy since the item is bought by bot if no player bids after
336 // Otherwise bid if roll for bid was successful
337 if ((auction->BuyoutOrUnitPrice && successBid && bidPrice >= auction->BuyoutOrUnitPrice) ||
338 (successBuy && (!successBid || urand(1, 5) == 1)))
339 BuyEntry(auction, auctionHouse); // buyout
340 else if (successBid)
341 PlaceBidToEntry(auction, auctionHouse, bidPrice); // bid
342
343 itr->second.LastChecked = now;
344 --cycles;
345 ++itr;
346 }
347
348 // Clear not needed entries
349 config.SameItemInfo.clear();
350}
351
353{
354 switch (quality)
355 {
370 default:
371 return 1 * SILVER;
372 }
373}
374
376{
377 switch (quality)
378 {
393 default:
394 return 100;
395 }
396}
397
398// Buys the auction and does necessary actions to complete the buyout
400{
401 TC_LOG_DEBUG("ahbot", "AHBot: Entry {} bought at {:.2f}g", auction->Id, float(auction->BuyoutOrUnitPrice) / float(GOLD));
402
403 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
404
405 ObjectGuid newBidder = sAuctionBotConfig->GetRandCharExclude(auction->Owner);
406
407 // Send mail to previous bidder if any
408 if (!auction->Bidder.IsEmpty() && !sAuctionBotConfig->IsBotChar(auction->Bidder))
409 auctionHouse->SendAuctionOutbid(auction, newBidder, auction->BuyoutOrUnitPrice, trans);
410
411 // Set bot as bidder and set new bid amount
412 auction->Bidder = newBidder;
413 auction->BidAmount = auction->BuyoutOrUnitPrice;
414
415 // Copy data before freeing AuctionPosting in auctionHouse->RemoveAuction
416 // Because auctionHouse->SendAuctionWon can unload items if bidder is offline
417 // we need to RemoveAuction before sending mails
418 AuctionPosting copy = *auction;
419 auctionHouse->RemoveAuction(trans, auction);
420
421 // Mails must be under transaction control too to prevent data loss
422 auctionHouse->SendAuctionSold(&copy, nullptr, trans);
423 auctionHouse->SendAuctionWon(&copy, nullptr, trans);
424
425 // Run SQLs
426 CharacterDatabase.CommitTransaction(trans);
427}
428
429// Bids on the auction and does the necessary actions for bidding
431{
432 TC_LOG_DEBUG("ahbot", "AHBot: Bid placed to entry {}, {:.2f}g", auction->Id, float(bidPrice) / float(GOLD));
433
434 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
435
436 ObjectGuid newBidder = sAuctionBotConfig->GetRandCharExclude(auction->Owner);
437
438 // Send mail to previous bidder if any
439 if (!auction->Bidder.IsEmpty() && !sAuctionBotConfig->IsBotChar(auction->Bidder))
440 auctionHouse->SendAuctionOutbid(auction, newBidder, bidPrice, trans);
441
442 // Set bot as bidder and set new bid amount
443 auction->Bidder = newBidder;
444 auction->BidAmount = bidPrice;
446
447 // Update auction to DB
449 stmt->setUInt64(0, auction->Bidder.GetCounter());
450 stmt->setUInt64(1, auction->BidAmount);
451 stmt->setUInt8(2, auction->ServerFlags.AsUnderlyingType());
452 stmt->setUInt32(3, auction->Id);
453 trans->Append(stmt);
454
455 // Run SQLs
456 CharacterDatabase.CommitTransaction(trans);
457}
std::map< uint32, BuyerAuctionEval > CheckEntryMap
@ CONFIG_AHBOT_BUYER_BASEPRICE_GRAY
@ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GRAY
@ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_ORANGE
@ CONFIG_AHBOT_BUYER_RECHECK_INTERVAL
@ CONFIG_AHBOT_BUYER_BASEPRICE_BLUE
@ CONFIG_AHBOT_BUYER_BASEPRICE_PURPLE
@ CONFIG_AHBOT_BUYER_BASEPRICE_YELLOW
@ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_YELLOW
@ CONFIG_AHBOT_BUYER_BASEPRICE_ORANGE
@ CONFIG_AHBOT_BUYER_BASEPRICE_GREEN
@ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_BLUE
@ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_PURPLE
@ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_WHITE
@ CONFIG_AHBOT_BUYER_CHANCEMULTIPLIER_GREEN
@ CONFIG_AHBOT_BUYER_BASEPRICE_WHITE
@ CONFIG_AHBOT_BUYER_CHANCE_FACTOR
#define MAX_AUCTION_HOUSE_TYPE
AuctionHouseType
#define sAuctionBotConfig
#define sAuctionMgr
AuctionPostingServerFlag
@ CHAR_UPD_AUCTION_BID
@ MINUTE
Definition: Common.h:29
SQLTransaction< CharacterDatabaseConnection > CharacterDatabaseTransaction
DatabaseWorkerPool< CharacterDatabaseConnection > CharacterDatabase
Accessor to the character database.
Definition: DatabaseEnv.cpp:21
uint64_t uint64
Definition: Define.h:141
uint32_t uint32
Definition: Define.h:142
#define TC_LOG_DEBUG(filterType__,...)
Definition: Log.h:156
float frand(float min, float max)
Definition: Random.cpp:55
uint32 urand(uint32 min, uint32 max)
Definition: Random.cpp:42
@ ITEM_QUALITY_UNCOMMON
@ ITEM_QUALITY_RARE
@ ITEM_QUALITY_NORMAL
@ ITEM_QUALITY_LEGENDARY
@ ITEM_QUALITY_POOR
@ ITEM_QUALITY_ARTIFACT
@ ITEM_QUALITY_EPIC
@ SILVER
@ GOLD
uint32 GetChanceMultiplier(uint32 quality)
void BuyAndBidItems(BuyerConfiguration &config)
void PrepareListOfEntry(BuyerConfiguration &config)
BuyerConfiguration _houseConfig[MAX_AUCTION_HOUSE_TYPE]
void BuyEntry(AuctionPosting *auction, AuctionHouseObject *auctionHouse)
bool RollBuyChance(BuyerItemInfo const *ahInfo, AuctionPosting const *auction)
void PlaceBidToEntry(AuctionPosting *auction, AuctionHouseObject *auctionHouse, uint32 bidPrice)
bool RollBidChance(BuyerItemInfo const *ahInfo, AuctionPosting const *auction, uint32 bidPrice)
uint32 GetItemInformation(BuyerConfiguration &config)
bool Initialize() override
bool Update(AuctionHouseType houseType) override
uint32 GetVendorPrice(uint32 quality)
void LoadBuyerValues(BuyerConfiguration &config)
static char const * GetHouseTypeName(AuctionHouseType houseType)
std::map< uint32, AuctionPosting >::iterator GetAuctionsEnd()
void SendAuctionWon(AuctionPosting const *auction, Player *bidder, CharacterDatabaseTransaction trans) const
AuctionPosting * GetAuction(uint32 auctionId)
void SendAuctionSold(AuctionPosting const *auction, Player *owner, CharacterDatabaseTransaction trans) const
std::map< uint32, AuctionPosting >::iterator GetAuctionsBegin()
void RemoveAuction(CharacterDatabaseTransaction trans, AuctionPosting *auction, std::map< uint32, AuctionPosting >::iterator *auctionItr=nullptr)
void SendAuctionOutbid(AuctionPosting const *auction, ObjectGuid newBidder, uint64 newBidAmount, CharacterDatabaseTransaction trans) const
constexpr std::underlying_type_t< T > AsUnderlyingType() const
Definition: EnumFlag.h:122
Definition: Item.h:170
uint32 GetQuality() const
Definition: Item.h:337
ItemTemplate const * GetTemplate() const
Definition: Item.cpp:1141
BonusData const * GetBonus() const
Definition: Item.h:186
uint32 GetItemLevel(Player const *owner) const
Definition: Item.cpp:2279
uint32 GetSellPrice(Player const *owner) const
Definition: Item.cpp:2248
LowType GetCounter() const
Definition: ObjectGuid.h:293
bool IsEmpty() const
Definition: ObjectGuid.h:319
void setUInt8(const uint8 index, const uint8 value)
void setUInt32(const uint8 index, const uint32 value)
void setUInt64(const uint8 index, const uint64 value)
time_t GetGameTime()
Definition: GameTime.cpp:44
bool IsCommodity() const
ObjectGuid Bidder
std::vector< Item * > Items
AuctionsBucketData * Bucket
ObjectGuid Owner
static uint64 CalculateMinIncrement(uint64 bidAmount)
EnumFlag< AuctionPostingServerFlag > ServerFlags
uint64 BuyoutOrUnitPrice
AuctionsBucketKey Key
BuyerItemInfoMap SameItemInfo
CheckEntryMap EligibleItems
AuctionHouseType GetHouseType() const
uint32 GetQuality() const
Definition: ItemTemplate.h:779