diff options
Diffstat (limited to 'src/server/game/Handlers/AuctionHouseHandler.cpp')
-rw-r--r-- | src/server/game/Handlers/AuctionHouseHandler.cpp | 1178 |
1 files changed, 745 insertions, 433 deletions
diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp index 5fb0a62103b..593405627c2 100644 --- a/src/server/game/Handlers/AuctionHouseHandler.cpp +++ b/src/server/game/Handlers/AuctionHouseHandler.cpp @@ -22,6 +22,7 @@ #include "CharacterCache.h" #include "Creature.h" #include "DatabaseEnv.h" +#include "GameTime.h" #include "Item.h" #include "Language.h" #include "Log.h" @@ -32,14 +33,18 @@ #include "Util.h" #include "World.h" #include "WorldPacket.h" +#include <sstream> -//void called when player click on auctioneer npc -void WorldSession::HandleAuctionHelloOpcode(WorldPackets::AuctionHouse::AuctionHelloRequest& packet) +void WorldSession::HandleAuctionBrowseQuery(WorldPackets::AuctionHouse::AuctionBrowseQuery& browseQuery) { - Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(packet.Guid, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); - if (!unit) + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player); + if (throttle.Throttled) + return; + + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(browseQuery.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + if (!creature) { - TC_LOG_DEBUG("network", "WORLD: HandleAuctionHelloOpcode - Unit (%s) not found or you can't interact with him.", packet.Guid.ToString().c_str()); + TC_LOG_DEBUG("network", "WORLD: HandleAuctionListItems - %s not found or you can't interact with him.", browseQuery.Auctioneer.ToString().c_str()); return; } @@ -47,344 +52,292 @@ void WorldSession::HandleAuctionHelloOpcode(WorldPackets::AuctionHouse::AuctionH if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - SendAuctionHello(packet.Guid, unit); -} + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); -//this void causes that auction window is opened -void WorldSession::SendAuctionHello(ObjectGuid guid, Creature* unit) -{ - if (GetPlayer()->getLevel() < sWorld->getIntConfig(CONFIG_AUCTION_LEVEL_REQ)) - { - SendNotification(GetTrinityString(LANG_AUCTION_REQ), sWorld->getIntConfig(CONFIG_AUCTION_LEVEL_REQ)); - return; - } + TC_LOG_DEBUG("auctionHouse", "Auctionhouse search (%s), searchedname: %s, levelmin: %u, levelmax: %u, filters: %u", + browseQuery.Auctioneer.ToString().c_str(), browseQuery.Name.c_str(), browseQuery.MinLevel, browseQuery.MaxLevel, AsUnderlyingType(browseQuery.Filters)); - AuctionHouseEntry const* ahEntry = AuctionHouseMgr::GetAuctionHouseEntry(unit->getFaction(), nullptr); - if (!ahEntry) + std::wstring name; + if (!Utf8toWStr(browseQuery.Name, name)) return; - WorldPackets::AuctionHouse::AuctionHelloResponse auctionHelloResponse; - auctionHelloResponse.Guid = guid; - auctionHelloResponse.OpenForBusiness = false; // 3.3.3: 1 - AH enabled, 0 - AH disabled - SendPacket(auctionHelloResponse.Write()); -} + Optional<AuctionSearchClassFilters> classFilters; -void WorldSession::SendAuctionCommandResult(AuctionEntry* auction, uint32 action, uint32 errorCode, uint32 /*bidError = 0*/) -{ - WorldPackets::AuctionHouse::AuctionCommandResult auctionCommandResult; - auctionCommandResult.InitializeAuction(auction); - auctionCommandResult.Command = action; - auctionCommandResult.ErrorCode = errorCode; - SendPacket(auctionCommandResult.Write()); -} + WorldPackets::AuctionHouse::AuctionListBucketsResult listBucketsResult; + if (!browseQuery.ItemClassFilters.empty()) + { + classFilters = boost::in_place(); -void WorldSession::SendAuctionOutBidNotification(AuctionEntry const* auction, Item const* item) -{ - WorldPackets::AuctionHouse::AuctionOutbidNotification packet; - packet.BidAmount = auction->bid; - packet.MinIncrement = auction->GetAuctionOutBid(); - packet.Info.Initialize(auction, item); - SendPacket(packet.Write()); -} + for (auto const& classFilter : browseQuery.ItemClassFilters) + { + if (!classFilter.SubClassFilters.empty()) + { + for (auto const& subClassFilter : classFilter.SubClassFilters) + { + if (classFilter.ItemClass < MAX_ITEM_CLASS) + { + classFilters->Classes[classFilter.ItemClass].SubclassMask |= 1 << subClassFilter.ItemSubclass; + if (subClassFilter.ItemSubclass < MAX_ITEM_SUBCLASS_TOTAL) + classFilters->Classes[classFilter.ItemClass].InvTypes[subClassFilter.ItemSubclass] = subClassFilter.InvTypeMask; + } + } + } + else + classFilters->Classes[classFilter.ItemClass].SubclassMask = AuctionSearchClassFilters::FILTER_SKIP_SUBCLASS; + } + } -void WorldSession::SendAuctionClosedNotification(AuctionEntry const* auction, float mailDelay, bool sold, Item const* item) -{ - WorldPackets::AuctionHouse::AuctionClosedNotification packet; - packet.Info.Initialize(auction, item); - packet.ProceedsMailDelay = mailDelay; - packet.Sold = sold; - SendPacket(packet.Write()); -} + auctionHouse->BuildListBuckets(listBucketsResult, _player, + name, browseQuery.MinLevel, browseQuery.MaxLevel, browseQuery.Filters, classFilters, + browseQuery.KnownPets.data(), browseQuery.KnownPets.size(), browseQuery.MaxPetLevel, + browseQuery.Offset, browseQuery.Sorts.data(), browseQuery.Sorts.size()); -void WorldSession::SendAuctionWonNotification(AuctionEntry const* auction, Item const* item) -{ - WorldPackets::AuctionHouse::AuctionWonNotification packet; - packet.Info.Initialize(auction, item); - SendPacket(packet.Write()); + listBucketsResult.BrowseMode = AuctionHouseBrowseMode::Search; + listBucketsResult.DesiredDelay = uint32(throttle.DelayUntilNext.count()); + SendPacket(listBucketsResult.Write()); } -void WorldSession::SendAuctionOwnerBidNotification(AuctionEntry const* auction, Item const* item) +void WorldSession::HandleAuctionCancelCommoditiesPurchase(WorldPackets::AuctionHouse::AuctionCancelCommoditiesPurchase& cancelCommoditiesPurchase) { - WorldPackets::AuctionHouse::AuctionOwnerBidNotification packet; - packet.Info.Initialize(auction, item); - packet.Bidder = ObjectGuid::Create<HighGuid::Player>(auction->bidder); - packet.MinIncrement = auction->GetAuctionOutBid(); - SendPacket(packet.Write()); + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::PlaceBid); + if (throttle.Throttled) + return; + + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(cancelCommoditiesPurchase.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + if (!creature) + { + TC_LOG_DEBUG("network", "WORLD: HandleAuctionCancelCommoditiesPurchase - %s not found or you can't interact with him.", + cancelCommoditiesPurchase.Auctioneer.ToString().c_str()); + return; + } + + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); + auctionHouse->CancelCommodityQuote(_player->GetGUID()); } -//this void creates new auction and adds auction to some auctionhouse -void WorldSession::HandleAuctionSellItem(WorldPackets::AuctionHouse::AuctionSellItem& packet) +void WorldSession::HandleAuctionConfirmCommoditiesPurchase(WorldPackets::AuctionHouse::AuctionConfirmCommoditiesPurchase& confirmCommoditiesPurchase) { - for (auto const& item : packet.Items) - if (!item.Guid || !item.UseCount || item.UseCount > 1000) - return; - - if (!packet.MinBid || !packet.RunTime) + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::PlaceBid); + if (throttle.Throttled) return; - if (packet.MinBid > MAX_MONEY_AMOUNT || packet.BuyoutPrice > MAX_MONEY_AMOUNT) + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(confirmCommoditiesPurchase.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + if (!creature) { - TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Player %s (%s) attempted to sell item with higher price than max gold amount.", _player->GetName().c_str(), _player->GetGUID().ToString().c_str()); - SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR); + TC_LOG_DEBUG("network", "WORLD: HandleAuctionConfirmCommoditiesPurchase - %s not found or you can't interact with him.", confirmCommoditiesPurchase.Auctioneer.ToString().c_str()); return; } + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); - if (!creature) + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + if (auctionHouse->BuyCommodity(trans, _player, confirmCommoditiesPurchase.ItemID, confirmCommoditiesPurchase.Quantity, throttle.DelayUntilNext)) { - TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) not found or you can't interact with him.", packet.Auctioneer.ToString().c_str()); - return; + AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete([this, buyerGuid = _player->GetGUID(), throttle](bool success) + { + if (GetPlayer() && GetPlayer()->GetGUID() == buyerGuid) + { + if (success) + { + GetPlayer()->UpdateCriteria(CRITERIA_TYPE_WON_AUCTIONS, 1); + SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::Ok, throttle.DelayUntilNext); + } + else + SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, throttle.DelayUntilNext); + } + }); } +} - uint32 houseId = 0; - AuctionHouseEntry const* auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(creature->getFaction(), &houseId); - if (!auctionHouseEntry) +void WorldSession::HandleAuctionHelloOpcode(WorldPackets::AuctionHouse::AuctionHelloRequest& hello) +{ + Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(hello.Guid, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + if (!unit) { - TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) has wrong faction.", packet.Auctioneer.ToString().c_str()); + TC_LOG_DEBUG("network", "WORLD: HandleAuctionHelloOpcode - Unit (%s) not found or you can't interact with him.", hello.Guid.ToString().c_str()); return; } - packet.RunTime *= MINUTE; + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + + SendAuctionHello(hello.Guid, unit); +} + +void WorldSession::HandleAuctionListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItems& listBidderItems) +{ + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player); + if (throttle.Throttled) + return; - switch (packet.RunTime) + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(listBidderItems.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + if (!creature) { - case 1 * MIN_AUCTION_TIME: - case 2 * MIN_AUCTION_TIME: - case 4 * MIN_AUCTION_TIME: - break; - default: - return; + TC_LOG_DEBUG("network", "WORLD: HandleAuctionListBidderItems - %s not found or you can't interact with him.", listBidderItems.Auctioneer.ToString().c_str()); + return; } + // remove fake death if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - uint32 finalCount = 0; - std::array<Item*, decltype(packet.Items)::max_capacity::value> items; - items.fill(nullptr); - for (std::size_t i = 0; i < packet.Items.size(); ++i) - { - items[i] = _player->GetItemByGuid(packet.Items[i].Guid); - - if (!items[i]) - { - SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_ITEM_NOT_FOUND); - return; - } + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - if (sAuctionMgr->GetAItem(items[i]->GetGUID().GetCounter()) || !items[i]->CanBeTraded() || items[i]->IsNotEmptyBag() || - items[i]->GetTemplate()->GetFlags() & ITEM_FLAG_CONJURED || *items[i]->m_itemData->Expiration || - items[i]->GetCount() < packet.Items[i].UseCount) - { - SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR); - return; - } + WorldPackets::AuctionHouse::AuctionListBidderItemsResult result; - finalCount += packet.Items[i].UseCount; - } + Player* player = GetPlayer(); + auctionHouse->BuildListBidderItems(result, player, listBidderItems.Offset, listBidderItems.Sorts.data(), listBidderItems.Sorts.size()); + result.DesiredDelay = uint32(throttle.DelayUntilNext.count()); + SendPacket(result.Write()); +} - if (packet.Items.empty()) - { - SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR); +void WorldSession::HandleAuctionListBucketsByBucketKeys(WorldPackets::AuctionHouse::AuctionListBucketsByBucketKeys& listBucketsByBucketKeys) +{ + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player); + if (throttle.Throttled) return; - } - if (!finalCount) + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(listBucketsByBucketKeys.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + if (!creature) { - SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR); + TC_LOG_DEBUG("network", "WORLD: HandleAuctionListBucketsByBucketKeys - %s not found or you can't interact with him.", + listBucketsByBucketKeys.Auctioneer.ToString().c_str()); return; } - // check if there are 2 identical guids, in this case user is most likely cheating - for (std::size_t i = 0; i < packet.Items.size() - 1; ++i) - { - for (std::size_t j = i + 1; j < packet.Items.size(); ++j) - { - if (packet.Items[i].Guid == packet.Items[j].Guid) - { - SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR); - return; - } - if (items[i]->GetEntry() != items[j]->GetEntry()) - { - SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_ITEM_NOT_FOUND); - return; - } - } - } + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - for (std::size_t i = 0; i < packet.Items.size(); ++i) - { - if (items[i]->GetMaxStackCount() < finalCount) - { - SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR); - return; - } - } + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - Item* item = items[0]; + WorldPackets::AuctionHouse::AuctionListBucketsResult listBucketsResult; - uint32 auctionTime = uint32(packet.RunTime * sWorld->getRate(RATE_AUCTION_TIME)); - AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); + auctionHouse->BuildListBuckets(listBucketsResult, _player, + listBucketsByBucketKeys.BucketKeys.data(), listBucketsByBucketKeys.BucketKeys.size(), + listBucketsByBucketKeys.Sorts.data(), listBucketsByBucketKeys.Sorts.size()); - uint64 deposit = sAuctionMgr->GetAuctionDeposit(auctionHouseEntry, packet.RunTime, item, finalCount); - if (!_player->HasEnoughMoney(deposit)) + listBucketsResult.BrowseMode = AuctionHouseBrowseMode::SpecificKeys; + listBucketsResult.DesiredDelay = uint32(throttle.DelayUntilNext.count()); + SendPacket(listBucketsResult.Write()); +} + +void WorldSession::HandleAuctionListItemsByBucketKey(WorldPackets::AuctionHouse::AuctionListItemsByBucketKey& listItemsByBucketKey) +{ + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player); + if (throttle.Throttled) + return; + + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(listItemsByBucketKey.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + if (!creature) { - SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_NOT_ENOUGH_MONEY); + TC_LOG_DEBUG("network", "WORLD: HandleAuctionListItemsByBucketKey - %s not found or you can't interact with him.", listItemsByBucketKey.Auctioneer.ToString().c_str()); return; } - AuctionEntry* AH = new AuctionEntry(); + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) - AH->auctioneer = UI64LIT(23442); ///@TODO - HARDCODED DB GUID, BAD BAD BAD - else - AH->auctioneer = packet.Auctioneer.GetCounter(); + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - // Required stack size of auction matches to current item stack size, just move item to auctionhouse - if (packet.Items.size() == 1 && item->GetCount() == packet.Items[0].UseCount) - { - if (HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE)) - { - sLog->outCommand(GetAccountId(), "GM %s (Account: %u) create auction: %s (Entry: %u Count: %u)", - GetPlayerName().c_str(), GetAccountId(), item->GetTemplate()->GetDefaultLocaleName(), item->GetEntry(), item->GetCount()); - } + WorldPackets::AuctionHouse::AuctionListItemsResult listItemsResult; + listItemsResult.DesiredDelay = uint32(throttle.DelayUntilNext.count()); + listItemsResult.BucketKey = listItemsByBucketKey.BucketKey; + ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(listItemsByBucketKey.BucketKey.ItemID); + listItemsResult.ListType = itemTemplate && itemTemplate->GetMaxStackSize() > 1 ? AuctionHouseListType::Commodities : AuctionHouseListType::Items; - AH->Id = sObjectMgr->GenerateAuctionID(); - AH->itemGUIDLow = item->GetGUID().GetCounter(); - AH->itemEntry = item->GetEntry(); - AH->itemCount = item->GetCount(); - AH->owner = _player->GetGUID().GetCounter(); - AH->startbid = packet.MinBid; - AH->bidder = UI64LIT(0); - AH->bid = 0; - AH->buyout = packet.BuyoutPrice; - AH->expire_time = time(NULL) + auctionTime; - AH->deposit = deposit; - AH->etime = packet.RunTime; - AH->auctionHouseEntry = auctionHouseEntry; - - TC_LOG_INFO("network", "CMSG_AUCTION_SELL_ITEM: %s %s is selling item %s %s to auctioneer " UI64FMTD " with count %u with initial bid " UI64FMTD " with buyout " UI64FMTD " and with time %u (in sec) in auctionhouse %u", - _player->GetGUID().ToString().c_str(), _player->GetName().c_str(), item->GetGUID().ToString().c_str(), item->GetTemplate()->GetDefaultLocaleName(), AH->auctioneer, item->GetCount(), packet.MinBid, packet.BuyoutPrice, auctionTime, AH->GetHouseId()); - - // Add to pending auctions, or fail with insufficient funds error - if (!sAuctionMgr->PendingAuctionAdd(_player, AH)) - { - SendAuctionCommandResult(AH, AUCTION_SELL_ITEM, ERR_AUCTION_NOT_ENOUGH_MONEY); - return; - } + auctionHouse->BuildListAuctionItems(listItemsResult, _player, listItemsByBucketKey.BucketKey, listItemsByBucketKey.Offset, + listItemsByBucketKey.Sorts.data(), listItemsByBucketKey.Sorts.size()); - sAuctionMgr->AddAItem(item); - auctionHouse->AddAuction(AH); - _player->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true); + SendPacket(listItemsResult.Write()); +} - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - item->DeleteFromInventoryDB(trans); - item->SaveToDB(trans); +void WorldSession::HandleAuctionListItemsByItemID(WorldPackets::AuctionHouse::AuctionListItemsByItemID& listItemsByItemID) +{ + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player); + if (throttle.Throttled) + return; - AH->SaveToDB(trans); - _player->SaveInventoryAndGoldToDB(trans); - CharacterDatabase.CommitTransaction(trans); + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(listItemsByItemID.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + if (!creature) + { + TC_LOG_DEBUG("network", "WORLD: HandleAuctionListItemsByItemID - %s not found or you can't interact with him.", listItemsByItemID.Auctioneer.ToString().c_str()); + return; + } - SendAuctionCommandResult(AH, AUCTION_SELL_ITEM, ERR_AUCTION_OK); + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - GetPlayer()->UpdateCriteria(CRITERIA_TYPE_CREATE_AUCTION, 1); - } - else // Required stack size of auction does not match to current item stack size, clone item and set correct stack size - { - Item* newItem = item->CloneItem(finalCount, _player); - if (!newItem) - { - TC_LOG_ERROR("network", "CMSG_AUCTION_SELL_ITEM: Could not create clone of item %u", item->GetEntry()); - SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR); - delete AH; - return; - } + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - if (HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE)) - { - sLog->outCommand(GetAccountId(), "GM %s (Account: %u) create auction: %s (Entry: %u Count: %u)", - GetPlayerName().c_str(), GetAccountId(), newItem->GetTemplate()->GetDefaultLocaleName(), newItem->GetEntry(), newItem->GetCount()); - } + WorldPackets::AuctionHouse::AuctionListItemsResult listItemsResult; + listItemsResult.DesiredDelay = uint32(throttle.DelayUntilNext.count()); + listItemsResult.BucketKey.ItemID = listItemsByItemID.ItemID; + ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(listItemsByItemID.ItemID); + listItemsResult.ListType = itemTemplate && itemTemplate->GetMaxStackSize() > 1 ? AuctionHouseListType::Commodities : AuctionHouseListType::Items; - AH->Id = sObjectMgr->GenerateAuctionID(); - AH->itemGUIDLow = newItem->GetGUID().GetCounter(); - AH->itemEntry = newItem->GetEntry(); - AH->itemCount = newItem->GetCount(); - AH->owner = _player->GetGUID().GetCounter(); - AH->startbid = packet.MinBid; - AH->bidder = UI64LIT(0); - AH->bid = 0; - AH->buyout = packet.BuyoutPrice; - AH->expire_time = time(NULL) + auctionTime; - AH->deposit = deposit; - AH->etime = packet.RunTime; - AH->auctionHouseEntry = auctionHouseEntry; - - TC_LOG_INFO("network", "CMSG_AUCTION_SELL_ITEM: %s %s is selling %s %s to auctioneer " UI64FMTD " with count %u with initial bid " UI64FMTD " with buyout " UI64FMTD " and with time %u (in sec) in auctionhouse %u", - _player->GetGUID().ToString().c_str(), _player->GetName().c_str(), newItem->GetGUID().ToString().c_str(), newItem->GetTemplate()->GetDefaultLocaleName(), AH->auctioneer, newItem->GetCount(), packet.MinBid, packet.BuyoutPrice, auctionTime, AH->GetHouseId()); - - // Add to pending auctions, or fail with insufficient funds error - if (!sAuctionMgr->PendingAuctionAdd(_player, AH)) - { - SendAuctionCommandResult(AH, AUCTION_SELL_ITEM, ERR_AUCTION_NOT_ENOUGH_MONEY); - return; - } + auctionHouse->BuildListAuctionItems(listItemsResult, _player, listItemsByItemID.ItemID, listItemsByItemID.Offset, + listItemsByItemID.Sorts.data(), listItemsByItemID.Sorts.size()); - sAuctionMgr->AddAItem(newItem); - auctionHouse->AddAuction(AH); + SendPacket(listItemsResult.Write()); +} - for (std::size_t i = 0; i < packet.Items.size(); ++i) - { - Item* item2 = items[i]; +//this void sends player info about his auctions +void WorldSession::HandleAuctionListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItems& listOwnerItems) +{ + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player); + if (throttle.Throttled) + return; - // Item stack count equals required count, ready to delete item - cloned item will be used for auction - if (item2->GetCount() == packet.Items[i].UseCount) - { - _player->MoveItemFromInventory(item2->GetBagSlot(), item2->GetSlot(), true); + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(listOwnerItems.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + if (!creature) + { + TC_LOG_DEBUG("network", "WORLD: HandleAuctionListOwnerItems - %s not found or you can't interact with him.", listOwnerItems.Auctioneer.ToString().c_str()); + return; + } - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - item2->DeleteFromInventoryDB(trans); - item2->DeleteFromDB(trans); - CharacterDatabase.CommitTransaction(trans); - delete item2; - } - else // Item stack count is bigger than required count, update item stack count and save to database - cloned item will be used for auction - { - item2->SetCount(item2->GetCount() - packet.Items[i].UseCount); - item2->SetState(ITEM_CHANGED, _player); - _player->ItemRemovedQuestCheck(item2->GetEntry(), packet.Items[i].UseCount); - item2->SendUpdateToPlayer(_player); - - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - item2->SaveToDB(trans); - CharacterDatabase.CommitTransaction(trans); - } - } + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - newItem->SaveToDB(trans); - AH->SaveToDB(trans); - _player->SaveInventoryAndGoldToDB(trans); - CharacterDatabase.CommitTransaction(trans); + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - SendAuctionCommandResult(AH, AUCTION_SELL_ITEM, ERR_AUCTION_OK); + WorldPackets::AuctionHouse::AuctionListOwnerItemsResult result; - GetPlayer()->UpdateCriteria(CRITERIA_TYPE_CREATE_AUCTION, 1); - } + auctionHouse->BuildListOwnerItems(result, _player, listOwnerItems.Offset, listOwnerItems.Sorts.data(), listOwnerItems.Sorts.size()); + result.DesiredDelay = uint32(throttle.DelayUntilNext.count()); + SendPacket(result.Write()); } // this function is called when client bids or buys out auction -void WorldSession::HandleAuctionPlaceBid(WorldPackets::AuctionHouse::AuctionPlaceBid& packet) +void WorldSession::HandleAuctionPlaceBid(WorldPackets::AuctionHouse::AuctionPlaceBid& placeBid) { - if (!packet.AuctionID || !packet.BidAmount) - return; // check for cheaters + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::PlaceBid); + if (throttle.Throttled) + return; - Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(placeBid.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); if (!creature) { - TC_LOG_DEBUG("network", "WORLD: HandleAuctionPlaceBid - %s not found or you can't interact with him.", packet.Auctioneer.ToString().c_str()); + TC_LOG_DEBUG("network", "WORLD: HandleAuctionPlaceBid - %s not found or you can't interact with him.", placeBid.Auctioneer.ToString().c_str()); + return; + } + + // auction house does not deal with copper + if (placeBid.BidAmount % SILVER) + { + SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::BidIncrement, throttle.DelayUntilNext); return; } @@ -394,121 +347,119 @@ void WorldSession::HandleAuctionPlaceBid(WorldPackets::AuctionHouse::AuctionPlac AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - AuctionEntry* auction = auctionHouse->GetAuction(packet.AuctionID); - Player* player = GetPlayer(); - - if (!auction || auction->owner == player->GetGUID().GetCounter()) + AuctionPosting* auction = auctionHouse->GetAuction(placeBid.AuctionID); + if (!auction || auction->IsCommodity()) { - //you cannot bid your own auction: - SendAuctionCommandResult(NULL, AUCTION_PLACE_BID, ERR_AUCTION_BID_OWN); + SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::ItemNotFound, throttle.DelayUntilNext); return; } - // impossible have online own another character (use this for speedup check in case online owner) - ObjectGuid ownerGuid = ObjectGuid::Create<HighGuid::Player>(auction->owner); - Player* auction_owner = ObjectAccessor::FindPlayer(ownerGuid); - if (!auction_owner && sCharacterCache->GetCharacterAccountIdByGuid(ownerGuid) == player->GetSession()->GetAccountId()) + Player* player = GetPlayer(); + + // check auction owner - cannot buy own auctions + if (auction->Owner == player->GetGUID() || auction->OwnerAccount == GetAccountGUID()) { - //you cannot bid your another character auction: - SendAuctionCommandResult(NULL, AUCTION_PLACE_BID, ERR_AUCTION_BID_OWN); + SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::BidOwn, throttle.DelayUntilNext); return; } - // cheating - if (packet.BidAmount <= auction->bid || packet.BidAmount < auction->startbid) - return; + bool canBid = auction->MinBid != 0; + bool canBuyout = auction->BuyoutOrUnitPrice != 0; - // price too low for next bid if not buyout - if ((packet.BidAmount < auction->buyout || auction->buyout == 0) && - packet.BidAmount < auction->bid + auction->GetAuctionOutBid()) + // buyout attempt with wrong amount + if (!canBid && placeBid.BidAmount != auction->BuyoutOrUnitPrice) { - // client already test it but just in case ... - SendAuctionCommandResult(auction, AUCTION_PLACE_BID, ERR_AUCTION_HIGHER_BID); + SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::BidIncrement, throttle.DelayUntilNext); return; } - if (!player->HasEnoughMoney(packet.BidAmount)) + uint64 minBid = auction->BidAmount ? auction->BidAmount + auction->CalculateMinIncrement() : auction->MinBid; + if (canBid && placeBid.BidAmount < minBid) { - // client already test it but just in case ... - SendAuctionCommandResult(auction, AUCTION_PLACE_BID, ERR_AUCTION_NOT_ENOUGH_MONEY); + SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::HigherBid, throttle.DelayUntilNext); return; } CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - if (packet.BidAmount < auction->buyout || auction->buyout == 0) + uint64 priceToPay = placeBid.BidAmount; + if (!auction->Bidder.IsEmpty()) { - if (auction->bidder) - { - if (auction->bidder == player->GetGUID().GetCounter()) - player->ModifyMoney(-int64(packet.BidAmount - auction->bid)); - else - { - // mail to last bidder and return money - sAuctionMgr->SendAuctionOutbiddedMail(auction, packet.BidAmount, GetPlayer(), trans); - player->ModifyMoney(-int64(packet.BidAmount)); - } - } + // return money to previous bidder + if (auction->Bidder != player->GetGUID()) + auctionHouse->SendAuctionOutbid(auction, player->GetGUID(), placeBid.BidAmount, trans); else - player->ModifyMoney(-int64(packet.BidAmount)); + priceToPay = placeBid.BidAmount - auction->BidAmount; + } - auction->bidder = player->GetGUID().GetCounter(); - auction->bid = packet.BidAmount; - GetPlayer()->UpdateCriteria(CRITERIA_TYPE_HIGHEST_AUCTION_BID, packet.BidAmount); + // check money + if (!player->HasEnoughMoney(priceToPay)) + { + SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext); + return; + } - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_BID); - stmt->setUInt64(0, auction->bidder); - stmt->setUInt64(1, auction->bid); - stmt->setUInt32(2, auction->Id); - trans->Append(stmt); + player->ModifyMoney(-int64(priceToPay)); + auction->Bidder = player->GetGUID(); + auction->BidAmount = placeBid.BidAmount; - SendAuctionCommandResult(auction, AUCTION_PLACE_BID, ERR_AUCTION_OK); + if (canBuyout && placeBid.BidAmount == auction->BuyoutOrUnitPrice) + { + // buyout + auctionHouse->SendAuctionWon(auction, player, trans); + auctionHouse->SendAuctionSold(auction, nullptr, trans); - // Not sure if we must send this now. - Player* owner = ObjectAccessor::FindConnectedPlayer(ObjectGuid::Create<HighGuid::Player>(auction->owner)); - Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow); - if (owner && item) - owner->GetSession()->SendAuctionOwnerBidNotification(auction, item); + auctionHouse->RemoveAuction(trans, auction); } else { - //buyout: - if (player->GetGUID().GetCounter() == auction->bidder) - player->ModifyMoney(-int64(auction->buyout - auction->bid)); - else + // place bid + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_BID); + stmt->setUInt64(0, auction->Bidder.GetCounter()); + stmt->setUInt64(1, auction->BidAmount); + stmt->setUInt32(2, auction->Id); + trans->Append(stmt); + + if (auction->BidderHistory.insert(player->GetGUID()).second) { - player->ModifyMoney(-int64(auction->buyout)); - if (auction->bidder) //buyout for bidded auction .. - sAuctionMgr->SendAuctionOutbiddedMail(auction, auction->buyout, GetPlayer(), trans); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION_BIDDER); + stmt->setUInt32(0, auction->Id); + stmt->setUInt64(1, player->GetGUID().GetCounter()); + trans->Append(stmt); } - auction->bidder = player->GetGUID().GetCounter(); - auction->bid = auction->buyout; - GetPlayer()->UpdateCriteria(CRITERIA_TYPE_HIGHEST_AUCTION_BID, auction->buyout); - - SendAuctionCommandResult(auction, AUCTION_PLACE_BID, ERR_AUCTION_OK); - - //- Mails must be under transaction control too to prevent data loss - sAuctionMgr->SendAuctionSalePendingMail(auction, trans); - sAuctionMgr->SendAuctionSuccessfulMail(auction, trans); - sAuctionMgr->SendAuctionWonMail(auction, trans); - - auction->DeleteFromDB(trans); - sAuctionMgr->RemoveAItem(auction->itemGUIDLow); - auctionHouse->RemoveAuction(auction); + // Not sure if we must send this now. + if (Player* owner = ObjectAccessor::FindConnectedPlayer(auction->Owner)) + owner->GetSession()->SendAuctionOwnerBidNotification(auction); } player->SaveInventoryAndGoldToDB(trans); - CharacterDatabase.CommitTransaction(trans); + AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete( + [this, auctionId = placeBid.AuctionID, bidAmount = placeBid.BidAmount, auctionPlayerGuid = _player->GetGUID(), throttle](bool success) + { + if (GetPlayer() && GetPlayer()->GetGUID() == auctionPlayerGuid) + { + if (success) + { + GetPlayer()->UpdateCriteria(CRITERIA_TYPE_HIGHEST_AUCTION_BID, bidAmount); + SendAuctionCommandResult(auctionId, AuctionCommand::PlaceBid, AuctionResult::Ok, throttle.DelayUntilNext); + } + else + SendAuctionCommandResult(auctionId, AuctionCommand::PlaceBid, AuctionResult::DatabaseError, throttle.DelayUntilNext); + } + }); } -//this void is called when auction_owner cancels his auction -void WorldSession::HandleAuctionRemoveItem(WorldPackets::AuctionHouse::AuctionRemoveItem& packet) +void WorldSession::HandleAuctionRemoveItem(WorldPackets::AuctionHouse::AuctionRemoveItem& removeItem) { - Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::Cancel); + if (throttle.Throttled) + return; + + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(removeItem.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); if (!creature) { - TC_LOG_DEBUG("network", "WORLD: HandleAuctionRemoveItem - %s not found or you can't interact with him.", packet.Auctioneer.ToString().c_str()); + TC_LOG_DEBUG("network", "WORLD: HandleAuctionRemoveItem - %s not found or you can't interact with him.", removeItem.Auctioneer.ToString().c_str()); return; } @@ -518,64 +469,59 @@ void WorldSession::HandleAuctionRemoveItem(WorldPackets::AuctionHouse::AuctionRe AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - AuctionEntry* auction = auctionHouse->GetAuction(packet.AuctionID); + AuctionPosting* auction = auctionHouse->GetAuction(removeItem.AuctionID); Player* player = GetPlayer(); CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - if (auction && auction->owner == player->GetGUID().GetCounter()) + if (auction && auction->Owner == player->GetGUID()) { - Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow); - if (item) + if (auction->Bidder.IsEmpty()) // If we have a bidder, we have to send him the money he paid { - if (auction->bidder) // If we have a bidder, we have to send him the money he paid + uint64 cancelCost = CalculatePct(auction->BidAmount, 5u); + if (!player->HasEnoughMoney(cancelCost)) //player doesn't have enough money { - uint64 auctionCut = auction->GetAuctionCut(); - if (!player->HasEnoughMoney(auctionCut)) //player doesn't have enough money, maybe message needed - return; - sAuctionMgr->SendAuctionCancelledToBidderMail(auction, trans); - player->ModifyMoney(-int64(auctionCut)); + SendAuctionCommandResult(0, AuctionCommand::Cancel, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext); + return; } - - // item will deleted or added to received mail list - MailDraft(auction->BuildAuctionMailSubject(AUCTION_CANCELED), AuctionEntry::BuildAuctionMailBody(0, 0, auction->buyout, auction->deposit, 0)) - .AddItem(item) - .SendMailTo(trans, player, auction, MAIL_CHECK_MASK_COPIED); - } - else - { - TC_LOG_ERROR("network", "Auction id: %u got non existing item (item guid : " UI64FMTD ")!", auction->Id, auction->itemGUIDLow); - SendAuctionCommandResult(NULL, AUCTION_CANCEL, ERR_AUCTION_DATABASE_ERROR); - return; + auctionHouse->SendAuctionCancelledToBidder(auction, trans); + player->ModifyMoney(-int64(cancelCost)); } + + auctionHouse->SendAuctionRemoved(auction, player, trans); } else { - SendAuctionCommandResult(NULL, AUCTION_CANCEL, ERR_AUCTION_DATABASE_ERROR); + SendAuctionCommandResult(0, AuctionCommand::Cancel, AuctionResult::DatabaseError, throttle.DelayUntilNext); //this code isn't possible ... maybe there should be assert - TC_LOG_ERROR("entities.player.cheat", "CHEATER: %s tried to cancel auction (id: %u) of another player or auction is NULL", player->GetGUID().ToString().c_str(), packet.AuctionID); + TC_LOG_ERROR("entities.player.cheat", "CHEATER: %s tried to cancel auction (id: %u) of another player or auction is NULL", player->GetGUID().ToString().c_str(), removeItem.AuctionID); return; } - //inform player, that auction is removed - SendAuctionCommandResult(auction, AUCTION_CANCEL, ERR_AUCTION_OK); + // client bug - instead of removing auction in the UI, it only substracts 1 from visible count + uint32 auctionIdForClient = auction->IsCommodity() ? 0 : auction->Id; // Now remove the auction - player->SaveInventoryAndGoldToDB(trans); - auction->DeleteFromDB(trans); - CharacterDatabase.CommitTransaction(trans); - - sAuctionMgr->RemoveAItem(auction->itemGUIDLow); - auctionHouse->RemoveAuction(auction); + auctionHouse->RemoveAuction(trans, auction); + AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete( + [this, auctionIdForClient, auctionPlayerGuid = _player->GetGUID(), throttle](bool success) + { + if (GetPlayer() && GetPlayer()->GetGUID() == auctionPlayerGuid) + { + if (success) + SendAuctionCommandResult(auctionIdForClient, AuctionCommand::Cancel, AuctionResult::Ok, throttle.DelayUntilNext); //inform player, that auction is removed + else + SendAuctionCommandResult(0, AuctionCommand::Cancel, AuctionResult::DatabaseError, throttle.DelayUntilNext); + } + }); } -//called when player lists his bids -void WorldSession::HandleAuctionListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItems& packet) +void WorldSession::HandleAuctionReplicateItems(WorldPackets::AuctionHouse::AuctionReplicateItems& replicateItems) { - Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(replicateItems.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); if (!creature) { - TC_LOG_DEBUG("network", "WORLD: HandleAuctionListBidderItems - %s not found or you can't interact with him.", packet.Auctioneer.ToString().c_str()); + TC_LOG_DEBUG("network", "WORLD: HandleReplicateItems - %s not found or you can't interact with him.", replicateItems.Auctioneer.ToString().c_str()); return; } @@ -585,102 +531,415 @@ void WorldSession::HandleAuctionListBidderItems(WorldPackets::AuctionHouse::Auct AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - WorldPackets::AuctionHouse::AuctionListBidderItemsResult result; + WorldPackets::AuctionHouse::AuctionReplicateResponse response; - Player* player = GetPlayer(); - auctionHouse->BuildListBidderItems(result, player); - result.DesiredDelay = 300; - SendPacket(result.Write()); + auctionHouse->BuildReplicate(response, GetPlayer(), + replicateItems.ChangeNumberGlobal, replicateItems.ChangeNumberCursor, replicateItems.ChangeNumberTombstone, replicateItems.Count); + + response.DesiredDelay = sWorld->getIntConfig(CONFIG_AUCTION_SEARCH_DELAY) * 5; + response.Result = 0; + SendPacket(response.Write()); } -//this void sends player info about his auctions -void WorldSession::HandleAuctionListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItems& packet) +void WorldSession::HandleAuctionSellCommodity(WorldPackets::AuctionHouse::AuctionSellCommodity& sellCommodity) { - Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::SellItem); + if (throttle.Throttled) + return; + + if (!sellCommodity.UnitPrice || sellCommodity.UnitPrice > MAX_MONEY_AMOUNT) + { + TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Player %s (%s) attempted to sell item with invalid price.", _player->GetName().c_str(), _player->GetGUID().ToString().c_str()); + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext); + return; + } + + // auction house does not deal with copper + if (sellCommodity.UnitPrice % SILVER) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext); + return; + } + + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(sellCommodity.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); if (!creature) { - TC_LOG_DEBUG("network", "WORLD: HandleAuctionListOwnerItems - %s not found or you can't interact with him.", packet.Auctioneer.ToString().c_str()); + TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) not found or you can't interact with him.", sellCommodity.Auctioneer.ToString().c_str()); return; } - // remove fake death + uint32 houseId = 0; + AuctionHouseEntry const* auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(creature->getFaction(), &houseId); + if (!auctionHouseEntry) + { + TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) has wrong faction.", sellCommodity.Auctioneer.ToString().c_str()); + return; + } + + switch (sellCommodity.RunTime) + { + case 1 * MIN_AUCTION_TIME / MINUTE: + case 2 * MIN_AUCTION_TIME / MINUTE: + case 4 * MIN_AUCTION_TIME / MINUTE: + break; + default: + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::AuctionHouseBusy, throttle.DelayUntilNext); + return; + } + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + // find all items for sale + uint64 totalCount = 0; + std::unordered_map<ObjectGuid, std::pair<Item*, uint64>> items2; + + for (WorldPackets::AuctionHouse::AuctionItemForSale const& itemForSale : sellCommodity.Items) + { + Item* item = _player->GetItemByGuid(itemForSale.Guid); + if (!item) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext); + return; + } + + if (item->GetTemplate()->GetMaxStackSize() == 1) + { + // not commodity, must use different packet + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext); + return; + } + + // verify that all items belong to the same bucket + if (!items2.empty() && AuctionsBucketKey::ForItem(item) != AuctionsBucketKey::ForItem(items2.begin()->second.first)) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext); + return; + } + + if (sAuctionMgr->GetAItem(item->GetGUID()) || !item->CanBeTraded() || item->IsNotEmptyBag() || + item->GetTemplate()->GetFlags() & ITEM_FLAG_CONJURED || *item->m_itemData->Expiration || + item->GetCount() < itemForSale.UseCount) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext); + return; + } + + std::pair<Item*, uint64>& soldItem = items2[item->GetGUID()]; + soldItem.first = item; + soldItem.second += itemForSale.UseCount; + if (item->GetCount() < soldItem.second) + { + // check that we have enough of this item to sell + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext); + return; + } + + totalCount += itemForSale.UseCount; + } + + if (!totalCount) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext); + return; + } + + Seconds auctionTime = Seconds(int64(std::chrono::duration_cast<Seconds>(Minutes(sellCommodity.RunTime)).count() * double(sWorld->getRate(RATE_AUCTION_TIME)))); AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - WorldPackets::AuctionHouse::AuctionListOwnerItemsResult result; + uint64 deposit = AuctionHouseMgr::GetCommodityAuctionDeposit(items2.begin()->second.first->GetTemplate(), Minutes(sellCommodity.RunTime), totalCount); + if (!_player->HasEnoughMoney(deposit)) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext); + return; + } - auctionHouse->BuildListOwnerItems(result, _player); - result.DesiredDelay = 300; - SendPacket(result.Write()); + uint32 auctionId = sObjectMgr->GenerateAuctionID(); + AuctionPosting auction; + auction.Id = auctionId; + auction.Owner = _player->GetGUID(); + auction.OwnerAccount = GetAccountGUID(); + auction.BuyoutOrUnitPrice = sellCommodity.UnitPrice; + auction.Deposit = deposit; + auction.StartTime = GameTime::GetGameTimeSystemPoint(); + auction.EndTime = auction.StartTime + auctionTime; + + // keep track of what was cloned to undo/modify counts later + std::unordered_map<Item* /*original*/, std::unique_ptr<Item> /*clone*/> clones; + for (std::pair<ObjectGuid const, std::pair<Item*, uint64>>& it : items2) + { + Item* itemForSale; + if (it.second.first->GetCount() != it.second.second) + { + itemForSale = it.second.first->CloneItem(it.second.second, _player); + if (!itemForSale) + { + TC_LOG_ERROR("network", "CMSG_AUCTION_SELL_COMMODITY: Could not create clone of item %u", it.second.first->GetEntry()); + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext); + return; + } + + clones.emplace(it.second.first, itemForSale); + } + } + + if (!sAuctionMgr->PendingAuctionAdd(_player, auctionHouse->GetAuctionHouseId(), auction.Id, auction.Deposit)) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext); + return; + } + + TC_LOG_INFO("network", "CMSG_AUCTION_SELL_COMMODITY: %s %s is selling item %s %s to auctioneer %s with count " UI64FMTD " with with unit price " UI64FMTD " and with time %u (in sec) in auctionhouse %u", + _player->GetGUID().ToString().c_str(), _player->GetName().c_str(), items2.begin()->second.first->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()).c_str(), + ([&items2]() + { + std::stringstream ss; + auto itr = items2.begin(); + ss << (itr++)->first.ToString(); + for (; itr != items2.end(); ++itr) + ss << ',' << itr->first.ToString(); + return ss.str(); + }()).c_str(), + creature->GetGUID().ToString().c_str(), totalCount, sellCommodity.UnitPrice, uint32(auctionTime.count()), auctionHouse->GetAuctionHouseId()); + + if (HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE)) + { + Item* logItem = items2.begin()->second.first; + sLog->outCommand(GetAccountId(), "GM %s (Account: %u) create auction: %s (Entry: %u Count: " UI64FMTD ")", + GetPlayerName().c_str(), GetAccountId(), logItem->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()), logItem->GetEntry(), totalCount); + } + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + + for (std::pair<ObjectGuid const, std::pair<Item*, uint64>> const& it : items2) + { + Item* itemForSale = it.second.first; + auto cloneItr = clones.find(it.second.first); + if (cloneItr != clones.end()) + { + Item* original = itemForSale; + original->SetCount(original->GetCount() - uint32(it.second.second)); + original->SetState(ITEM_CHANGED, _player); + _player->ItemRemovedQuestCheck(original->GetEntry(), uint32(it.second.second)); + original->SaveToDB(trans); + + itemForSale = cloneItr->second.release(); + } + else + { + _player->MoveItemFromInventory(itemForSale->GetBagSlot(), itemForSale->GetSlot(), true); + itemForSale->DeleteFromInventoryDB(trans); + } + + itemForSale->SaveToDB(trans); + auction.Items.push_back(itemForSale); + } + + auctionHouse->AddAuction(trans, std::move(auction)); + _player->SaveInventoryAndGoldToDB(trans); + + AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete([this, auctionId, auctionPlayerGuid = _player->GetGUID(), throttle](bool success) + { + if (GetPlayer() && GetPlayer()->GetGUID() == auctionPlayerGuid) + { + if (success) + { + GetPlayer()->UpdateCriteria(CRITERIA_TYPE_CREATE_AUCTION, 1); + SendAuctionCommandResult(auctionId, AuctionCommand::SellItem, AuctionResult::Ok, throttle.DelayUntilNext); + } + else + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext); + } + }); } -//this void is called when player clicks on search button -void WorldSession::HandleAuctionListItems(WorldPackets::AuctionHouse::AuctionBrowseQuery& browseQuery) +void WorldSession::HandleAuctionSellItem(WorldPackets::AuctionHouse::AuctionSellItem& sellItem) { - Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(browseQuery.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::SellItem); + if (throttle.Throttled) + return; + + if (sellItem.Items.size() != 1 || sellItem.Items[0].UseCount != 1) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext); + return; + } + + if (!sellItem.MinBid && !sellItem.BuyoutPrice) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext); + return; + } + + if (sellItem.MinBid > MAX_MONEY_AMOUNT || sellItem.BuyoutPrice > MAX_MONEY_AMOUNT) + { + TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Player %s (%s) attempted to sell item with higher price than max gold amount.", _player->GetName().c_str(), _player->GetGUID().ToString().c_str()); + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::Inventory, throttle.DelayUntilNext, EQUIP_ERR_TOO_MUCH_GOLD); + return; + } + + // auction house does not deal with copper + if (sellItem.MinBid % SILVER || sellItem.BuyoutPrice % SILVER) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext); + return; + } + + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(sellItem.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); if (!creature) { - TC_LOG_DEBUG("network", "WORLD: HandleAuctionListItems - %s not found or you can't interact with him.", browseQuery.Auctioneer.ToString().c_str()); + TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) not found or you can't interact with him.", sellItem.Auctioneer.ToString().c_str()); return; } - // remove fake death + uint32 houseId = 0; + AuctionHouseEntry const* auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(creature->getFaction(), &houseId); + if (!auctionHouseEntry) + { + TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) has wrong faction.", sellItem.Auctioneer.ToString().c_str()); + return; + } + + switch (sellItem.RunTime) + { + case 1 * MIN_AUCTION_TIME / MINUTE: + case 2 * MIN_AUCTION_TIME / MINUTE: + case 4 * MIN_AUCTION_TIME / MINUTE: + break; + default: + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::AuctionHouseBusy, throttle.DelayUntilNext); + return; + } + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); + Item* item = _player->GetItemByGuid(sellItem.Items[0].Guid); + if (!item) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext); + return; + } - TC_LOG_DEBUG("auctionHouse", "Auctionhouse search (%s), searchedname: %s, levelmin: %u, levelmax: %u, filters: %u", - browseQuery.Auctioneer.ToString().c_str(), browseQuery.Name.c_str(), browseQuery.MinLevel, browseQuery.MaxLevel , AsUnderlyingType(browseQuery.Filters)); + if (item->GetTemplate()->GetMaxStackSize() > 1) + { + // commodity, must use different packet + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext); + return; + } - // converting string that we try to find to lower case - std::wstring wsearchedname; - if (!Utf8toWStr(browseQuery.Name, wsearchedname)) + if (sAuctionMgr->GetAItem(item->GetGUID()) || !item->CanBeTraded() || item->IsNotEmptyBag() || + item->GetTemplate()->GetFlags() & ITEM_FLAG_CONJURED || *item->m_itemData->Expiration || + item->GetCount() != 1) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext); return; + } - wstrToLower(wsearchedname); + Seconds auctionTime = Seconds(int64(std::chrono::duration_cast<Seconds>(Minutes(sellItem.RunTime)).count() * double(sWorld->getRate(RATE_AUCTION_TIME)))); + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - Optional<AuctionSearchClassFilters> classFilters; + uint64 deposit = AuctionHouseMgr::GetItemAuctionDeposit(_player, item, Minutes(sellItem.RunTime)); + if (!_player->HasEnoughMoney(deposit)) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext); + return; + } - WorldPackets::AuctionHouse::AuctionListItemsResult result; - if (!browseQuery.ItemClassFilters.empty()) + uint32 auctionId = sObjectMgr->GenerateAuctionID(); + + AuctionPosting auction; + auction.Id = auctionId; + auction.Owner = _player->GetGUID(); + auction.OwnerAccount = GetAccountGUID(); + auction.MinBid = sellItem.MinBid; + auction.BuyoutOrUnitPrice = sellItem.BuyoutPrice; + auction.Deposit = deposit; + auction.BidAmount = sellItem.MinBid; + auction.StartTime = GameTime::GetGameTimeSystemPoint(); + auction.EndTime = auction.StartTime + auctionTime; + + if (HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE)) { - classFilters = boost::in_place(); + sLog->outCommand(GetAccountId(), "GM %s (Account: %u) create auction: %s (Entry: %u Count: %u)", + GetPlayerName().c_str(), GetAccountId(), item->GetTemplate()->GetDefaultLocaleName(), item->GetEntry(), item->GetCount()); + } - for (auto const& classFilter : browseQuery.ItemClassFilters) + auction.Items.push_back(item); + + TC_LOG_INFO("network", "CMSG_AuctionAction::SellItem: %s %s is selling item %s %s to auctioneer %s with count %u with initial bid " UI64FMTD " with buyout " UI64FMTD " and with time %u (in sec) in auctionhouse %u", + _player->GetGUID().ToString().c_str(), _player->GetName().c_str(), item->GetGUID().ToString().c_str(), item->GetTemplate()->GetDefaultLocaleName(), + creature->GetGUID().ToString().c_str(), item->GetCount(), sellItem.MinBid, sellItem.BuyoutPrice, uint32(auctionTime.count()), auctionHouse->GetAuctionHouseId()); + + // Add to pending auctions, or fail with insufficient funds error + if (!sAuctionMgr->PendingAuctionAdd(_player, auctionHouse->GetAuctionHouseId(), auctionId, auction.Deposit)) + { + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext); + return; + } + + _player->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true); + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + item->DeleteFromInventoryDB(trans); + item->SaveToDB(trans); + + auctionHouse->AddAuction(trans, std::move(auction)); + _player->SaveInventoryAndGoldToDB(trans); + AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete([this, auctionId, auctionPlayerGuid = _player->GetGUID(), throttle](bool success) + { + if (GetPlayer() && GetPlayer()->GetGUID() == auctionPlayerGuid) { - if (!classFilter.SubClassFilters.empty()) + if (success) { - for (auto const& subClassFilter : classFilter.SubClassFilters) - { - if (classFilter.ItemClass < MAX_ITEM_CLASS) - { - classFilters->Classes[classFilter.ItemClass].SubclassMask |= 1 << subClassFilter.ItemSubclass; - if (subClassFilter.ItemSubclass < MAX_ITEM_SUBCLASS_TOTAL) - classFilters->Classes[classFilter.ItemClass].InvTypes[subClassFilter.ItemSubclass] = subClassFilter.InvTypeMask; - } - } + GetPlayer()->UpdateCriteria(CRITERIA_TYPE_CREATE_AUCTION, 1); + SendAuctionCommandResult(auctionId, AuctionCommand::SellItem, AuctionResult::Ok, throttle.DelayUntilNext); } else - classFilters->Classes[classFilter.ItemClass].SubclassMask = AuctionSearchClassFilters::FILTER_SKIP_SUBCLASS; + SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext); } - } + }); +} + +void WorldSession::HandleAuctionSetFavoriteItem(WorldPackets::AuctionHouse::AuctionSetFavoriteItem& setFavoriteItem) +{ + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player); + if (throttle.Throttled) + return; - auctionHouse->BuildListAuctionItems(result, _player, wsearchedname, browseQuery.Offset, browseQuery.MinLevel, browseQuery.MaxLevel, - browseQuery.Filters, classFilters); + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - result.DesiredDelay = sWorld->getIntConfig(CONFIG_AUCTION_SEARCH_DELAY); - SendPacket(result.Write()); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_FAVORITE_AUCTION); + stmt->setUInt64(0, _player->GetGUID().GetCounter()); + stmt->setUInt32(1, setFavoriteItem.Item.Order); + trans->Append(stmt); + + if (!setFavoriteItem.IsNotFavorite) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_FAVORITE_AUCTION); + stmt->setUInt64(0, _player->GetGUID().GetCounter()); + stmt->setUInt32(1, setFavoriteItem.Item.Order); + stmt->setUInt32(2, setFavoriteItem.Item.ItemID); + stmt->setUInt32(3, setFavoriteItem.Item.ItemLevel); + stmt->setUInt32(4, setFavoriteItem.Item.BattlePetSpeciesID); + stmt->setUInt32(5, setFavoriteItem.Item.SuffixItemNameDescriptionID); + trans->Append(stmt); + } + + CharacterDatabase.CommitTransaction(trans); } -void WorldSession::HandleReplicateItems(WorldPackets::AuctionHouse::AuctionReplicateItems& packet) +void WorldSession::HandleAuctionStartCommoditiesPurchase(WorldPackets::AuctionHouse::AuctionStartCommoditiesPurchase& startCommoditiesPurchase) { - Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); + AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::PlaceBid); + if (throttle.Throttled) + return; + + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(startCommoditiesPurchase.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE); if (!creature) { - TC_LOG_DEBUG("network", "WORLD: HandleReplicateItems - %s not found or you can't interact with him.", packet.Auctioneer.ToString().c_str()); + TC_LOG_DEBUG("network", "WORLD: HandleAuctionStartCommoditiesPurchase - %s not found or you can't interact with him.", + startCommoditiesPurchase.Auctioneer.ToString().c_str()); return; } @@ -690,11 +949,64 @@ void WorldSession::HandleReplicateItems(WorldPackets::AuctionHouse::AuctionRepli AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); - WorldPackets::AuctionHouse::AuctionReplicateResponse response; + WorldPackets::AuctionHouse::AuctionCommodityQuote auctionCommodityQuote; - auctionHouse->BuildReplicate(response, GetPlayer(), packet.ChangeNumberGlobal, packet.ChangeNumberCursor, packet.ChangeNumberTombstone, packet.Count); + if (CommodityQuote const* quote = auctionHouse->CreateCommodityQuote(_player, startCommoditiesPurchase.ItemID, startCommoditiesPurchase.Quantity)) + { + auctionCommodityQuote.TotalPrice = quote->TotalPrice; + auctionCommodityQuote.Quantity = quote->Quantity; + auctionCommodityQuote.QuoteDuration = std::chrono::duration_cast<Milliseconds>(quote->ValidTo - GameTime::GetGameTimeSteadyPoint()).count(); + } - response.DesiredDelay = sWorld->getIntConfig(CONFIG_AUCTION_SEARCH_DELAY) * 5; - response.Result = 0; - SendPacket(response.Write()); + auctionCommodityQuote.DesiredDelay = uint32(throttle.DelayUntilNext.count()); + + SendPacket(auctionCommodityQuote.Write()); +} + +//this void causes that auction window is opened +void WorldSession::SendAuctionHello(ObjectGuid guid, Creature* unit) +{ + if (GetPlayer()->getLevel() < sWorld->getIntConfig(CONFIG_AUCTION_LEVEL_REQ)) + { + SendNotification(GetTrinityString(LANG_AUCTION_REQ), sWorld->getIntConfig(CONFIG_AUCTION_LEVEL_REQ)); + return; + } + + AuctionHouseEntry const* ahEntry = AuctionHouseMgr::GetAuctionHouseEntry(unit->getFaction(), nullptr); + if (!ahEntry) + return; + + WorldPackets::AuctionHouse::AuctionHelloResponse auctionHelloResponse; + auctionHelloResponse.Guid = guid; + auctionHelloResponse.OpenForBusiness = true; // 3.3.3: 1 - AH enabled, 0 - AH disabled + SendPacket(auctionHelloResponse.Write()); +} + +void WorldSession::SendAuctionCommandResult(uint32 auctionId, AuctionCommand command, AuctionResult errorCode, Milliseconds delayForNextAction, InventoryResult bagError /*= 0*/) +{ + WorldPackets::AuctionHouse::AuctionCommandResult auctionCommandResult; + auctionCommandResult.AuctionID = auctionId; + auctionCommandResult.Command = AsUnderlyingType(command); + auctionCommandResult.ErrorCode = AsUnderlyingType(errorCode); + auctionCommandResult.BagResult = AsUnderlyingType(bagError); + auctionCommandResult.DesiredDelay = uint32(delayForNextAction.count()); + SendPacket(auctionCommandResult.Write()); +} + +void WorldSession::SendAuctionClosedNotification(AuctionPosting const* auction, float mailDelay, bool sold) +{ + WorldPackets::AuctionHouse::AuctionClosedNotification packet; + packet.Info.Initialize(auction); + packet.ProceedsMailDelay = mailDelay; + packet.Sold = sold; + SendPacket(packet.Write()); +} + +void WorldSession::SendAuctionOwnerBidNotification(AuctionPosting const* auction) +{ + WorldPackets::AuctionHouse::AuctionOwnerBidNotification packet; + packet.Info.Initialize(auction); + packet.Bidder = auction->Bidder; + packet.MinIncrement = auction->CalculateMinIncrement(); + SendPacket(packet.Write()); } |