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)),
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)), _hasMoreResults(false)
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 _items.insert(where, item);
359 if (_items.size() > _maxResults + _offset)
360 {
361 _items.pop_back();
362 _hasMoreResults = true;
363 }
364 }
365
366 std::span<T const* const> GetResultRange() const
367 {
368 return std::span(_items.begin() + _offset, _items.end());
369 }
370
371 bool HasMoreResults() const
372 {
373 return _hasMoreResults;
374 }
375
376private:
379 std::size_t _maxResults;
380 std::vector<T const*> _items;
382};
383
384AuctionHouseMgr::AuctionHouseMgr() : mHordeAuctions(6), mAllianceAuctions(2), mNeutralAuctions(1), mGoblinAuctions(7), _replicateIdGenerator(0)
385{
387}
388
390{
391 for (std::pair<ObjectGuid const, Item*>& itemPair : _itemsByGuid)
392 delete itemPair.second;
393}
394
400
402{
404 return &mNeutralAuctions;
405
406 // teams have linked auction houses
407 FactionTemplateEntry const* uEntry = sFactionTemplateStore.LookupEntry(factionTemplateId);
408 if (!uEntry)
409 return &mNeutralAuctions;
410 else if (uEntry->FactionGroup & FACTION_MASK_ALLIANCE)
411 return &mAllianceAuctions;
412 else if (uEntry->FactionGroup & FACTION_MASK_HORDE)
413 return &mHordeAuctions;
414 else
415 return &mNeutralAuctions;
416}
417
419{
420 switch (auctionHouseId)
421 {
422 case 1:
423 return &mNeutralAuctions;
424 case 2:
425 return &mAllianceAuctions;
426 case 6:
427 return &mHordeAuctions;
428 case 7:
429 return &mGoblinAuctions;
430 default:
431 break;
432 }
433 return &mNeutralAuctions;
434}
435
440
442{
443 uint32 sellPrice = item->GetSellPrice();
444 return uint64(std::ceil(std::floor(fmax(0.15 * quantity * sellPrice, 100.0)) / int64(SILVER)) * int64(SILVER)) * (time.count() / (MIN_AUCTION_TIME / MINUTE));
445}
446
448{
449 uint32 sellPrice = item->GetSellPrice(player);
450 return uint64(std::ceil(std::floor(fmax(sellPrice * 0.15, 100.0)) / int64(SILVER)) * int64(SILVER)) * (time.count() / (MIN_AUCTION_TIME / MINUTE));
451}
452
454{
455 return BuildAuctionMailSubject(auction->Items[0]->GetEntry(), type, auction->Id, auction->GetTotalItemCount(),
456 auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID), auction->Items[0]->GetContext(), auction->Items[0]->GetBonusListIDs());
457}
458
460{
461 return BuildAuctionMailSubject(itemId, type, 0, itemCount, 0, ItemContext::NONE, {});
462}
463
464std::string AuctionHouseMgr::BuildAuctionMailSubject(uint32 itemId, AuctionMailType type, uint32 auctionId, uint32 itemCount, uint32 battlePetSpeciesId,
465 ItemContext context, std::vector<int32> const& bonusListIds)
466{
467 return Trinity::StringFormat("{}:0:{}:{}:{}:{}:0:0:0:0:{}:{}:{}",
468 itemId, AsUnderlyingType(type), auctionId, itemCount, battlePetSpeciesId, context, bonusListIds.size(), fmt::join(bonusListIds, ":"));
469}
470
472{
473 return Trinity::StringFormat("{}:{}:{}:0", guid.ToString(), bid, buyout);
474}
475
476std::string AuctionHouseMgr::BuildAuctionSoldMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment)
477{
478 return Trinity::StringFormat("{}:{}:{}:{}:{}:0", guid.ToString(), bid, buyout, deposit, consignment);
479}
480
481std::string AuctionHouseMgr::BuildAuctionInvoiceMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment, uint32 moneyDelay, uint32 eta)
482{
483 return Trinity::StringFormat("{}:{}:{}:{}:{}:{}:{}:0", guid.ToString(), bid, buyout, deposit, consignment, moneyDelay, eta);
484}
485
487{
488 uint32 oldMSTime = getMSTime();
489
490 // need to clear in case we are reloading
491 if (!_itemsByGuid.empty())
492 {
493 for (std::pair<ObjectGuid const, Item*>& itemPair : _itemsByGuid)
494 delete itemPair.second;
495
496 _itemsByGuid.clear();
497 }
498
499 // data needs to be at first place for Item::LoadFromDB
500 uint32 count = 0;
501 std::unordered_map<uint32, std::vector<Item*>> itemsByAuction;
502 std::unordered_map<uint32, GuidUnorderedSet> biddersByAuction;
503
504 if (PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTION_ITEMS)))
505 {
506 do
507 {
508 Field* fields = result->Fetch();
509
510 ObjectGuid::LowType itemGuid = fields[0].GetUInt64();
511 uint32 itemEntry = fields[1].GetUInt32();
512
513 ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry);
514 if (!proto)
515 {
516 TC_LOG_ERROR("misc", "AuctionHouseMgr::LoadAuctionItems: Unknown item (GUID: {} item entry: #{}) in auction, skipped.", itemGuid, itemEntry);
517 continue;
518 }
519
520 Item* item = NewItemOrBag(proto);
521 if (!item->LoadFromDB(itemGuid, ObjectGuid::Create<HighGuid::Player>(fields[52].GetUInt64()), fields, itemEntry))
522 {
523 delete item;
524 continue;
525 }
526 uint32 auctionId = fields[53].GetUInt32();
527 itemsByAuction[auctionId].push_back(item);
528
529 ++count;
530 } while (result->NextRow());
531 }
532
533 TC_LOG_INFO("server.loading", ">> Loaded {} auction items in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
534
535 oldMSTime = getMSTime();
536
537 count = 0;
538
540 {
541 do
542 {
543 Field* fields = result->Fetch();
544 biddersByAuction[fields[0].GetUInt32()].insert(ObjectGuid::Create<HighGuid::Player>(fields[1].GetUInt64()));
545
546 } while (result->NextRow());
547 }
548
549 TC_LOG_INFO("server.loading", ">> Loaded {} auction bidders in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
550
551 oldMSTime = getMSTime();
552
553 count = 0;
554
555 if (PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTIONS)))
556 {
557 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
558 do
559 {
560 Field* fields = result->Fetch();
561
562 AuctionPosting auction;
563 auction.Id = fields[0].GetUInt32();
564 uint32 auctionHouseId = fields[1].GetUInt32();
565 AuctionHouseObject* auctionHouse = GetAuctionsById(auctionHouseId);
566 if (!auctionHouse)
567 {
568 TC_LOG_ERROR("misc", "Auction {} has wrong auctionHouseId {}", auction.Id, auctionHouseId);
570 stmt->setUInt32(0, auction.Id);
571 trans->Append(stmt);
572 continue;
573 }
574
575 auto itemsItr = itemsByAuction.find(auction.Id);
576 if (itemsItr == itemsByAuction.end())
577 {
578 TC_LOG_ERROR("misc", "Auction {} has no items", auction.Id);
580 stmt->setUInt32(0, auction.Id);
581 trans->Append(stmt);
582 continue;
583 }
584
585 auction.Items = std::move(itemsItr->second);
586 auction.Owner = ObjectGuid::Create<HighGuid::Player>(fields[2].GetUInt64());
587 auction.OwnerAccount = ObjectGuid::Create<HighGuid::WowAccount>(sCharacterCache->GetCharacterAccountIdByGuid(auction.Owner));
588 if (uint64 bidder = fields[3].GetUInt64())
589 auction.Bidder = ObjectGuid::Create<HighGuid::Player>(bidder);
590
591 auction.MinBid = fields[4].GetUInt64();
592 auction.BuyoutOrUnitPrice = fields[5].GetUInt64();
593 auction.Deposit = fields[6].GetUInt64();
594 auction.BidAmount = fields[7].GetUInt64();
595 auction.StartTime = std::chrono::system_clock::from_time_t(fields[8].GetInt64());
596 auction.EndTime = std::chrono::system_clock::from_time_t(fields[9].GetInt64());
597 auction.ServerFlags = static_cast<AuctionPostingServerFlag>(fields[10].GetUInt8());
598
599 auto biddersItr = biddersByAuction.find(auction.Id);
600 if (biddersItr != biddersByAuction.end())
601 auction.BidderHistory = std::move(biddersItr->second);
602
603 auctionHouse->AddAuction(nullptr, std::move(auction));
604
605 ++count;
606 } while (result->NextRow());
607
608 CharacterDatabase.CommitTransaction(trans);
609 }
610
611 TC_LOG_INFO("server.loading", ">> Loaded {} auctions in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
612}
613
615{
616 ASSERT(item);
617 ASSERT_WITH_SIDE_EFFECTS(_itemsByGuid.emplace(item->GetGUID(), item).second);
618}
619
620bool AuctionHouseMgr::RemoveAItem(ObjectGuid itemGuid, bool deleteItem /*= false*/, CharacterDatabaseTransaction* trans /*= nullptr*/)
621{
622 auto i = _itemsByGuid.find(itemGuid);
623 if (i == _itemsByGuid.end())
624 return false;
625
626 if (deleteItem)
627 {
628 ASSERT(trans);
629 i->second->FSetState(ITEM_REMOVED);
630 i->second->SaveToDB(*trans);
631 }
632
633 _itemsByGuid.erase(i);
634 return true;
635}
636
637bool AuctionHouseMgr::PendingAuctionAdd(Player const* player, uint32 auctionHouseId, uint32 auctionId, uint64 deposit)
638{
639 auto itr = _pendingAuctionsByPlayer.find(player->GetGUID());
640 if (itr != _pendingAuctionsByPlayer.end())
641 {
642 // Get deposit so far
643 uint64 totalDeposit = 0;
644 for (PendingAuctionInfo const& thisAuction : itr->second.Auctions)
645 totalDeposit += thisAuction.Deposit;
646
647 // Add this deposit
648 totalDeposit += deposit;
649
650 if (!player->HasEnoughMoney(totalDeposit))
651 return false;
652 }
653 else
654 itr = _pendingAuctionsByPlayer.emplace(std::piecewise_construct, std::forward_as_tuple(player->GetGUID()), std::forward_as_tuple()).first;
655
656 itr->second.Auctions.push_back({ auctionId, auctionHouseId, deposit });
657 return true;
658}
659
660std::size_t AuctionHouseMgr::PendingAuctionCount(Player const* player) const
661{
662 auto itr = _pendingAuctionsByPlayer.find(player->GetGUID());
663 if (itr != _pendingAuctionsByPlayer.end())
664 return itr->second.Auctions.size();
665
666 return 0;
667}
668
670{
671 auto iterMap = _pendingAuctionsByPlayer.find(player->GetGUID());
672 if (iterMap == _pendingAuctionsByPlayer.end())
673 return;
674
675 uint64 totaldeposit = 0;
676 auto itrAH = iterMap->second.Auctions.begin();
677 for (; itrAH != iterMap->second.Auctions.end(); ++itrAH)
678 {
679 if (!player->HasEnoughMoney(totaldeposit + itrAH->Deposit))
680 break;
681
682 totaldeposit += itrAH->Deposit;
683 }
684
685 // expire auctions we cannot afford
686 if (itrAH != iterMap->second.Auctions.end())
687 {
688 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
689
690 do
691 {
692 PendingAuctionInfo const& pendingAuction = *itrAH;
693 if (AuctionPosting* auction = GetAuctionsById(pendingAuction.AuctionHouseId)->GetAuction(pendingAuction.AuctionId))
694 auction->EndTime = GameTime::GetSystemTime();
695
698 stmt->setUInt32(1, pendingAuction.AuctionId);
699 trans->Append(stmt);
700 ++itrAH;
701 } while (itrAH != iterMap->second.Auctions.end());
702 CharacterDatabase.CommitTransaction(trans);
703 }
704
705 _pendingAuctionsByPlayer.erase(player->GetGUID());
706 player->ModifyMoney(-int64(totaldeposit));
707}
708
710{
711 for (auto itr = _pendingAuctionsByPlayer.begin(); itr != _pendingAuctionsByPlayer.end();)
712 {
713 ObjectGuid playerGUID = itr->first;
714 if (Player* player = ObjectAccessor::FindConnectedPlayer(playerGUID))
715 {
716 // Check if there were auctions since last update process if not
717 if (PendingAuctionCount(player) == itr->second.LastAuctionsSize)
718 {
719 ++itr;
720 PendingAuctionProcess(player);
721 }
722 else
723 {
724 ++itr;
725 _pendingAuctionsByPlayer[playerGUID].LastAuctionsSize = PendingAuctionCount(player);
726 }
727 }
728 else
729 {
730 // Expire any auctions that we couldn't get a deposit for
731 TC_LOG_WARN("auctionHouse", "Player {} was offline, unable to retrieve deposit!", playerGUID.ToString());
732 ++itr;
733 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
734 for (PendingAuctionInfo const& pendingAuction : itr->second.Auctions)
735 {
736 if (AuctionPosting* auction = GetAuctionsById(pendingAuction.AuctionHouseId)->GetAuction(pendingAuction.AuctionId))
737 auction->EndTime = GameTime::GetSystemTime();
738
741 stmt->setUInt32(1, pendingAuction.AuctionId);
742 trans->Append(stmt);
743 }
744 CharacterDatabase.CommitTransaction(trans);
745 _pendingAuctionsByPlayer.erase(playerGUID);
746 }
747 }
748}
749
751{
756
757 TimePoint now = GameTime::Now();
759 {
760 for (auto itr = _playerThrottleObjects.begin(); itr != _playerThrottleObjects.end();)
761 {
762 if (itr->second.PeriodEnd < now)
763 itr = _playerThrottleObjects.erase(itr);
764 else
765 ++itr;
766 }
767
769 }
770}
771
776
777AuctionThrottleResult AuctionHouseMgr::CheckThrottle(Player const* player, bool addonTainted, AuctionCommand command /*= AuctionCommand::SellItem*/)
778{
779 TimePoint now = GameTime::Now();
780 auto itr = _playerThrottleObjects.emplace(std::piecewise_construct, std::forward_as_tuple(player->GetGUID()), std::forward_as_tuple());
781 if (itr.second || now > itr.first->second.PeriodEnd)
782 {
783 itr.first->second.PeriodEnd = now + Minutes(1);
784 itr.first->second.QueriesRemaining = 100;
785 }
786
787 if (!itr.first->second.QueriesRemaining)
788 {
789 player->GetSession()->SendAuctionCommandResult(0, command, AuctionResult::AuctionHouseBusy, std::chrono::duration_cast<Milliseconds>(itr.first->second.PeriodEnd - now));
790 return { {}, true };
791 }
792
793 if (!--itr.first->second.QueriesRemaining)
794 return { std::chrono::duration_cast<Milliseconds>(itr.first->second.PeriodEnd - now), false };
795 else
796 return { Milliseconds(sWorld->getIntConfig(addonTainted ? CONFIG_AUCTION_TAINTED_SEARCH_DELAY : CONFIG_AUCTION_SEARCH_DELAY)), false };
797}
798
800{
801 uint32 houseid = 1; // Auction House
802
804 {
805 // FIXME: found way for proper auctionhouse selection by another way
806 // AuctionHouse.dbc have faction field with _player_ factions associated with auction house races.
807 // but no easy way convert creature faction to player race faction for specific city
808 switch (factionTemplateId)
809 {
810 case 120: houseid = 7; break; // booty bay, Blackwater Auction House
811 case 474: houseid = 7; break; // gadgetzan, Blackwater Auction House
812 case 855: houseid = 7; break; // everlook, Blackwater Auction House
813 default: // default
814 {
815 FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(factionTemplateId);
816 if (!u_entry)
817 houseid = 1; // Auction House
818 else if (u_entry->FactionGroup & FACTION_MASK_ALLIANCE)
819 houseid = 2; // Alliance Auction House
820 else if (u_entry->FactionGroup & FACTION_MASK_HORDE)
821 houseid = 6; // Horde Auction House
822 else
823 houseid = 1; // Auction House
824 break;
825 }
826 }
827 }
828
829 if (houseId)
830 *houseId = houseid;
831
832 return sAuctionHouseStore.LookupEntry(houseid);
833}
834
835AuctionHouseObject::AuctionHouseObject(uint32 auctionHouseId) : _auctionHouse(sAuctionHouseStore.AssertEntry(auctionHouseId))
836{
837}
838
840
845
850
852{
854 auto [bucketItr, isNew] = _buckets.try_emplace(key);
855 AuctionsBucketData* bucket = &bucketItr->second;
856 if (isNew)
857 {
858 // we don't have any item for this key yet, create new bucket
859 bucket->Key = key;
860
861 ItemTemplate const* itemTemplate = auction.Items[0]->GetTemplate();
862 bucket->ItemClass = itemTemplate->GetClass();
863 bucket->ItemSubClass = itemTemplate->GetSubClass();
864 bucket->InventoryType = itemTemplate->GetInventoryType();
865 bucket->RequiredLevel = auction.Items[0]->GetRequiredLevel();
866 switch (itemTemplate->GetClass())
867 {
869 case ITEM_CLASS_ARMOR:
870 bucket->SortLevel = key.ItemLevel;
871 break;
873 bucket->SortLevel = itemTemplate->GetContainerSlots();
874 break;
875 case ITEM_CLASS_GEM:
877 bucket->SortLevel = itemTemplate->GetBaseItemLevel();
878 break;
880 bucket->SortLevel = std::max<uint8>(1, bucket->RequiredLevel);
881 break;
884 bucket->SortLevel = 1;
885 break;
887 bucket->SortLevel = itemTemplate->GetSubClass() != ITEM_SUBCLASS_BOOK ? itemTemplate->GetRequiredSkillRank() : itemTemplate->GetBaseRequiredLevel();
888 break;
889 default:
890 break;
891 }
892
893 for (LocaleConstant locale = LOCALE_enUS; locale < TOTAL_LOCALES; locale = LocaleConstant(locale + 1))
894 {
895 if (locale == LOCALE_none)
896 continue;
897
898 std::wstring utf16name;
899 if (!Utf8toWStr(auction.Items[0]->GetNameForLocaleIdx(locale), utf16name))
900 continue;
901
902 bucket->FullName[locale] = wstrCaseAccentInsensitiveParse(utf16name, locale);
903 }
904 }
905
906 // update cache fields
907 uint64 priceToDisplay = auction.BuyoutOrUnitPrice ? auction.BuyoutOrUnitPrice : auction.BidAmount;
908 if (!bucket->MinPrice || priceToDisplay < bucket->MinPrice)
909 bucket->MinPrice = priceToDisplay;
910
911 if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = auction.Items[0]->GetItemModifiedAppearance())
912 {
913 auto itr = std::ranges::find(bucket->ItemModifiedAppearanceId, itemModifiedAppearance->ID, Trinity::TupleElement<0>);
914
915 if (itr == bucket->ItemModifiedAppearanceId.end())
916 itr = std::ranges::find(bucket->ItemModifiedAppearanceId, 0u, Trinity::TupleElement<0>);
917
918 if (itr != bucket->ItemModifiedAppearanceId.end())
919 {
920 itr->first = itemModifiedAppearance->ID;
921 ++itr->second;
922 }
923 }
924
925 uint32 quality;
926
927 if (!auction.Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID))
928 {
929 quality = auction.Items[0]->GetQuality();
930 }
931 else
932 {
933 quality = (auction.Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF;
934 for (Item* item : auction.Items)
935 {
936 uint8 battlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
937 if (!bucket->MinBattlePetLevel)
938 bucket->MinBattlePetLevel = battlePetLevel;
939 else if (bucket->MinBattlePetLevel > battlePetLevel)
940 bucket->MinBattlePetLevel = battlePetLevel;
941
942 bucket->MaxBattlePetLevel = std::max<uint8>(bucket->MaxBattlePetLevel, battlePetLevel);
943 bucket->SortLevel = bucket->MaxBattlePetLevel;
944 }
945 }
946
948 ++bucket->QualityCounts[quality];
949
950 if (trans)
951 {
953 stmt->setUInt32(0, auction.Id);
954 stmt->setUInt32(1, _auctionHouse->ID);
955 stmt->setUInt64(2, auction.Owner.GetCounter());
956 stmt->setUInt64(3, ObjectGuid::Empty.GetCounter());
957 stmt->setUInt64(4, auction.MinBid);
958 stmt->setUInt64(5, auction.BuyoutOrUnitPrice);
959 stmt->setUInt64(6, auction.Deposit);
960 stmt->setUInt64(7, auction.BidAmount);
961 stmt->setInt64(8, std::chrono::system_clock::to_time_t(auction.StartTime));
962 stmt->setInt64(9, std::chrono::system_clock::to_time_t(auction.EndTime));
963 stmt->setUInt8(10, auction.ServerFlags.AsUnderlyingType());
964 trans->Append(stmt);
965
966 for (Item* item : auction.Items)
967 {
968 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION_ITEMS);
969 stmt->setUInt32(0, auction.Id);
970 stmt->setUInt64(1, item->GetGUID().GetCounter());
971 trans->Append(stmt);
972 }
973 }
974
975 for (Item* item : auction.Items)
976 sAuctionMgr->AddAItem(item);
977
978 auction.Bucket = bucket;
979 _playerOwnedAuctions.emplace(auction.Owner, auction.Id);
980 for (ObjectGuid bidder : auction.BidderHistory)
981 _playerBidderAuctions.emplace(bidder, auction.Id);
982
983 AuctionPosting* addedAuction = &(_itemsByAuctionId[auction.Id] = std::move(auction));
984
986 AuctionPosting::Sorter insertSorter(LOCALE_enUS, std::span(&priceSort, 1));
987 bucket->Auctions.insert(std::ranges::lower_bound(bucket->Auctions, addedAuction, std::cref(insertSorter)), addedAuction);
988
989 sScriptMgr->OnAuctionAdd(this, addedAuction);
990}
991
992std::map<uint32, AuctionPosting>::node_type AuctionHouseObject::RemoveAuction(CharacterDatabaseTransaction trans, AuctionPosting* auction,
993 std::map<uint32, AuctionPosting>::iterator* auctionItr /*= nullptr*/)
994{
995 AuctionsBucketData* bucket = auction->Bucket;
996
997 std::erase(bucket->Auctions, auction);
998 if (!bucket->Auctions.empty())
999 {
1000 // update cache fields
1001 uint64 priceToDisplay = auction->BuyoutOrUnitPrice ? auction->BuyoutOrUnitPrice : auction->BidAmount;
1002 if (bucket->MinPrice == priceToDisplay)
1003 {
1004 bucket->MinPrice = std::numeric_limits<uint64>::max();
1005 for (AuctionPosting const* remainingAuction : bucket->Auctions)
1006 bucket->MinPrice = std::min(bucket->MinPrice, remainingAuction->BuyoutOrUnitPrice ? remainingAuction->BuyoutOrUnitPrice : remainingAuction->BidAmount);
1007 }
1008
1009 if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = auction->Items[0]->GetItemModifiedAppearance())
1010 {
1011 auto itr = std::find_if(bucket->ItemModifiedAppearanceId.begin(), bucket->ItemModifiedAppearanceId.end(),
1012 [itemModifiedAppearance](std::pair<uint32, uint32> const& appearance)
1013 {
1014 return appearance.first == itemModifiedAppearance->ID;
1015 });
1016
1017 if (itr != bucket->ItemModifiedAppearanceId.end())
1018 if (!--itr->second)
1019 itr->first = 0;
1020 }
1021
1022 uint32 quality;
1023
1024 if (!auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID))
1025 {
1026 quality = auction->Items[0]->GetQuality();
1027 }
1028 else
1029 {
1030 quality = (auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF;
1031 bucket->MinBattlePetLevel = 0;
1032 bucket->MaxBattlePetLevel = 0;
1033 for (AuctionPosting const* remainingAuction : bucket->Auctions)
1034 {
1035 for (Item* item : remainingAuction->Items)
1036 {
1037 uint8 battlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
1038 if (!bucket->MinBattlePetLevel)
1039 bucket->MinBattlePetLevel = battlePetLevel;
1040 else if (bucket->MinBattlePetLevel > battlePetLevel)
1041 bucket->MinBattlePetLevel = battlePetLevel;
1042
1043 bucket->MaxBattlePetLevel = std::max<uint8>(bucket->MaxBattlePetLevel, battlePetLevel);
1044 }
1045 }
1046 }
1047
1048 if (!--bucket->QualityCounts[quality])
1050 }
1051 else
1052 {
1053 auction->Bucket = nullptr;
1054 _buckets.erase(bucket->Key);
1055 }
1056
1058 stmt->setUInt32(0, auction->Id);
1059 trans->Append(stmt);
1060
1061 for (Item* item : auction->Items)
1062 sAuctionMgr->RemoveAItem(item->GetGUID());
1063
1064 sScriptMgr->OnAuctionRemove(this, auction);
1065
1067 for (ObjectGuid bidder : auction->BidderHistory)
1069
1070 if (auctionItr)
1071 return _itemsByAuctionId.extract((*auctionItr)++);
1072 else
1073 return _itemsByAuctionId.extract(auction->Id);
1074}
1075
1077{
1079 TimePoint curTimeSteady = GameTime::Now();
1081
1082 // Clear expired throttled players
1083 for (auto itr = _replicateThrottleMap.begin(); itr != _replicateThrottleMap.end();)
1084 {
1085 if (itr->second.NextAllowedReplication <= curTimeSteady)
1086 itr = _replicateThrottleMap.erase(itr);
1087 else
1088 ++itr;
1089 }
1090
1091 for (auto itr = _commodityQuotes.begin(); itr != _commodityQuotes.end();)
1092 {
1093 if (itr->second.ValidTo < curTimeSteady)
1094 itr = _commodityQuotes.erase(itr);
1095 else
1096 ++itr;
1097 }
1098
1099 if (_itemsByAuctionId.empty())
1100 return;
1101
1102 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
1103
1104 for (auto it = _itemsByAuctionId.begin(); it != _itemsByAuctionId.end();)
1105 {
1106 AuctionPosting* auction = &it->second;
1108 if (auction->EndTime > curTime + 1min)
1109 {
1110 ++it;
1111 continue;
1112 }
1113
1114 std::map<uint32, AuctionPosting>::node_type removedAuctionNode = RemoveAuction(trans, auction, &it);
1115 auction = &removedAuctionNode.mapped();
1116
1118 if (auction->Bidder.IsEmpty())
1119 {
1120 sScriptMgr->OnAuctionExpire(this, auction);
1121 SendAuctionExpired(auction, trans);
1122 }
1124 else
1125 {
1126 sScriptMgr->OnAuctionSuccessful(this, auction);
1127 //we should send an "item sold" message if the seller is online
1128 //we send the item to the winner
1129 //we send the money to the seller
1130 SendAuctionSold(auction, nullptr, trans);
1131 SendAuctionWon(auction, nullptr, trans);
1132 }
1133 }
1134
1135 // Run DB changes
1136 CharacterDatabase.CommitTransaction(trans);
1137}
1138
1140 std::wstring const& name, uint8 minLevel, uint8 maxLevel, EnumFlag<AuctionHouseFilterMask> filters, Optional<AuctionSearchClassFilters> const& classFilters,
1141 std::span<uint8 const> knownPetBits, uint8 maxKnownPetLevel, uint32 offset, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1142{
1143 std::unordered_set<uint32> knownAppearanceIds;
1144 boost::dynamic_bitset<uint8> knownPetSpecies;
1145 // prepare uncollected filter for more efficient searches
1147 {
1148 knownAppearanceIds = player->GetSession()->GetCollectionMgr()->GetAppearanceIds();
1149 knownPetSpecies.resize(std::max(knownPetSpecies.size() * 32, std::size_t(sBattlePetSpeciesStore.GetNumRows())));
1150 boost::from_block_range(knownPetBits.begin(), knownPetBits.end(), knownPetSpecies);
1151 }
1152
1154
1155 for (std::pair<AuctionsBucketKey const, AuctionsBucketData> const& bucket : _buckets)
1156 {
1157 AuctionsBucketData const* bucketData = &bucket.second;
1158 if (!name.empty())
1159 {
1161 {
1162 if (bucketData->FullName[player->GetSession()->GetSessionDbcLocale()] != name)
1163 continue;
1164 }
1165 else
1166 if (bucketData->FullName[player->GetSession()->GetSessionDbcLocale()].find(name) == std::wstring::npos)
1167 continue;
1168 }
1169
1170 if (minLevel && bucketData->RequiredLevel < minLevel)
1171 continue;
1172
1173 if (maxLevel && bucketData->RequiredLevel > maxLevel)
1174 continue;
1175
1176 if (!filters.HasFlag(bucketData->QualityMask))
1177 continue;
1178
1179 if (classFilters)
1180 {
1181 // if we dont want any class filters, Optional is not initialized
1182 // if we dont want this class included, SubclassMask is set to FILTER_SKIP_CLASS
1183 // if we want this class and did not specify and subclasses, its set to FILTER_SKIP_SUBCLASS
1184 // otherwise full restrictions apply
1185 if (classFilters->Classes[bucketData->ItemClass].SubclassMask == AuctionSearchClassFilters::FILTER_SKIP_CLASS)
1186 continue;
1187
1188 if (classFilters->Classes[bucketData->ItemClass].SubclassMask != AuctionSearchClassFilters::FILTER_SKIP_SUBCLASS)
1189 {
1190 if (!(classFilters->Classes[bucketData->ItemClass].SubclassMask & (1 << bucketData->ItemSubClass)))
1191 continue;
1192
1193 if (!(classFilters->Classes[bucketData->ItemClass].InvTypes[bucketData->ItemSubClass] & (UI64LIT(1) << bucketData->InventoryType)))
1194 continue;
1195 }
1196 }
1197
1199 {
1200 // appearances - by ItemAppearanceId, not ItemModifiedAppearanceId
1201 if (bucketData->InventoryType != INVTYPE_NON_EQUIP && bucketData->InventoryType != INVTYPE_BAG)
1202 {
1203 bool hasAll = true;
1204 for (std::pair<uint32, uint32> bucketAppearance : bucketData->ItemModifiedAppearanceId)
1205 {
1206 if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(bucketAppearance.first))
1207 {
1208 if (!knownAppearanceIds.contains(itemModifiedAppearance->ItemAppearanceID))
1209 {
1210 hasAll = false;
1211 break;
1212 }
1213 }
1214 }
1215
1216 if (hasAll)
1217 continue;
1218 }
1219 // caged pets
1220 else if (bucket.first.BattlePetSpeciesId)
1221 {
1222 if (knownPetSpecies.test(bucket.first.BattlePetSpeciesId))
1223 continue;
1224 }
1225 // toys
1226 else if (sDB2Manager.IsToyItem(bucket.first.ItemId))
1227 {
1228 if (player->GetSession()->GetCollectionMgr()->HasToy(bucket.first.ItemId))
1229 continue;
1230 }
1231 // mounts
1232 // recipes
1233 // pet items
1234 else if (bucketData->ItemClass == ITEM_CLASS_CONSUMABLE || bucketData->ItemClass == ITEM_CLASS_RECIPE || bucketData->ItemClass == ITEM_CLASS_MISCELLANEOUS)
1235 {
1236 ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(bucket.first.ItemId));
1237 if (itemTemplate->Effects.size() >= 2 && (itemTemplate->Effects[0]->SpellID == 483 || itemTemplate->Effects[0]->SpellID == 55884))
1238 {
1239 if (player->HasSpell(itemTemplate->Effects[1]->SpellID))
1240 continue;
1241
1242 if (BattlePetSpeciesEntry const* battlePetSpecies = BattlePets::BattlePetMgr::GetBattlePetSpeciesBySpell(itemTemplate->Effects[1]->SpellID))
1243 if (knownPetSpecies.test(battlePetSpecies->ID))
1244 continue;
1245 }
1246 }
1247 }
1248
1250 {
1251 if (bucketData->RequiredLevel && player->GetLevel() < bucketData->RequiredLevel)
1252 continue;
1253
1254 if (player->CanUseItem(sObjectMgr->GetItemTemplate(bucket.first.ItemId), true) != EQUIP_ERR_OK)
1255 continue;
1256
1257 // cannot learn caged pets whose level exceeds highest level of currently owned pet
1258 if (bucketData->MinBattlePetLevel && bucketData->MinBattlePetLevel > maxKnownPetLevel)
1259 continue;
1260 }
1261
1263 {
1264 ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(bucket.first.ItemId));
1265 if (itemTemplate->GetRequiredExpansion() != sWorld->getIntConfig(CONFIG_EXPANSION))
1266 continue;
1267 }
1268
1269 // TODO: this one needs to access loot history to know highest item level for every inventory type
1270 //if (filters.HasFlag(AuctionHouseFilterMask::UpgradesOnly))
1271 //{
1272 //}
1273
1274 builder.AddItem(bucketData);
1275 }
1276
1277 for (AuctionsBucketData const* resultBucket : builder.GetResultRange())
1278 {
1279 listBucketsResult.Buckets.emplace_back();
1280 WorldPackets::AuctionHouse::BucketInfo& bucketInfo = listBucketsResult.Buckets.back();
1281 resultBucket->BuildBucketInfo(&bucketInfo, player);
1282 }
1283
1284 listBucketsResult.HasMoreResults = builder.HasMoreResults();
1285}
1286
1288 std::span<WorldPackets::AuctionHouse::AuctionBucketKey const> keys,
1289 std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1290{
1291 std::vector<AuctionsBucketData const*> buckets;
1292 buckets.reserve(keys.size());
1293 for (WorldPackets::AuctionHouse::AuctionBucketKey const& key : keys)
1294 {
1295 auto bucketItr = _buckets.find(AuctionsBucketKey(key));
1296 if (bucketItr != _buckets.end())
1297 buckets.push_back(&bucketItr->second);
1298 }
1299
1300 AuctionsBucketData::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts);
1301 std::ranges::sort(buckets, std::cref(sorter));
1302
1303 for (AuctionsBucketData const* resultBucket : buckets)
1304 {
1305 listBucketsResult.Buckets.emplace_back();
1306 WorldPackets::AuctionHouse::BucketInfo& bucketInfo = listBucketsResult.Buckets.back();
1307 resultBucket->BuildBucketInfo(&bucketInfo, player);
1308 }
1309
1310 listBucketsResult.HasMoreResults = false;
1311}
1312
1314 uint32 /*offset*/, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1315{
1316 // always full list
1317 std::vector<AuctionPosting const*> auctions;
1318 for (auto const& auctionId : Trinity::Containers::MapEqualRange(_playerBidderAuctions, player->GetGUID()))
1319 if (AuctionPosting const* auction = Trinity::Containers::MapGetValuePtr(_itemsByAuctionId, auctionId.second))
1320 auctions.push_back(auction);
1321
1322 AuctionPosting::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts);
1323 std::ranges::sort(auctions, std::cref(sorter));
1324
1325 for (AuctionPosting const* resultAuction : auctions)
1326 {
1327 listBiddedItemsResult.Items.emplace_back();
1328 WorldPackets::AuctionHouse::AuctionItem& auctionItem = listBiddedItemsResult.Items.back();
1329 resultAuction->BuildAuctionItem(&auctionItem, true, true, true, false);
1330 }
1331
1332 listBiddedItemsResult.HasMoreResults = false;
1333}
1334
1336 uint32 offset, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1337{
1338 listItemsResult.TotalCount = 0;
1339 if (AuctionsBucketData const* bucket = Trinity::Containers::MapGetValuePtr(_buckets, bucketKey))
1340 {
1342
1343 for (AuctionPosting const* auction : bucket->Auctions)
1344 {
1345 builder.AddItem(auction);
1346 for (Item* item : auction->Items)
1347 listItemsResult.TotalCount += item->GetCount();
1348 }
1349
1350 for (AuctionPosting const* resultAuction : builder.GetResultRange())
1351 {
1352 listItemsResult.Items.emplace_back();
1353 WorldPackets::AuctionHouse::AuctionItem& auctionItem = listItemsResult.Items.back();
1354 resultAuction->BuildAuctionItem(&auctionItem, false, false, resultAuction->OwnerAccount != player->GetSession()->GetAccountGUID(),
1355 resultAuction->Bidder.IsEmpty());
1356 }
1357
1358 listItemsResult.HasMoreResults = builder.HasMoreResults();
1359 }
1360}
1361
1363 uint32 offset, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1364{
1366 auto itr = _buckets.lower_bound(AuctionsBucketKey(itemId, 0, 0, 0));
1367 auto end = _buckets.end();
1368 listItemsResult.TotalCount = 0;
1369 while (itr != end && itr->first.ItemId == itemId)
1370 {
1371 for (AuctionPosting const* auction : itr->second.Auctions)
1372 {
1373 builder.AddItem(auction);
1374 for (Item* item : auction->Items)
1375 listItemsResult.TotalCount += item->GetCount();
1376 }
1377
1378 ++itr;
1379 }
1380
1381 for (AuctionPosting const* resultAuction : builder.GetResultRange())
1382 {
1383 listItemsResult.Items.emplace_back();
1384 WorldPackets::AuctionHouse::AuctionItem& auctionItem = listItemsResult.Items.back();
1385 resultAuction->BuildAuctionItem(&auctionItem, false, true, resultAuction->OwnerAccount != player->GetSession()->GetAccountGUID(),
1386 resultAuction->Bidder.IsEmpty());
1387 }
1388
1389 listItemsResult.HasMoreResults = builder.HasMoreResults();
1390}
1391
1393 uint32 /*offset*/, std::span<WorldPackets::AuctionHouse::AuctionSortDef const> sorts) const
1394{
1395 // always full list
1396 std::vector<AuctionPosting const*> auctions;
1397 for (auto const& auctionId : Trinity::Containers::MapEqualRange(_playerOwnedAuctions, player->GetGUID()))
1398 if (AuctionPosting const* auction = Trinity::Containers::MapGetValuePtr(_itemsByAuctionId, auctionId.second))
1399 auctions.push_back(auction);
1400
1401 AuctionPosting::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts);
1402 std::ranges::sort(auctions, std::cref(sorter));
1403
1404 for (AuctionPosting const* resultAuction : auctions)
1405 {
1406 listOwnedItemsResult.Items.emplace_back();
1407 WorldPackets::AuctionHouse::AuctionItem& auctionItem = listOwnedItemsResult.Items.back();
1408 resultAuction->BuildAuctionItem(&auctionItem, true, true, false, false);
1409 }
1410
1411 listOwnedItemsResult.HasMoreResults = false;
1412}
1413
1415 uint32 global, uint32 cursor, uint32 tombstone, uint32 count)
1416{
1417 TimePoint curTime = GameTime::Now();
1418
1419 auto throttleItr = _replicateThrottleMap.find(player->GetGUID());
1420 if (throttleItr != _replicateThrottleMap.end())
1421 {
1422 if (throttleItr->second.Global != global || throttleItr->second.Cursor != cursor || throttleItr->second.Tombstone != tombstone)
1423 return;
1424
1425 if (!throttleItr->second.IsReplicationInProgress() && throttleItr->second.NextAllowedReplication > curTime)
1426 return;
1427 }
1428 else
1429 {
1430 throttleItr = _replicateThrottleMap.emplace(player->GetGUID(), PlayerReplicateThrottleData{}).first;
1431 throttleItr->second.NextAllowedReplication = curTime + Seconds(sWorld->getIntConfig(CONFIG_AUCTION_REPLICATE_DELAY));
1432 throttleItr->second.Global = sAuctionMgr->GenerateReplicationId();
1433 }
1434
1435 if (_itemsByAuctionId.empty() || !count)
1436 return;
1437
1438 auto itr = _itemsByAuctionId.upper_bound(cursor);
1439 for (; itr != _itemsByAuctionId.end(); ++itr)
1440 {
1441 AuctionPosting const& auction = itr->second;
1442
1443 replicateResponse.Items.emplace_back();
1444 WorldPackets::AuctionHouse::AuctionItem& auctionItem = replicateResponse.Items.back();
1445 auction.BuildAuctionItem(&auctionItem, false, true, true, auction.Bidder.IsEmpty());
1446 if (!--count)
1447 break;
1448 }
1449
1450 replicateResponse.ChangeNumberGlobal = throttleItr->second.Global;
1451 replicateResponse.ChangeNumberCursor = throttleItr->second.Cursor = !replicateResponse.Items.empty() ? replicateResponse.Items.back().AuctionID : 0;
1452 replicateResponse.ChangeNumberTombstone = throttleItr->second.Tombstone = !count ? _itemsByAuctionId.rbegin()->first : 0;
1453}
1454
1456{
1457 return std::max(int64(CalculatePct(bidAmount, _auctionHouse->ConsignmentRate) * double(sWorld->getRate(RATE_AUCTION_CUT))), SI64LIT(0));
1458}
1459
1461{
1462 ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId);
1463 if (!itemTemplate)
1464 return nullptr;
1465
1466 auto bucketItr = _buckets.find(AuctionsBucketKey::ForCommodity(itemTemplate));
1467 if (bucketItr == _buckets.end())
1468 return nullptr;
1469
1470 uint64 totalPrice = 0;
1471 uint32 remainingQuantity = quantity;
1472 for (AuctionPosting const* auction : bucketItr->second.Auctions)
1473 {
1474 if (auction->Owner == player->GetGUID() || auction->OwnerAccount == player->GetSession()->GetAccountGUID())
1475 continue;
1476
1477 for (Item* auctionItem : auction->Items)
1478 {
1479 if (auctionItem->GetCount() >= remainingQuantity)
1480 {
1481 totalPrice += auction->BuyoutOrUnitPrice * remainingQuantity;
1482 remainingQuantity = 0;
1483 break;
1484 }
1485
1486 totalPrice += auction->BuyoutOrUnitPrice * auctionItem->GetCount();
1487 remainingQuantity -= auctionItem->GetCount();
1488 }
1489 }
1490
1491 // not enough items on auction house
1492 if (remainingQuantity)
1493 return nullptr;
1494
1495 if (!player->HasEnoughMoney(totalPrice))
1496 return nullptr;
1497
1498 CommodityQuote* quote = &_commodityQuotes[player->GetGUID()];
1499 quote->TotalPrice = totalPrice;
1500 quote->Quantity = quantity;
1501 quote->ValidTo = GameTime::Now() + 30s;
1502 return quote;
1503}
1504
1509
1510bool AuctionHouseObject::BuyCommodity(CharacterDatabaseTransaction trans, Player* player, uint32 itemId, uint32 quantity, Milliseconds delayForNextAction)
1511{
1512 ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId);
1513 if (!itemTemplate)
1514 return false;
1515
1516 auto bucketItr = _buckets.find(AuctionsBucketKey::ForCommodity(itemTemplate));
1517 if (bucketItr == _buckets.end())
1518 {
1520 return false;
1521 }
1522
1523 auto quote = _commodityQuotes.extract(player->GetGUID());
1524 if (!quote)
1525 {
1527 return false;
1528 }
1529
1530 uint64 totalPrice = 0;
1531 uint32 remainingQuantity = quantity;
1532 std::vector<AuctionPosting*> auctions;
1533 for (auto auctionItr = bucketItr->second.Auctions.begin(); auctionItr != bucketItr->second.Auctions.end();)
1534 {
1535 AuctionPosting* auction = *auctionItr++;
1536 if (auction->Owner == player->GetGUID() || auction->OwnerAccount == player->GetSession()->GetAccountGUID())
1537 continue;
1538
1539 auctions.push_back(auction);
1540 for (Item* auctionItem : auction->Items)
1541 {
1542 if (auctionItem->GetCount() >= remainingQuantity)
1543 {
1544 totalPrice += auction->BuyoutOrUnitPrice * remainingQuantity;
1545 remainingQuantity = 0;
1546 auctionItr = bucketItr->second.Auctions.end();
1547 break;
1548 }
1549
1550 totalPrice += auction->BuyoutOrUnitPrice * auctionItem->GetCount();
1551 remainingQuantity -= auctionItem->GetCount();
1552 }
1553 }
1554
1555 // not enough items on auction house
1556 if (remainingQuantity)
1557 {
1559 return false;
1560 }
1561
1562 // something was bought between creating quote and finalizing transaction
1563 // but we allow lower price if new items were posted at lower price
1564 if (totalPrice > quote.mapped().TotalPrice)
1565 {
1567 return false;
1568 }
1569
1570 if (!player->HasEnoughMoney(totalPrice))
1571 {
1573 return false;
1574 }
1575
1576 Optional<ObjectGuid> uniqueSeller;
1577
1578 // prepare items
1579 struct MailedItemsBatch
1580 {
1581 std::array<Item*, MAX_MAIL_ITEMS> Items = { };
1582 uint64 TotalPrice = 0;
1583 uint32 Quantity = 0;
1584
1585 std::size_t ItemsCount = 0;
1586
1587 bool IsFull() const { return ItemsCount >= Items.size(); }
1588 void AddItem(Item* item, uint64 unitPrice)
1589 {
1590 Items[ItemsCount++] = item;
1591 Quantity += item->GetCount();
1592 TotalPrice += unitPrice * item->GetCount();
1593 }
1594 };
1595
1596 std::vector<MailedItemsBatch> items;
1597 items.emplace_back();
1598
1599 remainingQuantity = quantity;
1600 std::vector<std::size_t> removedItemsFromAuction;
1601
1602 for (auto auctionItr = bucketItr->second.Auctions.begin(); auctionItr != bucketItr->second.Auctions.end();)
1603 {
1604 AuctionPosting* auction = *auctionItr++;
1605 if (auction->Owner == player->GetGUID() || auction->OwnerAccount == player->GetSession()->GetAccountGUID())
1606 continue;
1607
1608 if (!uniqueSeller)
1609 uniqueSeller = auction->Owner;
1610 else if (*uniqueSeller != auction->Owner)
1611 uniqueSeller = ObjectGuid::Empty;
1612
1613 uint32 boughtFromAuction = 0;
1614 std::size_t removedItems = 0;
1615 for (Item* auctionItem : auction->Items)
1616 {
1617 MailedItemsBatch* itemsBatch = &items.back();
1618 if (itemsBatch->IsFull())
1619 {
1620 items.emplace_back();
1621 itemsBatch = &items.back();
1622 }
1623
1624 if (auctionItem->GetCount() > remainingQuantity)
1625 {
1626 Item* clonedItem = auctionItem->CloneItem(remainingQuantity, player);
1627 if (!clonedItem)
1628 {
1630 return false;
1631 }
1632
1633 auctionItem->SetCount(auctionItem->GetCount() - remainingQuantity);
1634 auctionItem->FSetState(ITEM_CHANGED);
1635 auctionItem->SaveToDB(trans);
1636 itemsBatch->AddItem(clonedItem, auction->BuyoutOrUnitPrice);
1637 boughtFromAuction += remainingQuantity;
1638 remainingQuantity = 0;
1639 auctionItr = bucketItr->second.Auctions.end();
1640 break;
1641 }
1642
1643 itemsBatch->AddItem(auctionItem, auction->BuyoutOrUnitPrice);
1644 boughtFromAuction += auctionItem->GetCount();
1645 remainingQuantity -= auctionItem->GetCount();
1646 ++removedItems;
1647 }
1648
1649 removedItemsFromAuction.push_back(removedItems);
1650
1652 {
1653 uint32 bidderAccId = player->GetSession()->GetAccountId();
1654 std::string ownerName;
1655 if (!sCharacterCache->GetCharacterNameByGuid(auction->Owner, ownerName))
1656 ownerName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
1657
1658 sLog->OutCommand(bidderAccId, "GM {} (Account: {}) bought commodity in auction: {} (Entry: {} Count: {}) and pay money: {}. Original owner {} (Account: {})",
1659 player->GetName(), bidderAccId, items[0].Items[0]->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()),
1660 items[0].Items[0]->GetEntry(), boughtFromAuction, auction->BuyoutOrUnitPrice * boughtFromAuction, ownerName,
1661 sCharacterCache->GetCharacterAccountIdByGuid(auction->Owner));
1662 }
1663
1664 uint64 auctionHouseCut = CalculateAuctionHouseCut(auction->BuyoutOrUnitPrice * boughtFromAuction);
1665 uint64 depositPart = AuctionHouseMgr::GetCommodityAuctionDeposit(items[0].Items[0]->GetTemplate(), std::chrono::duration_cast<Minutes>(auction->EndTime - auction->StartTime),
1666 boughtFromAuction);
1667 uint64 profit = auction->BuyoutOrUnitPrice * boughtFromAuction + depositPart - auctionHouseCut;
1668
1669 if (Player* owner = ObjectAccessor::FindConnectedPlayer(auction->Owner))
1670 {
1671 owner->UpdateCriteria(CriteriaType::MoneyEarnedFromAuctions, profit);
1672 owner->UpdateCriteria(CriteriaType::HighestAuctionSale, profit);
1673 owner->GetSession()->SendAuctionClosedNotification(auction, (float)sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), true);
1674 }
1675
1677 AuctionHouseMgr::BuildAuctionSoldMailBody(player->GetGUID(), auction->BuyoutOrUnitPrice * boughtFromAuction, boughtFromAuction, depositPart, auctionHouseCut))
1678 .AddMoney(profit)
1680 }
1681
1682 player->ModifyMoney(-int64(totalPrice));
1683 player->SaveInventoryAndGoldToDB(trans);
1684
1685 for (MailedItemsBatch const& batch : items)
1686 {
1688 AuctionHouseMgr::BuildAuctionWonMailBody(*uniqueSeller, batch.TotalPrice, batch.Quantity));
1689
1690 for (std::size_t i = 0; i < batch.ItemsCount; ++i)
1691 {
1693 stmt->setUInt64(0, batch.Items[i]->GetGUID().GetCounter());
1694 trans->Append(stmt);
1695
1696 batch.Items[i]->SetOwnerGUID(player->GetGUID());
1697 batch.Items[i]->SaveToDB(trans);
1698 mail.AddItem(batch.Items[i]);
1699 }
1700
1701 mail.SendMailTo(trans, player, this, MAIL_CHECK_MASK_COPIED);
1702 }
1703
1705 packet.Info.Initialize(_auctionHouse->ID, auctions[0], items[0].Items[0]);
1706 player->SendDirectMessage(packet.Write());
1707
1708 for (std::size_t i = 0; i < auctions.size(); ++i)
1709 {
1710 if (removedItemsFromAuction[i] == auctions[i]->Items.size())
1711 RemoveAuction(trans, auctions[i]); // bought all items
1712 else if (removedItemsFromAuction[i])
1713 {
1714 auto lastRemovedItem = auctions[i]->Items.begin() + removedItemsFromAuction[i];
1715 for (auto itr = auctions[i]->Items.begin(); itr != lastRemovedItem; ++itr)
1716 sAuctionMgr->RemoveAItem((*itr)->GetGUID());
1717
1718 auctions[i]->Items.erase(auctions[i]->Items.begin(), lastRemovedItem);
1719 }
1720 }
1721
1722 return true;
1723}
1724
1725// this function notified old bidder that his bid is no longer highest
1727{
1728 Player* oldBidder = ObjectAccessor::FindConnectedPlayer(auction->Bidder);
1729
1730 // old bidder exist
1731 if ((oldBidder || sCharacterCache->HasCharacterCacheEntry(auction->Bidder)) && !sAuctionBotConfig->IsBotChar(auction->Bidder))
1732 {
1733 if (oldBidder)
1734 {
1736 packet.BidAmount = newBidAmount;
1738 packet.Info.AuctionID = auction->Id;
1739 packet.Info.Bidder = newBidder;
1740 packet.Info.Item.Initialize(auction->Items[0]);
1741 oldBidder->SendDirectMessage(packet.Write());
1742 }
1743
1745 .AddMoney(auction->BidAmount)
1746 .SendMailTo(trans, MailReceiver(oldBidder, auction->Bidder), this, MAIL_CHECK_MASK_COPIED);
1747 }
1748}
1749
1751{
1752 uint32 bidderAccId = 0;
1753 if (!bidder)
1754 bidder = ObjectAccessor::FindConnectedPlayer(auction->Bidder); // try lookup bidder when called from ::Update
1755
1756 // data for gm.log
1757 std::string bidderName;
1758 bool logGmTrade = auction->ServerFlags.HasFlag(AuctionPostingServerFlag::GmLogBuyer);
1759
1760 if (bidder)
1761 {
1762 bidderAccId = bidder->GetSession()->GetAccountId();
1763 bidderName = bidder->GetName();
1764 }
1765 else
1766 {
1767 bidderAccId = sCharacterCache->GetCharacterAccountIdByGuid(auction->Bidder);
1768
1769 if (logGmTrade && !sCharacterCache->GetCharacterNameByGuid(auction->Bidder, bidderName))
1770 bidderName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
1771 }
1772
1773 if (logGmTrade)
1774 {
1775 std::string ownerName;
1776 if (!sCharacterCache->GetCharacterNameByGuid(auction->Owner, ownerName))
1777 ownerName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
1778
1779 uint32 ownerAccId = sCharacterCache->GetCharacterAccountIdByGuid(auction->Owner);
1780
1781 sLog->OutCommand(bidderAccId, "GM {} (Account: {}) won item in auction: {} (Entry: {} Count: {}) and pay money: {}. Original owner {} (Account: {})",
1782 bidderName, bidderAccId, auction->Items[0]->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()),
1783 auction->Items[0]->GetEntry(), auction->GetTotalItemCount(), auction->BidAmount, ownerName, ownerAccId);
1784 }
1785
1786 // receiver exist
1787 if ((bidder || bidderAccId) && !sAuctionBotConfig->IsBotChar(auction->Bidder))
1788 {
1791
1792 // set owner to bidder (to prevent delete item with sender char deleting)
1793 // owner in `data` will set at mail receive and item extracting
1794 for (Item* item : auction->Items)
1795 {
1797 stmt->setUInt64(0, auction->Bidder.GetCounter());
1798 stmt->setUInt64(1, item->GetGUID().GetCounter());
1799 trans->Append(stmt);
1800
1801 mail.AddItem(item);
1802 }
1803
1804 if (bidder)
1805 {
1807 packet.Info.Initialize(_auctionHouse->ID, auction, auction->Items[0]);
1808 bidder->SendDirectMessage(packet.Write());
1809
1810 // FIXME: for offline player need also
1812 }
1813
1814 mail.SendMailTo(trans, MailReceiver(bidder, auction->Bidder), this, MAIL_CHECK_MASK_COPIED);
1815 }
1816 else
1817 {
1818 // bidder doesn't exist, delete the item
1819 for (Item* item : auction->Items)
1820 {
1821 item->FSetState(ITEM_REMOVED);
1822 item->SaveToDB(trans);
1823 }
1824 }
1825}
1826
1827//call this method to send mail to auction owner, when auction is successful, it does not clear ram
1829{
1830 if (!owner)
1832
1833 // owner exist
1834 if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner))
1835 {
1836 uint64 auctionHouseCut = CalculateAuctionHouseCut(auction->BidAmount);
1837 uint64 profit = auction->BidAmount + auction->Deposit - auctionHouseCut;
1838
1839 //FIXME: what do if owner offline
1840 if (owner)
1841 {
1844 //send auction owner notification, bidder must be current!
1845 owner->GetSession()->SendAuctionClosedNotification(auction, (float)sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), true);
1846 }
1847
1849 AuctionHouseMgr::BuildAuctionSoldMailBody(auction->Bidder, auction->BidAmount, auction->BuyoutOrUnitPrice, auction->Deposit, auctionHouseCut))
1850 .AddMoney(profit)
1851 .SendMailTo(trans, MailReceiver(owner, auction->Owner), this, MAIL_CHECK_MASK_COPIED, sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY));
1852 }
1853}
1854
1856{
1858 // owner exist
1859 if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner))
1860 {
1861 if (owner)
1862 owner->GetSession()->SendAuctionClosedNotification(auction, 0.0f, false);
1863
1864 auto itemItr = auction->Items.begin();
1865 while (itemItr != auction->Items.end())
1866 {
1868
1869 for (std::size_t i = 0; i < MAX_MAIL_ITEMS && itemItr != auction->Items.end(); ++i, ++itemItr)
1870 mail.AddItem(*itemItr);
1871
1872 mail.SendMailTo(trans, MailReceiver(owner, auction->Owner), this, MAIL_CHECK_MASK_COPIED, 0);
1873 }
1874 }
1875 else
1876 {
1877 // owner doesn't exist, delete the item
1878 for (Item* item : auction->Items)
1879 {
1880 item->FSetState(ITEM_REMOVED);
1881 item->SaveToDB(trans);
1882 }
1883 }
1884}
1885
1887{
1888 auto itemItr = auction->Items.begin();
1889 while (itemItr != auction->Items.end())
1890 {
1892
1893 for (std::size_t i = 0; i < MAX_MAIL_ITEMS && itemItr != auction->Items.end(); ++i, ++itemItr)
1894 draft.AddItem(*itemItr);
1895
1896 draft.SendMailTo(trans, owner, this, MAIL_CHECK_MASK_COPIED);
1897 }
1898}
1899
1900//this function sends mail, when auction is cancelled to old bidder
1902{
1904
1905 // bidder exist
1906 if ((bidder || sCharacterCache->HasCharacterCacheEntry(auction->Bidder)) && !sAuctionBotConfig->IsBotChar(auction->Bidder))
1908 .AddMoney(auction->BidAmount)
1909 .SendMailTo(trans, MailReceiver(bidder, auction->Bidder), this, MAIL_CHECK_MASK_COPIED);
1910}
1911
1913{
1914 if (!owner)
1916
1917 // owner exist (online or offline)
1918 if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner))
1919 {
1921 eta += Seconds(sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY));
1922 if (owner)
1923 eta += owner->GetSession()->GetTimezoneOffset();
1924
1928 .SendMailTo(trans, MailReceiver(owner, auction->Owner), this, MAIL_CHECK_MASK_COPIED);
1929 }
1930}
#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:569
@ FACTION_MASK_ALLIANCE
Definition DBCEnums.h:1009
@ FACTION_MASK_HORDE
Definition DBCEnums.h:1010
ItemContext
Definition DBCEnums.h:1315
@ 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:85
#define ASSERT_NOTNULL(pointer)
Definition Errors.h:82
#define ASSERT
Definition Errors.h:80
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:190
#define TC_LOG_INFO(filterType__, message__,...)
Definition Log.h:184
#define sLog
Definition Log.h:156
#define TC_LOG_WARN(filterType__, message__,...)
Definition Log.h:187
@ MAIL_CHECK_MASK_COPIED
This mail was returned. Do not allow returning mail back again.
Definition Mail.h:55
#define MAX_MAIL_ITEMS
Definition Mail.h:35
#define sObjectMgr
Definition ObjectMgr.h:1885
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
ObjectGuid const & GetGUID() const
Definition BaseEntity.h:163
static BattlePetSpeciesEntry const * GetBattlePetSpeciesBySpell(uint32 spellId)
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:179
Item * CloneItem(uint32 count, Player const *player=nullptr) const
Definition Item.cpp:1751
ItemTemplate const * GetTemplate() const
Definition Item.cpp:1233
BonusData const * GetBonus() const
Definition Item.h:195
uint32 GetItemLevel(Player const *owner) const
Definition Item.cpp:2265
uint32 GetSellPrice(Player const *owner) const
Definition Item.cpp:2234
virtual bool LoadFromDB(ObjectGuid::LowType guid, ObjectGuid ownerGuid, Field *fields, uint32 entry)
Definition Item.cpp:907
uint32 GetCount() const
Definition Item.h:283
uint32 GetModifier(ItemModifier modifier) const
Definition Item.cpp:2470
int32 GetRequiredLevel() const
Definition Item.cpp:2864
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:145
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:24850
void SendDirectMessage(WorldPacket const *data) const
Definition Player.cpp:6283
void SaveInventoryAndGoldToDB(CharacterDatabaseTransaction trans)
Definition Player.cpp:20843
WorldSession * GetSession() const
Definition Player.h:2272
void UpdateCriteria(CriteriaType type, uint64 miscValue1=0, uint64 miscValue2=0, uint64 miscValue3=0, WorldObject *ref=nullptr)
Definition Player.cpp:27588
bool HasSpell(uint32 spell) const override
Definition Player.cpp:3735
bool HasEnoughMoney(uint64 amount) const
Definition Player.h:1907
InventoryResult CanUseItem(Item *pItem, bool not_loading=true) const
Definition Player.cpp:11214
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:757
std::string const & GetName() const
Definition Object.h:342
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:916
@ CONFIG_AUCTION_TAINTED_SEARCH_DELAY
Definition World.h:416
@ CONFIG_MAIL_DELIVERY_DELAY
Definition World.h:294
@ CONFIG_AUCTION_REPLICATE_DELAY
Definition World.h:414
@ CONFIG_EXPANSION
Definition World.h:306
@ CONFIG_AUCTION_SEARCH_DELAY
Definition World.h:415
@ RATE_AUCTION_CUT
Definition World.h:514
@ 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)