diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp index c46f59dad..da2b658a3 100644 --- a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp +++ b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp @@ -34,6 +34,186 @@ 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; + sObjectMgr->GetPlayerNameByGUID(left->owner.GetCounter(), leftName); + + std::string rightName; + sObjectMgr->GetPlayerNameByGUID(right->owner.GetCounter(), 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() { } @@ -541,139 +721,192 @@ void AuctionHouseObject::BuildListOwnerItems(WorldPacket& data, Player* player, 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*/) + uint32& count, uint32& totalcount, uint8 /*getAll*/, AuctionSortOrderVector const& sortOrder) { uint32 itrcounter = 0; + 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()) { - totalcount = Getcount(); - if (listfrom < totalcount) + auto itr = GetAuctionsBegin(); + for (; itr != GetAuctionsEnd(); ++itr) { - AuctionEntryMap::iterator itr = AuctionsMap.begin(); - std::advance(itr, listfrom); - for (; itr != AuctionsMap.end(); ++itr) - { - itr->second->BuildAuctionInfo(data); - if ((++count) >= 50) - break; - } + auctionShortlist.push_back(itr->second); + } + } + else + { + time_t curTime = sWorld->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 (!AsyncAuctionListingMgr::IsAuctionListingAllowed()) // pussywizard: World::Update is waiting for us... + { + if ((itrcounter++) % 100 == 0) // check condition every 100 iterations + { + if (avgDiffTracker.getAverage() >= 30 || getMSTimeDiff(World::GetGameTimeMS(), getMSTime()) >= 10) // pussywizard: stop immediately if diff is high or waiting too long + { + return false; + } + } + } + + AuctionEntry* Aentry = itr->second; + // Skip expired auctions + if (Aentry->expire_time < curTime) + { + 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) + { + const ItemRandomSuffixEntry* itemRandEntry = sItemRandomSuffixStore.LookupEntry(-item->GetItemRandomPropertyId()); + if (itemRandEntry) + suffix = &itemRandEntry->Name; + } + else + { + const ItemRandomPropertiesEntry* 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); } - return true; } - time_t curTime = sWorld->GetGameTime(); - - int loc_idx = player->GetSession()->GetSessionDbLocaleIndex(); - int locdbc_idx = player->GetSession()->GetSessionDbcLocale(); - - for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr) + // Check if sort enabled, and first sort column is valid, if not don't sort + if (sortOrder.size() > 0) { - if (!AsyncAuctionListingMgr::IsAuctionListingAllowed()) // pussywizard: World::Update is waiting for us... - if ((itrcounter++) % 100 == 0) // check condition every 100 iterations - if (avgDiffTracker.getAverage() >= 30 || getMSTimeDiff(World::GetGameTimeMS(), getMSTime()) >= 10) // pussywizard: stop immediately if diff is high or waiting too long - return false; - - AuctionEntry* Aentry = itr->second; - // Skip expired auctions - if (Aentry->expire_time < curTime) - 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) + AuctionSortInfo const& sortInfo = *sortOrder.begin(); + if (sortInfo.sortOrder >= AUCTION_SORT_MINLEVEL && sortInfo.sortOrder < AUCTION_SORT_MAX && sortInfo.sortOrder != AUCTION_SORT_UNK4) { - // 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) + // Partial sort to improve performance a bit, but the last pages will burn + if (listfrom + 50 <= auctionShortlist.size()) { - // 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) - { - const ItemRandomSuffixEntry* itemRandEntry = sItemRandomSuffixStore.LookupEntry(-item->GetItemRandomPropertyId()); - if (itemRandEntry) - suffix = &itemRandEntry->Name; - } - else - { - const ItemRandomPropertiesEntry* 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]; - } + 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)); } - - // Perform the search (with or without suffix) - if (!Utf8FitTo(name, wsearchedname)) - continue; } + } + 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; - Aentry->BuildAuctionInfo(data); + auction->BuildAuctionInfo(data); } ++totalcount; } diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.h b/src/server/game/AuctionHouse/AuctionHouseMgr.h index 61508e85d..fdeb6c08d 100644 --- a/src/server/game/AuctionHouse/AuctionHouseMgr.h +++ b/src/server/game/AuctionHouse/AuctionHouseMgr.h @@ -70,6 +70,33 @@ enum AuctionHouses AUCTIONHOUSE_NEUTRAL = 7 }; +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() : sortOrder(AUCTION_SORT_MAX), isDesc(true) { } + + AuctionSortOrder sortOrder; + bool isDesc; +}; + +typedef std::vector AuctionSortOrderVector; + struct AuctionEntry { uint32 Id; @@ -135,7 +162,7 @@ public: 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); + uint32& count, uint32& totalcount, uint8 getAll, AuctionSortOrderVector const& sortOrder); private: AuctionEntryMap AuctionsMap; diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp index 207ba8521..3ff69168a 100644 --- a/src/server/game/Handlers/AuctionHouseHandler.cpp +++ b/src/server/game/Handlers/AuctionHouseHandler.cpp @@ -727,28 +727,40 @@ void WorldSession::HandleAuctionListItems(WorldPacket& recvData) uint8 getAll; recvData >> getAll; - // this block looks like it uses some lame byte packing or similar... - uint8 unkCnt; - recvData >> unkCnt; - for (uint8 i = 0; i < unkCnt; i++) + // Read sort block + uint8 sortOrderCount; + recvData >> sortOrderCount; + AuctionSortOrderVector sortOrder; + for (uint8 i = 0; i < sortOrderCount; i++) { - recvData.read_skip(); - recvData.read_skip(); + uint8 sortMode; + uint8 isDesc; + recvData >> sortMode; + recvData >> isDesc; + AuctionSortInfo sortInfo; + sortInfo.isDesc = (isDesc == 1); + sortInfo.sortOrder = static_cast(sortMode); + sortOrder.push_back(std::move(sortInfo)); } // remove fake death if (_player->HasUnitState(UNIT_STATE_DIED)) + { _player->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + } // pussywizard: const uint32 delay = 2000; const uint32 now = World::GetGameTimeMS(); uint32 diff = getMSTimeDiff(_lastAuctionListItemsMSTime, now); if (diff > delay) + { diff = delay; + } _lastAuctionListItemsMSTime = now + delay - diff; std::lock_guard guard(AsyncAuctionListingMgr::GetTempLock()); - AsyncAuctionListingMgr::GetTempList().push_back( AuctionListItemsDelayEvent(delay - diff, _player->GetGUID(), guid, searchedname, listfrom, levelmin, levelmax, usable, auctionSlotID, auctionMainCategory, auctionSubCategory, quality, getAll) ); + AsyncAuctionListingMgr::GetTempList().push_back(AuctionListItemsDelayEvent(delay - diff, _player->GetGUID(), guid, searchedname, listfrom, levelmin, levelmax, usable, auctionSlotID, + auctionMainCategory, auctionSubCategory, quality, getAll, sortOrder)); } void WorldSession::HandleAuctionListPendingSales(WorldPacket& recvData) diff --git a/src/server/game/Misc/AsyncAuctionListing.cpp b/src/server/game/Misc/AsyncAuctionListing.cpp index 889961302..d52908e1e 100644 --- a/src/server/game/Misc/AsyncAuctionListing.cpp +++ b/src/server/game/Misc/AsyncAuctionListing.cpp @@ -16,7 +16,6 @@ */ #include "AsyncAuctionListing.h" -#include "AuctionHouseMgr.h" #include "Creature.h" #include "ObjectAccessor.h" #include "Opcodes.h" @@ -64,7 +63,7 @@ bool AuctionListItemsDelayEvent::Execute() bool result = auctionHouse->BuildListAuctionItems(data, plr, wsearchedname, _listfrom, _levelmin, _levelmax, _usable, _auctionSlotID, _auctionMainCategory, _auctionSubCategory, _quality, - count, totalcount, _getAll); + count, totalcount, _getAll, _sortOrder); if (!result) return false; diff --git a/src/server/game/Misc/AsyncAuctionListing.h b/src/server/game/Misc/AsyncAuctionListing.h index c7e80f82d..40476ed81 100644 --- a/src/server/game/Misc/AsyncAuctionListing.h +++ b/src/server/game/Misc/AsyncAuctionListing.h @@ -18,10 +18,8 @@ #ifndef __ASYNCAUCTIONLISTING_H #define __ASYNCAUCTIONLISTING_H -#include "Common.h" -#include "EventProcessor.h" -#include "WorldPacket.h" -#include "ObjectGuid.h" +#include "AuctionHouseMgr.h" + #include class AuctionListOwnerItemsDelayEvent : public BasicEvent @@ -43,8 +41,10 @@ private: class AuctionListItemsDelayEvent { public: - AuctionListItemsDelayEvent(uint32 msTimer, 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) : - _msTimer(msTimer), _playerguid(playerguid), _creatureguid(creatureguid), _searchedname(searchedname), _listfrom(listfrom), _levelmin(levelmin), _levelmax(levelmax), _usable(usable), _auctionSlotID(auctionSlotID), _auctionMainCategory(auctionMainCategory), _auctionSubCategory(auctionSubCategory), _quality(quality), _getAll(getAll) { } + AuctionListItemsDelayEvent(uint32 msTimer, 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) : + _msTimer(msTimer), _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(); @@ -61,6 +61,7 @@ public: uint32 _auctionSubCategory; uint32 _quality; uint8 _getAll; + AuctionSortOrderVector _sortOrder; }; class AsyncAuctionListingMgr