mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-20 01:15:35 +01:00
This reverts commit ebcff354a2.
This needs more work. I thought the mail that was sent from expired auctions created a new item_instance, it does not.
888 lines
32 KiB
C++
888 lines
32 KiB
C++
/*
|
|
* Copyright (C) 2008-2011 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 "Logging/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;
|
|
}
|
|
|
|
AuctionHouseObject * AuctionHouseMgr::GetAuctionsMap(uint32 factionTemplateId)
|
|
{
|
|
if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
|
|
return &mNeutralAuctions;
|
|
|
|
// team have linked auction houses
|
|
FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(factionTemplateId);
|
|
if (!u_entry)
|
|
return &mNeutralAuctions;
|
|
else if (u_entry->ourMask & FACTION_MASK_ALLIANCE)
|
|
return &mAllianceAuctions;
|
|
else if (u_entry->ourMask & FACTION_MASK_HORDE)
|
|
return &mHordeAuctions;
|
|
else
|
|
return &mNeutralAuctions;
|
|
}
|
|
|
|
uint32 AuctionHouseMgr::GetAuctionDeposit(AuctionHouseEntry const* entry, uint32 time, Item *pItem, uint32 count)
|
|
{
|
|
uint32 MSV = pItem->GetTemplate()->SellPrice;
|
|
|
|
if (MSV <= 0)
|
|
return AH_MINIMUM_DEPOSIT;
|
|
|
|
float multiplier = CalculatePctN(float(entry->depositPercent), 3);
|
|
uint32 timeHr = (((time / 60) / 60) / 12);
|
|
uint32 deposit = uint32(((multiplier * MSV * count / 3) * timeHr * 3) * sWorld->getRate(RATE_AUCTION_DEPOSIT));
|
|
|
|
sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "MSV: %u", MSV);
|
|
sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Items: %u", count);
|
|
sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Multiplier: %f", multiplier);
|
|
sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Deposit: %u", deposit);
|
|
|
|
if (deposit < AH_MINIMUM_DEPOSIT)
|
|
return AH_MINIMUM_DEPOSIT;
|
|
else
|
|
return deposit;
|
|
}
|
|
|
|
//does not clear ram
|
|
void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry *auction, SQLTransaction& trans)
|
|
{
|
|
Item *pItem = GetAItem(auction->item_guidlow);
|
|
if (!pItem)
|
|
return;
|
|
|
|
uint32 bidder_accId = 0;
|
|
uint64 bidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
|
|
Player *bidder = sObjectMgr->GetPlayer(bidder_guid);
|
|
// data for gm.log
|
|
if (sWorld->getBoolConfig(CONFIG_GM_LOG_TRADE))
|
|
{
|
|
uint32 bidder_security = 0;
|
|
std::string bidder_name;
|
|
if (bidder)
|
|
{
|
|
bidder_accId = bidder->GetSession()->GetAccountId();
|
|
bidder_security = bidder->GetSession()->GetSecurity();
|
|
bidder_name = bidder->GetName();
|
|
}
|
|
else
|
|
{
|
|
bidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(bidder_guid);
|
|
bidder_security = sAccountMgr->GetSecurity(bidder_accId, realmID);
|
|
|
|
if (bidder_security > SEC_PLAYER) // not do redundant DB requests
|
|
{
|
|
if (!sObjectMgr->GetPlayerNameByGUID(bidder_guid, bidder_name))
|
|
bidder_name = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
|
|
}
|
|
}
|
|
if (bidder_security > SEC_PLAYER)
|
|
{
|
|
std::string owner_name;
|
|
if (!sObjectMgr->GetPlayerNameByGUID(auction->owner, owner_name))
|
|
owner_name = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
|
|
|
|
uint32 owner_accid = sObjectMgr->GetPlayerAccountIdByGUID(auction->owner);
|
|
|
|
sLog->outCommand(bidder_accId, "GM %s (Account: %u) won item in auction: %s (Entry: %u Count: %u) and pay money: %u. Original owner %s (Account: %u)",
|
|
bidder_name.c_str(), bidder_accId, pItem->GetTemplate()->Name1.c_str(), pItem->GetEntry(), pItem->GetCount(), auction->bid, owner_name.c_str(), owner_accid);
|
|
}
|
|
}
|
|
|
|
// receiver exist
|
|
if (bidder || bidder_accId)
|
|
{
|
|
std::ostringstream msgAuctionWonSubject;
|
|
msgAuctionWonSubject << auction->item_template << ":0:" << AUCTION_WON;
|
|
|
|
std::ostringstream msgAuctionWonBody;
|
|
msgAuctionWonBody.width(16);
|
|
msgAuctionWonBody << std::right << std::hex << auction->owner;
|
|
msgAuctionWonBody << std::dec << ":" << auction->bid << ":" << auction->buyout;
|
|
sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "AuctionWon body string : %s", msgAuctionWonBody.str().c_str());
|
|
|
|
// 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_SET_ITEM_OWNER);
|
|
stmt->setUInt32(0, auction->bidder);
|
|
stmt->setUInt32(1, pItem->GetGUIDLow());
|
|
trans->Append(stmt);
|
|
|
|
if (bidder)
|
|
{
|
|
bidder->GetSession()->SendAuctionBidderNotification(auction->GetHouseId(), auction->Id, bidder_guid, 0, 0, auction->item_template);
|
|
// FIXME: for offline player need also
|
|
bidder->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WON_AUCTIONS, 1);
|
|
}
|
|
|
|
MailDraft(msgAuctionWonSubject.str(), msgAuctionWonBody.str())
|
|
.AddItem(pItem)
|
|
.SendMailTo(trans, MailReceiver(bidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
|
|
}
|
|
}
|
|
|
|
void AuctionHouseMgr::SendAuctionSalePendingMail(AuctionEntry * auction, SQLTransaction& trans)
|
|
{
|
|
uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
|
|
Player *owner = sObjectMgr->GetPlayer(owner_guid);
|
|
uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
|
|
// owner exist (online or offline)
|
|
if (owner || owner_accId)
|
|
{
|
|
std::ostringstream msgAuctionSalePendingSubject;
|
|
msgAuctionSalePendingSubject << auction->item_template << ":0:" << AUCTION_SALE_PENDING;
|
|
|
|
std::ostringstream msgAuctionSalePendingBody;
|
|
uint32 auctionCut = auction->GetAuctionCut();
|
|
|
|
time_t distrTime = time(NULL) + sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY);
|
|
|
|
msgAuctionSalePendingBody.width(16);
|
|
msgAuctionSalePendingBody << std::right << std::hex << auction->bidder;
|
|
msgAuctionSalePendingBody << std::dec << ":" << auction->bid << ":" << auction->buyout;
|
|
msgAuctionSalePendingBody << ":" << auction->deposit << ":" << auctionCut << ":0:";
|
|
msgAuctionSalePendingBody << secsToTimeBitFields(distrTime);
|
|
|
|
sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "AuctionSalePending body string : %s", msgAuctionSalePendingBody.str().c_str());
|
|
|
|
MailDraft(msgAuctionSalePendingSubject.str(), msgAuctionSalePendingBody.str())
|
|
.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)
|
|
{
|
|
uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
|
|
Player *owner = sObjectMgr->GetPlayer(owner_guid);
|
|
uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
|
|
// owner exist
|
|
if (owner || owner_accId)
|
|
{
|
|
std::ostringstream msgAuctionSuccessfulSubject;
|
|
msgAuctionSuccessfulSubject << auction->item_template << ":0:" << AUCTION_SUCCESSFUL;
|
|
|
|
std::ostringstream auctionSuccessfulBody;
|
|
uint32 auctionCut = auction->GetAuctionCut();
|
|
|
|
auctionSuccessfulBody.width(16);
|
|
auctionSuccessfulBody << std::right << std::hex << auction->bidder;
|
|
auctionSuccessfulBody << std::dec << ":" << auction->bid << ":" << auction->buyout;
|
|
auctionSuccessfulBody << ":" << auction->deposit << ":" << auctionCut;
|
|
|
|
sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "AuctionSuccessful body string : %s", auctionSuccessfulBody.str().c_str());
|
|
|
|
uint32 profit = auction->bid + auction->deposit - auctionCut;
|
|
|
|
//FIXME: what do if owner offline
|
|
if (owner)
|
|
{
|
|
owner->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_EARNED_BY_AUCTIONS, profit);
|
|
owner->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_AUCTION_SOLD, auction->bid);
|
|
//send auction owner notification, bidder must be current!
|
|
owner->GetSession()->SendAuctionOwnerNotification(auction);
|
|
}
|
|
MailDraft(msgAuctionSuccessfulSubject.str(), auctionSuccessfulBody.str())
|
|
.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 *pItem = GetAItem(auction->item_guidlow);
|
|
if (!pItem)
|
|
return;
|
|
|
|
uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
|
|
Player *owner = sObjectMgr->GetPlayer(owner_guid);
|
|
uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
|
|
// owner exist
|
|
if (owner || owner_accId)
|
|
{
|
|
std::ostringstream subject;
|
|
subject << auction->item_template << ":0:" << AUCTION_EXPIRED << ":0:0";
|
|
|
|
if (owner)
|
|
owner->GetSession()->SendAuctionOwnerNotification(auction);
|
|
|
|
MailDraft(subject.str(), "") // TODO: fix body
|
|
.AddItem(pItem)
|
|
.SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED, 0);
|
|
}
|
|
}
|
|
|
|
//this function sends mail to old bidder
|
|
void AuctionHouseMgr::SendAuctionOutbiddedMail(AuctionEntry *auction, uint32 newPrice, Player* newBidder, SQLTransaction& trans)
|
|
{
|
|
uint64 oldBidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
|
|
Player *oldBidder = sObjectMgr->GetPlayer(oldBidder_guid);
|
|
|
|
uint32 oldBidder_accId = 0;
|
|
if (!oldBidder)
|
|
oldBidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(oldBidder_guid);
|
|
|
|
// old bidder exist
|
|
if (oldBidder || oldBidder_accId)
|
|
{
|
|
std::ostringstream msgAuctionOutbiddedSubject;
|
|
msgAuctionOutbiddedSubject << auction->item_template << ":0:" << AUCTION_OUTBIDDED << ":0:0";
|
|
|
|
if (oldBidder && newBidder)
|
|
oldBidder->GetSession()->SendAuctionBidderNotification(auction->GetHouseId(), auction->Id, newBidder->GetGUID(), newPrice, auction->GetAuctionOutBid(), auction->item_template);
|
|
|
|
MailDraft(msgAuctionOutbiddedSubject.str(), "") // TODO: fix body
|
|
.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)
|
|
{
|
|
uint64 bidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
|
|
Player *bidder = sObjectMgr->GetPlayer(bidder_guid);
|
|
|
|
uint32 bidder_accId = 0;
|
|
if (!bidder)
|
|
bidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(bidder_guid);
|
|
|
|
// bidder exist
|
|
if (bidder || bidder_accId)
|
|
{
|
|
std::ostringstream msgAuctionCancelledSubject;
|
|
msgAuctionCancelledSubject << auction->item_template << ":0:" << AUCTION_CANCELLED_TO_BIDDER << ":0:0";
|
|
|
|
MailDraft(msgAuctionCancelledSubject.str(), "") // TODO: fix body
|
|
.AddMoney(auction->bid)
|
|
.SendMailTo(trans, MailReceiver(bidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
|
|
}
|
|
}
|
|
|
|
void AuctionHouseMgr::LoadAuctionItems()
|
|
{
|
|
uint32 oldMSTime = getMSTime();
|
|
|
|
// data needs to be at first place for Item::LoadFromDB
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_LOAD_AUCTION_ITEMS);
|
|
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
|
|
|
if (!result)
|
|
{
|
|
sLog->outString(">> Loaded 0 auction items. DB table `auctionhouse` or `item_instance` is empty!");
|
|
sLog->outString();
|
|
return;
|
|
}
|
|
|
|
uint32 count = 0;
|
|
|
|
do
|
|
{
|
|
|
|
Field* fields = result->Fetch();
|
|
|
|
uint32 item_guid = fields[11].GetUInt32();
|
|
uint32 item_template = fields[12].GetUInt32();
|
|
|
|
ItemTemplate const *proto = sObjectMgr->GetItemTemplate(item_template);
|
|
if (!proto)
|
|
{
|
|
sLog->outError("AuctionHouseMgr::LoadAuctionItems: Unknown item (GUID: %u id: #%u) in auction, skipped.", item_guid, item_template);
|
|
continue;
|
|
}
|
|
|
|
Item *item = NewItemOrBag(proto);
|
|
if (!item->LoadFromDB(item_guid, 0, fields, item_template))
|
|
{
|
|
delete item;
|
|
continue;
|
|
}
|
|
AddAItem(item);
|
|
|
|
++count;
|
|
}
|
|
while (result->NextRow());
|
|
|
|
sLog->outString(">> Loaded %u auction items in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
|
sLog->outString();
|
|
}
|
|
|
|
void AuctionHouseMgr::LoadAuctions()
|
|
{
|
|
uint32 oldMSTime = getMSTime();
|
|
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_LOAD_AUCTIONS);
|
|
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
|
|
|
if (!result)
|
|
{
|
|
sLog->outString(">> Loaded 0 auctions. DB table `auctionhouse` is empty.");
|
|
sLog->outString();
|
|
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);
|
|
|
|
sLog->outString(">> Loaded %u auctions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
|
sLog->outString();
|
|
}
|
|
|
|
void AuctionHouseMgr::AddAItem(Item* it)
|
|
{
|
|
ASSERT(it);
|
|
ASSERT(mAitems.find(it->GetGUIDLow()) == mAitems.end());
|
|
mAitems[it->GetGUIDLow()] = it;
|
|
}
|
|
|
|
bool AuctionHouseMgr::RemoveAItem(uint32 id)
|
|
{
|
|
ItemMap::iterator i = mAitems.find(id);
|
|
if (i == mAitems.end())
|
|
return false;
|
|
|
|
mAitems.erase(i);
|
|
return true;
|
|
}
|
|
|
|
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->ourMask & FACTION_MASK_ALLIANCE)
|
|
houseid = 1; // human auction house
|
|
else if (u_entry->ourMask & 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, uint32 /*item_template*/)
|
|
{
|
|
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;
|
|
|
|
QueryResult result = CharacterDatabase.PQuery("SELECT id FROM auctionhouse WHERE time <= %u ORDER BY TIME ASC", (uint32)curTime+60);
|
|
|
|
if (!result)
|
|
return;
|
|
|
|
do
|
|
{
|
|
// from auctionhousehandler.cpp, creates auction pointer & player pointer
|
|
AuctionEntry* auction = GetAuction(result->Fetch()->GetUInt32());
|
|
|
|
if (!auction)
|
|
continue;
|
|
|
|
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
|
|
|
///- Either cancel the auction if there was no bidder
|
|
if (auction->bidder == 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);
|
|
}
|
|
|
|
uint32 item_template = auction->item_template;
|
|
|
|
///- In any case clear the auction
|
|
auction->DeleteFromDB(trans);
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
|
|
RemoveAuction(auction, item_template);
|
|
sAuctionMgr->RemoveAItem(auction->item_guidlow);
|
|
}
|
|
while (result->NextRow());
|
|
}
|
|
|
|
void AuctionHouseObject::BuildListBidderItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount)
|
|
{
|
|
for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
|
|
{
|
|
AuctionEntry *Aentry = itr->second;
|
|
if (Aentry && Aentry->bidder == player->GetGUIDLow())
|
|
{
|
|
if (itr->second->BuildAuctionInfo(data))
|
|
++count;
|
|
|
|
++totalcount;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AuctionHouseObject::BuildListOwnerItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount)
|
|
{
|
|
for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
|
|
{
|
|
AuctionEntry *Aentry = itr->second;
|
|
if (Aentry && Aentry->owner == player->GetGUIDLow())
|
|
{
|
|
if (Aentry->BuildAuctionInfo(data))
|
|
++count;
|
|
|
|
++totalcount;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AuctionHouseObject::BuildListAuctionItems(WorldPacket& data, Player* player,
|
|
std::wstring const& wsearchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, uint8 usable,
|
|
uint32 inventoryType, uint32 itemClass, uint32 itemSubClass, uint32 quality,
|
|
uint32& count, uint32& totalcount)
|
|
{
|
|
int loc_idx = player->GetSession()->GetSessionDbLocaleIndex();
|
|
int locdbc_idx = player->GetSession()->GetSessionDbcLocale();
|
|
|
|
for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
|
|
{
|
|
AuctionEntry *Aentry = itr->second;
|
|
Item *item = sAuctionMgr->GetAItem(Aentry->item_guidlow);
|
|
if (!item)
|
|
continue;
|
|
|
|
ItemTemplate const *proto = item->GetTemplate();
|
|
|
|
if (itemClass != 0xffffffff && proto->Class != itemClass)
|
|
continue;
|
|
|
|
if (itemSubClass != 0xffffffff && proto->SubClass != itemSubClass)
|
|
continue;
|
|
|
|
if (inventoryType != 0xffffffff && proto->InventoryType != inventoryType)
|
|
continue;
|
|
|
|
if (quality != 0xffffffff && proto->Quality != quality)
|
|
continue;
|
|
|
|
if (levelmin != 0x00 && (proto->RequiredLevel < levelmin || (levelmax != 0x00 && proto->RequiredLevel > levelmax)))
|
|
continue;
|
|
|
|
if (usable != 0x00 && 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->Name1;
|
|
if (name.empty())
|
|
continue;
|
|
|
|
// local name
|
|
if (loc_idx >= 0)
|
|
if (ItemLocale const *il = sObjectMgr->GetItemLocale(proto->ItemId))
|
|
sObjectMgr->GetLocaleString(il->Name, loc_idx, name);
|
|
|
|
// DO NOT use GetItemEnchantMod(proto->RandomProperty) as it may return a result
|
|
// that matches the search but it may not equal item->GetItemRandomPropertyId()
|
|
// used in BuildAuctionInfo() which then causes wrong items to be listed
|
|
int32 propRefID = item->GetItemRandomPropertyId();
|
|
|
|
if (propRefID)
|
|
{
|
|
// Append the suffix to the name (ie: of the Monkey) if one exists
|
|
// These are found in ItemRandomProperties.dbc, not ItemRandomSuffix.dbc
|
|
// even though the DBC names seem misleading
|
|
const ItemRandomPropertiesEntry *itemRandProp = sItemRandomPropertiesStore.LookupEntry(propRefID);
|
|
|
|
if (itemRandProp)
|
|
{
|
|
char* const* temp = itemRandProp->nameSuffix;
|
|
//char* temp = itemRandProp->nameSuffix;
|
|
|
|
// dbc local name
|
|
if (temp)
|
|
{
|
|
if (locdbc_idx >= 0)
|
|
{
|
|
// Append the suffix (ie: of the Monkey) to the name using localization
|
|
name += " ";
|
|
name += temp[locdbc_idx];
|
|
}
|
|
else
|
|
{
|
|
// Invalid localization? Append the suffix using default enUS
|
|
name += " ";
|
|
name += temp[LOCALE_enUS];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 (count < 50 && totalcount >= listfrom)
|
|
{
|
|
++count;
|
|
Aentry->BuildAuctionInfo(data);
|
|
}
|
|
++totalcount;
|
|
}
|
|
}
|
|
|
|
//this function inserts to WorldPacket auction's data
|
|
bool AuctionEntry::BuildAuctionInfo(WorldPacket & data) const
|
|
{
|
|
Item *pItem = sAuctionMgr->GetAItem(item_guidlow);
|
|
if (!pItem)
|
|
{
|
|
sLog->outError("auction to item, that doesn't exist !!!!");
|
|
return false;
|
|
}
|
|
data << uint32(Id);
|
|
data << uint32(pItem->GetEntry());
|
|
|
|
for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; ++i)
|
|
{
|
|
data << uint32(pItem->GetEnchantmentId(EnchantmentSlot(i)));
|
|
data << uint32(pItem->GetEnchantmentDuration(EnchantmentSlot(i)));
|
|
data << uint32(pItem->GetEnchantmentCharges(EnchantmentSlot(i)));
|
|
}
|
|
|
|
data << int32(pItem->GetItemRandomPropertyId()); //random item property id
|
|
data << uint32(pItem->GetItemSuffixFactor()); //SuffixFactor
|
|
data << uint32(pItem->GetCount()); //item->count
|
|
data << uint32(pItem->GetSpellCharges()); //item->charge FFFFFFF
|
|
data << uint32(0); //Unknown
|
|
data << uint64(owner); //Auction->owner
|
|
data << uint32(startbid); //Auction->startbid (not sure if useful)
|
|
data << uint32(bid ? GetAuctionOutBid() : 0);
|
|
//minimal outbid
|
|
data << uint32(buyout); //auction->buyout
|
|
data << uint32((expire_time-time(NULL))*IN_MILLISECONDS);//time left
|
|
data << uint64(bidder); //auction->bidder current
|
|
data << uint32(bid); //current bid
|
|
return true;
|
|
}
|
|
|
|
uint32 AuctionEntry::GetAuctionCut() const
|
|
{
|
|
int32 cut = int32(CalculatePctU(bid, auctionHouseEntry->cutPercent) * 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 = CalculatePctN(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_ADD_AUCTION);
|
|
stmt->setUInt32(0, Id);
|
|
stmt->setUInt32(1, auctioneer);
|
|
stmt->setUInt32(2, item_guidlow);
|
|
stmt->setUInt32(3, owner);
|
|
stmt->setInt32 (4, int32(buyout));
|
|
stmt->setUInt64(5, uint64(expire_time));
|
|
stmt->setUInt32(6, bidder);
|
|
stmt->setInt32 (7, int32(bid));
|
|
stmt->setInt32 (8, int32(startbid));
|
|
stmt->setInt32 (9, int32(deposit));
|
|
trans->Append(stmt);
|
|
}
|
|
|
|
bool AuctionEntry::LoadFromDB(Field* fields)
|
|
{
|
|
Id = fields[0].GetUInt32();
|
|
auctioneer = fields[1].GetUInt32();
|
|
item_guidlow = fields[2].GetUInt32();
|
|
item_template = fields[3].GetUInt32();
|
|
owner = fields[4].GetUInt32();
|
|
buyout = fields[5].GetUInt32();
|
|
expire_time = fields[6].GetUInt32();
|
|
bidder = fields[7].GetUInt32();
|
|
bid = fields[8].GetUInt32();
|
|
startbid = fields[9].GetUInt32();
|
|
deposit = fields[10].GetUInt32();
|
|
|
|
CreatureData const* auctioneerData = sObjectMgr->GetCreatureData(auctioneer);
|
|
if (!auctioneerData)
|
|
{
|
|
sLog->outError("Auction %u has not a existing auctioneer (GUID : %u)", Id, auctioneer);
|
|
return false;
|
|
}
|
|
|
|
CreatureTemplate const* auctioneerInfo = sObjectMgr->GetCreatureTemplate(auctioneerData->id);
|
|
if (!auctioneerInfo)
|
|
{
|
|
sLog->outError("Auction %u has not a existing auctioneer (GUID : %u Entry: %u)", Id, auctioneer, auctioneerData->id);
|
|
return false;
|
|
}
|
|
|
|
factionTemplateId = auctioneerInfo->faction_A;
|
|
auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(factionTemplateId);
|
|
if (!auctionHouseEntry)
|
|
{
|
|
sLog->outError("Auction %u has auctioneer (GUID : %u Entry: %u) with wrong faction %u", Id, auctioneer, auctioneerData->id, factionTemplateId);
|
|
return false;
|
|
}
|
|
|
|
// check if sold item exists for guid
|
|
// and item_template in fact (GetAItem will fail if problematic in result check in AuctionHouseMgr::LoadAuctionItems)
|
|
if (!sAuctionMgr->GetAItem(item_guidlow))
|
|
{
|
|
sLog->outError("Auction %u has not a existing item : %u", Id, item_guidlow);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void AuctionHouseMgr::DeleteExpiredAuctionsAtStartup()
|
|
{
|
|
// Deletes expired auctions. Should be called at server start before loading auctions.
|
|
|
|
// DO NOT USE after auctions are already loaded since this deletes from the DB
|
|
// and assumes the auctions HAVE NOT been loaded into a list or AuctionEntryMap yet
|
|
|
|
uint32 oldMSTime = getMSTime();
|
|
uint32 expirecount = 0;
|
|
time_t curTime = sWorld->GetGameTime();
|
|
|
|
// Query the DB to see if there are any expired auctions
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_LOAD_EXPIRED_AUCTIONS);
|
|
stmt->setUInt32(0, (uint32)curTime+60);
|
|
PreparedQueryResult expAuctions = CharacterDatabase.Query(stmt);
|
|
|
|
if (!expAuctions)
|
|
{
|
|
sLog->outString(">> No expired auctions to delete");
|
|
sLog->outString();
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
Field* fields = expAuctions->Fetch();
|
|
|
|
AuctionEntry *auction = new AuctionEntry();
|
|
|
|
// Can't use LoadFromDB() because it assumes the auction map is loaded
|
|
if (!auction->LoadFromFieldList(fields))
|
|
{
|
|
// For some reason the record in the DB is broken (possibly corrupt
|
|
// faction info). Delete the object and move on.
|
|
delete auction;
|
|
continue;
|
|
}
|
|
|
|
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
|
|
|
if (auction->bidder==0)
|
|
{
|
|
// Cancel the auction, there was no bidder
|
|
sAuctionMgr->SendAuctionExpiredMail(auction, trans);
|
|
}
|
|
else
|
|
{
|
|
// Send the item to the winner and money to seller
|
|
sAuctionMgr->SendAuctionSuccessfulMail(auction, trans);
|
|
sAuctionMgr->SendAuctionWonMail(auction, trans);
|
|
}
|
|
|
|
// Call the appropriate AuctionHouseObject script
|
|
// ** Do we need to do this while core is still loading? **
|
|
sScriptMgr->OnAuctionExpire(GetAuctionsMap(auction->factionTemplateId), auction);
|
|
|
|
// Delete the auction from the DB
|
|
auction->DeleteFromDB(trans);
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
|
|
// Release memory
|
|
delete auction;
|
|
++expirecount;
|
|
|
|
} while (expAuctions->NextRow());
|
|
|
|
sLog->outString(">> Deleted %u expired auctions in %u ms", expirecount, GetMSTimeDiffToNow(oldMSTime));
|
|
sLog->outString();
|
|
|
|
}
|
|
|
|
bool AuctionEntry::LoadFromFieldList(Field* fields)
|
|
{
|
|
// Loads an AuctionEntry item from a field list. Unlike "LoadFromDB()", this one
|
|
// does not require the AuctionEntryMap to have been loaded with items. It simply
|
|
// acts as a wrapper to fill out an AuctionEntry struct from a field list
|
|
|
|
Id = fields[0].GetUInt32();
|
|
auctioneer = fields[1].GetUInt32();
|
|
item_guidlow = fields[2].GetUInt32();
|
|
item_template = fields[3].GetUInt32();
|
|
owner = fields[4].GetUInt32();
|
|
buyout = fields[5].GetUInt32();
|
|
expire_time = fields[6].GetUInt32();
|
|
bidder = fields[7].GetUInt32();
|
|
bid = fields[8].GetUInt32();
|
|
startbid = fields[9].GetUInt32();
|
|
deposit = fields[10].GetUInt32();
|
|
|
|
CreatureData const* auctioneerData = sObjectMgr->GetCreatureData(auctioneer);
|
|
if (!auctioneerData)
|
|
{
|
|
sLog->outError("AuctionEntry::LoadFromFieldList() - Auction %u has not a existing auctioneer (GUID : %u)", Id, auctioneer);
|
|
return false;
|
|
}
|
|
|
|
CreatureTemplate const* auctioneerInfo = sObjectMgr->GetCreatureTemplate(auctioneerData->id);
|
|
if (!auctioneerInfo)
|
|
{
|
|
sLog->outError("AuctionEntry::LoadFromFieldList() - Auction %u has not a existing auctioneer (GUID : %u Entry: %u)", Id, auctioneer, auctioneerData->id);
|
|
return false;
|
|
}
|
|
|
|
factionTemplateId = auctioneerInfo->faction_A;
|
|
auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(factionTemplateId);
|
|
|
|
if (!auctionHouseEntry)
|
|
{
|
|
sLog->outError("AuctionEntry::LoadFromFieldList() - Auction %u has auctioneer (GUID : %u Entry: %u) with wrong faction %u", Id, auctioneer, auctioneerData->id, factionTemplateId);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} |