TrinityCore
Loading...
Searching...
No Matches
AuctionHouseMgr.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
18#include "AuctionHouseMgr.h"
19#include "AccountMgr.h"
20#include "AuctionHouseBot.h"
21#include "AuctionHousePackets.h"
22#include "Bag.h"
23#include "BattlePetMgr.h"
24#include "CharacterCache.h"
25#include "CollectionMgr.h"
26#include "Common.h"
27#include "DB2Stores.h"
28#include "DatabaseEnv.h"
29#include "GameTime.h"
30#include "Language.h"
31#include "Log.h"
32#include "Mail.h"
33#include "MapUtils.h"
34#include "ObjectAccessor.h"
35#include "ObjectMgr.h"
36#include "Player.h"
37#include "ScriptMgr.h"
38#include "World.h"
39#include "WorldSession.h"
40#include "WowTime.h"
41#include <boost/dynamic_bitset.hpp>
42#include <fmt/ranges.h>
43#include <numeric>
44#include <vector>
45
50
52 ItemId(key.ItemID), ItemLevel(key.ItemLevel), BattlePetSpeciesId(key.BattlePetSpeciesID.value_or(0)),
53 SuffixItemNameDescriptionId(key.ItemSuffix.value_or(0))
54{
55}
56
58{
60 hash.UpdateData(key.ItemId);
61 hash.UpdateData(key.ItemLevel);
64 return hash.Value;
65}
66
68{
69 ItemTemplate const* itemTemplate = item->GetTemplate();
70 if (itemTemplate->GetMaxStackSize() == 1)
71 {
72 return
73 {
74 item->GetEntry(),
75 uint16(Item::GetItemLevel(itemTemplate, *item->GetBonus(), 0, item->GetRequiredLevel(), 0, 0, 0, false, 0, 0)),
77 uint16(item->GetBonus()->Suffix)
78 };
79 }
80 else
81 return ForCommodity(itemTemplate);
82}
83
85{
86 return { itemTemplate->GetId(), uint16(itemTemplate->GetBaseItemLevel()), 0, 0 };
87}
88
90{
91 bucketInfo->Key = Key;
92 bucketInfo->MinPrice = MinPrice;
93 bucketInfo->RequiredLevel = RequiredLevel;
94 bucketInfo->TotalQuantity = 0;
95
96 for (AuctionPosting const* auction : Auctions)
97 {
98 for (Item* item : auction->Items)
99 {
100 bucketInfo->TotalQuantity += item->GetCount();
101
103 {
104 uint32 breedData = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA);
105 uint32 breedId = breedData & 0xFFFFFF;
106 uint8 quality = uint8((breedData >> 24) & 0xFF);
107 uint8 level = uint8(item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL));
108
109 bucketInfo->MaxBattlePetQuality = bucketInfo->MaxBattlePetQuality ? std::max(*bucketInfo->MaxBattlePetQuality, quality) : quality;
110 bucketInfo->MaxBattlePetLevel = bucketInfo->MaxBattlePetLevel ? std::max(*bucketInfo->MaxBattlePetLevel, level) : level;
111 bucketInfo->BattlePetBreedID = breedId;
112 if (!bucketInfo->BattlePetLevelMask)
113 bucketInfo->BattlePetLevelMask = 0;
114
115 *bucketInfo->BattlePetLevelMask |= 1 << (level - 1);
116 }
117 }
118
119 bucketInfo->ContainsOwnerItem = bucketInfo->ContainsOwnerItem || auction->Owner == player->GetGUID();
120 }
121
122 bucketInfo->ContainsOnlyCollectedAppearances = true;
123 for (std::pair<uint32, uint32> appearance : ItemModifiedAppearanceId)
124 {
125 if (appearance.first)
126 {
127 bucketInfo->ItemModifiedAppearanceIDs.push_back(appearance.first);
128 if (!player->GetSession()->GetCollectionMgr()->HasItemAppearance(appearance.first).first)
129 bucketInfo->ContainsOnlyCollectedAppearances = false;
130 }
131 }
132}
133
135{
136 return Items.size() > 1 || Items[0]->GetTemplate()->GetMaxStackSize() > 1;
137}
138
140{
141 return std::accumulate(Items.begin(), Items.end(), 0u, [](uint32 totalCount, Item const* item)
142 {
143 return totalCount + item->GetCount();
144 });
145}
146
148 bool alwaysSendItem, bool sendKey, bool censorServerInfo, bool censorBidInfo) const
149{
150 // SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT, SMSG_AUCTION_LIST_ITEMS_RESULT (if not commodity), SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT, SMSG_AUCTION_REPLICATE_RESPONSE (if not commodity)
151 //auctionItem->Item - here to unify comment
152
153 // all (not optional<>)
154 auctionItem->Count = int32(GetTotalItemCount());
155 auctionItem->Flags = Items[0]->m_itemData->DynamicFlags;
156 auctionItem->AuctionID = Id;
157 auctionItem->Owner = Owner;
158
159 // prices set when filled
160 if (IsCommodity())
161 {
162 if (alwaysSendItem)
163 {
164 auctionItem->Item.emplace();
165 auctionItem->Item->Initialize(Items[0]);
166 }
167
168 auctionItem->UnitPrice = BuyoutOrUnitPrice;
169 }
170 else
171 {
172 auctionItem->Item.emplace();
173 auctionItem->Item->Initialize(Items[0]);
174 auctionItem->Charges = Items[0]->GetSpellCharges();
175 for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; i++)
176 {
177 uint32 enchantId = Items[0]->GetEnchantmentId(EnchantmentSlot(i));
178 if (!enchantId)
179 continue;
180
181 auctionItem->Enchantments.emplace_back(enchantId, Items[0]->GetEnchantmentDuration(EnchantmentSlot(i)), Items[0]->GetEnchantmentCharges(EnchantmentSlot(i)), i);
182 }
183
184 for (uint8 i = 0; i < Items[0]->m_itemData->Gems.size(); ++i)
185 {
186 UF::SocketedGem const* gemData = &Items[0]->m_itemData->Gems[i];
187 if (gemData->ItemID)
188 {
190 gem.Slot = i;
191 gem.Item.Initialize(gemData);
192 auctionItem->Gems.push_back(gem);
193 }
194 }
195
196 if (MinBid)
197 auctionItem->MinBid = MinBid;
198
199 if (uint64 minIncrement = CalculateMinIncrement())
200 auctionItem->MinIncrement = minIncrement;
201
203 auctionItem->BuyoutPrice = BuyoutOrUnitPrice;
204 }
205
206 // all (not optional<>)
207 auctionItem->DurationLeft = uint32(std::max(std::chrono::duration_cast<Milliseconds>(EndTime - GameTime::GetSystemTime()).count(), Milliseconds::zero().count()));
208 auctionItem->DeleteReason = 0;
209
210 // SMSG_AUCTION_LIST_ITEMS_RESULT (only if owned)
211 auctionItem->CensorServerSideInfo = censorServerInfo;
212 auctionItem->ItemGuid = IsCommodity() ? ObjectGuid::Empty : Items[0]->GetGUID();
213 auctionItem->OwnerAccountID = OwnerAccount;
214 auctionItem->EndTime = uint32(std::chrono::system_clock::to_time_t(EndTime));
215
216 // SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT, SMSG_AUCTION_LIST_ITEMS_RESULT (if has bid), SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT, SMSG_AUCTION_REPLICATE_RESPONSE (if has bid)
217 auctionItem->CensorBidInfo = censorBidInfo;
218 if (!Bidder.IsEmpty())
219 {
220 auctionItem->Bidder = Bidder;
221 auctionItem->BidAmount = BidAmount;
222 }
223
224 // SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT, SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT, SMSG_AUCTION_REPLICATE_RESPONSE (if commodity)
225 if (sendKey)
226 auctionItem->AuctionBucketKey.emplace(AuctionsBucketKey::ForItem(Items[0]));
227
228 // all
229 if (!Items[0]->m_itemData->Creator->IsEmpty())
230 auctionItem->Creator = *Items[0]->m_itemData->Creator;
231}
232
234{
235 return CalculatePct(bidAmount / SILVER /*ignore copper*/, 5) * SILVER;
236}
237
239{
240public:
241 Sorter(LocaleConstant locale, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts)
242 : _locale(locale), _sorts(sorts) { }
243
244 bool operator()(AuctionsBucketData const* left, AuctionsBucketData const* right) const
245 {
247 {
248 int64 ordering = CompareColumns(sort.SortOrder, left, right);
249 if (ordering != 0)
250 return (ordering < 0) == !sort.ReverseSort;
251 }
252
253 return left->Key < right->Key;
254 }
255
256private:
258 {
259 switch (column)
260 {
264 return int64(left->MinPrice) - int64(right->MinPrice);
266 return left->FullName[_locale].compare(right->FullName[_locale]);
268 return int32(left->SortLevel) - int32(right->SortLevel);
269 default:
270 break;
271 }
272
273 return 0;
274 }
275
277 std::span<WorldPackets::AuctionHouse::AuctionSortDef const> _sorts;
278};
279
281{
282public:
283 Sorter(LocaleConstant locale, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts)
284 : _locale(locale), _sorts(sorts) { }
285
286 bool operator()(AuctionPosting const* left, AuctionPosting const* right) const
287 {
289 {
290 int64 ordering = CompareColumns(sort.SortOrder, left, right);
291 if (ordering != 0)
292 return (ordering < 0) == !sort.ReverseSort;
293 }
294
295 // Auctions are processed in LIFO order
296 if (left->StartTime != right->StartTime)
297 return left->StartTime > right->StartTime;
298
299 return left->Id > right->Id;
300 }
301
302private:
304 {
305 switch (column)
306 {
308 {
309 int64 leftPrice = left->BuyoutOrUnitPrice ? left->BuyoutOrUnitPrice : (left->BidAmount ? left->BidAmount : left->MinBid);
310 int64 rightPrice = right->BuyoutOrUnitPrice ? right->BuyoutOrUnitPrice : (right->BidAmount ? right->BidAmount : right->MinBid);
311 return leftPrice - rightPrice;
312 }
314 return left->Bucket->FullName[_locale].compare(right->Bucket->FullName[_locale]);
316 {
317 int32 leftLevel = !left->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID)
318 ? left->Bucket->SortLevel
319 : left->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
320 int32 rightLevel = !right->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID)
321 ? right->Bucket->SortLevel
322 : right->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
323 return leftLevel - rightLevel;
324 }
326 return int64(left->BidAmount) - int64(right->BidAmount);
328 return int64(left->BuyoutOrUnitPrice) - int64(right->BuyoutOrUnitPrice);
330 return (left->EndTime - right->EndTime).count();
331 default:
332 break;
333 }
334
335 return 0;
336 }
337
339 std::span<WorldPackets::AuctionHouse::AuctionSortDef const> _sorts;
340};
341
342template<typename T>
344{
345public:
346 using Sorter = typename T::Sorter;
347
348 AuctionsResultBuilder(uint32 offset, LocaleConstant locale, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts, AuctionHouseResultLimits maxResults)
349 : _offset(offset), _sorter(locale, sorts), _maxResults(AsUnderlyingType(maxResults)), _totalCount(0)
350 {
351 _items.reserve(_maxResults + offset + 1);
352 }
353
354 void AddItem(T const* item)
355 {
356 auto where = std::ranges::lower_bound(_items, item, std::cref(_sorter));
357
358 ++_totalCount;
359 _items.insert(where, item);
360 if (_items.size() > _maxResults + _offset)
361 _items.pop_back();
362 }
363
364 std::span<T const* const> GetResultRange() const
365 {
366 return std::span(_items.begin() + _offset, _items.end());
367 }
368
369 std::size_t GetTotalCount() const
370 {
371 return _totalCount;
372 }
373
374 bool HasMoreResults() const
375 {
376 return _totalCount > _items.size();
377 }
378
379private:
382 std::size_t _maxResults;
383 std::size_t _totalCount;
384 std::vector<T const*> _items;
385};
386
387AuctionHouseMgr::AuctionHouseMgr() : mHordeAuctions(6), mAllianceAuctions(2), mNeutralAuctions(1), mGoblinAuctions(7), _replicateIdGenerator(0)
388{
390}
391
393{
394 for (std::pair<ObjectGuid const, Item*>& itemPair : _itemsByGuid)
395 delete itemPair.second;
396}
397
403
405{
407 return &mNeutralAuctions;
408
409 // teams have linked auction houses
410 FactionTemplateEntry const* uEntry = sFactionTemplateStore.LookupEntry(factionTemplateId);
411 if (!uEntry)
412 return &mNeutralAuctions;
413 else if (uEntry->FactionGroup & FACTION_MASK_ALLIANCE)
414 return &mAllianceAuctions;
415 else if (uEntry->FactionGroup & FACTION_MASK_HORDE)
416 return &mHordeAuctions;
417 else
418 return &mNeutralAuctions;
419}
420
422{
423 switch (auctionHouseId)
424 {
425 case 1:
426 return &mNeutralAuctions;
427 case 2:
428 return &mAllianceAuctions;
429 case 6:
430 return &mHordeAuctions;
431 case 7:
432 return &mGoblinAuctions;
433 default:
434 break;
435 }
436 return &mNeutralAuctions;
437}
438
443
445{
446 uint32 sellPrice = item->GetSellPrice();
447 return uint64(std::ceil(std::floor(fmax(0.15 * quantity * sellPrice, 100.0)) / int64(SILVER)) * int64(SILVER)) * (time.count() / (MIN_AUCTION_TIME / MINUTE));
448}
449
451{
452 uint32 sellPrice = item->GetSellPrice(player);
453 return uint64(std::ceil(std::floor(fmax(sellPrice * 0.15, 100.0)) / int64(SILVER)) * int64(SILVER)) * (time.count() / (MIN_AUCTION_TIME / MINUTE));
454}
455
457{
458 return BuildAuctionMailSubject(auction->Items[0]->GetEntry(), type, auction->Id, auction->GetTotalItemCount(),
459 auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID), auction->Items[0]->GetContext(), auction->Items[0]->GetBonusListIDs());
460}
461
463{
464 return BuildAuctionMailSubject(itemId, type, 0, itemCount, 0, ItemContext::NONE, {});
465}
466
467std::string AuctionHouseMgr::BuildAuctionMailSubject(uint32 itemId, AuctionMailType type, uint32 auctionId, uint32 itemCount, uint32 battlePetSpeciesId,
468 ItemContext context, std::vector<int32> const& bonusListIds)
469{
470 return Trinity::StringFormat("{}:0:{}:{}:{}:{}:0:0:0:0:{}:{}:{}",
471 itemId, AsUnderlyingType(type), auctionId, itemCount, battlePetSpeciesId, context, bonusListIds.size(), fmt::join(bonusListIds, ":"));
472}
473
475{
476 return Trinity::StringFormat("{}:{}:{}:0", guid.ToString(), bid, buyout);
477}
478
479std::string AuctionHouseMgr::BuildAuctionSoldMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment)
480{
481 return Trinity::StringFormat("{}:{}:{}:{}:{}:0", guid.ToString(), bid, buyout, deposit, consignment);
482}
483
484std::string AuctionHouseMgr::BuildAuctionInvoiceMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment, uint32 moneyDelay, uint32 eta)
485{
486 return Trinity::StringFormat("{}:{}:{}:{}:{}:{}:{}:0", guid.ToString(), bid, buyout, deposit, consignment, moneyDelay, eta);
487}
488
490{
491 uint32 oldMSTime = getMSTime();
492
493 // need to clear in case we are reloading
494 if (!_itemsByGuid.empty())
495 {
496 for (std::pair<ObjectGuid const, Item*>& itemPair : _itemsByGuid)
497 delete itemPair.second;
498
499 _itemsByGuid.clear();
500 }
501
502 // data needs to be at first place for Item::LoadFromDB
503 uint32 count = 0;
504 std::unordered_map<uint32, std::vector<Item*>> itemsByAuction;
505 std::unordered_map<uint32, GuidUnorderedSet> biddersByAuction;
506
507 if (PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTION_ITEMS)))
508 {
509 do
510 {
511 Field* fields = result->Fetch();
512
513 ObjectGuid::LowType itemGuid = fields[0].GetUInt64();
514 uint32 itemEntry = fields[1].GetUInt32();
515
516 ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry);
517 if (!proto)
518 {
519 TC_LOG_ERROR("misc", "AuctionHouseMgr::LoadAuctionItems: Unknown item (GUID: {} item entry: #{}) in auction, skipped.", itemGuid, itemEntry);
520 continue;
521 }
522
523 Item* item = NewItemOrBag(proto);
524 if (!item->LoadFromDB(itemGuid, ObjectGuid::Create<HighGuid::Player>(fields[52].GetUInt64()), fields, itemEntry))
525 {
526 delete item;
527 continue;
528 }
529 uint32 auctionId = fields[53].GetUInt32();
530 itemsByAuction[auctionId].push_back(item);
531
532 ++count;
533 } while (result->NextRow());
534 }
535
536 TC_LOG_INFO("server.loading", ">> Loaded {} auction items in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
537
538 oldMSTime = getMSTime();
539
540 count = 0;
541
543 {
544 do
545 {
546 Field* fields = result->Fetch();
547 biddersByAuction[fields[0].GetUInt32()].insert(ObjectGuid::Create<HighGuid::Player>(fields[1].GetUInt64()));
548
549 } while (result->NextRow());
550 }
551
552 TC_LOG_INFO("server.loading", ">> Loaded {} auction bidders in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
553
554 oldMSTime = getMSTime();
555
556 count = 0;
557
558 if (PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTIONS)))
559 {
560 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
561 do
562 {
563 Field* fields = result->Fetch();
564
565 AuctionPosting auction;
566 auction.Id = fields[0].GetUInt32();
567 uint32 auctionHouseId = fields[1].GetUInt32();
568 AuctionHouseObject* auctionHouse = GetAuctionsById(auctionHouseId);
569 if (!auctionHouse)
570 {
571 TC_LOG_ERROR("misc", "Auction {} has wrong auctionHouseId {}", auction.Id, auctionHouseId);
573 stmt->setUInt32(0, auction.Id);
574 trans->Append(stmt);
575 continue;
576 }
577
578 auto itemsItr = itemsByAuction.find(auction.Id);
579 if (itemsItr == itemsByAuction.end())
580 {
581 TC_LOG_ERROR("misc", "Auction {} has no items", auction.Id);
583 stmt->setUInt32(0, auction.Id);
584 trans->Append(stmt);
585 continue;
586 }
587
588 auction.Items = std::move(itemsItr->second);
589 auction.Owner = ObjectGuid::Create<HighGuid::Player>(fields[2].GetUInt64());
590 auction.OwnerAccount = ObjectGuid::Create<HighGuid::WowAccount>(sCharacterCache->GetCharacterAccountIdByGuid(auction.Owner));
591 if (uint64 bidder = fields[3].GetUInt64())
592 auction.Bidder = ObjectGuid::Create<HighGuid::Player>(bidder);
593
594 auction.MinBid = fields[4].GetUInt64();
595 auction.BuyoutOrUnitPrice = fields[5].GetUInt64();
596 auction.Deposit = fields[6].GetUInt64();
597 auction.BidAmount = fields[7].GetUInt64();
598 auction.StartTime = std::chrono::system_clock::from_time_t(fields[8].GetInt64());
599 auction.EndTime = std::chrono::system_clock::from_time_t(fields[9].GetInt64());
600 auction.ServerFlags = static_cast<AuctionPostingServerFlag>(fields[10].GetUInt8());
601
602 auto biddersItr = biddersByAuction.find(auction.Id);
603 if (biddersItr != biddersByAuction.end())
604 auction.BidderHistory = std::move(biddersItr->second);
605
606 auctionHouse->AddAuction(nullptr, std::move(auction));
607
608 ++count;
609 } while (result->NextRow());
610
611 CharacterDatabase.CommitTransaction(trans);
612 }
613
614 TC_LOG_INFO("server.loading", ">> Loaded {} auctions in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
615}
616
618{
619 ASSERT(item);
620 ASSERT_WITH_SIDE_EFFECTS(_itemsByGuid.emplace(item->GetGUID(), item).second);
621}
622
623bool AuctionHouseMgr::RemoveAItem(ObjectGuid itemGuid, bool deleteItem /*= false*/, CharacterDatabaseTransaction* trans /*= nullptr*/)
624{
625 auto i = _itemsByGuid.find(itemGuid);
626 if (i == _itemsByGuid.end())
627 return false;
628
629 if (deleteItem)
630 {
631 ASSERT(trans);
632 i->second->FSetState(ITEM_REMOVED);
633 i->second->SaveToDB(*trans);
634 }
635
636 _itemsByGuid.erase(i);
637 return true;
638}
639
640bool AuctionHouseMgr::PendingAuctionAdd(Player const* player, uint32 auctionHouseId, uint32 auctionId, uint64 deposit)
641{
642 auto itr = _pendingAuctionsByPlayer.find(player->GetGUID());
643 if (itr != _pendingAuctionsByPlayer.end())
644 {
645 // Get deposit so far
646 uint64 totalDeposit = 0;
647 for (PendingAuctionInfo const& thisAuction : itr->second.Auctions)
648 totalDeposit += thisAuction.Deposit;
649
650 // Add this deposit
651 totalDeposit += deposit;
652
653 if (!player->HasEnoughMoney(totalDeposit))
654 return false;
655 }
656 else
657 itr = _pendingAuctionsByPlayer.emplace(std::piecewise_construct, std::forward_as_tuple(player->GetGUID()), std::forward_as_tuple()).first;
658
659 itr->second.Auctions.push_back({ auctionId, auctionHouseId, deposit });
660 return true;
661}
662
663std::size_t AuctionHouseMgr::PendingAuctionCount(Player const* player) const
664{
665 auto itr = _pendingAuctionsByPlayer.find(player->GetGUID());
666 if (itr != _pendingAuctionsByPlayer.end())
667 return itr->second.Auctions.size();
668
669 return 0;
670}
671
673{
674 auto iterMap = _pendingAuctionsByPlayer.find(player->GetGUID());
675 if (iterMap == _pendingAuctionsByPlayer.end())
676 return;
677
678 uint64 totaldeposit = 0;
679 auto itrAH = iterMap->second.Auctions.begin();
680 for (; itrAH != iterMap->second.Auctions.end(); ++itrAH)
681 {
682 if (!player->HasEnoughMoney(totaldeposit + itrAH->Deposit))
683 break;
684
685 totaldeposit += itrAH->Deposit;
686 }
687
688 // expire auctions we cannot afford
689 if (itrAH != iterMap->second.Auctions.end())
690 {
691 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
692
693 do
694 {
695 PendingAuctionInfo const& pendingAuction = *itrAH;
696 if (AuctionPosting* auction = GetAuctionsById(pendingAuction.AuctionHouseId)->GetAuction(pendingAuction.AuctionId))
697 auction->EndTime = GameTime::GetSystemTime();
698
701 stmt->setUInt32(1, pendingAuction.AuctionId);
702 trans->Append(stmt);
703 ++itrAH;
704 } while (itrAH != iterMap->second.Auctions.end());
705 CharacterDatabase.CommitTransaction(trans);
706 }
707
708 _pendingAuctionsByPlayer.erase(player->GetGUID());
709 player->ModifyMoney(-int64(totaldeposit));
710}
711
713{
714 for (auto itr = _pendingAuctionsByPlayer.begin(); itr != _pendingAuctionsByPlayer.end();)
715 {
716 ObjectGuid playerGUID = itr->first;
717 if (Player* player = ObjectAccessor::FindConnectedPlayer(playerGUID))
718 {
719 // Check if there were auctions since last update process if not
720 if (PendingAuctionCount(player) == itr->second.LastAuctionsSize)
721 {
722 ++itr;
723 PendingAuctionProcess(player);
724 }
725 else
726 {
727 ++itr;
728 _pendingAuctionsByPlayer[playerGUID].LastAuctionsSize = PendingAuctionCount(player);
729 }
730 }
731 else
732 {
733 // Expire any auctions that we couldn't get a deposit for
734 TC_LOG_WARN("auctionHouse", "Player {} was offline, unable to retrieve deposit!", playerGUID.ToString());
735 ++itr;
736 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
737 for (PendingAuctionInfo const& pendingAuction : itr->second.Auctions)
738 {
739 if (AuctionPosting* auction = GetAuctionsById(pendingAuction.AuctionHouseId)->GetAuction(pendingAuction.AuctionId))
740 auction->EndTime = GameTime::GetSystemTime();
741
744 stmt->setUInt32(1, pendingAuction.AuctionId);
745 trans->Append(stmt);
746 }
747 CharacterDatabase.CommitTransaction(trans);
748 _pendingAuctionsByPlayer.erase(playerGUID);
749 }
750 }
751}
752
754{
759
760 TimePoint now = GameTime::Now();
762 {
763 for (auto itr = _playerThrottleObjects.begin(); itr != _playerThrottleObjects.end();)
764 {
765 if (itr->second.PeriodEnd < now)
766 itr = _playerThrottleObjects.erase(itr);
767 else
768 ++itr;
769 }
770
772 }
773}
774
779
780AuctionThrottleResult AuctionHouseMgr::CheckThrottle(Player const* player, bool addonTainted, AuctionCommand command /*= AuctionCommand::SellItem*/)
781{
782 TimePoint now = GameTime::Now();
783 auto itr = _playerThrottleObjects.emplace(std::piecewise_construct, std::forward_as_tuple(player->GetGUID()), std::forward_as_tuple());
784 if (itr.second || now > itr.first->second.PeriodEnd)
785 {
786 itr.first->second.PeriodEnd = now + Minutes(1);
787 itr.first->second.QueriesRemaining = 100;
788 }
789
790 if (!itr.first->second.QueriesRemaining)
791 {
792 player->GetSession()->SendAuctionCommandResult(0, command, AuctionResult::AuctionHouseBusy, std::chrono::duration_cast<Milliseconds>(itr.first->second.PeriodEnd - now));
793 return { {}, true };
794 }
795
796 if (!--itr.first->second.QueriesRemaining)
797 return { std::chrono::duration_cast<Milliseconds>(itr.first->second.PeriodEnd - now), false };
798 else
799 return { Milliseconds(sWorld->getIntConfig(addonTainted ? CONFIG_AUCTION_TAINTED_SEARCH_DELAY : CONFIG_AUCTION_SEARCH_DELAY)), false };
800}
801
803{
804 uint32 houseid = 1; // Auction House
805
807 {
808 // FIXME: found way for proper auctionhouse selection by another way
809 // AuctionHouse.dbc have faction field with _player_ factions associated with auction house races.
810 // but no easy way convert creature faction to player race faction for specific city
811 switch (factionTemplateId)
812 {
813 case 120: houseid = 7; break; // booty bay, Blackwater Auction House
814 case 474: houseid = 7; break; // gadgetzan, Blackwater Auction House
815 case 855: houseid = 7; break; // everlook, Blackwater Auction House
816 default: // default
817 {
818 FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(factionTemplateId);
819 if (!u_entry)
820 houseid = 1; // Auction House
821 else if (u_entry->FactionGroup & FACTION_MASK_ALLIANCE)
822 houseid = 2; // Alliance Auction House
823 else if (u_entry->FactionGroup & FACTION_MASK_HORDE)
824 houseid = 6; // Horde Auction House
825 else
826 houseid = 1; // Auction House
827 break;
828 }
829 }
830 }
831
832 if (houseId)
833 *houseId = houseid;
834
835 return sAuctionHouseStore.LookupEntry(houseid);
836}
837
838AuctionHouseObject::AuctionHouseObject(uint32 auctionHouseId) : _auctionHouse(sAuctionHouseStore.AssertEntry(auctionHouseId))
839{
840}
841
843
848
853
855{
857 auto [bucketItr, isNew] = _buckets.try_emplace(key);
858 AuctionsBucketData* bucket = &bucketItr->second;
859 if (isNew)
860 {
861 // we don't have any item for this key yet, create new bucket
862 bucket->Key = key;
863
864 ItemTemplate const* itemTemplate = auction.Items[0]->GetTemplate();
865 bucket->ItemClass = itemTemplate->GetClass();
866 bucket->ItemSubClass = itemTemplate->GetSubClass();
867 bucket->InventoryType = itemTemplate->GetInventoryType();
868 bucket->RequiredLevel = auction.Items[0]->GetRequiredLevel();
869 switch (itemTemplate->GetClass())
870 {
872 case ITEM_CLASS_ARMOR:
873 bucket->SortLevel = key.ItemLevel;
874 break;
876 bucket->SortLevel = itemTemplate->GetContainerSlots();
877 break;
878 case ITEM_CLASS_GEM:
880 bucket->SortLevel = itemTemplate->GetBaseItemLevel();
881 break;
883 bucket->SortLevel = std::max<uint8>(1, bucket->RequiredLevel);
884 break;
887 bucket->SortLevel = 1;
888 break;
890 bucket->SortLevel = itemTemplate->GetSubClass() != ITEM_SUBCLASS_BOOK ? itemTemplate->GetRequiredSkillRank() : itemTemplate->GetBaseRequiredLevel();
891 break;
892 default:
893 break;
894 }
895
896 for (LocaleConstant locale = LOCALE_enUS; locale < TOTAL_LOCALES; locale = LocaleConstant(locale + 1))
897 {
898 if (locale == LOCALE_none)
899 continue;
900
901 std::wstring utf16name;
902 if (!Utf8toWStr(auction.Items[0]->GetNameForLocaleIdx(locale), utf16name))
903 continue;
904
905 bucket->FullName[locale] = wstrCaseAccentInsensitiveParse(utf16name, locale);
906 }
907 }
908
909 // update cache fields
910 uint64 priceToDisplay = auction.BuyoutOrUnitPrice ? auction.BuyoutOrUnitPrice : auction.BidAmount;
911 if (!bucket->MinPrice || priceToDisplay < bucket->MinPrice)
912 bucket->MinPrice = priceToDisplay;
913
914 if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = auction.Items[0]->GetItemModifiedAppearance())
915 {
916 auto itr = std::ranges::find(bucket->ItemModifiedAppearanceId, itemModifiedAppearance->ID, Trinity::TupleElement<0>);
917
918 if (itr == bucket->ItemModifiedAppearanceId.end())
919 itr = std::ranges::find(bucket->ItemModifiedAppearanceId, 0u, Trinity::TupleElement<0>);
920
921 if (itr != bucket->ItemModifiedAppearanceId.end())
922 {
923 itr->first = itemModifiedAppearance->ID;
924 ++itr->second;
925 }
926 }
927
928 uint32 quality;
929
930 if (!auction.Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID))
931 {
932 quality = auction.Items[0]->GetQuality();
933 }
934 else
935 {
936 quality = (auction.Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF;
937 for (Item* item : auction.Items)
938 {
939 uint8 battlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
940 if (!bucket->MinBattlePetLevel)
941 bucket->MinBattlePetLevel = battlePetLevel;
942 else if (bucket->MinBattlePetLevel > battlePetLevel)
943 bucket->MinBattlePetLevel = battlePetLevel;
944
945 bucket->MaxBattlePetLevel = std::max<uint8>(bucket->MaxBattlePetLevel, battlePetLevel);
946 bucket->SortLevel = bucket->MaxBattlePetLevel;
947 }
948 }
949
951 ++bucket->QualityCounts[quality];
952
953 if (trans)
954 {
956 stmt->setUInt32(0, auction.Id);
957 stmt->setUInt32(1, _auctionHouse->ID);
958 stmt->setUInt64(2, auction.Owner.GetCounter());
959 stmt->setUInt64(3, ObjectGuid::Empty.GetCounter());
960 stmt->setUInt64(4, auction.MinBid);
961 stmt->setUInt64(5, auction.BuyoutOrUnitPrice);
962 stmt->setUInt64(6, auction.Deposit);
963 stmt->setUInt64(7, auction.BidAmount);
964 stmt->setInt64(8, std::chrono::system_clock::to_time_t(auction.StartTime));
965 stmt->setInt64(9, std::chrono::system_clock::to_time_t(auction.EndTime));
966 stmt->setUInt8(10, auction.ServerFlags.AsUnderlyingType());
967 trans->Append(stmt);
968
969 for (Item* item : auction.Items)
970 {
971 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION_ITEMS);
972 stmt->setUInt32(0, auction.Id);
973 stmt->setUInt64(1, item->GetGUID().GetCounter());
974 trans->Append(stmt);
975 }
976 }
977
978 for (Item* item : auction.Items)
979 sAuctionMgr->AddAItem(item);
980
981 auction.Bucket = bucket;
982 _playerOwnedAuctions.emplace(auction.Owner, auction.Id);
983 for (ObjectGuid bidder : auction.BidderHistory)
984 _playerBidderAuctions.emplace(bidder, auction.Id);
985
986 AuctionPosting* addedAuction = &(_itemsByAuctionId[auction.Id] = std::move(auction));
987
989 AuctionPosting::Sorter insertSorter(LOCALE_enUS, std::span(&priceSort, 1));
990 bucket->Auctions.insert(std::ranges::lower_bound(bucket->Auctions, addedAuction, std::cref(insertSorter)), addedAuction);
991
992 sScriptMgr->OnAuctionAdd(this, addedAuction);
993}
994
995std::map<uint32, AuctionPosting>::node_type AuctionHouseObject::RemoveAuction(CharacterDatabaseTransaction trans, AuctionPosting* auction,
996 std::map<uint32, AuctionPosting>::iterator* auctionItr /*= nullptr*/)
997{
998 AuctionsBucketData* bucket = auction->Bucket;
999
1000 std::erase(bucket->Auctions, auction);
1001 if (!bucket->Auctions.empty())
1002 {
1003 // update cache fields
1004 uint64 priceToDisplay = auction->BuyoutOrUnitPrice ? auction->BuyoutOrUnitPrice : auction->BidAmount;
1005 if (bucket->MinPrice == priceToDisplay)
1006 {
1007 bucket->MinPrice = std::numeric_limits<uint64>::max();
1008 for (AuctionPosting const* remainingAuction : bucket->Auctions)
1009 bucket->MinPrice = std::min(bucket->MinPrice, remainingAuction->BuyoutOrUnitPrice ? remainingAuction->BuyoutOrUnitPrice : remainingAuction->BidAmount);
1010 }
1011
1012 if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = auction->Items[0]->GetItemModifiedAppearance())
1013 {
1014 auto itr = std::find_if(bucket->ItemModifiedAppearanceId.begin(), bucket->ItemModifiedAppearanceId.end(),
1015 [itemModifiedAppearance](std::pair<uint32, uint32> const& appearance)
1016 {
1017 return appearance.first == itemModifiedAppearance->ID;
1018 });
1019
1020 if (itr != bucket->ItemModifiedAppearanceId.end())
1021 if (!--itr->second)
1022 itr->first = 0;
1023 }
1024
1025 uint32 quality;
1026
1027 if (!auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID))
1028 {
1029 quality = auction->Items[0]->GetQuality();
1030 }
1031 else
1032 {
1033 quality = (auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF;
1034 bucket->MinBattlePetLevel = 0;
1035 bucket->MaxBattlePetLevel = 0;
1036 for (AuctionPosting const* remainingAuction : bucket->Auctions)
1037 {
1038 for (Item* item : remainingAuction->Items)
1039 {
1040 uint8 battlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
1041 if (!bucket->MinBattlePetLevel)
1042 bucket->MinBattlePetLevel = battlePetLevel;
1043 else if (bucket->MinBattlePetLevel > battlePetLevel)
1044 bucket->MinBattlePetLevel = battlePetLevel;
1045
1046 bucket->MaxBattlePetLevel = std::max<uint8>(bucket->MaxBattlePetLevel, battlePetLevel);
1047 }
1048 }
1049 }
1050
1051 if (!--bucket->QualityCounts[quality])
1053 }
1054 else
1055 {
1056 auction->Bucket = nullptr;
1057 _buckets.erase(bucket->Key);
1058 }
1059
1061 stmt->setUInt32(0, auction->Id);
1062 trans->Append(stmt);
1063
1064 for (Item* item : auction->Items)
1065 sAuctionMgr->RemoveAItem(item->GetGUID());
1066
1067 sScriptMgr->OnAuctionRemove(this, auction);
1068
1070 for (ObjectGuid bidder : auction->BidderHistory)
1072
1073 if (auctionItr)
1074 return _itemsByAuctionId.extract((*auctionItr)++);
1075 else
1076 return _itemsByAuctionId.extract(auction->Id);
1077}
1078
1080{
1082 TimePoint curTimeSteady = GameTime::Now();
1084
1085 // Clear expired throttled players
1086 for (auto itr = _replicateThrottleMap.begin(); itr != _replicateThrottleMap.end();)
1087 {
1088 if (itr->second.NextAllowedReplication <= curTimeSteady)
1089 itr = _replicateThrottleMap.erase(itr);
1090 else
1091 ++itr;
1092 }
1093
1094 for (auto itr = _commodityQuotes.begin(); itr != _commodityQuotes.end();)
1095 {
1096 if (itr->second.ValidTo < curTimeSteady)
1097 itr = _commodityQuotes.erase(itr);
1098 else
1099 ++itr;
1100 }
1101
1102 if (_itemsByAuctionId.empty())
1103 return;
1104
1105 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
1106
1107 for (auto it = _itemsByAuctionId.begin(); it != _itemsByAuctionId.end();)
1108 {
1109 AuctionPosting* auction = &it->second;
1111 if (auction->EndTime > curTime + 1min)
1112 {
1113 ++it;
1114 continue;
1115 }
1116
1117 std::map<uint32, AuctionPosting>::node_type removedAuctionNode = RemoveAuction(trans, auction, &it);
1118 auction = &removedAuctionNode.mapped();
1119
1121 if (auction->Bidder.IsEmpty())
1122 {
1123 sScriptMgr->OnAuctionExpire(this, auction);
1124 SendAuctionExpired(auction, trans);
1125 }
1127 else
1128 {
1129 sScriptMgr->OnAuctionSuccessful(this, auction);
1130 //we should send an "item sold" message if the seller is online
1131 //we send the item to the winner
1132 //we send the money to the seller
1133 SendAuctionSold(auction, nullptr, trans);
1134 SendAuctionWon(auction, nullptr, trans);
1135 }
1136 }
1137
1138 // Run DB changes
1139 CharacterDatabase.CommitTransaction(trans);
1140}
1141
1143 std::wstring const& name, uint8 minLevel, uint8 maxLevel, EnumFlag<AuctionHouseFilterMask> filters, Optional<AuctionSearchClassFilters> const& classFilters,
1144 std::span<uint8 const> knownPetBits, uint8 maxKnownPetLevel, uint32 offset, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1145{
1146 std::unordered_set<uint32> knownAppearanceIds;
1147 boost::dynamic_bitset<uint8> knownPetSpecies;
1148 // prepare uncollected filter for more efficient searches
1150 {
1151 knownAppearanceIds = player->GetSession()->GetCollectionMgr()->GetAppearanceIds();
1152 knownPetSpecies.resize(std::max(knownPetSpecies.size() * 32, std::size_t(sBattlePetSpeciesStore.GetNumRows())));
1153 boost::from_block_range(knownPetBits.begin(), knownPetBits.end(), knownPetSpecies);
1154 }
1155
1157
1158 for (std::pair<AuctionsBucketKey const, AuctionsBucketData> const& bucket : _buckets)
1159 {
1160 AuctionsBucketData const* bucketData = &bucket.second;
1161 if (!name.empty())
1162 {
1164 {
1165 if (bucketData->FullName[player->GetSession()->GetSessionDbcLocale()] != name)
1166 continue;
1167 }
1168 else
1169 if (bucketData->FullName[player->GetSession()->GetSessionDbcLocale()].find(name) == std::wstring::npos)
1170 continue;
1171 }
1172
1173 if (minLevel && bucketData->RequiredLevel < minLevel)
1174 continue;
1175
1176 if (maxLevel && bucketData->RequiredLevel > maxLevel)
1177 continue;
1178
1179 if (!filters.HasFlag(bucketData->QualityMask))
1180 continue;
1181
1182 if (classFilters)
1183 {
1184 // if we dont want any class filters, Optional is not initialized
1185 // if we dont want this class included, SubclassMask is set to FILTER_SKIP_CLASS
1186 // if we want this class and did not specify and subclasses, its set to FILTER_SKIP_SUBCLASS
1187 // otherwise full restrictions apply
1188 if (classFilters->Classes[bucketData->ItemClass].SubclassMask == AuctionSearchClassFilters::FILTER_SKIP_CLASS)
1189 continue;
1190
1191 if (classFilters->Classes[bucketData->ItemClass].SubclassMask != AuctionSearchClassFilters::FILTER_SKIP_SUBCLASS)
1192 {
1193 if (!(classFilters->Classes[bucketData->ItemClass].SubclassMask & (1 << bucketData->ItemSubClass)))
1194 continue;
1195
1196 if (!(classFilters->Classes[bucketData->ItemClass].InvTypes[bucketData->ItemSubClass] & (UI64LIT(1) << bucketData->InventoryType)))
1197 continue;
1198 }
1199 }
1200
1202 {
1203 // appearances - by ItemAppearanceId, not ItemModifiedAppearanceId
1204 if (bucketData->InventoryType != INVTYPE_NON_EQUIP && bucketData->InventoryType != INVTYPE_BAG)
1205 {
1206 bool hasAll = true;
1207 for (std::pair<uint32, uint32> bucketAppearance : bucketData->ItemModifiedAppearanceId)
1208 {
1209 if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(bucketAppearance.first))
1210 {
1211 if (!knownAppearanceIds.contains(itemModifiedAppearance->ItemAppearanceID))
1212 {
1213 hasAll = false;
1214 break;
1215 }
1216 }
1217 }
1218
1219 if (hasAll)
1220 continue;
1221 }
1222 // caged pets
1223 else if (bucket.first.BattlePetSpeciesId)
1224 {
1225 if (knownPetSpecies.test(bucket.first.BattlePetSpeciesId))
1226 continue;
1227 }
1228 // toys
1229 else if (sDB2Manager.IsToyItem(bucket.first.ItemId))
1230 {
1231 if (player->GetSession()->GetCollectionMgr()->HasToy(bucket.first.ItemId))
1232 continue;
1233 }
1234 // mounts
1235 // recipes
1236 // pet items
1237 else if (bucketData->ItemClass == ITEM_CLASS_CONSUMABLE || bucketData->ItemClass == ITEM_CLASS_RECIPE || bucketData->ItemClass == ITEM_CLASS_MISCELLANEOUS)
1238 {
1239 ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(bucket.first.ItemId));
1240 bool hasUnlearned = std::ranges::any_of(itemTemplate->Effects, [player, &knownPetSpecies](ItemEffectEntry const* itemEffect)
1241 {
1242 if (itemEffect->TriggerType != ITEM_SPELLTRIGGER_ON_LEARN)
1243 return false;
1244 if (player->HasSpell(itemEffect->SpellID))
1245 return false;
1246 if (BattlePetSpeciesEntry const* battlePetSpecies = BattlePets::BattlePetMgr::GetBattlePetSpeciesBySpell(itemEffect->SpellID))
1247 if (knownPetSpecies.test(battlePetSpecies->ID))
1248 return false;
1249 return true;
1250 });
1251 if (!hasUnlearned)
1252 continue;
1253 }
1254 }
1255
1257 {
1258 if (bucketData->RequiredLevel && player->GetLevel() < bucketData->RequiredLevel)
1259 continue;
1260
1261 if (player->CanUseItem(sObjectMgr->GetItemTemplate(bucket.first.ItemId), true) != EQUIP_ERR_OK)
1262 continue;
1263
1264 // cannot learn caged pets whose level exceeds highest level of currently owned pet
1265 if (bucketData->MinBattlePetLevel && bucketData->MinBattlePetLevel > maxKnownPetLevel)
1266 continue;
1267 }
1268
1270 {
1271 ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(bucket.first.ItemId));
1272 if (itemTemplate->GetRequiredExpansion() != sWorld->getIntConfig(CONFIG_EXPANSION))
1273 continue;
1274 }
1275
1276 // TODO: this one needs to access loot history to know highest item level for every inventory type
1277 //if (filters.HasFlag(AuctionHouseFilterMask::UpgradesOnly))
1278 //{
1279 //}
1280
1281 builder.AddItem(bucketData);
1282 }
1283
1284 for (AuctionsBucketData const* resultBucket : builder.GetResultRange())
1285 {
1286 WorldPackets::AuctionHouse::BucketInfo& bucketInfo = listBucketsResult.Buckets.emplace_back();
1287 resultBucket->BuildBucketInfo(&bucketInfo, player);
1288 }
1289
1290 listBucketsResult.Filters = filters;
1291 listBucketsResult.TotalCount = builder.GetTotalCount();
1292 listBucketsResult.HasMoreResults = builder.HasMoreResults();
1293}
1294
1296 std::span<WorldPackets::AuctionHouse::AuctionBucketKey const> keys,
1297 std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1298{
1299 std::vector<AuctionsBucketData const*> buckets;
1300 buckets.reserve(keys.size());
1301 for (WorldPackets::AuctionHouse::AuctionBucketKey const& key : keys)
1302 {
1303 auto bucketItr = _buckets.find(AuctionsBucketKey(key));
1304 if (bucketItr != _buckets.end())
1305 buckets.push_back(&bucketItr->second);
1306 }
1307
1308 AuctionsBucketData::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts);
1309 std::ranges::sort(buckets, std::cref(sorter));
1310
1311 for (AuctionsBucketData const* resultBucket : buckets)
1312 {
1313 WorldPackets::AuctionHouse::BucketInfo& bucketInfo = listBucketsResult.Buckets.emplace_back();
1314 resultBucket->BuildBucketInfo(&bucketInfo, player);
1315 }
1316
1317 listBucketsResult.TotalCount = buckets.size();
1318 listBucketsResult.HasMoreResults = false;
1319}
1320
1322 uint32 /*offset*/, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1323{
1324 // always full list
1325 std::vector<AuctionPosting const*> auctions;
1326 for (auto const& auctionId : Trinity::Containers::MapEqualRange(_playerBidderAuctions, player->GetGUID()))
1327 if (AuctionPosting const* auction = Trinity::Containers::MapGetValuePtr(_itemsByAuctionId, auctionId.second))
1328 auctions.push_back(auction);
1329
1330 AuctionPosting::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts);
1331 std::ranges::sort(auctions, std::cref(sorter));
1332
1333 for (AuctionPosting const* resultAuction : auctions)
1334 {
1335 WorldPackets::AuctionHouse::AuctionItem& auctionItem = listBiddedItemsResult.Items.emplace_back();
1336 resultAuction->BuildAuctionItem(&auctionItem, true, true, true, false);
1337 }
1338
1339 listBiddedItemsResult.HasMoreResults = false;
1340}
1341
1343 uint32 offset, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1344{
1345 listItemsResult.TotalQuantity = 0;
1346 if (AuctionsBucketData const* bucket = Trinity::Containers::MapGetValuePtr(_buckets, bucketKey))
1347 {
1349
1350 for (AuctionPosting const* auction : bucket->Auctions)
1351 {
1352 builder.AddItem(auction);
1353 for (Item* item : auction->Items)
1354 listItemsResult.TotalQuantity += item->GetCount();
1355 }
1356
1357 for (AuctionPosting const* resultAuction : builder.GetResultRange())
1358 {
1359 WorldPackets::AuctionHouse::AuctionItem& auctionItem = listItemsResult.Items.emplace_back();
1360 resultAuction->BuildAuctionItem(&auctionItem, false, false, resultAuction->OwnerAccount != player->GetSession()->GetAccountGUID(),
1361 resultAuction->Bidder.IsEmpty());
1362 }
1363
1364 listItemsResult.TotalCount = builder.GetTotalCount();
1365 listItemsResult.HasMoreResults = builder.HasMoreResults();
1366 }
1367}
1368
1370 uint32 offset, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1371{
1373 auto itr = _buckets.lower_bound(AuctionsBucketKey(itemId, 0, 0, 0));
1374 auto end = _buckets.end();
1375 listItemsResult.TotalQuantity = 0;
1376 while (itr != end && itr->first.ItemId == itemId)
1377 {
1378 for (AuctionPosting const* auction : itr->second.Auctions)
1379 {
1380 builder.AddItem(auction);
1381 for (Item* item : auction->Items)
1382 listItemsResult.TotalQuantity += item->GetCount();
1383 }
1384
1385 ++itr;
1386 }
1387
1388 for (AuctionPosting const* resultAuction : builder.GetResultRange())
1389 {
1390 WorldPackets::AuctionHouse::AuctionItem& auctionItem = listItemsResult.Items.emplace_back();
1391 resultAuction->BuildAuctionItem(&auctionItem, false, true, resultAuction->OwnerAccount != player->GetSession()->GetAccountGUID(),
1392 resultAuction->Bidder.IsEmpty());
1393 }
1394
1395 listItemsResult.TotalCount = builder.GetTotalCount();
1396 listItemsResult.HasMoreResults = builder.HasMoreResults();
1397}
1398
1400 uint32 /*offset*/, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1401{
1402 // always full list
1403 std::vector<AuctionPosting const*> auctions;
1404 for (auto const& auctionId : Trinity::Containers::MapEqualRange(_playerOwnedAuctions, player->GetGUID()))
1405 if (AuctionPosting const* auction = Trinity::Containers::MapGetValuePtr(_itemsByAuctionId, auctionId.second))
1406 auctions.push_back(auction);
1407
1408 AuctionPosting::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts);
1409 std::ranges::sort(auctions, std::cref(sorter));
1410
1411 for (AuctionPosting const* resultAuction : auctions)
1412 {
1413 WorldPackets::AuctionHouse::AuctionItem& auctionItem = listOwnedItemsResult.Items.emplace_back();
1414 resultAuction->BuildAuctionItem(&auctionItem, true, true, false, false);
1415 }
1416
1417 listOwnedItemsResult.HasMoreResults = false;
1418}
1419
1421 uint32 global, uint32 cursor, uint32 tombstone, uint32 count)
1422{
1423 TimePoint curTime = GameTime::Now();
1424
1425 auto throttleItr = _replicateThrottleMap.find(player->GetGUID());
1426 if (throttleItr != _replicateThrottleMap.end())
1427 {
1428 if (throttleItr->second.Global != global || throttleItr->second.Cursor != cursor || throttleItr->second.Tombstone != tombstone)
1429 return;
1430
1431 if (!throttleItr->second.IsReplicationInProgress() && throttleItr->second.NextAllowedReplication > curTime)
1432 return;
1433 }
1434 else
1435 {
1436 throttleItr = _replicateThrottleMap.emplace(player->GetGUID(), PlayerReplicateThrottleData{}).first;
1437 throttleItr->second.NextAllowedReplication = curTime + Seconds(sWorld->getIntConfig(CONFIG_AUCTION_REPLICATE_DELAY));
1438 throttleItr->second.Global = sAuctionMgr->GenerateReplicationId();
1439 }
1440
1441 if (_itemsByAuctionId.empty() || !count)
1442 return;
1443
1444 auto itr = _itemsByAuctionId.upper_bound(cursor);
1445 for (; itr != _itemsByAuctionId.end(); ++itr)
1446 {
1447 AuctionPosting const& auction = itr->second;
1448
1449 WorldPackets::AuctionHouse::AuctionItem& auctionItem = replicateResponse.Items.emplace_back();
1450 auction.BuildAuctionItem(&auctionItem, false, true, true, auction.Bidder.IsEmpty());
1451 if (!--count)
1452 break;
1453 }
1454
1455 replicateResponse.ChangeNumberGlobal = throttleItr->second.Global;
1456 replicateResponse.ChangeNumberCursor = throttleItr->second.Cursor = !replicateResponse.Items.empty() ? replicateResponse.Items.back().AuctionID : 0;
1457 replicateResponse.ChangeNumberTombstone = throttleItr->second.Tombstone = !count ? _itemsByAuctionId.rbegin()->first : 0;
1458}
1459
1461{
1462 return std::max(int64(CalculatePct(bidAmount, _auctionHouse->ConsignmentRate) * double(sWorld->getRate(RATE_AUCTION_CUT))), SI64LIT(0));
1463}
1464
1466{
1467 ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId);
1468 if (!itemTemplate)
1469 return nullptr;
1470
1471 auto bucketItr = _buckets.find(AuctionsBucketKey::ForCommodity(itemTemplate));
1472 if (bucketItr == _buckets.end())
1473 return nullptr;
1474
1475 uint64 totalPrice = 0;
1476 uint32 remainingQuantity = quantity;
1477 for (AuctionPosting const* auction : bucketItr->second.Auctions)
1478 {
1479 if (auction->Owner == player->GetGUID() || auction->OwnerAccount == player->GetSession()->GetAccountGUID())
1480 continue;
1481
1482 for (Item* auctionItem : auction->Items)
1483 {
1484 if (auctionItem->GetCount() >= remainingQuantity)
1485 {
1486 totalPrice += auction->BuyoutOrUnitPrice * remainingQuantity;
1487 remainingQuantity = 0;
1488 break;
1489 }
1490
1491 totalPrice += auction->BuyoutOrUnitPrice * auctionItem->GetCount();
1492 remainingQuantity -= auctionItem->GetCount();
1493 }
1494 }
1495
1496 // not enough items on auction house
1497 if (remainingQuantity)
1498 return nullptr;
1499
1500 if (!player->HasEnoughMoney(totalPrice))
1501 return nullptr;
1502
1503 CommodityQuote* quote = &_commodityQuotes[player->GetGUID()];
1504 quote->TotalPrice = totalPrice;
1505 quote->Quantity = quantity;
1506 quote->ValidTo = GameTime::Now() + 30s;
1507 return quote;
1508}
1509
1514
1515bool AuctionHouseObject::BuyCommodity(CharacterDatabaseTransaction trans, Player* player, uint32 itemId, uint32 quantity, Milliseconds delayForNextAction)
1516{
1517 ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId);
1518 if (!itemTemplate)
1519 return false;
1520
1521 auto bucketItr = _buckets.find(AuctionsBucketKey::ForCommodity(itemTemplate));
1522 if (bucketItr == _buckets.end())
1523 {
1525 return false;
1526 }
1527
1528 auto quote = _commodityQuotes.extract(player->GetGUID());
1529 if (!quote)
1530 {
1532 return false;
1533 }
1534
1535 uint64 totalPrice = 0;
1536 uint32 remainingQuantity = quantity;
1537 std::vector<AuctionPosting*> auctions;
1538 for (auto auctionItr = bucketItr->second.Auctions.begin(); auctionItr != bucketItr->second.Auctions.end();)
1539 {
1540 AuctionPosting* auction = *auctionItr++;
1541 if (auction->Owner == player->GetGUID() || auction->OwnerAccount == player->GetSession()->GetAccountGUID())
1542 continue;
1543
1544 auctions.push_back(auction);
1545 for (Item* auctionItem : auction->Items)
1546 {
1547 if (auctionItem->GetCount() >= remainingQuantity)
1548 {
1549 totalPrice += auction->BuyoutOrUnitPrice * remainingQuantity;
1550 remainingQuantity = 0;
1551 auctionItr = bucketItr->second.Auctions.end();
1552 break;
1553 }
1554
1555 totalPrice += auction->BuyoutOrUnitPrice * auctionItem->GetCount();
1556 remainingQuantity -= auctionItem->GetCount();
1557 }
1558 }
1559
1560 // not enough items on auction house
1561 if (remainingQuantity)
1562 {
1564 return false;
1565 }
1566
1567 // something was bought between creating quote and finalizing transaction
1568 // but we allow lower price if new items were posted at lower price
1569 if (totalPrice > quote.mapped().TotalPrice)
1570 {
1572 return false;
1573 }
1574
1575 if (!player->HasEnoughMoney(totalPrice))
1576 {
1578 return false;
1579 }
1580
1581 Optional<ObjectGuid> uniqueSeller;
1582
1583 // prepare items
1584 struct MailedItemsBatch
1585 {
1586 std::array<Item*, MAX_MAIL_ITEMS> Items = { };
1587 uint64 TotalPrice = 0;
1588 uint32 Quantity = 0;
1589
1590 std::size_t ItemsCount = 0;
1591
1592 bool IsFull() const { return ItemsCount >= Items.size(); }
1593 void AddItem(Item* item, uint64 unitPrice)
1594 {
1595 Items[ItemsCount++] = item;
1596 Quantity += item->GetCount();
1597 TotalPrice += unitPrice * item->GetCount();
1598 }
1599 };
1600
1601 std::vector<MailedItemsBatch> items;
1602 items.emplace_back();
1603
1604 remainingQuantity = quantity;
1605 std::vector<std::size_t> removedItemsFromAuction;
1606
1607 for (auto auctionItr = bucketItr->second.Auctions.begin(); auctionItr != bucketItr->second.Auctions.end();)
1608 {
1609 AuctionPosting* auction = *auctionItr++;
1610 if (auction->Owner == player->GetGUID() || auction->OwnerAccount == player->GetSession()->GetAccountGUID())
1611 continue;
1612
1613 if (!uniqueSeller)
1614 uniqueSeller = auction->Owner;
1615 else if (*uniqueSeller != auction->Owner)
1616 uniqueSeller = ObjectGuid::Empty;
1617
1618 uint32 boughtFromAuction = 0;
1619 std::size_t removedItems = 0;
1620 for (Item* auctionItem : auction->Items)
1621 {
1622 MailedItemsBatch* itemsBatch = &items.back();
1623 if (itemsBatch->IsFull())
1624 {
1625 items.emplace_back();
1626 itemsBatch = &items.back();
1627 }
1628
1629 if (auctionItem->GetCount() > remainingQuantity)
1630 {
1631 Item* clonedItem = auctionItem->CloneItem(remainingQuantity, player);
1632 if (!clonedItem)
1633 {
1635 return false;
1636 }
1637
1638 auctionItem->SetCount(auctionItem->GetCount() - remainingQuantity);
1639 auctionItem->FSetState(ITEM_CHANGED);
1640 auctionItem->SaveToDB(trans);
1641 itemsBatch->AddItem(clonedItem, auction->BuyoutOrUnitPrice);
1642 boughtFromAuction += remainingQuantity;
1643 remainingQuantity = 0;
1644 auctionItr = bucketItr->second.Auctions.end();
1645 break;
1646 }
1647
1648 itemsBatch->AddItem(auctionItem, auction->BuyoutOrUnitPrice);
1649 boughtFromAuction += auctionItem->GetCount();
1650 remainingQuantity -= auctionItem->GetCount();
1651 ++removedItems;
1652 }
1653
1654 removedItemsFromAuction.push_back(removedItems);
1655
1657 {
1658 uint32 bidderAccId = player->GetSession()->GetAccountId();
1659 std::string ownerName;
1660 if (!sCharacterCache->GetCharacterNameByGuid(auction->Owner, ownerName))
1661 ownerName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
1662
1663 sLog->OutCommand(bidderAccId, "GM {} (Account: {}) bought commodity in auction: {} (Entry: {} Count: {}) and pay money: {}. Original owner {} (Account: {})",
1664 player->GetName(), bidderAccId, items[0].Items[0]->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()),
1665 items[0].Items[0]->GetEntry(), boughtFromAuction, auction->BuyoutOrUnitPrice * boughtFromAuction, ownerName,
1666 sCharacterCache->GetCharacterAccountIdByGuid(auction->Owner));
1667 }
1668
1669 uint64 auctionHouseCut = CalculateAuctionHouseCut(auction->BuyoutOrUnitPrice * boughtFromAuction);
1670 uint64 depositPart = AuctionHouseMgr::GetCommodityAuctionDeposit(items[0].Items[0]->GetTemplate(), std::chrono::duration_cast<Minutes>(auction->EndTime - auction->StartTime),
1671 boughtFromAuction);
1672 uint64 profit = auction->BuyoutOrUnitPrice * boughtFromAuction + depositPart - auctionHouseCut;
1673
1674 if (Player* owner = ObjectAccessor::FindConnectedPlayer(auction->Owner))
1675 {
1676 owner->UpdateCriteria(CriteriaType::MoneyEarnedFromAuctions, profit);
1677 owner->UpdateCriteria(CriteriaType::HighestAuctionSale, profit);
1678 owner->GetSession()->SendAuctionClosedNotification(auction, (float)sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), true);
1679 }
1680
1682 AuctionHouseMgr::BuildAuctionSoldMailBody(player->GetGUID(), auction->BuyoutOrUnitPrice * boughtFromAuction, boughtFromAuction, depositPart, auctionHouseCut))
1683 .AddMoney(profit)
1685 }
1686
1687 player->ModifyMoney(-int64(totalPrice));
1688 player->SaveInventoryAndGoldToDB(trans);
1689
1690 for (MailedItemsBatch const& batch : items)
1691 {
1693 AuctionHouseMgr::BuildAuctionWonMailBody(*uniqueSeller, batch.TotalPrice, batch.Quantity));
1694
1695 for (std::size_t i = 0; i < batch.ItemsCount; ++i)
1696 {
1698 stmt->setUInt64(0, batch.Items[i]->GetGUID().GetCounter());
1699 trans->Append(stmt);
1700
1701 batch.Items[i]->SetOwnerGUID(player->GetGUID());
1702 batch.Items[i]->SaveToDB(trans);
1703 mail.AddItem(batch.Items[i]);
1704 }
1705
1707 }
1708
1710 packet.Info.Initialize(_auctionHouse->ID, auctions[0], items[0].Items[0]);
1711 player->SendDirectMessage(packet.Write());
1712
1713 for (std::size_t i = 0; i < auctions.size(); ++i)
1714 {
1715 if (removedItemsFromAuction[i] == auctions[i]->Items.size())
1716 RemoveAuction(trans, auctions[i]); // bought all items
1717 else if (removedItemsFromAuction[i])
1718 {
1719 auto lastRemovedItem = auctions[i]->Items.begin() + removedItemsFromAuction[i];
1720 for (auto itr = auctions[i]->Items.begin(); itr != lastRemovedItem; ++itr)
1721 sAuctionMgr->RemoveAItem((*itr)->GetGUID());
1722
1723 auctions[i]->Items.erase(auctions[i]->Items.begin(), lastRemovedItem);
1724 }
1725 }
1726
1727 return true;
1728}
1729
1730// this function notified old bidder that his bid is no longer highest
1732{
1733 Player* oldBidder = ObjectAccessor::FindConnectedPlayer(auction->Bidder);
1734
1735 // old bidder exist
1736 if ((oldBidder || sCharacterCache->HasCharacterCacheEntry(auction->Bidder)) && !sAuctionBotConfig->IsBotChar(auction->Bidder))
1737 {
1738 if (oldBidder)
1739 {
1741 packet.BidAmount = newBidAmount;
1743 packet.Info.AuctionID = auction->Id;
1744 packet.Info.Bidder = newBidder;
1745 packet.Info.Item.Initialize(auction->Items[0]);
1746 oldBidder->SendDirectMessage(packet.Write());
1747 }
1748
1750 .AddMoney(auction->BidAmount)
1751 .SendMailTo(trans, MailReceiver(oldBidder, auction->Bidder), this, MAIL_CHECK_MASK_COPIED | MAIL_CHECK_MASK_AUCTION);
1752 }
1753}
1754
1756{
1757 uint32 bidderAccId = 0;
1758 if (!bidder)
1759 bidder = ObjectAccessor::FindConnectedPlayer(auction->Bidder); // try lookup bidder when called from ::Update
1760
1761 // data for gm.log
1762 std::string bidderName;
1763 bool logGmTrade = auction->ServerFlags.HasFlag(AuctionPostingServerFlag::GmLogBuyer);
1764
1765 if (bidder)
1766 {
1767 bidderAccId = bidder->GetSession()->GetAccountId();
1768 bidderName = bidder->GetName();
1769 }
1770 else
1771 {
1772 bidderAccId = sCharacterCache->GetCharacterAccountIdByGuid(auction->Bidder);
1773
1774 if (logGmTrade && !sCharacterCache->GetCharacterNameByGuid(auction->Bidder, bidderName))
1775 bidderName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
1776 }
1777
1778 if (logGmTrade)
1779 {
1780 std::string ownerName;
1781 if (!sCharacterCache->GetCharacterNameByGuid(auction->Owner, ownerName))
1782 ownerName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
1783
1784 uint32 ownerAccId = sCharacterCache->GetCharacterAccountIdByGuid(auction->Owner);
1785
1786 sLog->OutCommand(bidderAccId, "GM {} (Account: {}) won item in auction: {} (Entry: {} Count: {}) and pay money: {}. Original owner {} (Account: {})",
1787 bidderName, bidderAccId, auction->Items[0]->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()),
1788 auction->Items[0]->GetEntry(), auction->GetTotalItemCount(), auction->BidAmount, ownerName, ownerAccId);
1789 }
1790
1791 // receiver exist
1792 if ((bidder || bidderAccId) && !sAuctionBotConfig->IsBotChar(auction->Bidder))
1793 {
1796
1797 // set owner to bidder (to prevent delete item with sender char deleting)
1798 // owner in `data` will set at mail receive and item extracting
1799 for (Item* item : auction->Items)
1800 {
1802 stmt->setUInt64(0, auction->Bidder.GetCounter());
1803 stmt->setUInt64(1, item->GetGUID().GetCounter());
1804 trans->Append(stmt);
1805
1806 mail.AddItem(item);
1807 }
1808
1809 if (bidder)
1810 {
1812 packet.Info.Initialize(_auctionHouse->ID, auction, auction->Items[0]);
1813 bidder->SendDirectMessage(packet.Write());
1814
1815 // FIXME: for offline player need also
1817 }
1818
1820 }
1821 else
1822 {
1823 // bidder doesn't exist, delete the item
1824 for (Item* item : auction->Items)
1825 {
1826 item->FSetState(ITEM_REMOVED);
1827 item->SaveToDB(trans);
1828 }
1829 }
1830}
1831
1832//call this method to send mail to auction owner, when auction is successful, it does not clear ram
1834{
1835 if (!owner)
1837
1838 // owner exist
1839 if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner))
1840 {
1841 uint64 auctionHouseCut = CalculateAuctionHouseCut(auction->BidAmount);
1842 uint64 profit = auction->BidAmount + auction->Deposit - auctionHouseCut;
1843
1844 //FIXME: what do if owner offline
1845 if (owner)
1846 {
1849 //send auction owner notification, bidder must be current!
1850 owner->GetSession()->SendAuctionClosedNotification(auction, (float)sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), true);
1851 }
1852
1854 AuctionHouseMgr::BuildAuctionSoldMailBody(auction->Bidder, auction->BidAmount, auction->BuyoutOrUnitPrice, auction->Deposit, auctionHouseCut))
1855 .AddMoney(profit)
1857 }
1858}
1859
1861{
1863 // owner exist
1864 if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner))
1865 {
1866 if (owner)
1867 owner->GetSession()->SendAuctionClosedNotification(auction, 0.0f, false);
1868
1869 auto itemItr = auction->Items.begin();
1870 while (itemItr != auction->Items.end())
1871 {
1873
1874 for (std::size_t i = 0; i < MAX_MAIL_ITEMS && itemItr != auction->Items.end(); ++i, ++itemItr)
1875 mail.AddItem(*itemItr);
1876
1877 mail.SendMailTo(trans, MailReceiver(owner, auction->Owner), this, MAIL_CHECK_MASK_COPIED | MAIL_CHECK_MASK_AUCTION, 0);
1878 }
1879 }
1880 else
1881 {
1882 // owner doesn't exist, delete the item
1883 for (Item* item : auction->Items)
1884 {
1885 item->FSetState(ITEM_REMOVED);
1886 item->SaveToDB(trans);
1887 }
1888 }
1889}
1890
1892{
1893 auto itemItr = auction->Items.begin();
1894 while (itemItr != auction->Items.end())
1895 {
1897
1898 for (std::size_t i = 0; i < MAX_MAIL_ITEMS && itemItr != auction->Items.end(); ++i, ++itemItr)
1899 draft.AddItem(*itemItr);
1900
1901 draft.SendMailTo(trans, owner, this, MAIL_CHECK_MASK_COPIED | MAIL_CHECK_MASK_AUCTION);
1902 }
1903}
1904
1905//this function sends mail, when auction is cancelled to old bidder
1907{
1909
1910 // bidder exist
1911 if ((bidder || sCharacterCache->HasCharacterCacheEntry(auction->Bidder)) && !sAuctionBotConfig->IsBotChar(auction->Bidder))
1913 .AddMoney(auction->BidAmount)
1915}
1916
1918{
1919 if (!owner)
1921
1922 // owner exist (online or offline)
1923 if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner))
1924 {
1926 eta += Seconds(sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY));
1927 if (owner)
1928 eta += owner->GetSession()->GetTimezoneOffset();
1929
1934 }
1935}
#define sAuctionBotConfig
eAuctionHouse
@ AH_MINIMUM_DEPOSIT
AuctionHouseResultLimits
uint32 constexpr MIN_AUCTION_TIME
AuctionHouseSortOrder
AuctionHouseFilterMask
#define sAuctionMgr
AuctionPostingServerFlag
AuctionCommand
AuctionMailType
#define sCharacterCache
@ CHAR_UPD_ITEM_OWNER
@ CHAR_SEL_AUCTION_ITEMS
@ CHAR_SEL_AUCTION_BIDDERS
@ CHAR_DEL_AUCTION
@ CHAR_UPD_AUCTION_EXPIRATION
@ CHAR_INS_AUCTION_ITEMS
@ CHAR_INS_AUCTION
@ CHAR_DEL_AUCTION_ITEMS_BY_ITEM
@ CHAR_SEL_AUCTIONS
LocaleConstant
Definition Common.h:51
@ LOCALE_none
Definition Common.h:61
@ TOTAL_LOCALES
Definition Common.h:65
@ LOCALE_enUS
Definition Common.h:52
@ MINUTE
Definition Common.h:32
DB2Storage< BattlePetSpeciesEntry > sBattlePetSpeciesStore("BattlePetSpecies.db2", &BattlePetSpeciesLoadInfo::Instance)
DB2Storage< ItemModifiedAppearanceEntry > sItemModifiedAppearanceStore("ItemModifiedAppearance.db2", &ItemModifiedAppearanceLoadInfo::Instance)
DB2Storage< FactionTemplateEntry > sFactionTemplateStore("FactionTemplate.db2", &FactionTemplateLoadInfo::Instance)
DB2Storage< AuctionHouseEntry > sAuctionHouseStore("AuctionHouse.db2", &AuctionHouseLoadInfo::Instance)
#define sDB2Manager
Definition DB2Stores.h:579
@ FACTION_MASK_ALLIANCE
Definition DBCEnums.h:1012
@ FACTION_MASK_HORDE
Definition DBCEnums.h:1013
ItemContext
Definition DBCEnums.h:1324
@ MoneyEarnedFromAuctions
SQLTransaction< CharacterDatabaseConnection > CharacterDatabaseTransaction
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< CharacterDatabaseConnection > CharacterDatabase
Accessor to the character database.
uint8_t uint8
Definition Define.h:156
int64_t int64
Definition Define.h:149
#define SI64LIT(N)
Definition Define.h:142
int32_t int32
Definition Define.h:150
uint64_t uint64
Definition Define.h:153
#define UI64LIT(N)
Definition Define.h:139
uint16_t uint16
Definition Define.h:155
uint32_t uint32
Definition Define.h:154
std::chrono::system_clock::time_point SystemTimePoint
Definition Duration.h:41
std::chrono::milliseconds Milliseconds
Milliseconds shorthand typedef.
Definition Duration.h:24
std::chrono::seconds Seconds
Seconds shorthand typedef.
Definition Duration.h:28
std::chrono::steady_clock::time_point TimePoint
time_point shorthand typedefs
Definition Duration.h:40
std::chrono::hours Hours
Hours shorthand typedef.
Definition Duration.h:36
std::chrono::minutes Minutes
Minutes shorthand typedef.
Definition Duration.h:32
#define ASSERT_WITH_SIDE_EFFECTS
Definition Errors.h:77
#define ASSERT_NOTNULL(pointer)
Definition Errors.h:74
#define ASSERT
Definition Errors.h:72
EnchantmentSlot
@ MAX_INSPECTED_ENCHANTMENT_SLOT
@ EQUIP_ERR_OK
Definition ItemDefines.h:26
@ ITEM_MODIFIER_BATTLE_PET_BREED_DATA
@ ITEM_MODIFIER_BATTLE_PET_SPECIES_ID
@ ITEM_MODIFIER_BATTLE_PET_LEVEL
@ ITEM_CLASS_RECIPE
@ ITEM_CLASS_CONTAINER
@ ITEM_CLASS_BATTLE_PETS
@ ITEM_CLASS_GEM
@ ITEM_CLASS_ITEM_ENHANCEMENT
@ ITEM_CLASS_ARMOR
@ ITEM_CLASS_MISCELLANEOUS
@ ITEM_CLASS_WEAPON
@ ITEM_CLASS_CONSUMABLE
@ ITEM_SUBCLASS_BOOK
@ INVTYPE_BAG
@ INVTYPE_NON_EQUIP
Item * NewItemOrBag(ItemTemplate const *proto)
Definition Item.cpp:55
@ ITEM_CHANGED
Definition Item.h:47
@ ITEM_REMOVED
Definition Item.h:49
@ LANG_UNKNOWN
Definition Language.h:77
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:182
#define TC_LOG_INFO(filterType__, message__,...)
Definition Log.h:176
#define sLog
Definition Log.h:156
#define TC_LOG_WARN(filterType__, message__,...)
Definition Log.h:179
@ MAIL_CHECK_MASK_AUCTION_WON
This mail is delivering won auction items.
Definition Mail.h:60
@ MAIL_CHECK_MASK_COPIED
This mail was copied. Do not allow making a copy of items in mail.
Definition Mail.h:56
@ MAIL_CHECK_MASK_AUCTION
This mail came from auction house.
Definition Mail.h:64
#define MAX_MAIL_ITEMS
Definition Mail.h:36
#define sObjectMgr
Definition ObjectMgr.h:1873
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
#define sScriptMgr
Definition ScriptMgr.h:1449
@ SILVER
uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
Definition Timer.h:57
uint32 getMSTime()
Definition Timer.h:33
std::wstring wstrCaseAccentInsensitiveParse(std::wstring_view wstr, LocaleConstant locale)
Definition Util.cpp:437
bool Utf8toWStr(char const *utf8str, size_t csize, wchar_t *wstr, size_t &wsize)
Definition Util.cpp:336
constexpr std::underlying_type< E >::type AsUnderlyingType(E enumValue)
Definition Util.h:565
T CalculatePct(T base, U pct)
Definition Util.h:72
void PendingAuctionProcess(Player *player)
static uint64 GetItemAuctionDeposit(Player const *player, Item const *item, Minutes time)
AuctionThrottleResult CheckThrottle(Player const *player, bool addonTainted, AuctionCommand command=AuctionCommand::SellItem)
static std::string BuildAuctionInvoiceMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment, uint32 moneyDelay, uint32 eta)
static std::string BuildAuctionMailSubject(uint32 itemId, AuctionMailType type, uint32 auctionId, uint32 itemCount, uint32 battlePetSpeciesId, ItemContext context, std::vector< int32 > const &bonusListIds)
static uint64 GetCommodityAuctionDeposit(ItemTemplate const *item, Minutes time, uint32 quantity)
static std::string BuildItemAuctionMailSubject(AuctionMailType type, AuctionPosting const *auction)
static std::string BuildAuctionSoldMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment)
TimePoint _playerThrottleObjectsCleanupTime
AuctionHouseObject mGoblinAuctions
AuctionHouseObject * GetAuctionsMap(uint32 factionTemplateId)
std::size_t PendingAuctionCount(Player const *player) const
static std::string BuildAuctionWonMailBody(ObjectGuid guid, uint64 bid, uint64 buyout)
AuctionHouseObject mNeutralAuctions
uint32 GenerateReplicationId()
bool PendingAuctionAdd(Player const *player, uint32 auctionHouseId, uint32 auctionId, uint64 deposit)
AuctionHouseObject mHordeAuctions
AuctionHouseObject mAllianceAuctions
void AddAItem(Item *item)
bool RemoveAItem(ObjectGuid itemGuid, bool deleteItem=false, CharacterDatabaseTransaction *trans=nullptr)
static std::string BuildCommodityAuctionMailSubject(AuctionMailType type, uint32 itemId, uint32 itemCount)
std::unordered_map< ObjectGuid, PlayerThrottleObject > _playerThrottleObjects
AuctionHouseObject * GetAuctionsById(uint32 auctionHouseId)
std::unordered_map< ObjectGuid, PlayerPendingAuctions > _pendingAuctionsByPlayer
static AuctionHouseEntry const * GetAuctionHouseEntry(uint32 factionTemplateId, uint32 *houseId)
std::unordered_map< ObjectGuid, Item * > _itemsByGuid
static AuctionHouseMgr * instance()
Item * GetAItem(ObjectGuid itemGuid)
std::map< uint32, AuctionPosting >::node_type RemoveAuction(CharacterDatabaseTransaction trans, AuctionPosting *auction, std::map< uint32, AuctionPosting >::iterator *auctionItr=nullptr)
void SendAuctionWon(AuctionPosting const *auction, Player *bidder, CharacterDatabaseTransaction trans) const
AuctionPosting * GetAuction(uint32 auctionId)
std::unordered_multimap< ObjectGuid, uint32 > _playerBidderAuctions
void AddAuction(CharacterDatabaseTransaction trans, AuctionPosting auction)
std::unordered_map< ObjectGuid, CommodityQuote > _commodityQuotes
void BuildListBiddedItems(WorldPackets::AuctionHouse::AuctionListBiddedItemsResult &listBiddedItemsResult, Player const *player, uint32 offset, std::span< WorldPackets::AuctionHouse::AuctionSortDef const > sorts) const
AuctionHouseObject(uint32 auctionHouseId)
std::map< uint32, AuctionPosting > _itemsByAuctionId
std::unordered_multimap< ObjectGuid, uint32 > _playerOwnedAuctions
void SendAuctionRemoved(AuctionPosting const *auction, Player *owner, CharacterDatabaseTransaction trans) const
void SendAuctionSold(AuctionPosting const *auction, Player *owner, CharacterDatabaseTransaction trans) const
uint64 CalculateAuctionHouseCut(uint64 bidAmount) const
void BuildListOwnedItems(WorldPackets::AuctionHouse::AuctionListOwnedItemsResult &listOwnedItemsResult, Player const *player, uint32 offset, std::span< WorldPackets::AuctionHouse::AuctionSortDef const > sorts) const
std::map< AuctionsBucketKey, AuctionsBucketData > _buckets
void SendAuctionInvoice(AuctionPosting const *auction, Player *owner, CharacterDatabaseTransaction trans) const
std::unordered_map< ObjectGuid, PlayerReplicateThrottleData > _replicateThrottleMap
uint32 GetAuctionHouseId() const
void BuildListBuckets(WorldPackets::AuctionHouse::AuctionListBucketsResult &listBucketsResult, Player const *player, std::wstring const &name, uint8 minLevel, uint8 maxLevel, EnumFlag< AuctionHouseFilterMask > filters, Optional< AuctionSearchClassFilters > const &classFilters, std::span< uint8 const > knownPetBits, uint8 maxKnownPetLevel, uint32 offset, std::span< WorldPackets::AuctionHouse::AuctionSortDef const > sorts) const
void BuildReplicate(WorldPackets::AuctionHouse::AuctionReplicateResponse &replicateResponse, Player *player, uint32 global, uint32 cursor, uint32 tombstone, uint32 count)
void SendAuctionCancelledToBidder(AuctionPosting const *auction, CharacterDatabaseTransaction trans) const
void BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult &listItemsResult, Player const *player, AuctionsBucketKey const &bucketKey, uint32 offset, std::span< WorldPackets::AuctionHouse::AuctionSortDef const > sorts) const
void CancelCommodityQuote(ObjectGuid guid)
AuctionHouseEntry const * _auctionHouse
CommodityQuote const * CreateCommodityQuote(Player const *player, uint32 itemId, uint32 quantity)
bool BuyCommodity(CharacterDatabaseTransaction trans, Player *player, uint32 itemId, uint32 quantity, Milliseconds delayForNextAction)
void SendAuctionOutbid(AuctionPosting const *auction, ObjectGuid newBidder, uint64 newBidAmount, CharacterDatabaseTransaction trans) const
void SendAuctionExpired(AuctionPosting const *auction, CharacterDatabaseTransaction trans) const
int64 CompareColumns(AuctionHouseSortOrder column, AuctionPosting const *left, AuctionPosting const *right) const
bool operator()(AuctionPosting const *left, AuctionPosting const *right) const
std::span< WorldPackets::AuctionHouse::AuctionSortDef const > _sorts
Sorter(LocaleConstant locale, std::span< WorldPackets::AuctionHouse::AuctionSortDef const > sorts)
Sorter(LocaleConstant locale, std::span< WorldPackets::AuctionHouse::AuctionSortDef const > sorts)
bool operator()(AuctionsBucketData const *left, AuctionsBucketData const *right) const
int64 CompareColumns(AuctionHouseSortOrder column, AuctionsBucketData const *left, AuctionsBucketData const *right) const
std::span< WorldPackets::AuctionHouse::AuctionSortDef const > _sorts
std::vector< T const * > _items
void AddItem(T const *item)
AuctionsResultBuilder(uint32 offset, LocaleConstant locale, std::span< WorldPackets::AuctionHouse::AuctionSortDef const > sorts, AuctionHouseResultLimits maxResults)
std::span< T const *const > GetResultRange() const
typename T::Sorter Sorter
std::size_t GetTotalCount() const
ObjectGuid const & GetGUID() const
Definition BaseEntity.h:163
bool HasToy(uint32 itemId) const
std::unordered_set< uint32 > GetAppearanceIds() const
std::pair< bool, bool > HasItemAppearance(uint32 itemModifiedAppearanceId) const
constexpr bool HasFlag(T flag) const
Definition EnumFlag.h:106
constexpr std::underlying_type_t< T > AsUnderlyingType() const
Definition EnumFlag.h:122
Class used to access individual fields of database query result.
Definition Field.h:94
uint64 GetUInt64() const noexcept
Definition Field.cpp:71
uint32 GetUInt32() const noexcept
Definition Field.cpp:57
uint8 GetUInt8() const noexcept
Definition Field.cpp:29
Definition Item.h:185
Item * CloneItem(uint32 count, Player const *player=nullptr) const
Definition Item.cpp:1751
ItemTemplate const * GetTemplate() const
Definition Item.cpp:1233
uint32 GetSellPrice(Player const *owner, bool forVendor=false) const
Definition Item.cpp:2234
BonusData const * GetBonus() const
Definition Item.h:201
uint32 GetItemLevel(Player const *owner) const
Definition Item.cpp:2283
virtual bool LoadFromDB(ObjectGuid::LowType guid, ObjectGuid ownerGuid, Field *fields, uint32 entry)
Definition Item.cpp:907
uint32 GetCount() const
Definition Item.h:289
uint32 GetModifier(ItemModifier modifier) const
Definition Item.cpp:2502
int32 GetRequiredLevel() const
Definition Item.cpp:2896
void SendMailTo(CharacterDatabaseTransaction trans, MailReceiver const &receiver, MailSender const &sender, MailCheckMask checked=MAIL_CHECK_MASK_NONE, uint32 deliver_delay=0)
Definition Mail.cpp:195
MailDraft & AddItem(Item *item)
Definition Mail.cpp:99
MailDraft & AddMoney(uint64 money)
Definition Mail.h:151
LowType GetCounter() const
Definition ObjectGuid.h:336
static ObjectGuid const Empty
Definition ObjectGuid.h:314
bool IsEmpty() const
Definition ObjectGuid.h:362
std::string ToString() const
uint64 LowType
Definition ObjectGuid.h:321
uint32 GetEntry() const
Definition Object.h:89
bool ModifyMoney(int64 amount, bool sendError=true)
Definition Player.cpp:24800
void SendDirectMessage(WorldPacket const *data) const
Definition Player.cpp:6193
void SaveInventoryAndGoldToDB(CharacterDatabaseTransaction trans)
Definition Player.cpp:20790
WorldSession * GetSession() const
Definition Player.h:2256
void UpdateCriteria(CriteriaType type, uint64 miscValue1=0, uint64 miscValue2=0, uint64 miscValue3=0, WorldObject *ref=nullptr)
Definition Player.cpp:27532
bool HasEnoughMoney(uint64 amount) const
Definition Player.h:1892
InventoryResult CanUseItem(Item *pItem, bool not_loading=true) const
Definition Player.cpp:11124
void setUInt32(uint8 index, uint32 value)
void setInt64(uint8 index, int64 value)
void setUInt64(uint8 index, uint64 value)
void setUInt8(uint8 index, uint8 value)
uint8 GetLevel() const
Definition Unit.h:769
std::string const & GetName() const
Definition Object.h:342
uint32 TotalQuantity
number of items (summing stack counts)
void SendAuctionClosedNotification(AuctionPosting const *auction, float mailDelay, bool sold)
void SendAuctionCommandResult(uint32 auctionId, AuctionCommand command, AuctionResult errorCode, Milliseconds delayForNextAction, InventoryResult bagResult=InventoryResult(0))
Notifies the client of the result of his last auction operation. It is called when the player bids,...
ObjectGuid GetAccountGUID() const
Minutes GetTimezoneOffset() const
LocaleConstant GetSessionDbcLocale() const
bool HasPermission(uint32 permissionId)
uint32 GetAccountId() const
CollectionMgr * GetCollectionMgr() const
uint32 GetPackedTime() const
Definition WowTime.cpp:23
#define sWorld
Definition World.h:919
@ CONFIG_AUCTION_TAINTED_SEARCH_DELAY
Definition World.h:417
@ CONFIG_MAIL_DELIVERY_DELAY
Definition World.h:294
@ CONFIG_AUCTION_REPLICATE_DELAY
Definition World.h:415
@ CONFIG_EXPANSION
Definition World.h:306
@ CONFIG_AUCTION_SEARCH_DELAY
Definition World.h:416
@ RATE_AUCTION_CUT
Definition World.h:519
@ CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION
Definition World.h:114
WowTime const * GetUtcWowTime()
Definition GameTime.cpp:101
SystemTimePoint GetSystemTime()
Current chrono system_clock time point.
Definition GameTime.cpp:62
TimePoint Now()
Current chrono steady_clock time point.
Definition GameTime.cpp:67
time_t GetGameTime()
Definition GameTime.cpp:52
TC_GAME_API Player * FindConnectedPlayer(ObjectGuid const &)
auto MapEqualRange(M &map, typename M::key_type const &key)
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition MapUtils.h:37
void MultimapErasePair(M &multimap, typename M::key_type const &key, typename M::mapped_type const &value)
Definition MapUtils.h:57
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
@ RBAC_PERM_LOG_GM_TRADE
Definition RBAC.h:64
bool IsCommodity() const
void BuildAuctionItem(WorldPackets::AuctionHouse::AuctionItem *auctionItem, bool alwaysSendItem, bool sendKey, bool censorServerInfo, bool censorBidInfo) const
std::vector< Item * > Items
AuctionsBucketData * Bucket
EnumFlag< AuctionPostingServerFlag > ServerFlags
ObjectGuid OwnerAccount
SystemTimePoint StartTime
uint32 GetTotalItemCount() const
GuidUnorderedSet BidderHistory
uint64 CalculateMinIncrement() const
SystemTimePoint EndTime
AuctionsBucketKey Key
std::array< std::wstring, TOTAL_LOCALES > FullName
AuctionHouseFilterMask QualityMask
std::array< std::pair< uint32, uint32 >, 4 > ItemModifiedAppearanceId
void BuildBucketInfo(WorldPackets::AuctionHouse::BucketInfo *bucketInfo, Player const *player) const
std::vector< AuctionPosting * > Auctions
std::array< uint32, MAX_ITEM_QUALITY > QualityCounts
uint16 SuffixItemNameDescriptionId
static AuctionsBucketKey ForCommodity(ItemTemplate const *itemTemplate)
static AuctionsBucketKey ForItem(Item const *item)
AuctionsBucketKey()=default
static std::size_t Hash(AuctionsBucketKey const &key)
uint32 Suffix
Definition Item.h:82
uint32 GetRequiredSkillRank() const
uint32 GetBaseItemLevel() const
uint32 GetContainerSlots() const
uint32 GetId() const
uint32 GetMaxStackSize() const
InventoryType GetInventoryType() const
std::vector< ItemEffectEntry const * > Effects
int32 GetBaseRequiredLevel() const
uint32 GetSubClass() const
uint32 GetSellPrice() const
uint8 GetRequiredExpansion() const
uint32 GetClass() const
constexpr void UpdateData(std::span< V, Extent > data) noexcept
Definition Hash.h:85
UpdateField< int32, 0, 1 > ItemID
void Initialize(int32 auctionHouseId, ::AuctionPosting const *auction, ::Item const *item)
std::vector< Item::ItemGemData > Gems
Optional< WorldPackets::AuctionHouse::AuctionBucketKey > AuctionBucketKey
std::vector< Item::ItemEnchantData > Enchantments
void Initialize(::Item const *item)