/* * Copyright (C) 2008-2016 TrinityCore * Copyright (C) 2005-2009 MaNGOS * * 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 "Common.h" #include "ObjectMgr.h" #include "Player.h" #include "World.h" #include "WorldPacket.h" #include "WorldSession.h" #include "DatabaseEnv.h" #include "ScriptMgr.h" #include "AccountMgr.h" #include "AuctionHouseMgr.h" #include "Item.h" #include "Language.h" #include "Log.h" #include enum eAuctionHouse { AH_MINIMUM_DEPOSIT = 100 }; AuctionHouseMgr::AuctionHouseMgr() { } AuctionHouseMgr::~AuctionHouseMgr() { for (ItemMap::iterator itr = mAitems.begin(); itr != mAitems.end(); ++itr) delete itr->second; } AuctionHouseMgr* AuctionHouseMgr::instance() { static AuctionHouseMgr instance; return &instance; } AuctionHouseObject* AuctionHouseMgr::GetAuctionsMap(uint32 factionTemplateId) { if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) return &mNeutralAuctions; // teams have linked auction houses FactionTemplateEntry const* uEntry = sFactionTemplateStore.LookupEntry(factionTemplateId); if (!uEntry) return &mNeutralAuctions; else if (uEntry->Mask & FACTION_MASK_ALLIANCE) return &mAllianceAuctions; else if (uEntry->Mask & FACTION_MASK_HORDE) return &mHordeAuctions; else return &mNeutralAuctions; } uint32 AuctionHouseMgr::GetAuctionDeposit(AuctionHouseEntry const* entry, uint32 time, Item* pItem, uint32 count) { uint32 MSV = pItem->GetTemplate()->GetSellPrice(); if (MSV <= 0) return AH_MINIMUM_DEPOSIT * sWorld->getRate(RATE_AUCTION_DEPOSIT); float multiplier = CalculatePct(float(entry->DepositRate), 3); uint32 timeHr = (((time / 60) / 60) / 12); uint32 deposit = uint32(MSV * multiplier * sWorld->getRate(RATE_AUCTION_DEPOSIT)); float remainderbase = float(MSV * multiplier * sWorld->getRate(RATE_AUCTION_DEPOSIT)) - deposit; deposit *= timeHr * count; int i = count; while (i > 0 && (remainderbase * i) != uint32(remainderbase * i)) i--; if (i) deposit += remainderbase * i * timeHr; TC_LOG_DEBUG("auctionHouse", "MSV: %u", MSV); TC_LOG_DEBUG("auctionHouse", "Items: %u", count); TC_LOG_DEBUG("auctionHouse", "Multiplier: %f", multiplier); TC_LOG_DEBUG("auctionHouse", "Deposit: %u", deposit); TC_LOG_DEBUG("auctionHouse", "Deposit rm: %f", remainderbase * count); if (deposit < AH_MINIMUM_DEPOSIT * sWorld->getRate(RATE_AUCTION_DEPOSIT)) return AH_MINIMUM_DEPOSIT * sWorld->getRate(RATE_AUCTION_DEPOSIT); else return deposit; } //does not clear ram void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry* auction, SQLTransaction& trans) { Item* item = GetAItem(auction->itemGUIDLow); if (!item) return; uint32 bidderAccId = 0; ObjectGuid bidderGuid = ObjectGuid::Create(auction->bidder); Player* bidder = ObjectAccessor::FindConnectedPlayer(bidderGuid); // data for gm.log std::string bidderName; bool logGmTrade = false; if (bidder) { bidderAccId = bidder->GetSession()->GetAccountId(); bidderName = bidder->GetName(); logGmTrade = bidder->GetSession()->HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE); } else { bidderAccId = ObjectMgr::GetPlayerAccountIdByGUID(bidderGuid); logGmTrade = AccountMgr::HasPermission(bidderAccId, rbac::RBAC_PERM_LOG_GM_TRADE, realm.Id.Realm); if (logGmTrade && !ObjectMgr::GetPlayerNameByGUID(bidderGuid, bidderName)) bidderName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN); } if (logGmTrade) { ObjectGuid ownerGuid = ObjectGuid::Create(auction->owner); std::string ownerName; if (!ObjectMgr::GetPlayerNameByGUID(ownerGuid, ownerName)) ownerName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN); uint32 ownerAccId = ObjectMgr::GetPlayerAccountIdByGUID(ownerGuid); sLog->outCommand(bidderAccId, "GM %s (Account: %u) won item in auction: %s (Entry: %u Count: %u) and pay money: %u. Original owner %s (Account: %u)", bidderName.c_str(), bidderAccId, item->GetTemplate()->GetDefaultLocaleName(), item->GetEntry(), item->GetCount(), auction->bid, ownerName.c_str(), ownerAccId); } // receiver exist if (bidder || bidderAccId) { // set owner to bidder (to prevent delete item with sender char deleting) // owner in `data` will set at mail receive and item extracting PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_OWNER); stmt->setUInt64(0, auction->bidder); stmt->setUInt64(1, item->GetGUID().GetCounter()); trans->Append(stmt); if (bidder) { bidder->GetSession()->SendAuctionWonNotification(auction, item); // FIXME: for offline player need also bidder->UpdateCriteria(CRITERIA_TYPE_WON_AUCTIONS, 1); } MailDraft(auction->BuildAuctionMailSubject(AUCTION_WON), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, 0, 0)) .AddItem(item) .SendMailTo(trans, MailReceiver(bidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED); } else { // bidder doesn't exist, delete the item sAuctionMgr->RemoveAItem(auction->itemGUIDLow, true); } } void AuctionHouseMgr::SendAuctionSalePendingMail(AuctionEntry* auction, SQLTransaction& trans) { ObjectGuid owner_guid = ObjectGuid::Create(auction->owner); Player* owner = ObjectAccessor::FindConnectedPlayer(owner_guid); uint32 owner_accId = ObjectMgr::GetPlayerAccountIdByGUID(owner_guid); // owner exist (online or offline) if (owner || owner_accId) MailDraft(auction->BuildAuctionMailSubject(AUCTION_SALE_PENDING), AuctionEntry::BuildAuctionMailBody(auction->bidder, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut())) .SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED); } //call this method to send mail to auction owner, when auction is successful, it does not clear ram void AuctionHouseMgr::SendAuctionSuccessfulMail(AuctionEntry* auction, SQLTransaction& trans) { ObjectGuid owner_guid = ObjectGuid::Create(auction->owner); Player* owner = ObjectAccessor::FindConnectedPlayer(owner_guid); uint32 owner_accId = ObjectMgr::GetPlayerAccountIdByGUID(owner_guid); Item* item = GetAItem(auction->itemGUIDLow); // owner exist if (owner || owner_accId) { uint32 profit = auction->bid + auction->deposit - auction->GetAuctionCut(); //FIXME: what do if owner offline if (owner && item) { owner->UpdateCriteria(CRITERIA_TYPE_GOLD_EARNED_BY_AUCTIONS, profit); owner->UpdateCriteria(CRITERIA_TYPE_HIGHEST_AUCTION_SOLD, auction->bid); //send auction owner notification, bidder must be current! owner->GetSession()->SendAuctionClosedNotification(auction, (float)sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), true, item); } MailDraft(auction->BuildAuctionMailSubject(AUCTION_SUCCESSFUL), AuctionEntry::BuildAuctionMailBody(auction->bidder, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut())) .AddMoney(profit) .SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED, sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY)); } } //does not clear ram void AuctionHouseMgr::SendAuctionExpiredMail(AuctionEntry* auction, SQLTransaction& trans) { //return an item in auction to its owner by mail Item* item = GetAItem(auction->itemGUIDLow); if (!item) return; ObjectGuid owner_guid = ObjectGuid::Create(auction->owner); Player* owner = ObjectAccessor::FindConnectedPlayer(owner_guid); uint32 owner_accId = ObjectMgr::GetPlayerAccountIdByGUID(owner_guid); // owner exist if (owner || owner_accId) { if (owner) owner->GetSession()->SendAuctionClosedNotification(auction, 0.0f, false, item); MailDraft(auction->BuildAuctionMailSubject(AUCTION_EXPIRED), AuctionEntry::BuildAuctionMailBody(0, 0, auction->buyout, auction->deposit, 0)) .AddItem(item) .SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED, 0); } else { // owner doesn't exist, delete the item sAuctionMgr->RemoveAItem(auction->itemGUIDLow, true); } } //this function sends mail to old bidder void AuctionHouseMgr::SendAuctionOutbiddedMail(AuctionEntry* auction, uint32 /*newPrice*/, Player* /*newBidder*/, SQLTransaction& trans) { ObjectGuid oldBidder_guid = ObjectGuid::Create(auction->bidder); Player* oldBidder = ObjectAccessor::FindConnectedPlayer(oldBidder_guid); uint32 oldBidder_accId = 0; if (!oldBidder) oldBidder_accId = ObjectMgr::GetPlayerAccountIdByGUID(oldBidder_guid); Item* item = GetAItem(auction->itemGUIDLow); // old bidder exist if (oldBidder || oldBidder_accId) { if (oldBidder && item) oldBidder->GetSession()->SendAuctionOutBidNotification(auction, item); MailDraft(auction->BuildAuctionMailSubject(AUCTION_OUTBIDDED), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut())) .AddMoney(auction->bid) .SendMailTo(trans, MailReceiver(oldBidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED); } } //this function sends mail, when auction is cancelled to old bidder void AuctionHouseMgr::SendAuctionCancelledToBidderMail(AuctionEntry* auction, SQLTransaction& trans) { ObjectGuid bidder_guid = ObjectGuid::Create(auction->bidder); Player* bidder = ObjectAccessor::FindConnectedPlayer(bidder_guid); uint32 bidder_accId = 0; if (!bidder) bidder_accId = ObjectMgr::GetPlayerAccountIdByGUID(bidder_guid); // bidder exist if (bidder || bidder_accId) MailDraft(auction->BuildAuctionMailSubject(AUCTION_CANCELLED_TO_BIDDER), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, auction->deposit, 0)) .AddMoney(auction->bid) .SendMailTo(trans, MailReceiver(bidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED); } void AuctionHouseMgr::LoadAuctionItems() { uint32 oldMSTime = getMSTime(); // need to clear in case we are reloading if (!mAitems.empty()) { for (ItemMap::iterator itr = mAitems.begin(); itr != mAitems.end(); ++itr) delete itr->second; mAitems.clear(); } // data needs to be at first place for Item::LoadFromDB PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTION_ITEMS); PreparedQueryResult result = CharacterDatabase.Query(stmt); if (!result) { TC_LOG_INFO("server.loading", ">> Loaded 0 auction items. DB table `auctionhouse` or `item_instance` is empty!"); return; } uint32 count = 0; do { Field* fields = result->Fetch(); ObjectGuid::LowType itemGuid = fields[0].GetUInt64(); uint32 itemEntry = fields[1].GetUInt32(); ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry); if (!proto) { TC_LOG_ERROR("misc", "AuctionHouseMgr::LoadAuctionItems: Unknown item (GUID: " UI64FMTD " item entry: #%u) in auction, skipped.", itemGuid, itemEntry); continue; } Item* item = NewItemOrBag(proto); if (!item->LoadFromDB(itemGuid, ObjectGuid::Empty, fields, itemEntry)) { delete item; continue; } AddAItem(item); ++count; } while (result->NextRow()); TC_LOG_INFO("server.loading", ">> Loaded %u auction items in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } void AuctionHouseMgr::LoadAuctions() { uint32 oldMSTime = getMSTime(); PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTIONS); PreparedQueryResult result = CharacterDatabase.Query(stmt); if (!result) { TC_LOG_INFO("server.loading", ">> Loaded 0 auctions. DB table `auctionhouse` is empty."); return; } uint32 count = 0; SQLTransaction trans = CharacterDatabase.BeginTransaction(); do { Field* fields = result->Fetch(); AuctionEntry* aItem = new AuctionEntry(); if (!aItem->LoadFromDB(fields)) { aItem->DeleteFromDB(trans); delete aItem; continue; } GetAuctionsMap(aItem->factionTemplateId)->AddAuction(aItem); ++count; } while (result->NextRow()); CharacterDatabase.CommitTransaction(trans); TC_LOG_INFO("server.loading", ">> Loaded %u auctions in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } void AuctionHouseMgr::AddAItem(Item* it) { ASSERT(it); ASSERT(mAitems.count(it->GetGUID().GetCounter()) == 0); mAitems[it->GetGUID().GetCounter()] = it; } bool AuctionHouseMgr::RemoveAItem(ObjectGuid::LowType id, bool deleteItem) { ItemMap::iterator i = mAitems.find(id); if (i == mAitems.end()) return false; if (deleteItem) { SQLTransaction trans = SQLTransaction(nullptr); i->second->FSetState(ITEM_REMOVED); i->second->SaveToDB(trans); } mAitems.erase(i); return true; } void AuctionHouseMgr::PendingAuctionAdd(Player* player, AuctionEntry* aEntry) { PlayerAuctions* thisAH; auto itr = pendingAuctionMap.find(player->GetGUID()); if (itr != pendingAuctionMap.end()) thisAH = itr->second.first; else { thisAH = new PlayerAuctions; pendingAuctionMap[player->GetGUID()] = AuctionPair(thisAH, 0); } thisAH->push_back(aEntry); } uint32 AuctionHouseMgr::PendingAuctionCount(const Player* player) const { auto const itr = pendingAuctionMap.find(player->GetGUID()); if (itr != pendingAuctionMap.end()) return itr->second.first->size(); return 0; } void AuctionHouseMgr::PendingAuctionProcess(Player* player) { auto iterMap = pendingAuctionMap.find(player->GetGUID()); if (iterMap == pendingAuctionMap.end()) return; PlayerAuctions* thisAH = iterMap->second.first; SQLTransaction trans = CharacterDatabase.BeginTransaction(); uint32 totalItems = 0; for (auto itrAH = thisAH->begin(); itrAH != thisAH->end(); ++itrAH) { AuctionEntry* AH = (*itrAH); totalItems += AH->itemCount; } uint32 totaldeposit = 0; auto itr = (*thisAH->begin()); if (Item* item = GetAItem(itr->itemGUIDLow)) totaldeposit = GetAuctionDeposit(itr->auctionHouseEntry, itr->etime, item, totalItems); uint32 depositremain = totaldeposit; for (auto itrAH = thisAH->begin(); itrAH != thisAH->end(); ++itrAH) { AuctionEntry* AH = (*itrAH); if (next(itrAH) == thisAH->end()) AH->deposit = depositremain; else { AH->deposit = totaldeposit / thisAH->size(); depositremain -= AH->deposit; } AH->DeleteFromDB(trans); AH->SaveToDB(trans); } CharacterDatabase.CommitTransaction(trans); pendingAuctionMap.erase(player->GetGUID()); delete thisAH; player->ModifyMoney(-int32(totaldeposit)); } void AuctionHouseMgr::UpdatePendingAuctions() { for (auto itr = pendingAuctionMap.begin(); itr != pendingAuctionMap.end();) { ObjectGuid playerGUID = itr->first; if (Player* player = ObjectAccessor::FindConnectedPlayer(playerGUID)) { // Check if there were auctions since last update process if not if (PendingAuctionCount(player) == itr->second.second) { ++itr; PendingAuctionProcess(player); } else { ++itr; pendingAuctionMap[playerGUID].second = PendingAuctionCount(player); } } else { // Expire any auctions that we couldn't get a deposit for TC_LOG_WARN("auctionHouse", "Player %s was offline, unable to retrieve deposit!", playerGUID.ToString().c_str()); PlayerAuctions* thisAH = itr->second.first; ++itr; SQLTransaction trans = CharacterDatabase.BeginTransaction(); for (auto AHitr = thisAH->begin(); AHitr != thisAH->end();) { AuctionEntry* AH = (*AHitr); ++AHitr; AH->expire_time = time(NULL); AH->DeleteFromDB(trans); AH->SaveToDB(trans); } CharacterDatabase.CommitTransaction(trans); pendingAuctionMap.erase(playerGUID); delete thisAH; } } } void AuctionHouseMgr::Update() { mHordeAuctions.Update(); mAllianceAuctions.Update(); mNeutralAuctions.Update(); } AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntry(uint32 factionTemplateId, uint32* houseId) { uint32 houseid = 7; // goblin auction house if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) { // 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 switch (factionTemplateId) { case 12: houseid = 1; break; // human case 29: houseid = 6; break; // orc, and generic for horde case 55: houseid = 2; break; // dwarf, and generic for alliance case 68: houseid = 4; break; // undead case 80: houseid = 3; break; // n-elf case 104: houseid = 5; break; // trolls case 120: houseid = 7; break; // booty bay, neutral case 474: houseid = 7; break; // gadgetzan, neutral case 855: houseid = 7; break; // everlook, neutral case 1604: houseid = 6; break; // b-elfs, default: // for unknown case { FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(factionTemplateId); if (!u_entry) houseid = 7; // goblin auction house else if (u_entry->Mask & FACTION_MASK_ALLIANCE) houseid = 1; // human auction house else if (u_entry->Mask & FACTION_MASK_HORDE) houseid = 6; // orc auction house else houseid = 7; // goblin auction house break; } } } if (houseId) *houseId = houseid; return sAuctionHouseStore.LookupEntry(houseid); } void AuctionHouseObject::AddAuction(AuctionEntry* auction) { ASSERT(auction); AuctionsMap[auction->Id] = auction; sScriptMgr->OnAuctionAdd(this, auction); } bool AuctionHouseObject::RemoveAuction(AuctionEntry* auction) { bool wasInMap = AuctionsMap.erase(auction->Id) ? true : false; sScriptMgr->OnAuctionRemove(this, auction); // we need to delete the entry, it is not referenced any more delete auction; return wasInMap; } void AuctionHouseObject::Update() { time_t curTime = sWorld->GetGameTime(); ///- Handle expired auctions // If storage is empty, no need to update. next == NULL in this case. if (AuctionsMap.empty()) return; // Clear expired throttled players for (PlayerGetAllThrottleMap::const_iterator itr = GetAllThrottleMap.begin(); itr != GetAllThrottleMap.end();) { if (itr->second.NextAllowedReplication <= curTime) itr = GetAllThrottleMap.erase(itr); else ++itr; } SQLTransaction trans = CharacterDatabase.BeginTransaction(); for (AuctionEntryMap::iterator it = AuctionsMap.begin(); it != AuctionsMap.end();) { // from auctionhousehandler.cpp, creates auction pointer & player pointer AuctionEntry* auction = it->second; // Increment iterator due to AuctionEntry deletion ++it; ///- filter auctions expired on next update if (auction->expire_time > curTime + 60) continue; ///- Either cancel the auction if there was no bidder if (auction->bidder == 0 && auction->bid == 0) { sAuctionMgr->SendAuctionExpiredMail(auction, trans); sScriptMgr->OnAuctionExpire(this, auction); } ///- Or perform the transaction else { //we should send an "item sold" message if the seller is online //we send the item to the winner //we send the money to the seller sAuctionMgr->SendAuctionSuccessfulMail(auction, trans); sAuctionMgr->SendAuctionWonMail(auction, trans); sScriptMgr->OnAuctionSuccessful(this, auction); } ///- In any case clear the auction auction->DeleteFromDB(trans); sAuctionMgr->RemoveAItem(auction->itemGUIDLow); RemoveAuction(auction); } // Run DB changes CharacterDatabase.CommitTransaction(trans); } void AuctionHouseObject::BuildListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItemsResult& packet, Player* player, uint32& totalcount) { for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr) { AuctionEntry* Aentry = itr->second; if (Aentry && Aentry->bidder == player->GetGUID().GetCounter()) { itr->second->BuildAuctionInfo(packet.Items, false); ++totalcount; } } } void AuctionHouseObject::BuildListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItemsResult& packet, Player* player, uint32& totalcount) { for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr) { AuctionEntry* Aentry = itr->second; if (Aentry && Aentry->owner == player->GetGUID().GetCounter()) { Aentry->BuildAuctionInfo(packet.Items, false); ++totalcount; } } } void AuctionHouseObject::BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult& packet, Player* player, std::wstring const& searchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, bool usable, Optional const& filters, uint32 quality) { time_t curTime = sWorld->GetGameTime(); for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr) { AuctionEntry* Aentry = itr->second; // Skip expired auctions if (Aentry->expire_time < curTime) continue; Item* item = sAuctionMgr->GetAItem(Aentry->itemGUIDLow); if (!item) continue; ItemTemplate const* proto = item->GetTemplate(); if (filters) { // if we dont want any class filters, Optional is not initialized // if we dont want this class included, SubclassMask is set to FILTER_SKIP_CLASS // if we want this class and did not specify and subclasses, its set to FILTER_SKIP_SUBCLASS // otherwise full restrictions apply if (filters->Classes[proto->GetClass()].SubclassMask == AuctionSearchFilters::FILTER_SKIP_CLASS) continue; if (filters->Classes[proto->GetClass()].SubclassMask != AuctionSearchFilters::FILTER_SKIP_SUBCLASS) { if (!(filters->Classes[proto->GetClass()].SubclassMask & (1 << proto->GetSubClass()))) continue; if (!(filters->Classes[proto->GetClass()].InvTypes[proto->GetSubClass()] & (1 << proto->GetInventoryType()))) continue; } } if (quality != 0xffffffff && proto->GetQuality() != quality) continue; if (levelmin != 0 && (proto->GetBaseRequiredLevel() < levelmin || (levelmax != 0 && proto->GetBaseRequiredLevel() > levelmax))) continue; if (usable && player->CanUseItem(item) != EQUIP_ERR_OK) 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 (!searchedname.empty()) { std::string name = proto->GetName(player->GetSession()->GetSessionDbcLocale()); if (name.empty()) continue; // 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 names seem misleading const char* suffix = nullptr; if (propRefID < 0) { const ItemRandomSuffixEntry* itemRandSuffix = sItemRandomSuffixStore.LookupEntry(-propRefID); if (itemRandSuffix) suffix = itemRandSuffix->Name->Str[player->GetSession()->GetSessionDbcLocale()]; } else { const ItemRandomPropertiesEntry* itemRandProp = sItemRandomPropertiesStore.LookupEntry(propRefID); if (itemRandProp) suffix = itemRandProp->Name->Str[player->GetSession()->GetSessionDbcLocale()]; } // 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; } } // Perform the search (with or without suffix) if (!Utf8FitTo(name, searchedname)) continue; } // Add the item if no search term or if entered search term was found if (packet.Items.size() < 50 && packet.TotalCount >= listfrom) Aentry->BuildAuctionInfo(packet.Items, true, item); ++packet.TotalCount; } } void AuctionHouseObject::BuildReplicate(WorldPackets::AuctionHouse::AuctionReplicateResponse& auctionReplicateResult, Player* player, uint32 global, uint32 cursor, uint32 tombstone, uint32 count) { time_t curTime = sWorld->GetGameTime(); auto throttleItr = GetAllThrottleMap.find(player->GetGUID()); if (throttleItr != GetAllThrottleMap.end()) { if (throttleItr->second.Global != global || throttleItr->second.Cursor != cursor || throttleItr->second.Tombstone != tombstone) return; if (!throttleItr->second.IsReplicationInProgress() && throttleItr->second.NextAllowedReplication > curTime) return; } else { throttleItr = GetAllThrottleMap.insert({ player->GetGUID(), PlayerGetAllThrottleData{} }).first; throttleItr->second.NextAllowedReplication = curTime + sWorld->getIntConfig(CONFIG_AUCTION_GETALL_DELAY); throttleItr->second.Global = uint32(curTime); } if (AuctionsMap.empty() || !count) return; auto itr = AuctionsMap.upper_bound(cursor); for (; itr != AuctionsMap.end(); ++itr) { AuctionEntry* auction = itr->second; if (auction->expire_time < curTime) continue; Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow); if (!item) continue; auction->BuildAuctionInfo(auctionReplicateResult.Items, true, item); if (!--count) break; } auctionReplicateResult.ChangeNumberGlobal = throttleItr->second.Global; auctionReplicateResult.ChangeNumberCursor = throttleItr->second.Cursor = !auctionReplicateResult.Items.empty() ? auctionReplicateResult.Items.back().AuctionItemID : 0; auctionReplicateResult.ChangeNumberTombstone = throttleItr->second.Tombstone = !count ? AuctionsMap.rbegin()->first : 0; } //this function inserts to WorldPacket auction's data void AuctionEntry::BuildAuctionInfo(std::vector& items, bool listAuctionItems, Item* sourceItem /*= nullptr*/) const { Item* item = (sourceItem) ? sourceItem : sAuctionMgr->GetAItem(itemGUIDLow); if (!item) { TC_LOG_ERROR("misc", "AuctionEntry::BuildAuctionInfo: Auction %u has a non-existent item: " UI64FMTD, Id, itemGUIDLow); return; } WorldPackets::AuctionHouse::AuctionItem auctionItem; auctionItem.AuctionItemID = Id; auctionItem.Item.Initialize(item); auctionItem.BuyoutPrice = buyout; auctionItem.CensorBidInfo = false; auctionItem.CensorServerSideInfo = listAuctionItems; auctionItem.Charges = item->GetSpellCharges(); auctionItem.Count = item->GetCount(); auctionItem.DeleteReason = 0; // Always 0 ? auctionItem.DurationLeft = (expire_time - time(NULL)) * IN_MILLISECONDS; auctionItem.EndTime = expire_time; auctionItem.Flags = 0; // todo auctionItem.ItemGuid = item->GetGUID(); auctionItem.MinBid = startbid; auctionItem.Owner = ObjectGuid::Create(owner); auctionItem.OwnerAccountID = ObjectGuid::Create(ObjectMgr::GetPlayerAccountIdByGUID(auctionItem.Owner)); auctionItem.MinIncrement = bidder ? GetAuctionOutBid() : 0; auctionItem.Bidder = bidder ? ObjectGuid::Create(bidder) : ObjectGuid::Empty; auctionItem.BidAmount = bidder ? bid : 0; for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; i++) { if (!item->GetEnchantmentId((EnchantmentSlot) i)) continue; auctionItem.Enchantments.emplace_back(item->GetEnchantmentId((EnchantmentSlot) i), item->GetEnchantmentDuration((EnchantmentSlot) i), item->GetEnchantmentCharges((EnchantmentSlot) i), i); } uint8 i = 0; for (ItemDynamicFieldGems const& gemData : item->GetGems()) { if (gemData.ItemId) { WorldPackets::Item::ItemGemData gem; gem.Slot = i; gem.Item.Initialize(&gemData); auctionItem.Gems.push_back(gem); } ++i; } items.emplace_back(auctionItem); } uint32 AuctionEntry::GetAuctionCut() const { int32 cut = int32(CalculatePct(bid, auctionHouseEntry->ConsignmentRate) * sWorld->getRate(RATE_AUCTION_CUT)); 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 { uint32 outbid = CalculatePct(bid, 5); return outbid ? outbid : 1; } void AuctionEntry::DeleteFromDB(SQLTransaction& trans) const { PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION); stmt->setUInt32(0, Id); trans->Append(stmt); } void AuctionEntry::SaveToDB(SQLTransaction& trans) const { PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION); stmt->setUInt32(0, Id); stmt->setUInt64(1, auctioneer); stmt->setUInt64(2, itemGUIDLow); stmt->setUInt64(3, owner); stmt->setUInt32(4, buyout); stmt->setUInt32(5, uint32(expire_time)); stmt->setUInt64(6, bidder); stmt->setUInt32(7, bid); stmt->setUInt32(8, startbid); stmt->setUInt32(9, deposit); trans->Append(stmt); } bool AuctionEntry::LoadFromDB(Field* fields) { Id = fields[0].GetUInt32(); auctioneer = fields[1].GetUInt64(); itemGUIDLow = fields[2].GetUInt64(); itemEntry = fields[3].GetUInt32(); itemCount = fields[4].GetUInt32(); owner = fields[5].GetUInt64(); buyout = fields[6].GetUInt32(); expire_time = fields[7].GetUInt32(); bidder = fields[8].GetUInt64(); bid = fields[9].GetUInt32(); startbid = fields[10].GetUInt32(); deposit = fields[11].GetUInt32(); CreatureData const* auctioneerData = sObjectMgr->GetCreatureData(auctioneer); if (!auctioneerData) { TC_LOG_ERROR("misc", "Auction %u has not a existing auctioneer (GUID : " UI64FMTD ")", Id, auctioneer); return false; } CreatureTemplate const* auctioneerInfo = sObjectMgr->GetCreatureTemplate(auctioneerData->id); if (!auctioneerInfo) { TC_LOG_ERROR("misc", "Auction %u has not a existing auctioneer (GUID : " UI64FMTD " Entry: %u)", Id, auctioneer, auctioneerData->id); return false; } factionTemplateId = auctioneerInfo->faction; auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(factionTemplateId, &houseId); if (!auctionHouseEntry) { TC_LOG_ERROR("misc", "Auction %u has auctioneer (GUID : " UI64FMTD " Entry: %u) with wrong faction %u", Id, auctioneer, auctioneerData->id, factionTemplateId); return false; } // check if sold item exists for guid // and itemEntry in fact (GetAItem will fail if problematic in result check in AuctionHouseMgr::LoadAuctionItems) if (!sAuctionMgr->GetAItem(itemGUIDLow)) { TC_LOG_ERROR("misc", "Auction %u has not a existing item : " UI64FMTD, Id, itemGUIDLow); return false; } return true; } std::string AuctionEntry::BuildAuctionMailSubject(MailAuctionAnswers response) const { std::ostringstream strm; strm << itemEntry << ":0:" << response << ':' << Id << ':' << itemCount; return strm.str(); } std::string AuctionEntry::BuildAuctionMailBody(uint64 lowGuid, uint32 bid, uint32 buyout, uint32 deposit, uint32 cut) { std::ostringstream strm; strm.width(16); strm << std::right << std::hex << ObjectGuid::Create(lowGuid); // HIGHGUID_PLAYER always present, even for empty guids strm << std::dec << ':' << bid << ':' << buyout; strm << ':' << deposit << ':' << cut; return strm.str(); }