/* * 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 General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "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.loc_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.loc_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->SendDirectMessage(&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()); }