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