Files
TrinityCore/src/server/game/AuctionHouse/AuctionHouseMgr.cpp
pete318 28f51306d4 Improved Auction house deposit handling.
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
2016-03-23 00:28:03 +00:00

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();
}