/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "AuctionHouseBot.h" #include "AuctionHouseMgr.h" #include "AuctionHousePackets.h" #include "AccountMgr.h" #include "Bag.h" #include "DB2Stores.h" #include "CharacterCache.h" #include "CollectionMgr.h" #include "Common.h" #include "Containers.h" #include "DatabaseEnv.h" #include "GameTime.h" #include "Language.h" #include "Log.h" #include "Mail.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Player.h" #include "Realm.h" #include "ScriptMgr.h" #include "SpellMgr.h" #include "World.h" #include "WorldPacket.h" #include "WorldSession.h" #include #include #include #include enum eAuctionHouse { AH_MINIMUM_DEPOSIT = 100 }; AuctionsBucketKey::AuctionsBucketKey(WorldPackets::AuctionHouse::AuctionBucketKey const& key) : ItemId(key.ItemID), ItemLevel(key.ItemLevel), BattlePetSpeciesId(key.BattlePetSpeciesID.value_or(0)), SuffixItemNameDescriptionId(key.SuffixItemNameDescriptionID.value_or(0)) { } std::size_t AuctionsBucketKey::Hash(AuctionsBucketKey const& key) { std::size_t hashVal = 0; Trinity::hash_combine(hashVal, std::hash()(key.ItemId)); Trinity::hash_combine(hashVal, std::hash()(key.ItemLevel)); Trinity::hash_combine(hashVal, std::hash()(key.BattlePetSpeciesId)); Trinity::hash_combine(hashVal, std::hash()(key.SuffixItemNameDescriptionId)); return hashVal; } AuctionsBucketKey AuctionsBucketKey::ForItem(Item* item) { ItemTemplate const* itemTemplate = item->GetTemplate(); if (itemTemplate->GetMaxStackSize() == 1) { return { item->GetEntry(), uint16(Item::GetItemLevel(itemTemplate, *item->GetBonus(), 0, item->GetRequiredLevel(), 0, 0, 0, false, 0)), uint16(item->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID)), uint16(item->GetBonus()->Suffix) }; } else return ForCommodity(itemTemplate); } AuctionsBucketKey AuctionsBucketKey::ForCommodity(ItemTemplate const* itemTemplate) { return { itemTemplate->GetId(), uint16(itemTemplate->GetBaseItemLevel()), 0, 0 }; } bool operator<(AuctionsBucketKey const& left, AuctionsBucketKey const& right) { if (left.ItemId < right.ItemId) return true; if (left.ItemId > right.ItemId) return false; if (left.ItemLevel < right.ItemLevel) return true; if (left.ItemLevel > right.ItemLevel) return false; if (left.BattlePetSpeciesId < right.BattlePetSpeciesId) return true; if (left.BattlePetSpeciesId > right.BattlePetSpeciesId) return false; if (left.SuffixItemNameDescriptionId < right.SuffixItemNameDescriptionId) return true; //if (left.SuffixItemNameDescriptionId > right.SuffixItemNameDescriptionId) // return false; // everything equal return false; } void AuctionsBucketData::BuildBucketInfo(WorldPackets::AuctionHouse::BucketInfo* bucketInfo, Player* player) const { bucketInfo->Key = Key; bucketInfo->MinPrice = MinPrice; bucketInfo->RequiredLevel = RequiredLevel; bucketInfo->TotalQuantity = 0; for (AuctionPosting const* auction : Auctions) { for (Item* item : auction->Items) { bucketInfo->TotalQuantity += item->GetCount(); if (Key.BattlePetSpeciesId) { uint32 breedData = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA); uint32 breedId = breedData & 0xFFFFFF; uint8 quality = uint8((breedData >> 24) & 0xFF); uint8 level = uint8(item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL)); bucketInfo->MaxBattlePetQuality = bucketInfo->MaxBattlePetQuality ? std::max(*bucketInfo->MaxBattlePetQuality, quality) : quality; bucketInfo->MaxBattlePetLevel = bucketInfo->MaxBattlePetLevel ? std::max(*bucketInfo->MaxBattlePetLevel, level) : level; bucketInfo->BattlePetBreedID = breedId; } } bucketInfo->ContainsOwnerItem = bucketInfo->ContainsOwnerItem || auction->Owner == player->GetGUID(); } bucketInfo->ContainsOnlyCollectedAppearances = true; for (std::pair appearance : ItemModifiedAppearanceId) { if (appearance.first) { bucketInfo->ItemModifiedAppearanceIDs.push_back(appearance.first); if (!player->GetSession()->GetCollectionMgr()->HasItemAppearance(appearance.first).first) bucketInfo->ContainsOnlyCollectedAppearances = false; } } } bool AuctionPosting::IsCommodity() const { return Items.size() > 1 || Items[0]->GetTemplate()->GetMaxStackSize() > 1; } uint32 AuctionPosting::GetTotalItemCount() const { return std::accumulate(Items.begin(), Items.end(), 0u, [](uint32 totalCount, Item* item) { return totalCount + item->GetCount(); }); } void AuctionPosting::BuildAuctionItem(WorldPackets::AuctionHouse::AuctionItem* auctionItem, bool alwaysSendItem, bool sendKey, bool censorServerInfo, bool censorBidInfo) const { // 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) //auctionItem->Item - here to unify comment // all (not optional<>) auctionItem->Count = int32(GetTotalItemCount()); auctionItem->Flags = Items[0]->m_itemData->DynamicFlags; auctionItem->AuctionID = Id; auctionItem->Owner = Owner; // prices set when filled if (IsCommodity()) { if (alwaysSendItem) { auctionItem->Item.emplace(); auctionItem->Item->Initialize(Items[0]); } auctionItem->UnitPrice = BuyoutOrUnitPrice; } else { auctionItem->Item.emplace(); auctionItem->Item->Initialize(Items[0]); auctionItem->Charges = std::max({ Items[0]->GetSpellCharges(0), Items[0]->GetSpellCharges(1), Items[0]->GetSpellCharges(2), Items[0]->GetSpellCharges(3), Items[0]->GetSpellCharges(4) }); for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; i++) { uint32 enchantId = Items[0]->GetEnchantmentId(EnchantmentSlot(i)); if (!enchantId) continue; auctionItem->Enchantments.emplace_back(enchantId, Items[0]->GetEnchantmentDuration(EnchantmentSlot(i)), Items[0]->GetEnchantmentCharges(EnchantmentSlot(i)), i); } for (uint8 i = 0; i < Items[0]->m_itemData->Gems.size(); ++i) { UF::SocketedGem const* gemData = &Items[0]->m_itemData->Gems[i]; if (gemData->ItemID) { WorldPackets::Item::ItemGemData gem; gem.Slot = i; gem.Item.Initialize(gemData); auctionItem->Gems.push_back(gem); } } if (MinBid) auctionItem->MinBid = MinBid; if (uint64 minIncrement = CalculateMinIncrement()) auctionItem->MinIncrement = minIncrement; if (BuyoutOrUnitPrice) auctionItem->BuyoutPrice = BuyoutOrUnitPrice; } // all (not optional<>) auctionItem->DurationLeft = uint32(std::max(std::chrono::duration_cast(EndTime - GameTime::GetGameTimeSystemPoint()).count(), Milliseconds::zero().count())); auctionItem->DeleteReason = 0; // SMSG_AUCTION_LIST_ITEMS_RESULT (only if owned) auctionItem->CensorServerSideInfo = censorServerInfo; auctionItem->ItemGuid = IsCommodity() ? ObjectGuid::Empty : Items[0]->GetGUID(); auctionItem->OwnerAccountID = OwnerAccount; auctionItem->EndTime = uint32(std::chrono::system_clock::to_time_t(EndTime)); // 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) auctionItem->CensorBidInfo = censorBidInfo; if (!Bidder.IsEmpty()) { auctionItem->Bidder = Bidder; auctionItem->BidAmount = BidAmount; } // SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT, SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT, SMSG_AUCTION_REPLICATE_RESPONSE (if commodity) if (sendKey) auctionItem->AuctionBucketKey.emplace(AuctionsBucketKey::ForItem(Items[0])); // all if (!Items[0]->m_itemData->Creator->IsEmpty()) auctionItem->Creator = *Items[0]->m_itemData->Creator; } uint64 AuctionPosting::CalculateMinIncrement(uint64 bidAmount) { return CalculatePct(bidAmount / SILVER /*ignore copper*/, 5) * SILVER; } class AuctionsBucketData::Sorter { public: Sorter(LocaleConstant locale, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) : _locale(locale), _sorts(sorts), _sortCount(sortCount) { } bool operator()(AuctionsBucketData const* left, AuctionsBucketData const* right) const { for (std::size_t i = 0; i < _sortCount; ++i) { int64 ordering = CompareColumns(_sorts[i].SortOrder, left, right); if (ordering != 0) return (ordering < 0) == !_sorts[i].ReverseSort; } return left->Key < right->Key; } private: int64 CompareColumns(AuctionHouseSortOrder column, AuctionsBucketData const* left, AuctionsBucketData const* right) const { switch (column) { case AuctionHouseSortOrder::Price: case AuctionHouseSortOrder::Bid: case AuctionHouseSortOrder::Buyout: return int64(left->MinPrice) - int64(right->MinPrice); case AuctionHouseSortOrder::Name: return left->FullName[_locale].compare(right->FullName[_locale]); case AuctionHouseSortOrder::Level: return int32(left->SortLevel) - int32(right->SortLevel); default: break; } return 0; } LocaleConstant _locale; WorldPackets::AuctionHouse::AuctionSortDef const* _sorts; std::size_t _sortCount; }; class AuctionPosting::Sorter { public: Sorter(LocaleConstant locale, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) : _locale(locale), _sorts(sorts), _sortCount(sortCount) { } bool operator()(AuctionPosting const* left, AuctionPosting const* right) const { for (std::size_t i = 0; i < _sortCount; ++i) { int64 ordering = CompareColumns(_sorts[i].SortOrder, left, right); if (ordering != 0) return (ordering < 0) == !_sorts[i].ReverseSort; } // Auctions are processed in LIFO order if (left->StartTime != right->StartTime) return left->StartTime > right->StartTime; return left->Id > right->Id; } private: int64 CompareColumns(AuctionHouseSortOrder column, AuctionPosting const* left, AuctionPosting const* right) const { switch (column) { case AuctionHouseSortOrder::Price: { int64 leftPrice = left->BuyoutOrUnitPrice ? left->BuyoutOrUnitPrice : (left->BidAmount ? left->BidAmount : left->MinBid); int64 rightPrice = right->BuyoutOrUnitPrice ? right->BuyoutOrUnitPrice : (right->BidAmount ? right->BidAmount : right->MinBid); return leftPrice - rightPrice; } case AuctionHouseSortOrder::Name: return left->Bucket->FullName[_locale].compare(right->Bucket->FullName[_locale]); case AuctionHouseSortOrder::Level: { int32 leftLevel = !left->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID) ? left->Bucket->SortLevel : left->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL); int32 rightLevel = !right->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID) ? right->Bucket->SortLevel : right->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL); return leftLevel - rightLevel; } case AuctionHouseSortOrder::Bid: return int64(left->BidAmount) - int64(right->BidAmount); case AuctionHouseSortOrder::Buyout: return int64(left->BuyoutOrUnitPrice) - int64(right->BuyoutOrUnitPrice); default: break; } return 0; } LocaleConstant _locale; WorldPackets::AuctionHouse::AuctionSortDef const* _sorts; std::size_t _sortCount; }; template class AuctionsResultBuilder { public: using Sorter = typename T::Sorter; AuctionsResultBuilder(uint32 offset, LocaleConstant locale, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount, AuctionHouseResultLimits maxResults) : _offset(offset), _sorter(locale, sorts, sortCount), _maxResults(AsUnderlyingType(maxResults)), _hasMoreResults(false) { _items.reserve(_maxResults + offset + 1); } void AddItem(T const* item) { auto where = std::lower_bound(_items.begin(), _items.end(), item, std::cref(_sorter)); _items.insert(where, item); if (_items.size() > _maxResults + _offset) { _items.pop_back(); _hasMoreResults = true; } } Trinity::IteratorPair::const_iterator> GetResultRange() const { return std::make_pair(_items.begin() + _offset, _items.end()); } bool HasMoreResults() const { return _hasMoreResults; } private: uint32 _offset; Sorter _sorter; std::size_t _maxResults; std::vector _items; bool _hasMoreResults; }; AuctionHouseMgr::AuctionHouseMgr() : mHordeAuctions(6), mAllianceAuctions(2), mNeutralAuctions(1), mGoblinAuctions(7), _replicateIdGenerator(0) { _playerThrottleObjectsCleanupTime = GameTime::GetGameTimeSteadyPoint() + Hours(1); } AuctionHouseMgr::~AuctionHouseMgr() { for (std::pair& itemPair : _itemsByGuid) delete itemPair.second; } AuctionHouseMgr* AuctionHouseMgr::instance() { static AuctionHouseMgr instance; return &instance; } AuctionHouseObject* AuctionHouseMgr::GetAuctionsMap(uint32 factionTemplateId) { if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) return &mNeutralAuctions; // teams have linked auction houses FactionTemplateEntry const* uEntry = sFactionTemplateStore.LookupEntry(factionTemplateId); if (!uEntry) return &mNeutralAuctions; else if (uEntry->FactionGroup & FACTION_MASK_ALLIANCE) return &mAllianceAuctions; else if (uEntry->FactionGroup & FACTION_MASK_HORDE) return &mHordeAuctions; else return &mNeutralAuctions; } AuctionHouseObject* AuctionHouseMgr::GetAuctionsById(uint32 auctionHouseId) { switch (auctionHouseId) { case 1: return &mNeutralAuctions; case 2: return &mAllianceAuctions; case 6: return &mHordeAuctions; case 7: return &mGoblinAuctions; default: break; } return &mNeutralAuctions; } Item* AuctionHouseMgr::GetAItem(ObjectGuid itemGuid) { return Trinity::Containers::MapGetValuePtr(_itemsByGuid, itemGuid); } uint64 AuctionHouseMgr::GetCommodityAuctionDeposit(ItemTemplate const* item, Minutes time, uint32 quantity) { uint32 sellPrice = item->GetSellPrice(); return uint64(std::ceil(std::floor(fmax(0.15 * quantity * sellPrice, 100.0)) / SILVER) * SILVER) * (time.count() / (MIN_AUCTION_TIME / MINUTE)); } uint64 AuctionHouseMgr::GetItemAuctionDeposit(Player* player, Item* item, Minutes time) { uint32 sellPrice = item->GetSellPrice(player); return uint64(std::ceil(std::floor(fmax(sellPrice * 0.15, 100.0)) / SILVER) * SILVER) * (time.count() / (MIN_AUCTION_TIME / MINUTE)); } std::string AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType type, AuctionPosting const* auction) { return BuildAuctionMailSubject(auction->Items[0]->GetEntry(), type, auction->Id, auction->GetTotalItemCount(), auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID), auction->Items[0]->GetContext(), *auction->Items[0]->m_itemData->BonusListIDs); } std::string AuctionHouseMgr::BuildCommodityAuctionMailSubject(AuctionMailType type, uint32 itemId, uint32 itemCount) { return BuildAuctionMailSubject(itemId, type, 0, itemCount, 0, ItemContext::NONE, {}); } std::string AuctionHouseMgr::BuildAuctionMailSubject(uint32 itemId, AuctionMailType type, uint32 auctionId, uint32 itemCount, uint32 battlePetSpeciesId, ItemContext context, std::vector const& bonusListIds) { std::ostringstream strm; strm << itemId << ':' << "0:" // OLD: itemRandomPropertiesId << AsUnderlyingType(type) << ':' << auctionId << ':' << itemCount << ':' << battlePetSpeciesId << ':' << "0:" << "0:" << "0:" << "0:" << uint32(context) << ':' << bonusListIds.size(); for (int32 bonusListId : bonusListIds) strm << ':' << bonusListId; return strm.str(); } std::string AuctionHouseMgr::BuildAuctionWonMailBody(ObjectGuid guid, uint64 bid, uint64 buyout) { return Trinity::StringFormat("%s:" UI64FMTD ":" UI64FMTD ":0", guid.ToString().c_str(), bid, buyout); } std::string AuctionHouseMgr::BuildAuctionSoldMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment) { return Trinity::StringFormat("%s:" UI64FMTD ":" UI64FMTD ":%u:" UI64FMTD ":0", guid.ToString().c_str(), bid, buyout, deposit, consignment); } std::string AuctionHouseMgr::BuildAuctionInvoiceMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment, uint32 moneyDelay, uint32 eta) { return Trinity::StringFormat("%s:" UI64FMTD ":" UI64FMTD ":%u:" UI64FMTD ":%u:%u:0", guid.ToString().c_str(), bid, buyout, deposit, consignment, moneyDelay, eta); } void AuctionHouseMgr::LoadAuctions() { uint32 oldMSTime = getMSTime(); // need to clear in case we are reloading if (!_itemsByGuid.empty()) { for (std::pair& itemPair : _itemsByGuid) delete itemPair.second; _itemsByGuid.clear(); } // data needs to be at first place for Item::LoadFromDB uint32 count = 0; std::unordered_map> itemsByAuction; std::unordered_map biddersByAuction; if (PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTION_ITEMS))) { do { Field* fields = result->Fetch(); ObjectGuid::LowType itemGuid = fields[0].GetUInt64(); uint32 itemEntry = fields[1].GetUInt32(); ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry); if (!proto) { TC_LOG_ERROR("misc", "AuctionHouseMgr::LoadAuctionItems: Unknown item (GUID: " UI64FMTD " item entry: #%u) in auction, skipped.", itemGuid, itemEntry); continue; } Item* item = NewItemOrBag(proto); if (!item->LoadFromDB(itemGuid, ObjectGuid::Create(fields[51].GetUInt64()), fields, itemEntry)) { delete item; continue; } uint32 auctionId = fields[52].GetUInt32(); itemsByAuction[auctionId].push_back(item); ++count; } while (result->NextRow()); } TC_LOG_INFO("server.loading", ">> Loaded %u auction items in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); oldMSTime = getMSTime(); count = 0; if (PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTION_BIDDERS))) { do { Field* fields = result->Fetch(); biddersByAuction[fields[0].GetUInt32()].insert(ObjectGuid::Create(fields[1].GetUInt64())); } while (result->NextRow()); } TC_LOG_INFO("server.loading", ">> Loaded %u auction bidders in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); oldMSTime = getMSTime(); count = 0; if (PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTIONS))) { CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); do { Field* fields = result->Fetch(); AuctionPosting auction; auction.Id = fields[0].GetUInt32(); uint32 auctionHouseId = fields[1].GetUInt32(); AuctionHouseObject* auctionHouse = GetAuctionsById(auctionHouseId); if (!auctionHouse) { TC_LOG_ERROR("misc", "Auction %u has wrong auctionHouseId %u", auction.Id, auctionHouseId); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION); stmt->setUInt32(0, auction.Id); trans->Append(stmt); continue; } auto itemsItr = itemsByAuction.find(auction.Id); if (itemsItr == itemsByAuction.end()) { TC_LOG_ERROR("misc", "Auction %u has no items", auction.Id); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION); stmt->setUInt32(0, auction.Id); trans->Append(stmt); continue; } auction.Items = std::move(itemsItr->second); auction.Owner = ObjectGuid::Create(fields[2].GetUInt64()); auction.OwnerAccount = ObjectGuid::Create(sCharacterCache->GetCharacterAccountIdByGuid(auction.Owner)); if (uint64 bidder = fields[3].GetUInt64()) auction.Bidder = ObjectGuid::Create(bidder); auction.MinBid = fields[4].GetUInt64(); auction.BuyoutOrUnitPrice = fields[5].GetUInt64(); auction.Deposit = fields[6].GetUInt64(); auction.BidAmount = fields[7].GetUInt64(); auction.StartTime = std::chrono::system_clock::from_time_t(fields[8].GetInt64()); auction.EndTime = std::chrono::system_clock::from_time_t(fields[9].GetInt64()); auto biddersItr = biddersByAuction.find(auction.Id); if (biddersItr != biddersByAuction.end()) auction.BidderHistory = std::move(biddersItr->second); auctionHouse->AddAuction(nullptr, std::move(auction)); ++count; } while (result->NextRow()); CharacterDatabase.CommitTransaction(trans); } TC_LOG_INFO("server.loading", ">> Loaded %u auctions in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } void AuctionHouseMgr::AddAItem(Item* item) { ASSERT(item); ASSERT(_itemsByGuid.count(item->GetGUID()) == 0); _itemsByGuid[item->GetGUID()] = item; } bool AuctionHouseMgr::RemoveAItem(ObjectGuid id, bool deleteItem /*= false*/, CharacterDatabaseTransaction* trans /*= nullptr*/) { auto i = _itemsByGuid.find(id); if (i == _itemsByGuid.end()) return false; if (deleteItem) { ASSERT(trans); i->second->FSetState(ITEM_REMOVED); i->second->SaveToDB(*trans); } _itemsByGuid.erase(i); return true; } bool AuctionHouseMgr::PendingAuctionAdd(Player* player, uint32 auctionHouseId, uint32 auctionId, uint64 deposit) { auto itr = _pendingAuctionsByPlayer.find(player->GetGUID()); if (itr != _pendingAuctionsByPlayer.end()) { // Get deposit so far uint64 totalDeposit = 0; for (PendingAuctionInfo const& thisAuction : itr->second.Auctions) totalDeposit += thisAuction.Deposit; // Add this deposit totalDeposit += deposit; if (!player->HasEnoughMoney(totalDeposit)) return false; } else itr = _pendingAuctionsByPlayer.emplace(std::piecewise_construct, std::forward_as_tuple(player->GetGUID()), std::forward_as_tuple()).first; itr->second.Auctions.push_back({ auctionId, auctionHouseId, deposit }); return true; } std::size_t AuctionHouseMgr::PendingAuctionCount(Player const* player) const { auto itr = _pendingAuctionsByPlayer.find(player->GetGUID()); if (itr != _pendingAuctionsByPlayer.end()) return itr->second.Auctions.size(); return 0; } void AuctionHouseMgr::PendingAuctionProcess(Player* player) { auto iterMap = _pendingAuctionsByPlayer.find(player->GetGUID()); if (iterMap == _pendingAuctionsByPlayer.end()) return; uint64 totaldeposit = 0; auto itrAH = iterMap->second.Auctions.begin(); for (; itrAH != iterMap->second.Auctions.end(); ++itrAH) { if (!player->HasEnoughMoney(totaldeposit + itrAH->Deposit)) break; totaldeposit += itrAH->Deposit; } // expire auctions we cannot afford if (itrAH != iterMap->second.Auctions.end()) { CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); do { PendingAuctionInfo const& pendingAuction = *itrAH; if (AuctionPosting* auction = GetAuctionsById(pendingAuction.AuctionHouseId)->GetAuction(pendingAuction.AuctionId)) auction->EndTime = GameTime::GetGameTimeSystemPoint(); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_EXPIRATION); stmt->setUInt32(0, uint32(GameTime::GetGameTime())); stmt->setUInt32(1, pendingAuction.AuctionId); trans->Append(stmt); ++itrAH; } while (itrAH != iterMap->second.Auctions.end()); CharacterDatabase.CommitTransaction(trans); } _pendingAuctionsByPlayer.erase(player->GetGUID()); player->ModifyMoney(-int64(totaldeposit)); } void AuctionHouseMgr::UpdatePendingAuctions() { for (auto itr = _pendingAuctionsByPlayer.begin(); itr != _pendingAuctionsByPlayer.end();) { ObjectGuid playerGUID = itr->first; if (Player* player = ObjectAccessor::FindConnectedPlayer(playerGUID)) { // Check if there were auctions since last update process if not if (PendingAuctionCount(player) == itr->second.LastAuctionsSize) { ++itr; PendingAuctionProcess(player); } else { ++itr; _pendingAuctionsByPlayer[playerGUID].LastAuctionsSize = PendingAuctionCount(player); } } else { // Expire any auctions that we couldn't get a deposit for TC_LOG_WARN("auctionHouse", "Player %s was offline, unable to retrieve deposit!", playerGUID.ToString().c_str()); ++itr; CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); for (PendingAuctionInfo const& pendingAuction : itr->second.Auctions) { if (AuctionPosting* auction = GetAuctionsById(pendingAuction.AuctionHouseId)->GetAuction(pendingAuction.AuctionId)) auction->EndTime = GameTime::GetGameTimeSystemPoint(); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_EXPIRATION); stmt->setUInt32(0, uint32(GameTime::GetGameTime())); stmt->setUInt32(1, pendingAuction.AuctionId); trans->Append(stmt); } CharacterDatabase.CommitTransaction(trans); _pendingAuctionsByPlayer.erase(playerGUID); } } } void AuctionHouseMgr::Update() { mHordeAuctions.Update(); mAllianceAuctions.Update(); mNeutralAuctions.Update(); mGoblinAuctions.Update(); std::chrono::steady_clock::time_point now = GameTime::GetGameTimeSteadyPoint(); if (now >= _playerThrottleObjectsCleanupTime) { for (auto itr = _playerThrottleObjects.begin(); itr != _playerThrottleObjects.end();) { if (itr->second.PeriodEnd < now) itr = _playerThrottleObjects.erase(itr); else ++itr; } _playerThrottleObjectsCleanupTime = now + 1h; } } uint32 AuctionHouseMgr::GenerateReplicationId() { return ++_replicateIdGenerator; } AuctionThrottleResult AuctionHouseMgr::CheckThrottle(Player* player, bool addonTainted, AuctionCommand command /*= AuctionCommand::SellItem*/) { std::chrono::steady_clock::time_point now = GameTime::GetGameTimeSteadyPoint(); auto itr = _playerThrottleObjects.emplace(std::piecewise_construct, std::forward_as_tuple(player->GetGUID()), std::forward_as_tuple()); if (itr.second || now > itr.first->second.PeriodEnd) { itr.first->second.PeriodEnd = now + Minutes(1); itr.first->second.QueriesRemaining = 100; } if (!itr.first->second.QueriesRemaining) { player->GetSession()->SendAuctionCommandResult(0, command, AuctionResult::AuctionHouseBusy, std::chrono::duration_cast(itr.first->second.PeriodEnd - now)); return { {}, true }; } if (!--itr.first->second.QueriesRemaining) return { std::chrono::duration_cast(itr.first->second.PeriodEnd - now), false }; else return { Milliseconds(sWorld->getIntConfig(addonTainted ? CONFIG_AUCTION_TAINTED_SEARCH_DELAY : CONFIG_AUCTION_SEARCH_DELAY)), false }; } AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntry(uint32 factionTemplateId, uint32* houseId) { uint32 houseid = 1; // Auction House if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) { // FIXME: found way for proper auctionhouse selection by another way // AuctionHouse.dbc have faction field with _player_ factions associated with auction house races. // but no easy way convert creature faction to player race faction for specific city switch (factionTemplateId) { case 120: houseid = 7; break; // booty bay, Blackwater Auction House case 474: houseid = 7; break; // gadgetzan, Blackwater Auction House case 855: houseid = 7; break; // everlook, Blackwater Auction House default: // default { FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(factionTemplateId); if (!u_entry) houseid = 1; // Auction House else if (u_entry->FactionGroup & FACTION_MASK_ALLIANCE) houseid = 2; // Alliance Auction House else if (u_entry->FactionGroup & FACTION_MASK_HORDE) houseid = 6; // Horde Auction House else houseid = 1; // Auction House break; } } } if (houseId) *houseId = houseid; return sAuctionHouseStore.LookupEntry(houseid); } AuctionHouseObject::AuctionHouseObject(uint32 auctionHouseId) : _auctionHouse(sAuctionHouseStore.AssertEntry(auctionHouseId)) { } AuctionHouseObject::~AuctionHouseObject() = default; uint32 AuctionHouseObject::GetAuctionHouseId() const { return _auctionHouse->ID; } AuctionPosting* AuctionHouseObject::GetAuction(uint32 auctionId) { return Trinity::Containers::MapGetValuePtr(_itemsByAuctionId, auctionId); } void AuctionHouseObject::AddAuction(CharacterDatabaseTransaction trans, AuctionPosting auction) { AuctionsBucketKey key = AuctionsBucketKey::ForItem(auction.Items[0]); AuctionsBucketData* bucket; auto bucketItr = _buckets.find(key); if (bucketItr == _buckets.end()) { // we don't have any item for this key yet, create new bucket bucketItr = _buckets.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple()).first; bucket = &bucketItr->second; bucket->Key = key; ItemTemplate const* itemTemplate = auction.Items[0]->GetTemplate(); bucket->ItemClass = itemTemplate->GetClass(); bucket->ItemSubClass = itemTemplate->GetSubClass(); bucket->InventoryType = itemTemplate->GetInventoryType(); bucket->RequiredLevel = auction.Items[0]->GetRequiredLevel(); switch (itemTemplate->GetClass()) { case ITEM_CLASS_WEAPON: case ITEM_CLASS_ARMOR: bucket->SortLevel = key.ItemLevel; break; case ITEM_CLASS_CONTAINER: bucket->SortLevel = itemTemplate->GetContainerSlots(); break; case ITEM_CLASS_GEM: case ITEM_CLASS_ITEM_ENHANCEMENT: bucket->SortLevel = itemTemplate->GetBaseItemLevel(); break; case ITEM_CLASS_CONSUMABLE: bucket->SortLevel = std::max(1, bucket->RequiredLevel); break; case ITEM_CLASS_MISCELLANEOUS: case ITEM_CLASS_BATTLE_PETS: bucket->SortLevel = 1; break; case ITEM_CLASS_RECIPE: bucket->SortLevel = itemTemplate->GetSubClass() != ITEM_SUBCLASS_BOOK ? itemTemplate->GetRequiredSkillRank() : itemTemplate->GetBaseRequiredLevel(); break; default: break; } for (LocaleConstant locale = LOCALE_enUS; locale < TOTAL_LOCALES; locale = LocaleConstant(locale + 1)) { if (locale == LOCALE_none) continue; std::wstring utf16name; if (!Utf8toWStr(auction.Items[0]->GetNameForLocaleIdx(locale), utf16name)) continue; bucket->FullName[locale] = wstrCaseAccentInsensitiveParse(utf16name, locale); } } else bucket = &bucketItr->second; // update cache fields uint64 priceToDisplay = auction.BuyoutOrUnitPrice ? auction.BuyoutOrUnitPrice : auction.BidAmount; if (!bucket->MinPrice || priceToDisplay < bucket->MinPrice) bucket->MinPrice = priceToDisplay; if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = auction.Items[0]->GetItemModifiedAppearance()) { auto itr = std::find_if(bucket->ItemModifiedAppearanceId.begin(), bucket->ItemModifiedAppearanceId.end(), [itemModifiedAppearance](std::pair const& appearance) { return appearance.first == itemModifiedAppearance->ID; }); if (itr == bucket->ItemModifiedAppearanceId.end()) itr = std::find_if(bucket->ItemModifiedAppearanceId.begin(), bucket->ItemModifiedAppearanceId.end(), [](std::pair const& appearance) { return appearance.first == 0; }); if (itr != bucket->ItemModifiedAppearanceId.end()) { itr->first = itemModifiedAppearance->ID; ++itr->second; } } uint32 quality; if (!auction.Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID)) { quality = auction.Items[0]->GetQuality(); } else { quality = (auction.Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF; for (Item* item : auction.Items) { uint8 battlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL); if (!bucket->MinBattlePetLevel) bucket->MinBattlePetLevel = battlePetLevel; else if (bucket->MinBattlePetLevel > battlePetLevel) bucket->MinBattlePetLevel = battlePetLevel; bucket->MaxBattlePetLevel = std::max(bucket->MaxBattlePetLevel, battlePetLevel); bucket->SortLevel = bucket->MaxBattlePetLevel; } } bucket->QualityMask |= static_cast(1 << (quality + 4)); ++bucket->QualityCounts[quality]; if (trans) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION); stmt->setUInt32(0, auction.Id); stmt->setUInt32(1, _auctionHouse->ID); stmt->setUInt64(2, auction.Owner.GetCounter()); stmt->setUInt64(3, ObjectGuid::Empty.GetCounter()); stmt->setUInt64(4, auction.MinBid); stmt->setUInt64(5, auction.BuyoutOrUnitPrice); stmt->setUInt64(6, auction.Deposit); stmt->setUInt64(7, auction.BidAmount); stmt->setInt64(8, std::chrono::system_clock::to_time_t(auction.StartTime)); stmt->setInt64(9, std::chrono::system_clock::to_time_t(auction.EndTime)); trans->Append(stmt); for (Item* item : auction.Items) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION_ITEMS); stmt->setUInt32(0, auction.Id); stmt->setUInt64(1, item->GetGUID().GetCounter()); trans->Append(stmt); } } for (Item* item : auction.Items) sAuctionMgr->AddAItem(item); auction.Bucket = bucket; _playerOwnedAuctions.emplace(auction.Owner, auction.Id); for (ObjectGuid bidder : auction.BidderHistory) _playerBidderAuctions.emplace(bidder, auction.Id); AuctionPosting* addedAuction = &(_itemsByAuctionId[auction.Id] = std::move(auction)); WorldPackets::AuctionHouse::AuctionSortDef priceSort{ AuctionHouseSortOrder::Price, false }; AuctionPosting::Sorter insertSorter(LOCALE_enUS, &priceSort, 1); bucket->Auctions.insert(std::lower_bound(bucket->Auctions.begin(), bucket->Auctions.end(), addedAuction, std::cref(insertSorter)), addedAuction); sScriptMgr->OnAuctionAdd(this, addedAuction); } void AuctionHouseObject::RemoveAuction(CharacterDatabaseTransaction trans, AuctionPosting* auction, std::map::iterator* auctionItr /*= nullptr*/) { AuctionsBucketData* bucket = auction->Bucket; bucket->Auctions.erase(std::remove(bucket->Auctions.begin(), bucket->Auctions.end(), auction), bucket->Auctions.end()); if (!bucket->Auctions.empty()) { // update cache fields uint64 priceToDisplay = auction->BuyoutOrUnitPrice ? auction->BuyoutOrUnitPrice : auction->BidAmount; if (bucket->MinPrice == priceToDisplay) { bucket->MinPrice = std::numeric_limits::max(); for (AuctionPosting const* remainingAuction : bucket->Auctions) bucket->MinPrice = std::min(bucket->MinPrice, remainingAuction->BuyoutOrUnitPrice ? remainingAuction->BuyoutOrUnitPrice : remainingAuction->BidAmount); } if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = auction->Items[0]->GetItemModifiedAppearance()) { auto itr = std::find_if(bucket->ItemModifiedAppearanceId.begin(), bucket->ItemModifiedAppearanceId.end(), [itemModifiedAppearance](std::pair const& appearance) { return appearance.first == itemModifiedAppearance->ID; }); if (itr != bucket->ItemModifiedAppearanceId.end()) if (!--itr->second) itr->first = 0; } uint32 quality; if (!auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID)) { quality = auction->Items[0]->GetQuality(); } else { quality = (auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF; bucket->MinBattlePetLevel = 0; bucket->MaxBattlePetLevel = 0; for (AuctionPosting const* remainingAuction : bucket->Auctions) { for (Item* item : remainingAuction->Items) { uint8 battlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL); if (!bucket->MinBattlePetLevel) bucket->MinBattlePetLevel = battlePetLevel; else if (bucket->MinBattlePetLevel > battlePetLevel) bucket->MinBattlePetLevel = battlePetLevel; bucket->MaxBattlePetLevel = std::max(bucket->MaxBattlePetLevel, battlePetLevel); } } } if (!--bucket->QualityCounts[quality]) bucket->QualityMask &= static_cast(~(1 << (quality + 4))); } else _buckets.erase(bucket->Key); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION); stmt->setUInt32(0, auction->Id); trans->Append(stmt); for (Item* item : auction->Items) sAuctionMgr->RemoveAItem(item->GetGUID()); sScriptMgr->OnAuctionRemove(this, auction); Trinity::Containers::MultimapErasePair(_playerOwnedAuctions, auction->Owner, auction->Id); for (ObjectGuid bidder : auction->BidderHistory) Trinity::Containers::MultimapErasePair(_playerBidderAuctions, bidder, auction->Id); if (auctionItr) *auctionItr = _itemsByAuctionId.erase(*auctionItr); else _itemsByAuctionId.erase(auction->Id); } void AuctionHouseObject::Update() { std::chrono::system_clock::time_point curTime = GameTime::GetGameTimeSystemPoint(); std::chrono::steady_clock::time_point curTimeSteady = GameTime::GetGameTimeSteadyPoint(); ///- Handle expired auctions // Clear expired throttled players for (auto itr = _replicateThrottleMap.begin(); itr != _replicateThrottleMap.end();) { if (itr->second.NextAllowedReplication <= curTimeSteady) itr = _replicateThrottleMap.erase(itr); else ++itr; } for (auto itr = _commodityQuotes.begin(); itr != _commodityQuotes.end();) { if (itr->second.ValidTo < curTimeSteady) itr = _commodityQuotes.erase(itr); else ++itr; } if (_itemsByAuctionId.empty()) return; CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); for (auto it = _itemsByAuctionId.begin(); it != _itemsByAuctionId.end();) { AuctionPosting* auction = &it->second; ///- filter auctions expired on next update if (auction->EndTime > curTime + 1min) { ++it; continue; } ///- Either cancel the auction if there was no bidder if (auction->Bidder.IsEmpty()) { SendAuctionExpired(auction, trans); sScriptMgr->OnAuctionExpire(this, auction); } ///- Or perform the transaction else { //we should send an "item sold" message if the seller is online //we send the item to the winner //we send the money to the seller SendAuctionWon(auction, nullptr, trans); SendAuctionSold(auction, nullptr, trans); sScriptMgr->OnAuctionSuccessful(this, auction); } ///- In any case clear the auction RemoveAuction(trans, auction, &it); } // Run DB changes CharacterDatabase.CommitTransaction(trans); } void AuctionHouseObject::BuildListBuckets(WorldPackets::AuctionHouse::AuctionListBucketsResult& listBucketsResult, Player* player, std::wstring const& name, uint8 minLevel, uint8 maxLevel, EnumFlag filters, Optional const& classFilters, uint8 const* knownPetBits, std::size_t knownPetBitsCount, uint8 maxKnownPetLevel, uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) { std::unordered_set knownAppearanceIds; boost::dynamic_bitset knownPetSpecies; // prepare uncollected filter for more efficient searches if (filters.HasFlag(AuctionHouseFilterMask::UncollectedOnly)) { knownAppearanceIds = player->GetSession()->GetCollectionMgr()->GetAppearanceIds(); knownPetSpecies.init_from_block_range(knownPetBits, knownPetBits + knownPetBitsCount); if (knownPetSpecies.size() < sBattlePetSpeciesStore.GetNumRows()) knownPetSpecies.resize(sBattlePetSpeciesStore.GetNumRows()); } AuctionsResultBuilder builder(offset, player->GetSession()->GetSessionDbcLocale(), sorts, sortCount, AuctionHouseResultLimits::Browse); for (std::pair const& bucket : _buckets) { AuctionsBucketData const* bucketData = &bucket.second; if (!name.empty()) { if (filters.HasFlag(AuctionHouseFilterMask::ExactMatch)) { if (bucketData->FullName[player->GetSession()->GetSessionDbcLocale()] != name) continue; } else if (bucketData->FullName[player->GetSession()->GetSessionDbcLocale()].find(name) == std::wstring::npos) continue; } if (minLevel && bucketData->RequiredLevel < minLevel) continue; if (maxLevel && bucketData->RequiredLevel > maxLevel) continue; if (!filters.HasFlag(bucketData->QualityMask)) continue; if (classFilters) { // if we dont want any class filters, Optional is not initialized // if we dont want this class included, SubclassMask is set to FILTER_SKIP_CLASS // if we want this class and did not specify and subclasses, its set to FILTER_SKIP_SUBCLASS // otherwise full restrictions apply if (classFilters->Classes[bucketData->ItemClass].SubclassMask == AuctionSearchClassFilters::FILTER_SKIP_CLASS) continue; if (classFilters->Classes[bucketData->ItemClass].SubclassMask != AuctionSearchClassFilters::FILTER_SKIP_SUBCLASS) { if (!(classFilters->Classes[bucketData->ItemClass].SubclassMask & (1 << bucketData->ItemSubClass))) continue; if (!(classFilters->Classes[bucketData->ItemClass].InvTypes[bucketData->ItemSubClass] & (1 << bucketData->InventoryType))) continue; } } if (filters.HasFlag(AuctionHouseFilterMask::UncollectedOnly)) { // appearances - by ItemAppearanceId, not ItemModifiedAppearanceId if (bucketData->InventoryType != INVTYPE_NON_EQUIP && bucketData->InventoryType != INVTYPE_BAG) { bool hasAll = true; for (std::pair bucketAppearance : bucketData->ItemModifiedAppearanceId) { if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(bucketAppearance.first)) { if (knownAppearanceIds.find(itemModifiedAppearance->ItemAppearanceID) == knownAppearanceIds.end()) { hasAll = false; break; } } } if (hasAll) continue; } // caged pets else if (bucket.first.BattlePetSpeciesId) { if (knownPetSpecies.test(bucket.first.BattlePetSpeciesId)) continue; } // toys else if (sDB2Manager.IsToyItem(bucket.first.ItemId)) { if (player->GetSession()->GetCollectionMgr()->HasToy(bucket.first.ItemId)) continue; } // mounts // recipes // pet items else if (bucketData->ItemClass == ITEM_CLASS_CONSUMABLE || bucketData->ItemClass == ITEM_CLASS_RECIPE || bucketData->ItemClass == ITEM_CLASS_MISCELLANEOUS) { ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(bucket.first.ItemId)); if (itemTemplate->Effects.size() >= 2 && (itemTemplate->Effects[0]->SpellID == 483 || itemTemplate->Effects[0]->SpellID == 55884)) { if (player->HasSpell(itemTemplate->Effects[1]->SpellID)) continue; if (BattlePetSpeciesEntry const* battlePetSpecies = sSpellMgr->GetBattlePetSpecies(itemTemplate->Effects[1]->SpellID)) if (knownPetSpecies.test(battlePetSpecies->ID)) continue; } } } if (filters.HasFlag(AuctionHouseFilterMask::UsableOnly)) { if (bucketData->RequiredLevel && player->GetLevel() < bucketData->RequiredLevel) continue; if (player->CanUseItem(sObjectMgr->GetItemTemplate(bucket.first.ItemId), true) != EQUIP_ERR_OK) continue; // cannot learn caged pets whose level exceeds highest level of currently owned pet if (bucketData->MinBattlePetLevel && bucketData->MinBattlePetLevel > maxKnownPetLevel) continue; } // TODO: this one needs to access loot history to know highest item level for every inventory type //if (filters.HasFlag(AuctionHouseFilterMask::UpgradesOnly)) //{ //} builder.AddItem(bucketData); } for (AuctionsBucketData const* resultBucket : builder.GetResultRange()) { listBucketsResult.Buckets.emplace_back(); WorldPackets::AuctionHouse::BucketInfo& bucketInfo = listBucketsResult.Buckets.back(); resultBucket->BuildBucketInfo(&bucketInfo, player); } listBucketsResult.HasMoreResults = builder.HasMoreResults(); } void AuctionHouseObject::BuildListBuckets(WorldPackets::AuctionHouse::AuctionListBucketsResult& listBucketsResult, Player* player, WorldPackets::AuctionHouse::AuctionBucketKey const* keys, std::size_t keysCount, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) { std::vector buckets; buckets.reserve(keysCount); for (std::size_t i = 0; i < keysCount; ++i) { auto bucketItr = _buckets.find(AuctionsBucketKey(keys[i])); if (bucketItr != _buckets.end()) buckets.push_back(&bucketItr->second); } AuctionsBucketData::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts, sortCount); std::sort(buckets.begin(), buckets.end(), std::cref(sorter)); for (AuctionsBucketData const* resultBucket : buckets) { listBucketsResult.Buckets.emplace_back(); WorldPackets::AuctionHouse::BucketInfo& bucketInfo = listBucketsResult.Buckets.back(); resultBucket->BuildBucketInfo(&bucketInfo, player); } listBucketsResult.HasMoreResults = false; } void AuctionHouseObject::BuildListBiddedItems(WorldPackets::AuctionHouse::AuctionListBiddedItemsResult& listBiddedItemsResult, Player* player, uint32 /*offset*/, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) const { // always full list std::vector auctions; for (auto const& auctionId : Trinity::Containers::MapEqualRange(_playerBidderAuctions, player->GetGUID())) if (AuctionPosting const* auction = Trinity::Containers::MapGetValuePtr(_itemsByAuctionId, auctionId.second)) auctions.push_back(auction); AuctionPosting::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts, sortCount); std::sort(auctions.begin(), auctions.end(), std::cref(sorter)); for (AuctionPosting const* resultAuction : auctions) { listBiddedItemsResult.Items.emplace_back(); WorldPackets::AuctionHouse::AuctionItem& auctionItem = listBiddedItemsResult.Items.back(); resultAuction->BuildAuctionItem(&auctionItem, true, true, true, false); } listBiddedItemsResult.HasMoreResults = false; } void AuctionHouseObject::BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult& listItemsResult, Player* player, AuctionsBucketKey const& bucketKey, uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) const { listItemsResult.TotalCount = 0; if (AuctionsBucketData const* bucket = Trinity::Containers::MapGetValuePtr(_buckets, bucketKey)) { AuctionsResultBuilder builder(offset, player->GetSession()->GetSessionDbcLocale(), sorts, sortCount, AuctionHouseResultLimits::Items); for (AuctionPosting const* auction : bucket->Auctions) { builder.AddItem(auction); for (Item* item : auction->Items) listItemsResult.TotalCount += item->GetCount(); } for (AuctionPosting const* resultAuction : builder.GetResultRange()) { listItemsResult.Items.emplace_back(); WorldPackets::AuctionHouse::AuctionItem& auctionItem = listItemsResult.Items.back(); resultAuction->BuildAuctionItem(&auctionItem, false, false, resultAuction->OwnerAccount != player->GetSession()->GetAccountGUID(), resultAuction->Bidder.IsEmpty()); } listItemsResult.HasMoreResults = builder.HasMoreResults(); } } void AuctionHouseObject::BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult& listItemsResult, Player* player, uint32 itemId, uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) const { AuctionsResultBuilder builder(offset, player->GetSession()->GetSessionDbcLocale(), sorts, sortCount, AuctionHouseResultLimits::Items); auto itr = _buckets.lower_bound(AuctionsBucketKey(itemId, 0, 0, 0)); auto end = _buckets.lower_bound(AuctionsBucketKey(itemId + 1, 0, 0, 0)); listItemsResult.TotalCount = 0; while (itr != end) { for (AuctionPosting const* auction : itr->second.Auctions) { builder.AddItem(auction); for (Item* item : auction->Items) listItemsResult.TotalCount += item->GetCount(); } ++itr; } for (AuctionPosting const* resultAuction : builder.GetResultRange()) { listItemsResult.Items.emplace_back(); WorldPackets::AuctionHouse::AuctionItem& auctionItem = listItemsResult.Items.back(); resultAuction->BuildAuctionItem(&auctionItem, false, true, resultAuction->OwnerAccount != player->GetSession()->GetAccountGUID(), resultAuction->Bidder.IsEmpty()); } listItemsResult.HasMoreResults = builder.HasMoreResults(); } void AuctionHouseObject::BuildListOwnedItems(WorldPackets::AuctionHouse::AuctionListOwnedItemsResult& listOwnedItemsResult, Player* player, uint32 /*offset*/, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) { // always full list std::vector auctions; for (auto const& auctionId : Trinity::Containers::MapEqualRange(_playerOwnedAuctions, player->GetGUID())) if (AuctionPosting const* auction = Trinity::Containers::MapGetValuePtr(_itemsByAuctionId, auctionId.second)) auctions.push_back(auction); AuctionPosting::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts, sortCount); std::sort(auctions.begin(), auctions.end(), std::cref(sorter)); for (AuctionPosting const* resultAuction : auctions) { listOwnedItemsResult.Items.emplace_back(); WorldPackets::AuctionHouse::AuctionItem& auctionItem = listOwnedItemsResult.Items.back(); resultAuction->BuildAuctionItem(&auctionItem, true, true, false, false); } listOwnedItemsResult.HasMoreResults = false; } /* void AuctionHouseObject::BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult& packet, Player* player, std::wstring const& searchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, EnumClassFlag filters, Optional const& classFilters) { time_t curTime = GameTime::GetGameTime(); for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr) { AuctionEntry* Aentry = itr->second; // Skip expired auctions if (Aentry->expire_time < curTime) continue; Item* item = sAuctionMgr->GetAItem(Aentry->itemGUIDLow); if (!item) continue; ItemTemplate const* proto = item->GetTemplate(); if (classFilters) { // if we dont want any class filters, Optional is not initialized // if we dont want this class included, SubclassMask is set to FILTER_SKIP_CLASS // if we want this class and did not specify and subclasses, its set to FILTER_SKIP_SUBCLASS // otherwise full restrictions apply if (classFilters->Classes[proto->GetClass()].SubclassMask == AuctionSearchClassFilters::FILTER_SKIP_CLASS) continue; if (classFilters->Classes[proto->GetClass()].SubclassMask != AuctionSearchClassFilters::FILTER_SKIP_SUBCLASS) { if (!(classFilters->Classes[proto->GetClass()].SubclassMask & (1 << proto->GetSubClass()))) continue; if (!(classFilters->Classes[proto->GetClass()].InvTypes[proto->GetSubClass()] & (1 << proto->GetInventoryType()))) continue; } } if (!filters.HasFlag(static_cast(1 << (proto->GetQuality() + 4)))) continue; if (levelmin != 0 && (item->GetRequiredLevel() < levelmin || (levelmax != 0 && item->GetRequiredLevel() > levelmax))) continue; if (filters.HasFlag(AuctionHouseFilterMask::UsableOnly) && player->CanUseItem(item) != EQUIP_ERR_OK) continue; // Allow search by suffix (ie: of the Monkey) or partial name (ie: Monkey) // No need to do any of this if no search term was entered if (!searchedname.empty()) { std::string name = proto->GetName(player->GetSession()->GetSessionDbcLocale()); if (name.empty()) continue; // TODO: Generate name using ItemNameDescription // Perform the search (with or without suffix) if (!Utf8FitTo(name, searchedname)) continue; } // Add the item if no search term or if entered search term was found if (packet.Items.size() < 50 && packet.TotalCount >= listfrom) Aentry->BuildAuctionInfo(packet.Items, true, item); ++packet.TotalCount; } } */ void AuctionHouseObject::BuildReplicate(WorldPackets::AuctionHouse::AuctionReplicateResponse& replicateResponse, Player* player, uint32 global, uint32 cursor, uint32 tombstone, uint32 count) { std::chrono::steady_clock::time_point curTime = GameTime::GetGameTimeSteadyPoint(); auto throttleItr = _replicateThrottleMap.find(player->GetGUID()); if (throttleItr != _replicateThrottleMap.end()) { if (throttleItr->second.Global != global || throttleItr->second.Cursor != cursor || throttleItr->second.Tombstone != tombstone) return; if (!throttleItr->second.IsReplicationInProgress() && throttleItr->second.NextAllowedReplication > curTime) return; } else { throttleItr = _replicateThrottleMap.emplace(player->GetGUID(), PlayerReplicateThrottleData{}).first; throttleItr->second.NextAllowedReplication = curTime + Seconds(sWorld->getIntConfig(CONFIG_AUCTION_REPLICATE_DELAY)); throttleItr->second.Global = sAuctionMgr->GenerateReplicationId(); } if (_itemsByAuctionId.empty() || !count) return; auto itr = _itemsByAuctionId.upper_bound(cursor); for (; itr != _itemsByAuctionId.end(); ++itr) { AuctionPosting const& auction = itr->second; replicateResponse.Items.emplace_back(); WorldPackets::AuctionHouse::AuctionItem& auctionItem = replicateResponse.Items.back(); auction.BuildAuctionItem(&auctionItem, false, true, true, auction.Bidder.IsEmpty()); if (!--count) break; } replicateResponse.ChangeNumberGlobal = throttleItr->second.Global; replicateResponse.ChangeNumberCursor = throttleItr->second.Cursor = !replicateResponse.Items.empty() ? replicateResponse.Items.back().AuctionID : 0; replicateResponse.ChangeNumberTombstone = throttleItr->second.Tombstone = !count ? _itemsByAuctionId.rbegin()->first : 0; } uint64 AuctionHouseObject::CalculateAuctionHouseCut(uint64 bidAmount) const { return std::max(int64(CalculatePct(bidAmount, _auctionHouse->ConsignmentRate) * double(sWorld->getRate(RATE_AUCTION_CUT))), SI64LIT(0)); } CommodityQuote const* AuctionHouseObject::CreateCommodityQuote(Player* player, uint32 itemId, uint32 quantity) { ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId); if (!itemTemplate) return nullptr; auto bucketItr = _buckets.find(AuctionsBucketKey::ForCommodity(itemTemplate)); if (bucketItr == _buckets.end()) return nullptr; uint64 totalPrice = 0; uint32 remainingQuantity = quantity; for (AuctionPosting const* auction : bucketItr->second.Auctions) { for (Item* auctionItem : auction->Items) { if (auctionItem->GetCount() >= remainingQuantity) { totalPrice += auction->BuyoutOrUnitPrice * remainingQuantity; remainingQuantity = 0; break; } totalPrice += auction->BuyoutOrUnitPrice * auctionItem->GetCount(); remainingQuantity -= auctionItem->GetCount(); } } // not enough items on auction house if (remainingQuantity) return nullptr; if (!player->HasEnoughMoney(totalPrice)) return nullptr; CommodityQuote* quote = &_commodityQuotes[player->GetGUID()]; quote->TotalPrice = totalPrice; quote->Quantity = quantity; quote->ValidTo = GameTime::GetGameTimeSteadyPoint() + 30s; return quote; } void AuctionHouseObject::CancelCommodityQuote(ObjectGuid guid) { _commodityQuotes.erase(guid); } bool AuctionHouseObject::BuyCommodity(CharacterDatabaseTransaction trans, Player* player, uint32 itemId, uint32 quantity, Milliseconds delayForNextAction) { ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId); if (!itemTemplate) return false; auto bucketItr = _buckets.find(AuctionsBucketKey::ForCommodity(itemTemplate)); if (bucketItr == _buckets.end()) { player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction); return false; } auto quote = _commodityQuotes.find(player->GetGUID()); if (quote == _commodityQuotes.end()) { player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction); return false; } std::shared_ptr removeQuote(nullptr, [this, quote](std::nullptr_t) { _commodityQuotes.erase(quote); }); uint64 totalPrice = 0; uint32 remainingQuantity = quantity; std::vector auctions; for (auto auctionItr = bucketItr->second.Auctions.begin(); auctionItr != bucketItr->second.Auctions.end();) { AuctionPosting* auction = *auctionItr++; auctions.push_back(auction); for (Item* auctionItem : auction->Items) { if (auctionItem->GetCount() >= remainingQuantity) { totalPrice += auction->BuyoutOrUnitPrice * remainingQuantity; remainingQuantity = 0; auctionItr = bucketItr->second.Auctions.end(); break; } totalPrice += auction->BuyoutOrUnitPrice * auctionItem->GetCount(); remainingQuantity -= auctionItem->GetCount(); } } // not enough items on auction house if (remainingQuantity) { player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction); return false; } // something was bought between creating quote and finalizing transaction // but we allow lower price if new items were posted at lower price if (totalPrice > quote->second.TotalPrice) { player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction); return false; } if (!player->HasEnoughMoney(totalPrice)) { player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction); return false; } Optional uniqueSeller; // prepare items struct MailedItemsBatch { std::array Items = { }; uint64 TotalPrice = 0; uint32 Quantity = 0; std::size_t ItemsCount = 0; bool IsFull() const { return ItemsCount >= Items.size(); } void AddItem(Item* item, uint64 unitPrice) { Items[ItemsCount++] = item; Quantity += item->GetCount(); TotalPrice += unitPrice * item->GetCount(); } }; std::vector items; items.emplace_back(); remainingQuantity = quantity; std::vector removedItemsFromAuction; for (auto auctionItr = bucketItr->second.Auctions.begin(); auctionItr != bucketItr->second.Auctions.end();) { AuctionPosting* auction = *auctionItr++; if (!uniqueSeller) uniqueSeller = auction->Owner; else if (*uniqueSeller != auction->Owner) uniqueSeller = ObjectGuid::Empty; uint32 boughtFromAuction = 0; std::size_t removedItems = 0; for (Item* auctionItem : auction->Items) { MailedItemsBatch* itemsBatch = &items.back(); if (itemsBatch->IsFull()) { items.emplace_back(); itemsBatch = &items.back(); } if (auctionItem->GetCount() > remainingQuantity) { Item* clonedItem = auctionItem->CloneItem(remainingQuantity, player); if (!clonedItem) { player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction); return false; } auctionItem->SetCount(auctionItem->GetCount() - remainingQuantity); auctionItem->FSetState(ITEM_CHANGED); auctionItem->SaveToDB(trans); itemsBatch->AddItem(clonedItem, auction->BuyoutOrUnitPrice); boughtFromAuction += remainingQuantity; remainingQuantity = 0; auctionItr = bucketItr->second.Auctions.end(); break; } itemsBatch->AddItem(auctionItem, auction->BuyoutOrUnitPrice); boughtFromAuction += auctionItem->GetCount(); remainingQuantity -= auctionItem->GetCount(); ++removedItems; } removedItemsFromAuction.push_back(removedItems); if (player->GetSession()->HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE)) { uint32 bidderAccId = player->GetSession()->GetAccountId(); std::string ownerName; if (!sCharacterCache->GetCharacterNameByGuid(auction->Owner, ownerName)) ownerName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN); sLog->outCommand(bidderAccId, "GM %s (Account: %u) bought commodity in auction: %s (Entry: %u Count: %u) and pay money: " UI64FMTD ". Original owner %s (Account: %u)", player->GetName().c_str(), bidderAccId, items[0].Items[0]->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()).c_str(), items[0].Items[0]->GetEntry(), boughtFromAuction, auction->BuyoutOrUnitPrice * boughtFromAuction, ownerName.c_str(), sCharacterCache->GetCharacterAccountIdByGuid(auction->Owner)); } uint64 auctionHouseCut = CalculateAuctionHouseCut(auction->BuyoutOrUnitPrice * boughtFromAuction); uint64 depositPart = AuctionHouseMgr::GetCommodityAuctionDeposit(items[0].Items[0]->GetTemplate(), std::chrono::duration_cast(auction->EndTime - auction->StartTime), boughtFromAuction); uint64 profit = auction->BuyoutOrUnitPrice * boughtFromAuction + depositPart - auctionHouseCut; if (Player* owner = ObjectAccessor::FindConnectedPlayer(auction->Owner)) { owner->UpdateCriteria(CriteriaType::MoneyEarnedFromAuctions, profit); owner->UpdateCriteria(CriteriaType::HighestAuctionSale, profit); owner->GetSession()->SendAuctionClosedNotification(auction, (float)sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), true); } MailDraft(AuctionHouseMgr::BuildCommodityAuctionMailSubject(AuctionMailType::Sold, itemId, boughtFromAuction), AuctionHouseMgr::BuildAuctionSoldMailBody(player->GetGUID(), auction->BuyoutOrUnitPrice * boughtFromAuction, boughtFromAuction, depositPart, auctionHouseCut)) .AddMoney(profit) .SendMailTo(trans, MailReceiver(ObjectAccessor::FindConnectedPlayer(auction->Owner), auction->Owner), this, MAIL_CHECK_MASK_COPIED, sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY)); } player->ModifyMoney(-int64(totalPrice)); player->SaveGoldToDB(trans); for (MailedItemsBatch const& batch : items) { MailDraft mail(AuctionHouseMgr::BuildCommodityAuctionMailSubject(AuctionMailType::Won, itemId, batch.Quantity), AuctionHouseMgr::BuildAuctionWonMailBody(*uniqueSeller, batch.TotalPrice, batch.Quantity)); for (std::size_t i = 0; i < batch.ItemsCount; ++i) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION_ITEMS_BY_ITEM); stmt->setUInt64(0, batch.Items[i]->GetGUID().GetCounter()); trans->Append(stmt); batch.Items[i]->SetOwnerGUID(player->GetGUID()); batch.Items[i]->SaveToDB(trans); mail.AddItem(batch.Items[i]); } mail.SendMailTo(trans, player, this, MAIL_CHECK_MASK_COPIED); } WorldPackets::AuctionHouse::AuctionWonNotification packet; packet.Info.Initialize(auctions[0], items[0].Items[0]); player->SendDirectMessage(packet.Write()); for (std::size_t i = 0; i < auctions.size(); ++i) { if (removedItemsFromAuction[i] == auctions[i]->Items.size()) RemoveAuction(trans, auctions[i]); // bought all items else if (removedItemsFromAuction[i]) { auto lastRemovedItem = auctions[i]->Items.begin() + removedItemsFromAuction[i]; for (auto itr = auctions[i]->Items.begin(); itr != lastRemovedItem; ++itr) sAuctionMgr->RemoveAItem((*itr)->GetGUID()); auctions[i]->Items.erase(auctions[i]->Items.begin(), lastRemovedItem); } } return true; } // this function notified old bidder that his bid is no longer highest void AuctionHouseObject::SendAuctionOutbid(AuctionPosting const* auction, ObjectGuid newBidder, uint64 newBidAmount, CharacterDatabaseTransaction trans) { Player* oldBidder = ObjectAccessor::FindConnectedPlayer(auction->Bidder); // old bidder exist if ((oldBidder || sCharacterCache->HasCharacterCacheEntry(auction->Bidder)) && !sAuctionBotConfig->IsBotChar(auction->Bidder)) { if (oldBidder) { WorldPackets::AuctionHouse::AuctionOutbidNotification packet; packet.BidAmount = newBidAmount; packet.MinIncrement = AuctionPosting::CalculateMinIncrement(newBidAmount); packet.Info.AuctionID = auction->Id; packet.Info.Bidder = newBidder; packet.Info.Item.Initialize(auction->Items[0]); oldBidder->SendDirectMessage(packet.Write()); } MailDraft(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Outbid, auction), "") .AddMoney(auction->BidAmount) .SendMailTo(trans, MailReceiver(oldBidder, auction->Bidder), this, MAIL_CHECK_MASK_COPIED); } } void AuctionHouseObject::SendAuctionWon(AuctionPosting const* auction, Player* bidder, CharacterDatabaseTransaction trans) { uint32 bidderAccId = 0; if (!bidder) bidder = ObjectAccessor::FindConnectedPlayer(auction->Bidder); // try lookup bidder when called from ::Update // data for gm.log std::string bidderName; bool logGmTrade = false; if (bidder) { bidderAccId = bidder->GetSession()->GetAccountId(); bidderName = bidder->GetName(); logGmTrade = bidder->GetSession()->HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE); } else { bidderAccId = sCharacterCache->GetCharacterAccountIdByGuid(auction->Bidder); logGmTrade = AccountMgr::HasPermission(bidderAccId, rbac::RBAC_PERM_LOG_GM_TRADE, realm.Id.Realm); if (logGmTrade && !sCharacterCache->GetCharacterNameByGuid(auction->Bidder, bidderName)) bidderName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN); } if (logGmTrade) { std::string ownerName; if (!sCharacterCache->GetCharacterNameByGuid(auction->Owner, ownerName)) ownerName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN); uint32 ownerAccId = sCharacterCache->GetCharacterAccountIdByGuid(auction->Owner); sLog->outCommand(bidderAccId, "GM %s (Account: %u) won item in auction: %s (Entry: %u Count: %u) and pay money: " UI64FMTD ". Original owner %s (Account: %u)", bidderName.c_str(), bidderAccId, auction->Items[0]->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()).c_str(), auction->Items[0]->GetEntry(), auction->GetTotalItemCount(), auction->BidAmount, ownerName.c_str(), ownerAccId); } // receiver exist if ((bidder || bidderAccId) && !sAuctionBotConfig->IsBotChar(auction->Bidder)) { MailDraft mail(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Won, auction), AuctionHouseMgr::BuildAuctionWonMailBody(auction->Owner, auction->BidAmount, auction->BuyoutOrUnitPrice)); // set owner to bidder (to prevent delete item with sender char deleting) // owner in `data` will set at mail receive and item extracting for (Item* item : auction->Items) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_OWNER); stmt->setUInt64(0, auction->Bidder.GetCounter()); stmt->setUInt64(1, item->GetGUID().GetCounter()); trans->Append(stmt); mail.AddItem(item); } if (bidder) { WorldPackets::AuctionHouse::AuctionWonNotification packet; packet.Info.Initialize(auction, auction->Items[0]); bidder->SendDirectMessage(packet.Write()); // FIXME: for offline player need also bidder->UpdateCriteria(CriteriaType::AuctionsWon, 1); } mail.SendMailTo(trans, MailReceiver(bidder, auction->Bidder), this, MAIL_CHECK_MASK_COPIED); } else { // bidder doesn't exist, delete the item for (Item* item : auction->Items) sAuctionMgr->RemoveAItem(item->GetGUID(), true, &trans); } } //call this method to send mail to auction owner, when auction is successful, it does not clear ram void AuctionHouseObject::SendAuctionSold(AuctionPosting const* auction, Player* owner, CharacterDatabaseTransaction trans) { if (!owner) owner = ObjectAccessor::FindConnectedPlayer(auction->Owner); // owner exist if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner)) { uint64 auctionHouseCut = CalculateAuctionHouseCut(auction->BidAmount); uint64 profit = auction->BidAmount + auction->Deposit - auctionHouseCut; //FIXME: what do if owner offline if (owner) { owner->UpdateCriteria(CriteriaType::MoneyEarnedFromAuctions, profit); owner->UpdateCriteria(CriteriaType::HighestAuctionSale, auction->BidAmount); //send auction owner notification, bidder must be current! owner->GetSession()->SendAuctionClosedNotification(auction, (float)sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), true); } MailDraft(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Sold, auction), AuctionHouseMgr::BuildAuctionSoldMailBody(auction->Bidder, auction->BidAmount, auction->BuyoutOrUnitPrice, auction->Deposit, auctionHouseCut)) .AddMoney(profit) .SendMailTo(trans, MailReceiver(owner, auction->Owner), this, MAIL_CHECK_MASK_COPIED, sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY)); } } void AuctionHouseObject::SendAuctionExpired(AuctionPosting const* auction, CharacterDatabaseTransaction trans) { Player* owner = ObjectAccessor::FindConnectedPlayer(auction->Owner); // owner exist if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner)) { if (owner) owner->GetSession()->SendAuctionClosedNotification(auction, 0.0f, false); auto itemItr = auction->Items.begin(); while (itemItr != auction->Items.end()) { MailDraft mail(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Expired, auction), ""); for (std::size_t i = 0; i < MAX_MAIL_ITEMS && itemItr != auction->Items.end(); ++i, ++itemItr) mail.AddItem(*itemItr); mail.SendMailTo(trans, MailReceiver(owner, auction->Owner), this, MAIL_CHECK_MASK_COPIED, 0); } } else { // owner doesn't exist, delete the item for (Item* item : auction->Items) sAuctionMgr->RemoveAItem(item->GetGUID(), true, &trans); } } void AuctionHouseObject::SendAuctionRemoved(AuctionPosting const* auction, Player* owner, CharacterDatabaseTransaction trans) { auto itemItr = auction->Items.begin(); while (itemItr != auction->Items.end()) { MailDraft draft(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Cancelled, auction), ""); for (std::size_t i = 0; i < MAX_MAIL_ITEMS && itemItr != auction->Items.end(); ++i, ++itemItr) draft.AddItem(*itemItr); draft.SendMailTo(trans, owner, this, MAIL_CHECK_MASK_COPIED); } } //this function sends mail, when auction is cancelled to old bidder void AuctionHouseObject::SendAuctionCancelledToBidder(AuctionPosting const* auction, CharacterDatabaseTransaction trans) { Player* bidder = ObjectAccessor::FindConnectedPlayer(auction->Bidder); // bidder exist if ((bidder || sCharacterCache->HasCharacterCacheEntry(auction->Bidder)) && !sAuctionBotConfig->IsBotChar(auction->Bidder)) MailDraft(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Removed, auction), "") .AddMoney(auction->BidAmount) .SendMailTo(trans, MailReceiver(bidder, auction->Bidder), this, MAIL_CHECK_MASK_COPIED); } void AuctionHouseObject::SendAuctionInvoice(AuctionPosting const* auction, Player* owner, CharacterDatabaseTransaction trans) { if (!owner) owner = ObjectAccessor::FindConnectedPlayer(auction->Owner); // owner exist (online or offline) if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner)) { ByteBuffer tempBuffer; tempBuffer.AppendPackedTime(GameTime::GetGameTime() + sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY)); uint32 eta = tempBuffer.read(); MailDraft(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Invoice, auction), AuctionHouseMgr::BuildAuctionInvoiceMailBody(auction->Bidder, auction->BidAmount, auction->BuyoutOrUnitPrice, auction->Deposit, CalculateAuctionHouseCut(auction->BidAmount), sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), eta)) .SendMailTo(trans, MailReceiver(owner, auction->Owner), this, MAIL_CHECK_MASK_COPIED); } }