fix(Core/AuctionHouse): Implemented sorting. Based on @r00ty-tc work. (#9011)

Fixes #8728
This commit is contained in:
UltraNix
2021-11-16 17:32:12 +01:00
committed by GitHub
parent eed40ce78a
commit 3895487dc2
5 changed files with 401 additions and 129 deletions

View File

@@ -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<AuctionEntry*> 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<char const*, 16> 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<char const*, 16> 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;
}

View File

@@ -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<AuctionSortInfo> 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;

View File

@@ -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<uint8>();
recvData.read_skip<uint8>();
uint8 sortMode;
uint8 isDesc;
recvData >> sortMode;
recvData >> isDesc;
AuctionSortInfo sortInfo;
sortInfo.isDesc = (isDesc == 1);
sortInfo.sortOrder = static_cast<AuctionSortOrder>(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<std::mutex> 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)

View File

@@ -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;

View File

@@ -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 <mutex>
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