From 4d349e87b53b4921568163d8189369a62ab2d0f5 Mon Sep 17 00:00:00 2001 From: Takenbacon Date: Wed, 18 Dec 2024 06:50:26 -0800 Subject: [PATCH] feat(Core/AuctionHouse): Rework auctionhouse search threading (#20830) --- src/server/apps/worldserver/Main.cpp | 60 -- .../apps/worldserver/worldserver.conf.dist | 8 +- .../game/AuctionHouse/AuctionHouseMgr.cpp | 564 ++------------ .../game/AuctionHouse/AuctionHouseMgr.h | 79 +- .../AuctionHouse/AuctionHouseSearcher.cpp | 722 ++++++++++++++++++ .../game/AuctionHouse/AuctionHouseSearcher.h | 302 ++++++++ src/server/game/Entities/Player/Player.h | 3 + .../game/Handlers/AuctionHouseHandler.cpp | 196 ++--- src/server/game/Mails/Mail.cpp | 2 +- src/server/game/Misc/AsyncAuctionListing.cpp | 76 -- src/server/game/Misc/AsyncAuctionListing.h | 80 -- src/server/game/Server/WorldSession.cpp | 5 +- src/server/game/Server/WorldSession.h | 4 - src/server/game/World/IWorld.h | 2 +- src/server/game/World/World.cpp | 19 +- src/server/game/World/World.h | 1 - 16 files changed, 1257 insertions(+), 866 deletions(-) create mode 100644 src/server/game/AuctionHouse/AuctionHouseSearcher.cpp create mode 100644 src/server/game/AuctionHouse/AuctionHouseSearcher.h delete mode 100644 src/server/game/Misc/AsyncAuctionListing.cpp delete mode 100644 src/server/game/Misc/AsyncAuctionListing.h diff --git a/src/server/apps/worldserver/Main.cpp b/src/server/apps/worldserver/Main.cpp index 01d182623dedb5..f3db3add62f05a 100644 --- a/src/server/apps/worldserver/Main.cpp +++ b/src/server/apps/worldserver/Main.cpp @@ -22,7 +22,6 @@ #include "ACSoap.h" #include "AppenderDB.h" #include "AsyncAcceptor.h" -#include "AsyncAuctionListing.h" #include "Banner.h" #include "BattlegroundMgr.h" #include "BigNumber.h" @@ -112,7 +111,6 @@ void StopDB(); bool LoadRealmInfo(Acore::Asio::IoContext& ioContext); AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext); void ShutdownCLIThread(std::thread* cliThread); -void AuctionListingRunnable(); void WorldUpdateLoop(); variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, [[maybe_unused]] std::string& cfg_service); @@ -397,15 +395,6 @@ int main(int argc, char** argv) cliThread.reset(new std::thread(CliThread), &ShutdownCLIThread); } - // Launch auction listing thread - std::shared_ptr auctionListingThread; - auctionListingThread.reset(new std::thread(AuctionListingRunnable), - [](std::thread* thr) - { - thr->join(); - delete thr; - }); - WorldUpdateLoop(); // Shutdown starts here @@ -713,55 +702,6 @@ bool LoadRealmInfo(Acore::Asio::IoContext& ioContext) return true; } -void AuctionListingRunnable() -{ - LOG_INFO("server", "Starting up Auction House Listing thread..."); - - while (!World::IsStopped()) - { - Milliseconds diff = AsyncAuctionListingMgr::GetDiff(); - AsyncAuctionListingMgr::ResetDiff(); - - if (!AsyncAuctionListingMgr::GetTempList().empty() || !AsyncAuctionListingMgr::GetList().empty()) - { - { - std::lock_guard guard(AsyncAuctionListingMgr::GetTempLock()); - - for (auto const& delayEvent: AsyncAuctionListingMgr::GetTempList()) - AsyncAuctionListingMgr::GetList().emplace_back(delayEvent); - - AsyncAuctionListingMgr::GetTempList().clear(); - } - - for (auto& itr: AsyncAuctionListingMgr::GetList()) - { - if (itr._pickupTimer <= diff) - { - itr._pickupTimer = Milliseconds::zero(); - } - else - { - itr._pickupTimer -= diff; - } - } - - for (auto itr = AsyncAuctionListingMgr::GetList().begin(); itr != AsyncAuctionListingMgr::GetList().end(); ++itr) - { - if ((*itr)._pickupTimer != Milliseconds::zero()) - continue; - - if ((*itr).Execute()) - AsyncAuctionListingMgr::GetList().erase(itr); - - break; - } - } - std::this_thread::sleep_for(1ms); - } - - LOG_INFO("server", "Auction House Listing thread exiting without problems."); -} - variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, [[maybe_unused]] std::string& configService) { options_description all("Allowed options"); diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index 12fe7bb3ff6ae5..f3754ca07e259f 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -4298,11 +4298,11 @@ Event.Announce = 0 ################################################################################################### # AUCTION HOUSE # -# AuctionHouse.SearchTimeout -# Description: Time (in milliseconds) after which an auction house search is discarded. -# Default: 1000 - (1 second) +# AuctionHouse.WorkerThreads +# Description: Count of auctionhouse searcher worker threads to spawn +# Default: 1 -AuctionHouse.SearchTimeout = 1000 +AuctionHouse.WorkerThreads = 1 # # LevelReq.Auction diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp index 0d2c96d5dd602e..f922e77fc4ec66 100644 --- a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp +++ b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp @@ -16,6 +16,7 @@ */ #include "AuctionHouseMgr.h" +#include "AuctionHouseSearcher.h" #include "Common.h" #include "DBCStores.h" #include "DatabaseEnv.h" @@ -32,194 +33,18 @@ constexpr auto AH_MINIMUM_DEPOSIT = 100; -// Proof of concept, we should shift the info we're obtaining in here into AuctionEntry probably -static bool SortAuction(AuctionEntry* left, AuctionEntry* right, AuctionSortOrderVector& sortOrder, Player* player, bool checkMinBidBuyout) -{ - for (auto& thisOrder : sortOrder) - { - switch (thisOrder.sortOrder) - { - case AUCTION_SORT_BID: - { - if (left->bid == right->bid) - { - if (checkMinBidBuyout) - { - if (left->buyout == right->buyout) - { - if (left->startbid == right->startbid) - { - continue; - } - - return thisOrder.isDesc ? left->startbid > right->startbid : left->startbid < right->startbid; - } - - return thisOrder.isDesc ? left->buyout > right->buyout : left->buyout < right->buyout; - } - - continue; - } - - return thisOrder.isDesc ? left->bid > right->bid : left->bid < right->bid; - } - case AUCTION_SORT_BUYOUT: - case AUCTION_SORT_BUYOUT_2: - { - if (left->buyout == right->buyout) - { - continue; - } - - return thisOrder.isDesc ? left->buyout > right->buyout : left->buyout < right->buyout; - } - case AUCTION_SORT_ITEM: - { - ItemTemplate const* protoLeft = sObjectMgr->GetItemTemplate(left->item_template); - ItemTemplate const* protoRight = sObjectMgr->GetItemTemplate(right->item_template); - if (!protoLeft || !protoRight) - { - continue; - } - - std::string leftName = protoLeft->Name1; - std::string rightName = protoRight->Name1; - if (leftName.empty() || rightName.empty()) - { - continue; - } - - LocaleConstant locale = LOCALE_enUS; - if (player && player->GetSession()) - { - locale = player->GetSession()->GetSessionDbLocaleIndex(); - } - - if (locale > LOCALE_enUS) - { - if (ItemLocale const* leftIl = sObjectMgr->GetItemLocale(protoLeft->ItemId)) - { - ObjectMgr::GetLocaleString(leftIl->Name, locale, leftName); - } - - if (ItemLocale const* rightIl = sObjectMgr->GetItemLocale(protoRight->ItemId)) - { - ObjectMgr::GetLocaleString(rightIl->Name, locale, rightName); - } - } - - int result = leftName.compare(rightName); - if (result == 0) - { - continue; - } - - return thisOrder.isDesc ? result > 0 : result < 0; - } - case AUCTION_SORT_MINLEVEL: - { - ItemTemplate const* protoLeft = sObjectMgr->GetItemTemplate(left->item_template); - ItemTemplate const* protoRight = sObjectMgr->GetItemTemplate(right->item_template); - if (!protoLeft || !protoRight) - { - continue; - } - - if (protoLeft->RequiredLevel == protoRight->RequiredLevel) - { - continue; - } - - return thisOrder.isDesc ? protoLeft->RequiredLevel > protoRight->RequiredLevel : protoLeft->RequiredLevel < protoRight->RequiredLevel; - } - case AUCTION_SORT_OWNER: - { - std::string leftName; - sCharacterCache->GetCharacterNameByGuid(left->owner, leftName); - - std::string rightName; - sCharacterCache->GetCharacterNameByGuid(right->owner, rightName); - - int result = leftName.compare(rightName); - if (result == 0) - { - continue; - } - - return thisOrder.isDesc ? result > 0 : result < 0; - } - case AUCTION_SORT_RARITY: - { - ItemTemplate const* protoLeft = sObjectMgr->GetItemTemplate(left->item_template); - ItemTemplate const* protoRight = sObjectMgr->GetItemTemplate(right->item_template); - if (!protoLeft || !protoRight) - { - continue; - } - - if (protoLeft->Quality == protoRight->Quality) - { - continue; - } - - return thisOrder.isDesc ? protoLeft->Quality > protoRight->Quality : protoLeft->Quality < protoRight->Quality; - } - case AUCTION_SORT_STACK: - { - if (left->itemCount == right->itemCount) - { - continue; - } - - if (!thisOrder.isDesc) - { - return (left->itemCount < right->itemCount); - } - - return (left->itemCount > right->itemCount); - } - case AUCTION_SORT_TIMELEFT: - { - if (left->expire_time == right->expire_time) - { - continue; - } - - return thisOrder.isDesc ? left->expire_time > right->expire_time : left->expire_time < right->expire_time; - } - case AUCTION_SORT_MINBIDBUY: - { - if (left->buyout == right->buyout) - { - if (left->startbid == right->startbid) - { - continue; - } - - return thisOrder.isDesc ? left->startbid > right->startbid : left->startbid < right->startbid; - } - - return thisOrder.isDesc ? left->buyout > right->buyout : left->buyout < right->buyout; - } - case AUCTION_SORT_MAX: - // Such sad travis appeasement - case AUCTION_SORT_UNK4: - default: - break; - } - } - - return false; -} - -AuctionHouseMgr::AuctionHouseMgr() +AuctionHouseMgr::AuctionHouseMgr() : _auctionHouseSearcher(new AuctionHouseSearcher()) { + _updateIntervalTimer.SetInterval(MINUTE * IN_MILLISECONDS); + _updateIntervalTimer.SetCurrent(MINUTE * IN_MILLISECONDS); } AuctionHouseMgr::~AuctionHouseMgr() { for (ItemMap::iterator itr = _mAitems.begin(); itr != _mAitems.end(); ++itr) delete itr->second; + + delete _auctionHouseSearcher; } AuctionHouseMgr* AuctionHouseMgr::instance() @@ -245,17 +70,19 @@ AuctionHouseObject* AuctionHouseMgr::GetAuctionsMap(uint32 factionTemplateId) return &_neutralAuctions; } -AuctionHouseObject* AuctionHouseMgr::GetAuctionsMapByHouseId(uint8 auctionHouseId) +AuctionHouseObject* AuctionHouseMgr::GetAuctionsMapByHouseId(AuctionHouseId auctionHouseId) { if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) return &_neutralAuctions; switch (auctionHouseId) { - case AUCTIONHOUSE_ALLIANCE: + case AuctionHouseId::Alliance: return &_allianceAuctions; - case AUCTIONHOUSE_HORDE: + case AuctionHouseId::Horde: return &_hordeAuctions; + case AuctionHouseId::Neutral: + default: break; } @@ -294,13 +121,9 @@ void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry* auction, CharacterDatabas uint32 bidder_accId = 0; Player* bidder = ObjectAccessor::FindConnectedPlayer(auction->bidder); if (bidder) - { bidder_accId = bidder->GetSession()->GetAccountId(); - } else - { bidder_accId = sCharacterCache->GetCharacterAccountIdByGuid(auction->bidder); - } // receiver exist if (bidder || bidder_accId) @@ -316,20 +139,20 @@ void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry* auction, CharacterDatabas if (bidder) { if (sendNotification) // can be changed in the hook - bidder->GetSession()->SendAuctionBidderNotification(auction->GetHouseId(), auction->Id, auction->bidder, 0, 0, auction->item_template); + bidder->GetSession()->SendAuctionBidderNotification((uint32)auction->GetHouseId(), auction->Id, auction->bidder, 0, 0, auction->item_template); if (updateAchievementCriteria) // can be changed in the hook bidder->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WON_AUCTIONS, 1); } else if (updateAchievementCriteria) - { sAchievementMgr->UpdateAchievementCriteriaForOfflinePlayer(auction->bidder.GetCounter(), ACHIEVEMENT_CRITERIA_TYPE_WON_AUCTIONS, 1); - } if (sendMail) // can be changed in the hook + { MailDraft(auction->BuildAuctionMailSubject(AUCTION_WON), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout)) - .AddItem(pItem) - .SendMailTo(trans, MailReceiver(bidder, auction->bidder.GetCounter()), auction, MAIL_CHECK_MASK_COPIED); + .AddItem(pItem) + .SendMailTo(trans, MailReceiver(bidder, auction->bidder.GetCounter()), auction, MAIL_CHECK_MASK_COPIED); + } } else sAuctionMgr->RemoveAItem(auction->item_guid, true, &trans); @@ -426,9 +249,11 @@ void AuctionHouseMgr::SendAuctionExpiredMail(AuctionEntry* auction, CharacterDat owner->GetSession()->SendAuctionOwnerNotification(auction); if (sendMail) // can be changed in the hook + { MailDraft(auction->BuildAuctionMailSubject(AUCTION_EXPIRED), AuctionEntry::BuildAuctionMailBody(ObjectGuid::Empty, 0, auction->buyout, auction->deposit)) - .AddItem(pItem) - .SendMailTo(trans, MailReceiver(owner, auction->owner.GetCounter()), auction, MAIL_CHECK_MASK_COPIED, 0); + .AddItem(pItem) + .SendMailTo(trans, MailReceiver(owner, auction->owner.GetCounter()), auction, MAIL_CHECK_MASK_COPIED, 0); + } } else sAuctionMgr->RemoveAItem(auction->item_guid, true, &trans); @@ -449,7 +274,7 @@ void AuctionHouseMgr::SendAuctionOutbiddedMail(AuctionEntry* auction, uint32 new sScriptMgr->OnBeforeAuctionHouseMgrSendAuctionOutbiddedMail(this, auction, oldBidder, oldBidder_accId, newBidder, newPrice, sendNotification, sendMail); if (oldBidder && newBidder && sendNotification) // can be changed in the hook - oldBidder->GetSession()->SendAuctionBidderNotification(auction->GetHouseId(), auction->Id, newBidder->GetGUID(), newPrice, auction->GetAuctionOutBid(), auction->item_template); + oldBidder->GetSession()->SendAuctionBidderNotification((uint32)auction->GetHouseId(), auction->Id, newBidder->GetGUID(), newPrice, AuctionEntry::CalculateAuctionOutBid(auction->bid), auction->item_template); if (sendMail) // can be changed in the hook MailDraft(auction->BuildAuctionMailSubject(AUCTION_OUTBIDDED), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut())) @@ -564,7 +389,7 @@ void AuctionHouseMgr::LoadAuctions() continue; } - GetAuctionsMapByHouseId(aItem->houseId)->AddAuction(aItem); + GetAuctionsMapByHouseId(AuctionHouseId(aItem->houseId))->AddAuction(aItem); count++; } while (result->NextRow()); @@ -598,40 +423,57 @@ bool AuctionHouseMgr::RemoveAItem(ObjectGuid itemGuid, bool deleteFromDB, Charac return true; } -void AuctionHouseMgr::Update() +void AuctionHouseMgr::Update(uint32 const diff) { - sScriptMgr->OnBeforeAuctionHouseMgrUpdate(); - _hordeAuctions.Update(); - _allianceAuctions.Update(); - _neutralAuctions.Update(); + _updateIntervalTimer.Update(diff); + if (_updateIntervalTimer.Passed()) + { + sScriptMgr->OnBeforeAuctionHouseMgrUpdate(); + + _hordeAuctions.Update(); + _allianceAuctions.Update(); + _neutralAuctions.Update(); + + _updateIntervalTimer.Reset(); + } + + _auctionHouseSearcher->Update(); } -AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntry(uint32 factionTemplateId) +AuctionHouseFaction AuctionHouseMgr::GetAuctionHouseFactionFromHouseId(AuctionHouseId ahHouseId) { - uint32 houseid = AUCTIONHOUSE_NEUTRAL; // goblin auction house - - if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) + switch (ahHouseId) { - //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 - FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(factionTemplateId); - if (!u_entry) - houseid = AUCTIONHOUSE_NEUTRAL; // goblin auction house - else if (u_entry->ourMask & FACTION_MASK_ALLIANCE) - houseid = AUCTIONHOUSE_ALLIANCE; // human auction house - else if (u_entry->ourMask & FACTION_MASK_HORDE) - houseid = AUCTIONHOUSE_HORDE; // orc auction house - else - houseid = AUCTIONHOUSE_NEUTRAL; // goblin auction house + case AuctionHouseId::Alliance: + return AuctionHouseFaction::Alliance; + case AuctionHouseId::Horde: + return AuctionHouseFaction::Horde; + case AuctionHouseId::Neutral: + return AuctionHouseFaction::Neutral; } + return AuctionHouseFaction::Neutral; +} - return sAuctionHouseStore.LookupEntry(houseid); +AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntryFromFactionTemplate(uint32 factionTemplateId) +{ + AuctionHouseId houseid; + FactionTemplateEntry const* uEntry = sFactionTemplateStore.LookupEntry(factionTemplateId); + + if (!uEntry || sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) + houseid = AuctionHouseId::Neutral; + else if (uEntry->ourMask & FACTION_MASK_ALLIANCE) + houseid = AuctionHouseId::Alliance; + else if (uEntry->ourMask & FACTION_MASK_HORDE) + houseid = AuctionHouseId::Horde; + else + houseid = AuctionHouseId::Neutral; + + return sAuctionHouseStore.LookupEntry((uint32)houseid); } -AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntryFromHouse(uint8 houseId) +AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntryFromHouse(AuctionHouseId ahHouseId) { - return (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) ? sAuctionHouseStore.LookupEntry(AUCTIONHOUSE_NEUTRAL) : sAuctionHouseStore.LookupEntry(houseId); + return (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) ? sAuctionHouseStore.LookupEntry((uint32)AuctionHouseId::Neutral) : sAuctionHouseStore.LookupEntry((uint32)ahHouseId); } void AuctionHouseObject::AddAuction(AuctionEntry* auction) @@ -639,12 +481,15 @@ void AuctionHouseObject::AddAuction(AuctionEntry* auction) ASSERT(auction); _auctionsMap[auction->Id] = auction; + sAuctionMgr->GetAuctionHouseSearcher()->AddAuction(auction); + sScriptMgr->OnAuctionAdd(this, auction); } bool AuctionHouseObject::RemoveAuction(AuctionEntry* auction) { bool wasInMap = !!_auctionsMap.erase(auction->Id); + sAuctionMgr->GetAuctionHouseSearcher()->RemoveAuction(auction); sScriptMgr->OnAuctionRemove(this, auction); @@ -700,273 +545,9 @@ void AuctionHouseObject::Update() CharacterDatabase.CommitTransaction(trans); } -void AuctionHouseObject::BuildListBidderItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount) -{ - for (AuctionEntryMap::const_iterator itr = _auctionsMap.begin(); itr != _auctionsMap.end(); ++itr) - { - AuctionEntry* Aentry = itr->second; - if (Aentry && Aentry->bidder == player->GetGUID()) - { - if (itr->second->BuildAuctionInfo(data)) - ++count; - - ++totalcount; - } - } -} - -void AuctionHouseObject::BuildListOwnerItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount) +AuctionHouseFaction AuctionEntry::GetFactionId() const { - for (AuctionEntryMap::const_iterator itr = _auctionsMap.begin(); itr != _auctionsMap.end(); ++itr) - { - AuctionEntry* Aentry = itr->second; - if (Aentry && Aentry->owner == player->GetGUID()) - { - if (Aentry->BuildAuctionInfo(data)) - ++count; - - ++totalcount; - } - } -} - -bool AuctionHouseObject::BuildListAuctionItems(WorldPacket& data, Player* player, - std::wstring const& wsearchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, uint8 usable, - uint32 inventoryType, uint32 itemClass, uint32 itemSubClass, uint32 quality, - uint32& count, uint32& totalcount, uint8 /*getAll*/, AuctionSortOrderVector const& sortOrder, Milliseconds searchTimeout) -{ - uint32 itrcounter = 0; - - // Ensures that listfrom is not greater that auctions count - listfrom = std::min(listfrom, static_cast(GetAuctions().size())); - - std::vector auctionShortlist; - - // pussywizard: optimization, this is a simplified case - if (itemClass == 0xffffffff && itemSubClass == 0xffffffff && inventoryType == 0xffffffff && quality == 0xffffffff && levelmin == 0x00 && levelmax == 0x00 && usable == 0x00 && wsearchedname.empty()) - { - auto itr = GetAuctionsBegin(); - for (; itr != GetAuctionsEnd(); ++itr) - { - auctionShortlist.push_back(itr->second); - } - } - else - { - auto curTime = GameTime::GetGameTime(); - - int loc_idx = player->GetSession()->GetSessionDbLocaleIndex(); - int locdbc_idx = player->GetSession()->GetSessionDbcLocale(); - - for (AuctionEntryMap::const_iterator itr = _auctionsMap.begin(); itr != _auctionsMap.end(); ++itr) - { - if ((itrcounter++) % 100 == 0) // check condition every 100 iterations - { - if (GetMSTimeDiff(GameTime::GetGameTimeMS(), GetTimeMS()) >= searchTimeout) // pussywizard: stop immediately if diff is high or waiting too long - { - return false; - } - } - - AuctionEntry* Aentry = itr->second; - if (!Aentry) - return false; - - // Skip expired auctions - if (Aentry->expire_time < curTime.count()) - { - continue; - } - - Item* item = sAuctionMgr->GetAItem(Aentry->item_guid); - if (!item) - { - continue; - } - - ItemTemplate const* proto = item->GetTemplate(); - if (itemClass != 0xffffffff && proto->Class != itemClass) - { - continue; - } - - if (itemSubClass != 0xffffffff && proto->SubClass != itemSubClass) - { - continue; - } - - if (inventoryType != 0xffffffff && proto->InventoryType != inventoryType) - { - // xinef: exception, robes are counted as chests - if (inventoryType != INVTYPE_CHEST || proto->InventoryType != INVTYPE_ROBE) - { - continue; - } - } - - if (quality != 0xffffffff && proto->Quality < quality) - { - continue; - } - - if (levelmin != 0x00 && (proto->RequiredLevel < levelmin || (levelmax != 0x00 && proto->RequiredLevel > levelmax))) - { - continue; - } - - if (usable != 0x00) - { - if (player->CanUseItem(item) != EQUIP_ERR_OK) - { - continue; - } - - // xinef: check already learded recipes and pets - if (proto->Spells[1].SpellTrigger == ITEM_SPELLTRIGGER_LEARN_SPELL_ID && player->HasSpell(proto->Spells[1].SpellId)) - { - 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 (!wsearchedname.empty()) - { - std::string name = proto->Name1; - if (name.empty()) - { - continue; - } - - // local name - if (loc_idx >= 0) - if (ItemLocale const* il = sObjectMgr->GetItemLocale(proto->ItemId)) - ObjectMgr::GetLocaleString(il->Name, loc_idx, name); - - // DO NOT use GetItemEnchantMod(proto->RandomProperty) as it may return a result - // that matches the search but it may not equal item->GetItemRandomPropertyId() - // used in BuildAuctionInfo() which then causes wrong items to be listed - int32 propRefID = item->GetItemRandomPropertyId(); - - if (propRefID) - { - // Append the suffix to the name (ie: of the Monkey) if one exists - // These are found in ItemRandomSuffix.dbc and ItemRandomProperties.dbc - // even though the DBC name seems misleading - std::array const* suffix = nullptr; - - if (propRefID < 0) - { - ItemRandomSuffixEntry const* itemRandEntry = sItemRandomSuffixStore.LookupEntry(-item->GetItemRandomPropertyId()); - if (itemRandEntry) - suffix = &itemRandEntry->Name; - } - else - { - ItemRandomPropertiesEntry const* itemRandEntry = sItemRandomPropertiesStore.LookupEntry(item->GetItemRandomPropertyId()); - if (itemRandEntry) - suffix = &itemRandEntry->Name; - } - - // dbc local name - if (suffix) - { - // Append the suffix (ie: of the Monkey) to the name using localization - // or default enUS if localization is invalid - name += ' '; - name += (*suffix)[locdbc_idx >= 0 ? locdbc_idx : LOCALE_enUS]; - } - } - - // Perform the search (with or without suffix) - if (!Utf8FitTo(name, wsearchedname)) - { - continue; - } - } - - auctionShortlist.push_back(Aentry); - } - } - - if (auctionShortlist.empty()) - { - return true; - } - - // Check if sort enabled, and first sort column is valid, if not don't sort - if (!sortOrder.empty()) - { - AuctionSortInfo const& sortInfo = *sortOrder.begin(); - if (sortInfo.sortOrder >= AUCTION_SORT_MINLEVEL && sortInfo.sortOrder < AUCTION_SORT_MAX && sortInfo.sortOrder != AUCTION_SORT_UNK4) - { - // Partial sort to improve performance a bit, but the last pages will burn - if (listfrom + 50 <= auctionShortlist.size()) - { - std::partial_sort(auctionShortlist.begin(), auctionShortlist.begin() + listfrom + 50, auctionShortlist.end(), - std::bind(SortAuction, std::placeholders::_1, std::placeholders::_2, sortOrder, player, sortInfo.sortOrder == AUCTION_SORT_BID)); - } - else - { - std::sort(auctionShortlist.begin(), auctionShortlist.end(), std::bind(SortAuction, std::placeholders::_1, std::placeholders::_2, sortOrder, - player, sortInfo.sortOrder == AUCTION_SORT_BID)); - } - } - } - - for (auto& auction : auctionShortlist) - { - // Add the item if no search term or if entered search term was found - if (count < 50 && totalcount >= listfrom) - { - Item* item = sAuctionMgr->GetAItem(auction->item_guid); - if (!item) - { - continue; - } - - ++count; - auction->BuildAuctionInfo(data); - } - ++totalcount; - } - - return true; -} - -//this function inserts to WorldPacket auction's data -bool AuctionEntry::BuildAuctionInfo(WorldPacket& data) const -{ - Item* item = sAuctionMgr->GetAItem(item_guid); - if (!item) - { - LOG_ERROR("auctionHouse", "AuctionEntry::BuildAuctionInfo: Auction {} has a non-existent item: {}", Id, item_guid.ToString()); - return false; - } - data << uint32(Id); - data << uint32(item->GetEntry()); - - for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; ++i) - { - data << uint32(item->GetEnchantmentId(EnchantmentSlot(i))); - data << uint32(item->GetEnchantmentDuration(EnchantmentSlot(i))); - data << uint32(item->GetEnchantmentCharges(EnchantmentSlot(i))); - } - - data << int32(item->GetItemRandomPropertyId()); // Random item property id - data << uint32(item->GetItemSuffixFactor()); // SuffixFactor - data << uint32(item->GetCount()); // item->count - data << uint32(item->GetSpellCharges()); // item->charge FFFFFFF - data << uint32(0); // Unknown - data << owner; // Auction->owner - data << uint32(startbid); // Auction->startbid (not sure if useful) - data << uint32(bid ? GetAuctionOutBid() : 0); - // Minimal outbid - data << uint32(buyout); // Auction->buyout - data << uint32((expire_time - GameTime::GetGameTime().count()) * IN_MILLISECONDS); // time left - data << bidder; // auction->bidder current - data << uint32(bid); // current bid - return true; + return AuctionHouseMgr::GetAuctionHouseFactionFromHouseId(houseId); } uint32 AuctionEntry::GetAuctionCut() const @@ -975,8 +556,13 @@ uint32 AuctionEntry::GetAuctionCut() const return std::max(cut, 0); } -/// the sum of outbid is (1% from current bid)*5, if bid is very small, it is 1c uint32 AuctionEntry::GetAuctionOutBid() const +{ + return CalculateAuctionOutBid(bid); +} + +/// the sum of outbid is (1% from current bid)*5, if bid is very small, it is 1c +uint32 AuctionEntry::CalculateAuctionOutBid(uint32 bid) { uint32 outbid = CalculatePct(bid, 5); return outbid ? outbid : 1; @@ -1008,7 +594,7 @@ void AuctionEntry::SaveToDB(CharacterDatabaseTransaction trans) const bool AuctionEntry::LoadFromDB(Field* fields) { Id = fields[0].Get(); - houseId = fields[1].Get(); + houseId = AuctionHouseId(fields[1].Get()); item_guid = ObjectGuid::Create(fields[2].Get()); item_template = fields[3].Get(); itemCount = fields[4].Get(); diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.h b/src/server/game/AuctionHouse/AuctionHouseMgr.h index d765cf1213ac87..6045d5a2c3d7d4 100644 --- a/src/server/game/AuctionHouse/AuctionHouseMgr.h +++ b/src/server/game/AuctionHouse/AuctionHouseMgr.h @@ -23,14 +23,28 @@ #include "DatabaseEnv.h" #include "EventProcessor.h" #include "ObjectGuid.h" +#include "Timer.h" #include "WorldPacket.h" #include class Item; class Player; +class AuctionHouseSearcher; #define MIN_AUCTION_TIME (12*HOUR) #define MAX_AUCTION_ITEMS 160 +#define MAX_AUCTIONS_PER_PAGE 50 +#define AUCTION_SEARCH_DELAY 300 + +/* + The max allowable single packet size in 3.3.5 client protocol is 0x7FFFFF. A single BuildAuctionInfo structure + has a size of 148 bytes. 148 * 55000 = 8140000 which gives us just under the max size of 8388607 with a little + bit of margin. + + Reference: https://wowpedia.fandom.com/wiki/API_QueryAuctionItems + "In 4.0.1, getAll mode only fetches up to 42554 items. This is usually adequate, but high-population realms might have more." +*/ +#define MAX_GETALL_RETURN 55000 enum AuctionError { @@ -63,44 +77,26 @@ enum MailAuctionAnswers AUCTION_SALE_PENDING = 6 }; -enum AuctionHouses -{ - AUCTIONHOUSE_ALLIANCE = 2, - AUCTIONHOUSE_HORDE = 6, - AUCTIONHOUSE_NEUTRAL = 7 -}; - -enum AuctionSortOrder +enum class AuctionHouseFaction : uint8 { - AUCTION_SORT_MINLEVEL = 0, - AUCTION_SORT_RARITY = 1, - AUCTION_SORT_BUYOUT = 2, - AUCTION_SORT_TIMELEFT = 3, - AUCTION_SORT_UNK4 = 4, - AUCTION_SORT_ITEM = 5, - AUCTION_SORT_MINBIDBUY = 6, - AUCTION_SORT_OWNER = 7, - AUCTION_SORT_BID = 8, - AUCTION_SORT_STACK = 9, - AUCTION_SORT_BUYOUT_2 = 10, - - AUCTION_SORT_MAX + Alliance, + Horde, + Neutral }; -struct AuctionSortInfo +enum class AuctionHouseId : uint8 { - AuctionSortInfo() = default; - - AuctionSortOrder sortOrder{AUCTION_SORT_MAX}; - bool isDesc{true}; + Alliance = 2, + Horde = 6, + Neutral = 7 }; -typedef std::vector AuctionSortOrderVector; +#define MAX_AUCTION_HOUSE_FACTIONS 3 struct AuctionEntry { uint32 Id; - uint8 houseId; + AuctionHouseId houseId; ObjectGuid item_guid; uint32 item_template; uint32 itemCount; @@ -114,10 +110,11 @@ struct AuctionEntry AuctionHouseEntry const* auctionHouseEntry; // in AuctionHouse.dbc // helpers - [[nodiscard]] uint8 GetHouseId() const { return houseId; } + [[nodiscard]] AuctionHouseId GetHouseId() const { return houseId; } + [[nodiscard]] AuctionHouseFaction GetFactionId() const; [[nodiscard]] uint32 GetAuctionCut() const; [[nodiscard]] uint32 GetAuctionOutBid() const; - bool BuildAuctionInfo(WorldPacket& data) const; + [[nodiscard]] static uint32 CalculateAuctionOutBid(uint32 bid); void DeleteFromDB(CharacterDatabaseTransaction trans) const; void SaveToDB(CharacterDatabaseTransaction trans) const; bool LoadFromDB(Field* fields); @@ -157,13 +154,6 @@ class AuctionHouseObject void Update(); - void BuildListBidderItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount); - void BuildListOwnerItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount); - bool BuildListAuctionItems(WorldPacket& data, Player* player, - std::wstring const& searchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, uint8 usable, - uint32 inventoryType, uint32 itemClass, uint32 itemSubClass, uint32 quality, - uint32& count, uint32& totalcount, uint8 getAll, AuctionSortOrderVector const& sortOrder, Milliseconds searchTimeout); - private: AuctionEntryMap _auctionsMap; @@ -183,7 +173,7 @@ class AuctionHouseMgr static AuctionHouseMgr* instance(); AuctionHouseObject* GetAuctionsMap(uint32 factionTemplateId); - AuctionHouseObject* GetAuctionsMapByHouseId(uint8 auctionHouseId); + AuctionHouseObject* GetAuctionsMapByHouseId(AuctionHouseId auctionHouseId); Item* GetAItem(ObjectGuid itemGuid) { @@ -203,8 +193,11 @@ class AuctionHouseMgr void SendAuctionCancelledToBidderMail(AuctionEntry* auction, CharacterDatabaseTransaction trans, bool sendMail = true); static uint32 GetAuctionDeposit(AuctionHouseEntry const* entry, uint32 time, Item* pItem, uint32 count); - static AuctionHouseEntry const* GetAuctionHouseEntry(uint32 factionTemplateId); - static AuctionHouseEntry const* GetAuctionHouseEntryFromHouse(uint8 houseId); + static AuctionHouseFaction GetAuctionHouseFactionFromHouseId(AuctionHouseId ahHouseId); + static AuctionHouseEntry const* GetAuctionHouseEntryFromFactionTemplate(uint32 factionTemplateId); + static AuctionHouseEntry const* GetAuctionHouseEntryFromHouse(AuctionHouseId ahHouseId); + + AuctionHouseSearcher* GetAuctionHouseSearcher() { return _auctionHouseSearcher; } public: //load first auction items, because of check if item exists, when loading @@ -214,7 +207,7 @@ class AuctionHouseMgr void AddAItem(Item* it); bool RemoveAItem(ObjectGuid itemGuid, bool deleteFromDB = false, CharacterDatabaseTransaction* trans = nullptr); - void Update(); + void Update(uint32 const diff); private: AuctionHouseObject _hordeAuctions; @@ -222,6 +215,10 @@ class AuctionHouseMgr AuctionHouseObject _neutralAuctions; ItemMap _mAitems; + + AuctionHouseSearcher* _auctionHouseSearcher; + + IntervalTimer _updateIntervalTimer; }; #define sAuctionMgr AuctionHouseMgr::instance() diff --git a/src/server/game/AuctionHouse/AuctionHouseSearcher.cpp b/src/server/game/AuctionHouse/AuctionHouseSearcher.cpp new file mode 100644 index 00000000000000..f1cda1fb14f833 --- /dev/null +++ b/src/server/game/AuctionHouse/AuctionHouseSearcher.cpp @@ -0,0 +1,722 @@ +/* + * This file is part of the AzerothCore 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 Affero General Public License as published by the + * Free Software Foundation; either version 3 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 Affero 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 "AuctionHouseMgr.h" +#include "AuctionHouseSearcher.h" +#include "CharacterCache.h" +#include "DBCStores.h" +#include "GameTime.h" +#include "Player.h" + +AuctionHouseWorkerThread::AuctionHouseWorkerThread(ProducerConsumerQueue* requestQueue, MPSCQueue* responseQueue) +{ + _workerThread = std::thread(&AuctionHouseWorkerThread::Run, this); + _requestQueue = requestQueue; + _responseQueue = responseQueue; + _stopped = false; +} + +void AuctionHouseWorkerThread::Stop() +{ + _stopped = true; + _workerThread.join(); +} + +void AuctionHouseWorkerThread::AddAuctionSearchUpdateToQueue(std::shared_ptr const auctionSearchUpdate) +{ + _auctionUpdatesQueue.add(auctionSearchUpdate); +} + +void AuctionHouseWorkerThread::Run() +{ + while (!_stopped) + { + std::this_thread::sleep_for(Milliseconds(25)); + + ProcessSearchUpdates(); + ProcessSearchRequests(); + } +} + +void AuctionHouseWorkerThread::ProcessSearchUpdates() +{ + std::shared_ptr auctionSearchUpdate; + while (_auctionUpdatesQueue.next(auctionSearchUpdate)) + { + switch (auctionSearchUpdate->updateType) + { + case AuctionSearcherUpdate::Type::ADD: + { + std::shared_ptr const auctionAdd = std::static_pointer_cast(auctionSearchUpdate); + SearchUpdateAdd(*auctionAdd.get()); + break; + } + case AuctionSearcherUpdate::Type::REMOVE: + { + std::shared_ptr const auctionRemove = std::static_pointer_cast(auctionSearchUpdate); + SearchUpdateRemove(*auctionRemove.get()); + break; + } + case AuctionSearcherUpdate::Type::UPDATE_BID: + { + std::shared_ptr const auctionUpdateBid = std::static_pointer_cast(auctionSearchUpdate); + SearchUpdateBid(*auctionUpdateBid.get()); + break; + } + default: + break; + } + } +} + +void AuctionHouseWorkerThread::SearchUpdateAdd(AuctionSearchAdd const& auctionAdd) +{ + SearchableAuctionEntriesMap& searchableAuctionMap = GetSearchableAuctionMap(auctionAdd.listFaction); + searchableAuctionMap.insert(std::make_pair(auctionAdd.searchableAuctionEntry->Id, auctionAdd.searchableAuctionEntry)); +} + +void AuctionHouseWorkerThread::SearchUpdateRemove(AuctionSearchRemove const& auctionRemove) +{ + SearchableAuctionEntriesMap& searchableAuctionMap = GetSearchableAuctionMap(auctionRemove.listFaction); + searchableAuctionMap.erase(auctionRemove.auctionId); +} + +void AuctionHouseWorkerThread::SearchUpdateBid(AuctionSearchUpdateBid const& auctionUpdateBid) +{ + SearchableAuctionEntriesMap const& searchableAuctionMap = GetSearchableAuctionMap(auctionUpdateBid.listFaction); + SearchableAuctionEntriesMap::const_iterator itr = searchableAuctionMap.find(auctionUpdateBid.auctionId); + if (itr != searchableAuctionMap.end()) + { + itr->second->bid = auctionUpdateBid.bid; + itr->second->bidderGuid = auctionUpdateBid.bidderGuid; + } +} + +void AuctionHouseWorkerThread::ProcessSearchRequests() +{ + AuctionSearcherRequest* searchRequest; + while (_requestQueue->Pop(searchRequest)) + { + switch (searchRequest->requestType) + { + case AuctionSearcherRequest::Type::LIST: + { + AuctionSearchListRequest const* searchListRequest = static_cast(searchRequest); + SearchListRequest(*searchListRequest); + break; + } + case AuctionSearcherRequest::Type::OWNER_LIST: + { + AuctionSearchOwnerListRequest const* searchOwnerListRequest = static_cast(searchRequest); + SearchOwnerListRequest(*searchOwnerListRequest); + break; + } + case AuctionSearcherRequest::Type::BIDDER_LIST: + { + AuctionSearchBidderListRequest const* searchBidderListRequest = static_cast(searchRequest); + SearchBidderListRequest(*searchBidderListRequest); + break; + } + default: + break; + } + + delete searchRequest; + } +} + +void AuctionHouseWorkerThread::SearchListRequest(AuctionSearchListRequest const& searchListRequest) +{ + SearchableAuctionEntriesMap const& searchableAuctionMap = GetSearchableAuctionMap(searchListRequest.listFaction); + uint32 count = 0, totalCount = 0; + + AuctionSearcherResponse* searchResponse = new AuctionSearcherResponse(); + searchResponse->playerGuid = searchListRequest.playerInfo.playerGuid; + searchResponse->packet.Initialize(SMSG_AUCTION_LIST_RESULT, (4 + 4 + 4)); + searchResponse->packet << (uint32)0; + + if (!searchListRequest.searchInfo.getAll) + { + SortableAuctionEntriesList auctionEntries; + BuildListAuctionItems(searchListRequest, auctionEntries, searchableAuctionMap); + + if (!searchListRequest.searchInfo.sorting.empty() && auctionEntries.size() > MAX_AUCTIONS_PER_PAGE) + { + AuctionSorter sorter(&searchListRequest.searchInfo.sorting, searchListRequest.playerInfo.locdbc_idx); + std::sort(auctionEntries.begin(), auctionEntries.end(), sorter); + } + + SortableAuctionEntriesList::const_iterator itr = auctionEntries.begin(); + if (searchListRequest.searchInfo.listfrom) + { + if (searchListRequest.searchInfo.listfrom > auctionEntries.size()) + itr = auctionEntries.end(); + else + itr += searchListRequest.searchInfo.listfrom; + } + + for (; itr != auctionEntries.end(); ++itr) + { + (*itr)->BuildAuctionInfo(searchResponse->packet); + + if (++count >= MAX_AUCTIONS_PER_PAGE) + break; + } + + totalCount = auctionEntries.size(); + } + else + { + // getAll handling + for (auto const& pair : searchableAuctionMap) + { + std::shared_ptr const& Aentry = pair.second; + ++count; + Aentry->BuildAuctionInfo(searchResponse->packet); + + if (count >= MAX_GETALL_RETURN) + break; + } + + totalCount = searchableAuctionMap.size(); + } + + searchResponse->packet.put(0, count); + searchResponse->packet << totalCount; + searchResponse->packet << uint32(AUCTION_SEARCH_DELAY); + + _responseQueue->Enqueue(searchResponse); +} + +void AuctionHouseWorkerThread::SearchOwnerListRequest(AuctionSearchOwnerListRequest const& searchOwnerListRequest) +{ + SearchableAuctionEntriesMap const& searchableAuctionMap = GetSearchableAuctionMap(searchOwnerListRequest.listFaction); + + AuctionSearcherResponse* searchResponse = new AuctionSearcherResponse(); + searchResponse->playerGuid = searchOwnerListRequest.ownerGuid; + searchResponse->packet.Initialize(SMSG_AUCTION_OWNER_LIST_RESULT, (4 + 4 + 4)); + searchResponse->packet << (uint32)0; // amount place holder + + uint32 count = 0; + uint32 totalcount = 0; + + for (auto const& pair : searchableAuctionMap) + { + if (pair.second->ownerGuid != searchOwnerListRequest.ownerGuid) + continue; + + std::shared_ptr const& auctionEntry = pair.second; + auctionEntry->BuildAuctionInfo(searchResponse->packet); + ++count; + ++totalcount; + } + + searchResponse->packet.put(0, count); + searchResponse->packet << (uint32)totalcount; + searchResponse->packet << uint32(AUCTION_SEARCH_DELAY); + + _responseQueue->Enqueue(searchResponse); +} + +void AuctionHouseWorkerThread::SearchBidderListRequest(AuctionSearchBidderListRequest const& searchBidderListRequest) +{ + SearchableAuctionEntriesMap const& searchableAuctionMap = GetSearchableAuctionMap(searchBidderListRequest.listFaction); + + AuctionSearcherResponse* searchResponse = new AuctionSearcherResponse(); + searchResponse->playerGuid = searchBidderListRequest.ownerGuid; + searchResponse->packet.Initialize(SMSG_AUCTION_BIDDER_LIST_RESULT, (4 + 4 + 4)); + searchResponse->packet << (uint32)0; //add 0 as count + + uint32 count = 0; + uint32 totalcount = 0; + + for (uint32 const auctionId : searchBidderListRequest.outbiddedAuctionIds) + { + SearchableAuctionEntriesMap::const_iterator itr = searchableAuctionMap.find(auctionId); + if (itr == searchableAuctionMap.end()) + continue; + + std::shared_ptr const& auctionEntry = itr->second; + auctionEntry->BuildAuctionInfo(searchResponse->packet); + ++count; + ++totalcount; + } + + for (auto const& pair : searchableAuctionMap) + { + if (pair.second->bidderGuid != searchBidderListRequest.ownerGuid) + continue; + + std::shared_ptr const& auctionEntry = pair.second; + auctionEntry->BuildAuctionInfo(searchResponse->packet); + ++count; + ++totalcount; + } + + searchResponse->packet.put(0, count); // add count to placeholder + searchResponse->packet << totalcount; + searchResponse->packet << uint32(AUCTION_SEARCH_DELAY); + + _responseQueue->Enqueue(searchResponse); +} + +void AuctionHouseWorkerThread::BuildListAuctionItems(AuctionSearchListRequest const& searchRequest, SortableAuctionEntriesList& auctionEntries, SearchableAuctionEntriesMap const& auctionMap) const +{ + // pussywizard: optimization, this is a simplified case for the default search state (no filters) + if (searchRequest.searchInfo.itemClass == 0xffffffff && searchRequest.searchInfo.itemSubClass == 0xffffffff + && searchRequest.searchInfo.inventoryType == 0xffffffff && searchRequest.searchInfo.quality == 0xffffffff + && searchRequest.searchInfo.levelmin == 0x00 && searchRequest.searchInfo.levelmax == 0x00 + && searchRequest.searchInfo.usable == 0x00 && searchRequest.searchInfo.wsearchedname.empty()) + { + for (auto const& pair : auctionMap) + auctionEntries.push_back(pair.second.get()); + + return; + } + + for (auto const& pair : auctionMap) + { + std::shared_ptr const& Aentry = pair.second; + SearchableAuctionEntryItem const& Aitem = Aentry->item; + ItemTemplate const* proto = Aitem.itemTemplate; + + if (searchRequest.searchInfo.itemClass != 0xffffffff && proto->Class != searchRequest.searchInfo.itemClass) + continue; + + if (searchRequest.searchInfo.itemSubClass != 0xffffffff && proto->SubClass != searchRequest.searchInfo.itemSubClass) + continue; + + if (searchRequest.searchInfo.inventoryType != 0xffffffff && proto->InventoryType != searchRequest.searchInfo.inventoryType) + { + // xinef: exception, robes are counted as chests + if (searchRequest.searchInfo.inventoryType != INVTYPE_CHEST || proto->InventoryType != INVTYPE_ROBE) + continue; + } + + if (searchRequest.searchInfo.quality != 0xffffffff && proto->Quality < searchRequest.searchInfo.quality) + continue; + + if (searchRequest.searchInfo.levelmin != 0x00 && (proto->RequiredLevel < searchRequest.searchInfo.levelmin + || (searchRequest.searchInfo.levelmax != 0x00 && proto->RequiredLevel > searchRequest.searchInfo.levelmax))) + { + continue; + } + + if (searchRequest.searchInfo.usable != 0x00) + { + if (!searchRequest.playerInfo.usablePlayerInfo.value().PlayerCanUseItem(proto)) + 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 (!searchRequest.searchInfo.wsearchedname.empty()) + { + if (Aitem.itemName[searchRequest.playerInfo.locdbc_idx].find(searchRequest.searchInfo.wsearchedname) == std::wstring::npos) + continue; + } + + auctionEntries.push_back(Aentry.get()); + } +} + +AuctionHouseSearcher::AuctionHouseSearcher() +{ + for (uint32 i = 0; i < sWorld->getIntConfig(CONFIG_AUCTIONHOUSE_WORKERTHREADS); ++i) + _workerThreads.push_back(std::make_unique(&_requestQueue, &_responseQueue)); +} + +AuctionHouseSearcher::~AuctionHouseSearcher() +{ + _requestQueue.Cancel(); + for (std::unique_ptr const& workerThread : _workerThreads) + workerThread->Stop(); +} + +void AuctionHouseSearcher::Update() +{ + AuctionSearcherResponse* response = nullptr; + while (_responseQueue.Dequeue(response)) + { + Player* player = ObjectAccessor::FindConnectedPlayer(response->playerGuid); + if (player) + player->GetSession()->SendPacket(&response->packet); + + delete response; + } +} + +void AuctionHouseSearcher::QueueSearchRequest(AuctionSearcherRequest* searchRequestInfo) +{ + _requestQueue.Push(searchRequestInfo); +} + +void AuctionHouseSearcher::AddAuction(AuctionEntry const* auctionEntry) +{ + Item* item = sAuctionMgr->GetAItem(auctionEntry->item_guid); + if (!item) + return; + + // SearchableAuctionEntry is a shared_ptr as it will be shared among all the worker threads and needs to be self-managed + std::shared_ptr searchableAuctionEntry = std::make_shared(); + searchableAuctionEntry->Id = auctionEntry->Id; + + // Auction info + searchableAuctionEntry->ownerGuid = auctionEntry->owner; + sCharacterCache->GetCharacterNameByGuid(auctionEntry->owner, searchableAuctionEntry->ownerName); + searchableAuctionEntry->startbid = auctionEntry->startbid; + searchableAuctionEntry->buyout = auctionEntry->buyout; + searchableAuctionEntry->expire_time = auctionEntry->expire_time; + searchableAuctionEntry->listFaction = auctionEntry->GetFactionId(); + searchableAuctionEntry->bid = auctionEntry->bid; + searchableAuctionEntry->bidderGuid = auctionEntry->bidder; + + // Item info + searchableAuctionEntry->item.entry = item->GetEntry(); + + for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; ++i) + { + searchableAuctionEntry->item.enchants[i].id = item->GetEnchantmentId(EnchantmentSlot(i)); + searchableAuctionEntry->item.enchants[i].duration = item->GetEnchantmentDuration(EnchantmentSlot(i)); + searchableAuctionEntry->item.enchants[i].charges = item->GetEnchantmentCharges(EnchantmentSlot(i)); + } + + searchableAuctionEntry->item.randomPropertyId = item->GetItemRandomPropertyId(); + searchableAuctionEntry->item.suffixFactor = item->GetItemSuffixFactor(); + searchableAuctionEntry->item.count = item->GetCount(); + searchableAuctionEntry->item.spellCharges = item->GetSpellCharges(); + searchableAuctionEntry->item.itemTemplate = item->GetTemplate(); + + searchableAuctionEntry->SetItemNames(); + + // Let the worker threads know we have a new auction + NotifyAllWorkers(std::make_shared(searchableAuctionEntry)); +} + +void AuctionHouseSearcher::RemoveAuction(AuctionEntry const* auctionEntry) +{ + NotifyAllWorkers(std::make_shared(auctionEntry->Id, auctionEntry->GetFactionId())); +} + +void AuctionHouseSearcher::UpdateBid(AuctionEntry const* auctionEntry) +{ + // Updating bids is a bit unique, we really only need to update a single worker as every worker thread contains + // a map of shared pointers to the same SearchableAuctionEntry's, so updating one will update them all. + NotifyOneWorker(std::make_shared(auctionEntry->Id, auctionEntry->GetFactionId(), auctionEntry->bid, auctionEntry->bidder)); +} + +void AuctionHouseSearcher::NotifyAllWorkers(std::shared_ptr const auctionSearchUpdate) +{ + for (std::unique_ptr const& workerThread : _workerThreads) + workerThread->AddAuctionSearchUpdateToQueue(auctionSearchUpdate); +} + +void AuctionHouseSearcher::NotifyOneWorker(std::shared_ptr const auctionSearchUpdate) +{ + // Just notify the first worker in the list, no big deal which + (*_workerThreads.begin())->AddAuctionSearchUpdateToQueue(auctionSearchUpdate); +} + +void SearchableAuctionEntry::BuildAuctionInfo(WorldPacket& data) const +{ + data << uint32(Id); + data << uint32(item.entry); + + for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; ++i) + { + data << uint32(item.enchants[i].id); + data << uint32(item.enchants[i].duration); + data << uint32(item.enchants[i].charges); + } + + data << int32(item.randomPropertyId); // Random item property id + data << uint32(item.suffixFactor); // SuffixFactor + data << uint32(item.count); // item->count + data << uint32(item.spellCharges); // item->charge FFFFFFF + data << uint32(0); // item->flags (client doesnt do anything with it) + data << ownerGuid; // Auction->owner + data << uint32(startbid); // Auction->startbid (not sure if useful) + data << uint32(bid ? AuctionEntry::CalculateAuctionOutBid(bid) : 0); + // Minimal outbid + data << uint32(buyout); // Auction->buyout + data << uint32((expire_time - GameTime::GetGameTime().count()) * IN_MILLISECONDS); // time left + data << bidderGuid; // auction->bidder current + data << uint32(bid); // current bid +} + +void SearchableAuctionEntry::SetItemNames() +{ + ItemTemplate const* proto = item.itemTemplate; + ItemLocale const* il = sObjectMgr->GetItemLocale(proto->ItemId); + + for (uint32 locale = 0; locale < TOTAL_LOCALES; ++locale) + { + if (proto->Name1.empty()) + continue; + + std::string itemName = proto->Name1; + + // local name + LocaleConstant locdbc_idx = sWorld->GetAvailableDbcLocale(static_cast(locale)); + if (locdbc_idx >= LOCALE_enUS && il) + ObjectMgr::GetLocaleString(il->Name, locale, itemName); + + // DO NOT use GetItemEnchantMod(proto->RandomProperty) as it may return a result + // that matches the search but it may not equal item->GetItemRandomPropertyId() + // used in BuildAuctionInfo() which then causes wrong items to be listed + int32 propRefID = item.randomPropertyId; + + if (propRefID) + { + // Append the suffix to the name (ie: of the Monkey) if one exists + // These are found in ItemRandomSuffix.dbc and ItemRandomProperties.dbc + // even though the DBC name seems misleading + std::array const* suffix = nullptr; + + if (propRefID < 0) + { + ItemRandomSuffixEntry const* itemRandEntry = sItemRandomSuffixStore.LookupEntry(-item.randomPropertyId); + if (itemRandEntry) + suffix = &itemRandEntry->Name; + } + else + { + ItemRandomPropertiesEntry const* itemRandEntry = sItemRandomPropertiesStore.LookupEntry(item.randomPropertyId); + if (itemRandEntry) + suffix = &itemRandEntry->Name; + } + + // dbc local name + if (suffix) + { + // Append the suffix (ie: of the Monkey) to the name using localization + // or default enUS if localization is invalid + itemName += ' '; + itemName += (*suffix)[locdbc_idx >= 0 ? locdbc_idx : LOCALE_enUS]; + } + } + + if (!Utf8toWStr(itemName, item.itemName[locale])) + continue; + + wstrToLower(item.itemName[locale]); + } +} + +int SearchableAuctionEntry::CompareAuctionEntry(uint32 column, SearchableAuctionEntry const& auc, int loc_idx) const +{ + switch (column) + { + case AUCTION_SORT_MINLEVEL: // level = 0 + { + ItemTemplate const* itemProto1 = item.itemTemplate; + ItemTemplate const* itemProto2 = auc.item.itemTemplate; + + if (itemProto1->RequiredLevel > itemProto2->RequiredLevel) + return -1; + else if (itemProto1->RequiredLevel < itemProto2->RequiredLevel) + return +1; + break; + } + case AUCTION_SORT_RARITY: // quality = 1 + { + ItemTemplate const* itemProto1 = item.itemTemplate; + ItemTemplate const* itemProto2 = auc.item.itemTemplate; + + if (itemProto1->Quality < itemProto2->Quality) + return -1; + else if (itemProto1->Quality > itemProto2->Quality) + return +1; + break; + } + case AUCTION_SORT_BUYOUT: // buyoutthenbid = 2 (UNUSED?) + if (buyout != auc.buyout) + { + if (buyout < auc.buyout) + return -1; + else if (buyout > auc.buyout) + return +1; + } + else + { + if (bid < auc.bid) + return -1; + else if (bid > auc.bid) + return +1; + } + break; + case AUCTION_SORT_TIMELEFT: // duration = 3 + if (expire_time < auc.expire_time) + return -1; + else if (expire_time > auc.expire_time) + return +1; + break; + case AUCTION_SORT_UNK4: // status = 4 (WRONG) + if (bidderGuid.GetCounter() < auc.bidderGuid.GetCounter()) + return -1; + else if (bidderGuid.GetCounter() > auc.bidderGuid.GetCounter()) + return +1; + break; + case AUCTION_SORT_ITEM: // name = 5 + { + int comparison = item.itemName[loc_idx].compare(auc.item.itemName[loc_idx]); + if (comparison > 0) + return -1; + else if (comparison < 0) + return +1; + + break; + } + case AUCTION_SORT_MINBIDBUY: // minbidbuyout = 6 + { + if (buyout != auc.buyout) + { + if (buyout > auc.buyout) + return -1; + else if (buyout < auc.buyout) + return +1; + } + else + { + if (bid < auc.bid) + return -1; + else if (bid > auc.bid) + return +1; + } + break; + } + case AUCTION_SORT_OWNER: // seller = 7 + { + int comparison = ownerName.compare(auc.ownerName); + if (comparison > 0) + return -1; + else if (comparison < 0) + return +1; + + break; + } + case AUCTION_SORT_BID: // bid = 8 + { + uint32 bid1 = bid ? bid : startbid; + uint32 bid2 = auc.bid ? auc.bid : auc.startbid; + + if (bid1 > bid2) + return -1; + else if (bid1 < bid2) + return +1; + break; + } + case AUCTION_SORT_STACK: // quantity = 9 + { + if (item.count < auc.item.count) + return -1; + else if (item.count > auc.item.count) + return +1; + break; + } + case AUCTION_SORT_BUYOUT_2: // buyout = 10 (UNUSED?) + if (buyout < auc.buyout) + return -1; + else if (buyout > auc.buyout) + return +1; + break; + default: + break; + } + + return 0; +} + +bool AuctionSorter::operator()(SearchableAuctionEntry const* auc1, SearchableAuctionEntry const* auc2) const +{ + if (_sort->empty()) // not sorted + return false; + + for (AuctionSortOrderVector::const_iterator itr = _sort->begin(); itr != _sort->end(); ++itr) + { + int res = auc1->CompareAuctionEntry(itr->sortOrder, *auc2, _loc_idx); + // "equal" by used column + if (res == 0) + continue; + // less/greater and normal/reversed ordered + return (res < 0) == itr->isDesc; + } + + return false; // "equal" by all sorts +} + +// Slightly simplified version of Player::CanUseItem. Only checks relevant to auctionhouse items +bool AuctionHouseUsablePlayerInfo::PlayerCanUseItem(ItemTemplate const* proto) const +{ + uint32 itemSkill = proto->GetSkill(); + if (itemSkill != 0) + { + if (GetSkillValue(itemSkill) == 0) + return false; + } + + if ((proto->AllowableClass & classMask) == 0 || (proto->AllowableRace & raceMask) == 0) + return false; + + if (proto->RequiredSkill != 0) + { + if (GetSkillValue(proto->RequiredSkill) == 0) + return false; + else if (GetSkillValue(proto->RequiredSkill) < proto->RequiredSkillRank) + return false; + } + + if (proto->RequiredSpell != 0 && !HasSpell(proto->RequiredSpell)) + return false; + + if (level < proto->RequiredLevel) + return false; + + if (proto->Spells[0].SpellId) + { + // this check is for vanilla recipies. Spells are learned through individual learning spells instead of spell 483 and 55884 + SpellEntry const* spellEntry = sSpellStore.LookupEntry(proto->Spells[0].SpellId); + if (spellEntry && spellEntry->Effect[0] == SPELL_EFFECT_LEARN_SPELL && spellEntry->EffectTriggerSpell[0]) + if (HasSpell(spellEntry->EffectTriggerSpell[0])) + return false; + + // this check is for tbc/wotlk recipies. Spells are learned through 483 and 55884, the second spell in the item will be the actual spell learned. + if (proto->Spells[0].SpellId == 483 || proto->Spells[0].SpellId == 55884) + if (HasSpell(proto->Spells[1].SpellId)) + return false; + } + + return true; +} + +uint16 AuctionHouseUsablePlayerInfo::GetSkillValue(uint32 skill) const +{ + if (!skill) + return 0; + + AuctionPlayerSkills::const_iterator itr = skills.find(skill); + if (itr == skills.end()) + return 0; + + return itr->second; +} + +bool AuctionHouseUsablePlayerInfo::HasSpell(uint32 spell) const +{ + AuctionPlayerSpells::const_iterator itr = spells.find(spell); + return (itr != spells.end()); +} diff --git a/src/server/game/AuctionHouse/AuctionHouseSearcher.h b/src/server/game/AuctionHouse/AuctionHouseSearcher.h new file mode 100644 index 00000000000000..b1f547f426b2d7 --- /dev/null +++ b/src/server/game/AuctionHouse/AuctionHouseSearcher.h @@ -0,0 +1,302 @@ +/* + * This file is part of the AzerothCore 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 Affero General Public License as published by the + * Free Software Foundation; either version 3 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 Affero 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 . + */ + +#ifndef _AUCTION_HOUSE_SEARCHER_H +#define _AUCTION_HOUSE_SEARCHER_H + +#include "AuctionHouseMgr.h" +#include "Common.h" +#include "Item.h" +#include "LockedQueue.h" +#include "MPSCQueue.h" +#include "PCQueue.h" +#include +#include +#include +#include + +struct ItemTemplate; + +enum AuctionSortOrder +{ + AUCTION_SORT_MINLEVEL = 0, + AUCTION_SORT_RARITY = 1, + AUCTION_SORT_BUYOUT = 2, + AUCTION_SORT_TIMELEFT = 3, + AUCTION_SORT_UNK4 = 4, + AUCTION_SORT_ITEM = 5, + AUCTION_SORT_MINBIDBUY = 6, + AUCTION_SORT_OWNER = 7, + AUCTION_SORT_BID = 8, + AUCTION_SORT_STACK = 9, + AUCTION_SORT_BUYOUT_2 = 10, + + AUCTION_SORT_MAX +}; + +struct AuctionSortInfo +{ + AuctionSortInfo() = default; + + AuctionSortOrder sortOrder{ AUCTION_SORT_MAX }; + bool isDesc{ true }; +}; + +struct AuctionEntryItemEnchants +{ + uint32 id; + uint32 duration; + uint32 charges; +}; + +struct SearchableAuctionEntryItem +{ + std::wstring itemName[TOTAL_LOCALES]; + uint32 entry; + AuctionEntryItemEnchants enchants[MAX_INSPECTED_ENCHANTMENT_SLOT]; + int32 randomPropertyId; + uint32 suffixFactor; + uint32 count; + int32 spellCharges; + ItemTemplate const* itemTemplate; +}; + +struct SearchableAuctionEntry +{ + uint32 Id; + ObjectGuid ownerGuid; + std::string ownerName; + uint32 buyout; + time_t expire_time; + uint32 startbid; + uint32 bid; + ObjectGuid bidderGuid; + AuctionHouseFaction listFaction; + SearchableAuctionEntryItem item; + + void BuildAuctionInfo(WorldPacket& data) const; + void SetItemNames(); + + int CompareAuctionEntry(uint32 column, SearchableAuctionEntry const& auc, int loc_idx) const; +}; + +typedef std::vector AuctionSortOrderVector; + +struct AuctionHouseSearchInfo +{ + std::wstring wsearchedname; + uint32 listfrom; + uint8 levelmin; + uint8 levelmax; + bool usable; + uint32 inventoryType; + uint32 itemClass; + uint32 itemSubClass; + uint32 quality; + bool getAll; + AuctionSortOrderVector sorting; +}; + +typedef std::unordered_map AuctionPlayerSkills; +typedef std::unordered_set AuctionPlayerSpells; + +struct AuctionHouseUsablePlayerInfo +{ + uint32 classMask; + uint32 raceMask; + uint8 level; + AuctionPlayerSkills skills; // active skills only + AuctionPlayerSpells spells; // active spells only + + bool PlayerCanUseItem(ItemTemplate const* proto) const; + uint16 GetSkillValue(uint32 skill) const; + bool HasSpell(uint32 spell) const; +}; + +struct AuctionHousePlayerInfo +{ + ObjectGuid playerGuid; + uint32 faction; + int loc_idx; + int locdbc_idx; + std::optional usablePlayerInfo; +}; + +struct AuctionSearcherRequest +{ + enum class Type : uint8 + { + LIST, + OWNER_LIST, + BIDDER_LIST + }; + + AuctionSearcherRequest(Type const _requestType, AuctionHouseFaction _listFaction) : requestType(_requestType), listFaction(_listFaction) { } + virtual ~AuctionSearcherRequest() = default; + + Type requestType; + AuctionHouseFaction listFaction; +}; + +struct AuctionSearchListRequest : AuctionSearcherRequest +{ + AuctionSearchListRequest(AuctionHouseFaction _listFaction, AuctionHouseSearchInfo const&& _searchInfo, AuctionHousePlayerInfo const&& _playerInfo) + : AuctionSearcherRequest(AuctionSearcherRequest::Type::LIST, _listFaction), searchInfo(_searchInfo), playerInfo(_playerInfo) { } + + AuctionHouseSearchInfo searchInfo; + AuctionHousePlayerInfo playerInfo; +}; + +struct AuctionSearchOwnerListRequest : AuctionSearcherRequest +{ + AuctionSearchOwnerListRequest(AuctionHouseFaction _listFaction, ObjectGuid _ownerGuid) + : AuctionSearcherRequest(AuctionSearcherRequest::Type::OWNER_LIST, _listFaction), ownerGuid(_ownerGuid) { } + + ObjectGuid ownerGuid; +}; + +struct AuctionSearchBidderListRequest : AuctionSearcherRequest +{ + AuctionSearchBidderListRequest(AuctionHouseFaction _listFaction, std::vector const&& _outbiddedAuctionIds, ObjectGuid _ownerGuid) + : AuctionSearcherRequest(AuctionSearcherRequest::Type::BIDDER_LIST, _listFaction), outbiddedAuctionIds(_outbiddedAuctionIds), ownerGuid(_ownerGuid) { } + + std::vector outbiddedAuctionIds; + ObjectGuid ownerGuid; +}; + +struct AuctionSearcherResponse +{ + ObjectGuid playerGuid; + WorldPacket packet; +}; + +struct AuctionSearcherUpdate +{ + enum class Type : uint8 + { + ADD, + REMOVE, + UPDATE_BID + }; + + AuctionSearcherUpdate(Type const _updateType, AuctionHouseFaction _listFaction) : updateType(_updateType), listFaction(_listFaction) { } + virtual ~AuctionSearcherUpdate() = default; + + Type updateType; + AuctionHouseFaction listFaction; +}; + +struct AuctionSearchAdd : AuctionSearcherUpdate +{ + AuctionSearchAdd(std::shared_ptr _searchableAuctionEntry) + : AuctionSearcherUpdate(AuctionSearcherUpdate::Type::ADD, _searchableAuctionEntry->listFaction), searchableAuctionEntry(_searchableAuctionEntry) { } + + std::shared_ptr searchableAuctionEntry; +}; + +struct AuctionSearchRemove : AuctionSearcherUpdate +{ + AuctionSearchRemove(uint32 _auctionId, AuctionHouseFaction _listFaction) + : AuctionSearcherUpdate(AuctionSearcherUpdate::Type::REMOVE, _listFaction), auctionId(_auctionId) { } + + uint32 auctionId; +}; + +struct AuctionSearchUpdateBid : AuctionSearcherUpdate +{ + AuctionSearchUpdateBid(uint32 _auctionId, AuctionHouseFaction _listFaction, uint32 _bid, ObjectGuid _bidderGuid) + : AuctionSearcherUpdate(AuctionSearcherUpdate::Type::UPDATE_BID, _listFaction), auctionId(_auctionId), bid(_bid), bidderGuid(_bidderGuid) { } + + uint32 auctionId; + uint32 bid; + ObjectGuid bidderGuid; +}; + +typedef std::unordered_map> SearchableAuctionEntriesMap; +typedef std::vector SortableAuctionEntriesList; + +class AuctionSorter +{ +public: + AuctionSorter(AuctionSortOrderVector const* sort, int loc_idx) : _sort(sort), _loc_idx(loc_idx) {} + bool operator()(SearchableAuctionEntry const* auc1, SearchableAuctionEntry const* auc2) const; + +private: + AuctionSortOrderVector const* _sort; + int _loc_idx; +}; + +class AuctionHouseWorkerThread +{ +public: + AuctionHouseWorkerThread(ProducerConsumerQueue* requestQueue, MPSCQueue* responseQueue); + + void Stop(); + + void AddAuctionSearchUpdateToQueue(std::shared_ptr const auctionSearchUpdate); + +private: + void Run(); + + void ProcessSearchUpdates(); + void SearchUpdateAdd(AuctionSearchAdd const& auctionAdd); + void SearchUpdateRemove(AuctionSearchRemove const& auctionRemove); + void SearchUpdateBid(AuctionSearchUpdateBid const& auctionUpdateBid); + + void ProcessSearchRequests(); + void SearchListRequest(AuctionSearchListRequest const& searchListRequest); + void SearchOwnerListRequest(AuctionSearchOwnerListRequest const& searchOwnerListRequest); + void SearchBidderListRequest(AuctionSearchBidderListRequest const& searchBidderListRequest); + + void BuildListAuctionItems(AuctionSearchListRequest const& searchRequest, SortableAuctionEntriesList& auctionEntries, SearchableAuctionEntriesMap const& auctionMap) const; + + SearchableAuctionEntriesMap& GetSearchableAuctionMap(AuctionHouseFaction faction) { return _searchableAuctionMap[static_cast(faction)]; }; + + SearchableAuctionEntriesMap _searchableAuctionMap[MAX_AUCTION_HOUSE_FACTIONS]; + LockedQueue> _auctionUpdatesQueue; + + ProducerConsumerQueue* _requestQueue; + MPSCQueue* _responseQueue; + + std::thread _workerThread; + std::atomic _stopped; +}; + +class AuctionHouseSearcher +{ +public: + AuctionHouseSearcher(); + ~AuctionHouseSearcher(); + + void Update(); + + void QueueSearchRequest(AuctionSearcherRequest* searchRequestInfo); + + void AddAuction(AuctionEntry const* auctionEntry); + void RemoveAuction(AuctionEntry const* auctionEntry); + void UpdateBid(AuctionEntry const* auctionEntry); + + void NotifyAllWorkers(std::shared_ptr const auctionSearchUpdate); + void NotifyOneWorker(std::shared_ptr const auctionSearchUpdate); + +private: + ProducerConsumerQueue _requestQueue; + MPSCQueue _responseQueue; + std::vector> _workerThreads; +}; + +#endif diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 60076e518f6f63..3460058e04ac0e 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1779,6 +1779,9 @@ class Player : public Unit, public GridObject [[nodiscard]] SpellCooldowns const& GetSpellCooldownMap() const { return m_spellCooldowns; } SpellCooldowns& GetSpellCooldownMap() { return m_spellCooldowns; } + SkillStatusMap const& GetSkillStatusMap() const { return mSkillStatus; } + SkillStatusMap& GetSkillStatusMap() { return mSkillStatus; } + void AddSpellMod(SpellModifier* mod, bool apply); bool IsAffectedBySpellmod(SpellInfo const* spellInfo, SpellModifier* mod, Spell* spell = nullptr); bool HasSpellMod(SpellModifier* mod, Spell* spell); diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp index 852022085fd9c6..d450a3db475093 100644 --- a/src/server/game/Handlers/AuctionHouseHandler.cpp +++ b/src/server/game/Handlers/AuctionHouseHandler.cpp @@ -15,8 +15,8 @@ * with this program. If not, see . */ -#include "AsyncAuctionListing.h" #include "AuctionHouseMgr.h" +#include "AuctionHouseSearcher.h" #include "Chat.h" #include "GameTime.h" #include "Language.h" @@ -61,7 +61,7 @@ void WorldSession::SendAuctionHello(ObjectGuid guid, Creature* unit) if (!sScriptMgr->CanSendAuctionHello(this, guid, unit)) return; - AuctionHouseEntry const* ahEntry = AuctionHouseMgr::GetAuctionHouseEntry(unit->GetFaction()); + AuctionHouseEntry const* ahEntry = AuctionHouseMgr::GetAuctionHouseEntryFromFactionTemplate(unit->GetFaction()); if (!ahEntry) return; @@ -165,7 +165,7 @@ void WorldSession::HandleAuctionSellItem(WorldPacket& recvData) return; } - AuctionHouseEntry const* auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(creature->GetFaction()); + AuctionHouseEntry const* auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntryFromFactionTemplate(creature->GetFaction()); if (!auctionHouseEntry) { LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit ({}) has wrong faction.", auctioneer.ToString()); @@ -267,13 +267,14 @@ void WorldSession::HandleAuctionSellItem(WorldPacket& recvData) AH->Id = sObjectMgr->GenerateAuctionID(); if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) - AH->houseId = AUCTIONHOUSE_NEUTRAL; + AH->houseId = AuctionHouseId::Neutral; else { CreatureData const* auctioneerData = sObjectMgr->GetCreatureData(creature->GetSpawnId()); if (!auctioneerData) { LOG_ERROR("network.opcode", "Data for auctioneer not found ({})", auctioneer.ToString()); + delete AH; return; } @@ -281,11 +282,12 @@ void WorldSession::HandleAuctionSellItem(WorldPacket& recvData) if (!auctioneerInfo) { LOG_ERROR("network.opcode", "Non existing auctioneer ({})", auctioneer.ToString()); + delete AH; return; } - const AuctionHouseEntry* AHEntry = sAuctionMgr->GetAuctionHouseEntry(auctioneerInfo->faction); - AH->houseId = AHEntry->houseId; + const AuctionHouseEntry* AHEntry = sAuctionMgr->GetAuctionHouseEntryFromFactionTemplate(auctioneerInfo->faction); + AH->houseId = AuctionHouseId(AHEntry->houseId); } // Required stack size of auction matches to current item stack size, just move item to auctionhouse @@ -443,7 +445,7 @@ void WorldSession::HandleAuctionPlaceBid(WorldPacket& recvData) // price too low for next bid if not buyout if ((price < auction->buyout || auction->buyout == 0) && - price < auction->bid + auction->GetAuctionOutBid()) + price < auction->bid + AuctionEntry::CalculateAuctionOutBid(auction->bid)) { //auction has already higher bid, client tests it! return; @@ -476,6 +478,9 @@ void WorldSession::HandleAuctionPlaceBid(WorldPacket& recvData) auction->bidder = player->GetGUID(); auction->bid = price; + + sAuctionMgr->GetAuctionHouseSearcher()->UpdateBid(auction); + GetPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_AUCTION_BID, price); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_BID); @@ -546,29 +551,23 @@ void WorldSession::HandleAuctionRemoveItem(WorldPacket& recvData) if (auction && auction->owner == player->GetGUID()) { Item* pItem = sAuctionMgr->GetAItem(auction->item_guid); - if (pItem) - { - if (auction->bidder) // If we have a bidder, we have to send him the money he paid - { - uint32 auctionCut = auction->GetAuctionCut(); - if (!player->HasEnoughMoney(auctionCut)) //player doesn't have enough money, maybe message needed - return; - //some auctionBidderNotification would be needed, but don't know that parts.. - sAuctionMgr->SendAuctionCancelledToBidderMail(auction, trans); - player->ModifyMoney(-int32(auctionCut)); - } + if (!pItem) + return; - // item will deleted or added to received mail list - MailDraft(auction->BuildAuctionMailSubject(AUCTION_CANCELED), AuctionEntry::BuildAuctionMailBody(ObjectGuid::Empty, 0, auction->buyout, auction->deposit)) - .AddItem(pItem) - .SendMailTo(trans, player, auction, MAIL_CHECK_MASK_COPIED); - } - else + if (auction->bidder) // If we have a bidder, we have to send him the money he paid { - LOG_ERROR("network.opcode", "Auction id: {} has non-existed item (item: {})!!!", auction->Id, auction->item_guid.ToString()); - SendAuctionCommandResult(0, AUCTION_CANCEL, ERR_AUCTION_DATABASE_ERROR); - return; + uint32 auctionCut = auction->GetAuctionCut(); + if (!player->HasEnoughMoney(auctionCut)) //player doesn't have enough money, maybe message needed + return; + //some auctionBidderNotification would be needed, but don't know that parts.. + sAuctionMgr->SendAuctionCancelledToBidderMail(auction, trans); + player->ModifyMoney(-int32(auctionCut)); } + + // item will deleted or added to received mail list + MailDraft(auction->BuildAuctionMailSubject(AUCTION_CANCELED), AuctionEntry::BuildAuctionMailBody(ObjectGuid::Empty, 0, auction->buyout, auction->deposit)) + .AddItem(pItem) + .SendMailTo(trans, player, auction, MAIL_CHECK_MASK_COPIED); } else { @@ -615,89 +614,58 @@ void WorldSession::HandleAuctionListBidderItems(WorldPacket& recvData) return; } + // Arbitrary cap, can be adjusted if needed + if (outbiddedCount > 1000) + return; + // remove fake death if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->GetFaction()); + AuctionHouseEntry const* ahEntry = AuctionHouseMgr::GetAuctionHouseEntryFromFactionTemplate(creature->GetFaction()); + if (!ahEntry) + return; - WorldPacket data(SMSG_AUCTION_BIDDER_LIST_RESULT, (4 + 4 + 4) + 30000); // pussywizard: ensure there is enough memory - Player* player = GetPlayer(); - data << (uint32) 0; //add 0 as count - uint32 count = 0; - uint32 totalcount = 0; - while (outbiddedCount > 0) //add all data, which client requires + AuctionHouseFaction auctionHouseFaction = AuctionHouseMgr::GetAuctionHouseFactionFromHouseId(AuctionHouseId(ahEntry->houseId)); + + // Client sends this list, which I'm honestly not entirely sure why? + std::vector auctionIds; + auctionIds.reserve(outbiddedCount); + while (outbiddedCount > 0) // add all data, which client requires { --outbiddedCount; uint32 outbiddedAuctionId; recvData >> outbiddedAuctionId; - AuctionEntry* auction = auctionHouse->GetAuction(outbiddedAuctionId); - if (auction && auction->BuildAuctionInfo(data)) - { - ++totalcount; - ++count; - } + auctionIds.push_back(outbiddedAuctionId); } - auctionHouse->BuildListBidderItems(data, player, count, totalcount); - data.put(0, count); // add count to placeholder - data << totalcount; - data << (uint32)300; //unk 2.3.0 - SendPacket(&data); + sAuctionMgr->GetAuctionHouseSearcher()->QueueSearchRequest(new AuctionSearchBidderListRequest(auctionHouseFaction, std::move(auctionIds), GetPlayer()->GetGUID())); } //this void sends player info about his auctions void WorldSession::HandleAuctionListOwnerItems(WorldPacket& recvData) { - // prevent crash caused by malformed packet ObjectGuid guid; uint32 listfrom; recvData >> guid; recvData >> listfrom; // not used in fact (this list does not have page control in client) - // pussywizard: - const Milliseconds now = GameTime::GetGameTimeMS(); - if (_lastAuctionListOwnerItemsMSTime > now) // list is pending - return; - - const Milliseconds delay = Milliseconds(4500); - Milliseconds diff = GetMSTimeDiff(_lastAuctionListOwnerItemsMSTime, now); - if (diff > delay) - diff = delay; - - _lastAuctionListOwnerItemsMSTime = now + delay; // set longest possible here, actual executing will change this to getMSTime of that moment - _player->m_Events.AddEvent(new AuctionListOwnerItemsDelayEvent(guid, _player->GetGUID()), _player->m_Events.CalculateTime(delay.count() - diff.count())); -} - -void WorldSession::HandleAuctionListOwnerItemsEvent(ObjectGuid creatureGuid) -{ - _lastAuctionListOwnerItemsMSTime = GameTime::GetGameTimeMS(); // pussywizard - - Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(creatureGuid, UNIT_NPC_FLAG_AUCTIONEER); + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_AUCTIONEER); if (!creature) - { - LOG_DEBUG("network", "WORLD: HandleAuctionListOwnerItems - Unit ({}) not found or you can't interact with him.", creatureGuid.ToString()); return; - } // remove fake death if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->GetFaction()); - - WorldPacket data(SMSG_AUCTION_OWNER_LIST_RESULT, (4 + 4 + 4) + 60000); // pussywizard: ensure there is enough memory - data << (uint32) 0; // amount place holder + AuctionHouseEntry const* ahEntry = AuctionHouseMgr::GetAuctionHouseEntryFromFactionTemplate(creature->GetFaction()); + if (!ahEntry) + return; - uint32 count = 0; - uint32 totalcount = 0; + AuctionHouseFaction auctionHouseFaction = AuctionHouseMgr::GetAuctionHouseFactionFromHouseId(AuctionHouseId(ahEntry->houseId)); - auctionHouse->BuildListOwnerItems(data, _player, count, totalcount); - data.put(0, count); - data << (uint32) totalcount; - data << (uint32) 0; - SendPacket(&data); + sAuctionMgr->GetAuctionHouseSearcher()->QueueSearchRequest(new AuctionSearchOwnerListRequest(auctionHouseFaction, GetPlayer()->GetGUID())); } //this void is called when player clicks on search button @@ -716,13 +684,16 @@ void WorldSession::HandleAuctionListItems(WorldPacket& recvData) recvData >> auctionSlotID >> auctionMainCategory >> auctionSubCategory; recvData >> quality >> usable; - //recvData.read_skip(); // pussywizard: this is the getAll option uint8 getAll; recvData >> getAll; // Read sort block uint8 sortOrderCount; recvData >> sortOrderCount; + + if (sortOrderCount > AUCTION_SORT_MAX) + return; + AuctionSortOrderVector sortOrder; for (uint8 i = 0; i < sortOrderCount; i++) { @@ -736,24 +707,63 @@ void WorldSession::HandleAuctionListItems(WorldPacket& recvData) sortOrder.push_back(std::move(sortInfo)); } + // converting string that we try to find to lower case + std::wstring wsearchedname; + if (!Utf8toWStr(searchedname, wsearchedname)) + return; + + wstrToLower(wsearchedname); + + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_AUCTIONEER); + if (!creature) + return; + // remove fake death if (_player->HasUnitState(UNIT_STATE_DIED)) - { _player->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - } - // pussywizard: - const Milliseconds delay = 2s; - const Milliseconds now = GameTime::GetGameTimeMS(); - Milliseconds diff = GetMSTimeDiff(_lastAuctionListItemsMSTime, now); - if (diff > delay) - { - diff = delay; + AuctionHouseEntry const* ahEntry = AuctionHouseMgr::GetAuctionHouseEntryFromFactionTemplate(creature->GetFaction()); + if (!ahEntry) + return; + + AuctionHouseFaction auctionHouseFaction = AuctionHouseMgr::GetAuctionHouseFactionFromHouseId(AuctionHouseId(ahEntry->houseId)); + + AuctionHouseSearchInfo ahSearchInfo; + ahSearchInfo.wsearchedname = wsearchedname; + ahSearchInfo.listfrom = listfrom; + ahSearchInfo.levelmin = levelmin; + ahSearchInfo.levelmax = levelmax; + ahSearchInfo.usable = usable; + ahSearchInfo.inventoryType = auctionSlotID; + ahSearchInfo.itemClass = auctionMainCategory; + ahSearchInfo.itemSubClass = auctionSubCategory; + ahSearchInfo.quality = quality; + ahSearchInfo.getAll = getAll; + ahSearchInfo.sorting = std::move(sortOrder); + + AuctionHousePlayerInfo ahPlayerInfo; + ahPlayerInfo.playerGuid = GetPlayer()->GetGUID(); + ahPlayerInfo.faction = GetPlayer()->GetFaction(); + ahPlayerInfo.loc_idx = GetPlayer()->GetSession()->GetSessionDbLocaleIndex(); + ahPlayerInfo.locdbc_idx = GetPlayer()->GetSession()->GetSessionDbcLocale(); + if (usable) + { + AuctionHouseUsablePlayerInfo usablePlayerInfo; + + SkillStatusMap const& skillMap = GetPlayer()->GetSkillStatusMap(); + for (auto const& pair : skillMap) + usablePlayerInfo.skills.insert(std::make_pair(pair.first, GetPlayer()->GetSkillValue(pair.first))); + + PlayerSpellMap const& spellMap = GetPlayer()->GetSpellMap(); + for (auto const& pair : spellMap) + { + if (pair.second->State != PLAYERSPELL_REMOVED && pair.second->IsInSpec(GetPlayer()->GetActiveSpec())) + usablePlayerInfo.spells.insert(pair.first); + } + ahPlayerInfo.usablePlayerInfo = std::move(usablePlayerInfo); } - _lastAuctionListItemsMSTime = now + delay - diff; - std::lock_guard guard(AsyncAuctionListingMgr::GetTempLock()); - AsyncAuctionListingMgr::GetTempList().emplace_back(delay - diff, _player->GetGUID(), guid, searchedname, listfrom, levelmin, levelmax, usable, auctionSlotID, - auctionMainCategory, auctionSubCategory, quality, getAll, sortOrder); + + sAuctionMgr->GetAuctionHouseSearcher()->QueueSearchRequest(new AuctionSearchListRequest(auctionHouseFaction, std::move(ahSearchInfo), std::move(ahPlayerInfo))); } void WorldSession::HandleAuctionListPendingSales(WorldPacket& recvData) diff --git a/src/server/game/Mails/Mail.cpp b/src/server/game/Mails/Mail.cpp index eddd7022879ae2..f8d838776f2cd4 100644 --- a/src/server/game/Mails/Mail.cpp +++ b/src/server/game/Mails/Mail.cpp @@ -63,7 +63,7 @@ MailSender::MailSender(CalendarEvent* sender) } MailSender::MailSender(AuctionEntry* sender) - : m_messageType(MAIL_AUCTION), m_senderId(sender->GetHouseId()), m_stationery(MAIL_STATIONERY_AUCTION) + : m_messageType(MAIL_AUCTION), m_senderId(uint32(sender->GetHouseId())), m_stationery(MAIL_STATIONERY_AUCTION) { } diff --git a/src/server/game/Misc/AsyncAuctionListing.cpp b/src/server/game/Misc/AsyncAuctionListing.cpp deleted file mode 100644 index acc657bc95660e..00000000000000 --- a/src/server/game/Misc/AsyncAuctionListing.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This file is part of the AzerothCore 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 Affero General Public License as published by the - * Free Software Foundation; either version 3 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 Affero 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 "AsyncAuctionListing.h" -#include "Creature.h" -#include "ObjectAccessor.h" -#include "Opcodes.h" -#include "Player.h" -#include "SpellAuraEffects.h" - -Milliseconds AsyncAuctionListingMgr::auctionListingDiff = Milliseconds::zero(); -std::list AsyncAuctionListingMgr::auctionListingList; -std::list AsyncAuctionListingMgr::auctionListingListTemp; -std::mutex AsyncAuctionListingMgr::auctionListingTempLock; - -bool AuctionListOwnerItemsDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) -{ - if (Player* plr = ObjectAccessor::FindPlayer(playerguid)) - plr->GetSession()->HandleAuctionListOwnerItemsEvent(creatureGuid); - return true; -} - -bool AuctionListItemsDelayEvent::Execute() -{ - Player* plr = ObjectAccessor::FindPlayer(_playerguid); - if (!plr || !plr->IsInWorld() || plr->IsDuringRemoveFromWorld() || plr->IsBeingTeleported()) - return true; - - Creature* creature = plr->GetNPCIfCanInteractWith(_creatureguid, UNIT_NPC_FLAG_AUCTIONEER); - if (!creature) - return true; - - AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->GetFaction()); - - WorldPacket data(SMSG_AUCTION_LIST_RESULT, (4 + 4 + 4) + 50 * ((16 + MAX_INSPECTED_ENCHANTMENT_SLOT * 3) * 4)); - uint32 count = 0; - uint32 totalcount = 0; - data << (uint32) 0; - - // converting string that we try to find to lower case - std::wstring wsearchedname; - if (!Utf8toWStr(_searchedname, wsearchedname)) - return true; - - wstrToLower(wsearchedname); - - uint32 searchTimeout = sWorld->getIntConfig(CONFIG_AUCTION_HOUSE_SEARCH_TIMEOUT); - bool result = auctionHouse->BuildListAuctionItems(data, plr, - wsearchedname, _listfrom, _levelmin, _levelmax, _usable, - _auctionSlotID, _auctionMainCategory, _auctionSubCategory, _quality, - count, totalcount, _getAll, _sortOrder, Milliseconds(searchTimeout)); - - if (result) - { - data.put(0, count); - data << (uint32) totalcount; - data << (uint32) 300; // clientside search cooldown [ms] (gray search button) - plr->GetSession()->SendPacket(&data); - } - - return true; -} diff --git a/src/server/game/Misc/AsyncAuctionListing.h b/src/server/game/Misc/AsyncAuctionListing.h deleted file mode 100644 index 0426b0691b13a6..00000000000000 --- a/src/server/game/Misc/AsyncAuctionListing.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * This file is part of the AzerothCore 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 Affero General Public License as published by the - * Free Software Foundation; either version 3 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 Affero 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 . - */ - -#ifndef __ASYNCAUCTIONLISTING_H -#define __ASYNCAUCTIONLISTING_H - -#include "AuctionHouseMgr.h" - -class AuctionListOwnerItemsDelayEvent : public BasicEvent -{ -public: - AuctionListOwnerItemsDelayEvent(ObjectGuid _creatureGuid, ObjectGuid guid) : creatureGuid(_creatureGuid), playerguid(guid) {} - ~AuctionListOwnerItemsDelayEvent() override {} - - bool Execute(uint64 e_time, uint32 p_time) override; - void Abort(uint64 /*e_time*/) override {} - -private: - ObjectGuid creatureGuid; - ObjectGuid playerguid; -}; - -class AuctionListItemsDelayEvent -{ -public: - AuctionListItemsDelayEvent(Milliseconds pickupTimer, ObjectGuid playerguid, ObjectGuid creatureguid, std::string searchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, - uint8 usable, uint32 auctionSlotID, uint32 auctionMainCategory, uint32 auctionSubCategory, uint32 quality, uint8 getAll, AuctionSortOrderVector sortOrder) : - _pickupTimer(pickupTimer), _playerguid(playerguid), _creatureguid(creatureguid), _searchedname(searchedname), _listfrom(listfrom), _levelmin(levelmin), _levelmax(levelmax),_usable(usable), - _auctionSlotID(auctionSlotID), _auctionMainCategory(auctionMainCategory), _auctionSubCategory(auctionSubCategory), _quality(quality), _getAll(getAll), _sortOrder(sortOrder) { } - - bool Execute(); - - Milliseconds _pickupTimer; - ObjectGuid _playerguid; - ObjectGuid _creatureguid; - std::string _searchedname; - uint32 _listfrom; - uint8 _levelmin; - uint8 _levelmax; - uint8 _usable; - uint32 _auctionSlotID; - uint32 _auctionMainCategory; - uint32 _auctionSubCategory; - uint32 _quality; - uint8 _getAll; - AuctionSortOrderVector _sortOrder; -}; - -class AsyncAuctionListingMgr -{ -public: - static void Update(Milliseconds diff) { auctionListingDiff += diff; } - static Milliseconds GetDiff() { return auctionListingDiff; } - static void ResetDiff() { auctionListingDiff = Milliseconds::zero(); } - static std::list& GetList() { return auctionListingList; } - static std::list& GetTempList() { return auctionListingListTemp; } - static std::mutex& GetTempLock() { return auctionListingTempLock; } - -private: - static Milliseconds auctionListingDiff; - static std::list auctionListingList; - static std::list auctionListingListTemp; - static std::mutex auctionListingTempLock; -}; - -#endif diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 73d7b89a6dbb32..feeecf4470eac6 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -107,8 +107,6 @@ WorldSession::WorldSession(uint32 id, std::string&& name, std::shared_ptrGetOption("DailyRBGArenaPoints.MinLevel", 71); - _int_configs[CONFIG_AUCTION_HOUSE_SEARCH_TIMEOUT] = sConfigMgr->GetOption("AuctionHouse.SearchTimeout", 1000); - ///- Read the "Data" directory from the config file std::string dataPath = sConfigMgr->GetOption("DataDir", "./"); if (dataPath.empty() || (dataPath.at(dataPath.length() - 1) != '/' && dataPath.at(dataPath.length() - 1) != '\\')) @@ -1488,6 +1485,9 @@ void World::LoadConfigSettings(bool reload) // Realm Availability _bool_configs[CONFIG_REALM_LOGIN_ENABLED] = sConfigMgr->GetOption("World.RealmAvailability", true); + // AH Worker threads + _int_configs[CONFIG_AUCTIONHOUSE_WORKERTHREADS] = sConfigMgr->GetOption("AuctionHouse.WorkerThreads", 1); + // SpellQueue _bool_configs[CONFIG_SPELL_QUEUE_ENABLED] = sConfigMgr->GetOption("SpellQueue.Enabled", true); _int_configs[CONFIG_SPELL_QUEUE_WINDOW] = sConfigMgr->GetOption("SpellQueue.Window", 400); @@ -2071,8 +2071,6 @@ void World::SetInitialWorldSettings() LoginDatabase.Execute(stmt); _timers[WUPDATE_WEATHERS].SetInterval(1 * IN_MILLISECONDS); - _timers[WUPDATE_AUCTIONS].SetInterval(MINUTE * IN_MILLISECONDS); - _timers[WUPDATE_AUCTIONS].SetCurrent(MINUTE * IN_MILLISECONDS); _timers[WUPDATE_UPTIME].SetInterval(_int_configs[CONFIG_UPTIME_UPDATE]*MINUTE * IN_MILLISECONDS); //Update "uptime" table based on configuration entry in minutes. @@ -2344,19 +2342,12 @@ void World::Update(uint32 diff) ResetGuildCap(); } - // pussywizard: handle auctions when the timer has passed - if (_timers[WUPDATE_AUCTIONS].Passed()) { - METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update expired auctions")); - - _timers[WUPDATE_AUCTIONS].Reset(); - // pussywizard: handle expired auctions, auctions expired when realm was offline are also handled here (not during loading when many required things aren't loaded yet) - sAuctionMgr->Update(); + METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update expired auctions")); + sAuctionMgr->Update(diff); } - AsyncAuctionListingMgr::Update(Milliseconds(diff)); - if (currentGameTime > _mail_expire_check_timer) { sObjectMgr->ReturnOrDeleteOldMails(true); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 0d71b24eb95692..bb62006011083c 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -58,7 +58,6 @@ enum ShutdownExitCode : uint8 /// Timers for different object refresh rates enum WorldTimers { - WUPDATE_AUCTIONS, WUPDATE_WEATHERS, WUPDATE_UPTIME, WUPDATE_CORPSES,