mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-28 21:02:14 +01:00
Including the weird quirks.
Changes:
* Deposit caculated according to same rule as client. (detailed below). Should always match client deposit now
* Deposit for multiple auctions now only collected once. Deferred calculation of deposit until all auctions listed.
Deposit calculation is as follows:
Base deposit calculation = MSV x 15/75% (depending on AH). However this is not rounded. Case to int is used (so always round down)
The remainder is held in a float.
The base is then multiplied by number of items, and the time multiplier (x2 for 24 hour, x4 for 48 hour)
The nearest (no of items or lower) no of items when multiplied by the remainder that creates a whole number is then multiplied by the time multiplier (x1/x2/x4) and then added to deposit.
Example:
Item sell price 1s25. Deposit 18.75c (15% of 1s25). So base deposit = 18, remainder 0.75. Time 24h (x2) Item count 1 = 36c. Remainder = 0.75 (n) Min = 1s. Deposit 1s
Item sell price 1s25. Deposit 18.75c (15% of 1s25). So base deposit = 18, remainder 0.75. Time 24h (x2) Item count 2 = 72c. Remainder = 1.50 (n) Min = 1s. Deposit 1s
Item sell price 1s25. Deposit 18.75c (15% of 1s25). So base deposit = 18, remainder 0.75. Time 24h (x2) Item count 3 = 108c. Remainder = 2.25 (n) Min = 1s. Deposit 1s08c
Item sell price 1s25. Deposit 18.75c (15% of 1s25). So base deposit = 18, remainder 0.75. Time 24h (x2) Item count 4 = 144c. Remainder = 3.00 (n) Min = 1s. Deposit 1s50c (144c + (3c * 2))
Horrible kludge, to re-create a very weird deposit method.
Closes #15674 (PR)
Closes #15643 (Issue)
(cherry picked from commit 0ac442f19f)
# Conflicts:
# src/server/game/AuctionHouse/AuctionHouseMgr.h
886 lines
31 KiB
C++
886 lines
31 KiB
C++
/*
|
|
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
|
|
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "Common.h"
|
|
#include "ObjectMgr.h"
|
|
#include "Player.h"
|
|
#include "World.h"
|
|
#include "WorldPacket.h"
|
|
#include "WorldSession.h"
|
|
#include "DatabaseEnv.h"
|
|
#include "DBCStores.h"
|
|
#include "ScriptMgr.h"
|
|
#include "AccountMgr.h"
|
|
#include "AuctionHouseMgr.h"
|
|
#include "Item.h"
|
|
#include "Language.h"
|
|
#include "Log.h"
|
|
#include <vector>
|
|
|
|
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<HighGuid::Player>(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<HighGuid::Player>(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->UpdateAchievementCriteria(ACHIEVEMENT_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<HighGuid::Player>(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<HighGuid::Player>(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->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_EARNED_BY_AUCTIONS, profit);
|
|
owner->UpdateAchievementCriteria(ACHIEVEMENT_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<HighGuid::Player>(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<HighGuid::Player>(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<HighGuid::Player>(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 itr = thisAH->begin(); itr != thisAH->end(); ++itr)
|
|
{
|
|
AuctionEntry* AH = (*itr);
|
|
|
|
if (next(itr) == 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 = 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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& wsearchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, uint8 usable,
|
|
uint32 inventoryType, uint32 itemClass, uint32 itemSubClass, uint32 quality, uint32& totalcount)
|
|
{
|
|
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 (itemClass != 0xffffffff && proto->GetClass() != itemClass)
|
|
continue;
|
|
|
|
if (itemSubClass != 0xffffffff && proto->GetSubClass() != itemSubClass)
|
|
continue;
|
|
|
|
if (inventoryType != 0xffffffff && proto->GetInventoryType() != InventoryType(inventoryType))
|
|
continue;
|
|
|
|
if (quality != 0xffffffff && proto->GetQuality() != quality)
|
|
continue;
|
|
|
|
if (levelmin != 0 && (proto->GetBaseRequiredLevel() < levelmin || (levelmax != 0 && proto->GetBaseRequiredLevel() > levelmax)))
|
|
continue;
|
|
|
|
if (usable != 0 && 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 (!wsearchedname.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, wsearchedname))
|
|
continue;
|
|
}
|
|
|
|
// Add the item if no search term or if entered search term was found
|
|
if (packet.Items.size() < 50 && totalcount >= listfrom)
|
|
Aentry->BuildAuctionInfo(packet.Items, true);
|
|
|
|
++totalcount;
|
|
}
|
|
}
|
|
|
|
//this function inserts to WorldPacket auction's data
|
|
void AuctionEntry::BuildAuctionInfo(std::vector<WorldPackets::AuctionHouse::AuctionItem>& items, bool listAuctionItems) const
|
|
{
|
|
Item* item = 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<HighGuid::Player>(owner);
|
|
auctionItem.OwnerAccountID = ObjectGuid::Create<HighGuid::WowAccount>(ObjectMgr::GetPlayerAccountIdByGUID(auctionItem.Owner));
|
|
auctionItem.MinIncrement = bidder ? GetAuctionOutBid() : 0;
|
|
auctionItem.Bidder = bidder ? ObjectGuid::Create<HighGuid::Player>(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);
|
|
}
|
|
|
|
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);
|
|
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<HighGuid::Player>(lowGuid); // HIGHGUID_PLAYER always present, even for empty guids
|
|
strm << std::dec << ':' << bid << ':' << buyout;
|
|
strm << ':' << deposit << ':' << cut;
|
|
return strm.str();
|
|
}
|