846 lines
36 KiB
C#
846 lines
36 KiB
C#
/*
|
|
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
|
*
|
|
* 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 3 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/>.
|
|
*/
|
|
|
|
using Framework.Constants;
|
|
using Framework.Database;
|
|
using Framework.Dynamic;
|
|
using Game.DataStorage;
|
|
using Game.Entities;
|
|
using Game.Mails;
|
|
using Game.Network.Packets;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.Contracts;
|
|
|
|
namespace Game
|
|
{
|
|
public class AuctionManager : Singleton<AuctionManager>
|
|
{
|
|
const int AH_MINIMUM_DEPOSIT = 100;
|
|
|
|
AuctionManager() { }
|
|
|
|
public AuctionHouseObject GetAuctionsMap(uint factionTemplateId)
|
|
{
|
|
if (WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionAuction))
|
|
return mNeutralAuctions;
|
|
|
|
// teams have linked auction houses
|
|
FactionTemplateRecord uEntry = CliDB.FactionTemplateStorage.LookupByKey(factionTemplateId);
|
|
if (uEntry == null)
|
|
return mNeutralAuctions;
|
|
else if ((uEntry.Mask & (int)FactionMasks.Alliance) != 0)
|
|
return mAllianceAuctions;
|
|
else if ((uEntry.Mask & (int)FactionMasks.Horde) != 0)
|
|
return mHordeAuctions;
|
|
else
|
|
return mNeutralAuctions;
|
|
}
|
|
|
|
public uint GetAuctionDeposit(AuctionHouseRecord entry, uint time, Item pItem, uint count)
|
|
{
|
|
uint MSV = pItem.GetTemplate().GetSellPrice();
|
|
|
|
if (MSV <= 0)
|
|
return AH_MINIMUM_DEPOSIT * (uint)WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit);
|
|
|
|
float multiplier = MathFunctions.CalculatePct((float)entry.DepositRate, 3);
|
|
uint timeHr = (((time / 60) / 60) / 12);
|
|
uint deposit = (uint)(((multiplier * MSV * count / 3) * timeHr * 3) * WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit));
|
|
|
|
Log.outDebug(LogFilter.Auctionhouse, "MSV: {0}", MSV);
|
|
Log.outDebug(LogFilter.Auctionhouse, "Items: {0}", count);
|
|
Log.outDebug(LogFilter.Auctionhouse, "Multiplier: {0}", multiplier);
|
|
Log.outDebug(LogFilter.Auctionhouse, "Deposit: {0}", deposit);
|
|
|
|
if (deposit < AH_MINIMUM_DEPOSIT * WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit))
|
|
return AH_MINIMUM_DEPOSIT * (uint)WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit);
|
|
else
|
|
return deposit;
|
|
}
|
|
|
|
public void SendAuctionWonMail(AuctionEntry auction, SQLTransaction trans)
|
|
{
|
|
Item item = GetAItem(auction.itemGUIDLow);
|
|
if (!item)
|
|
return;
|
|
|
|
uint bidderAccId = 0;
|
|
ObjectGuid bidderGuid = ObjectGuid.Create(HighGuid.Player, auction.bidder);
|
|
Player bidder = Global.ObjAccessor.FindPlayer(bidderGuid);
|
|
// data for gm.log
|
|
string bidderName = "";
|
|
bool logGmTrade = false;
|
|
|
|
if (bidder)
|
|
{
|
|
bidderAccId = bidder.GetSession().GetAccountId();
|
|
bidderName = bidder.GetName();
|
|
logGmTrade = bidder.GetSession().HasPermission(RBACPermissions.LogGmTrade);
|
|
}
|
|
else
|
|
{
|
|
bidderAccId = ObjectManager.GetPlayerAccountIdByGUID(bidderGuid);
|
|
logGmTrade = Global.AccountMgr.HasPermission(bidderAccId, RBACPermissions.LogGmTrade, Global.WorldMgr.GetRealm().Id.Realm);
|
|
|
|
if (logGmTrade && !ObjectManager.GetPlayerNameByGUID(bidderGuid, out bidderName))
|
|
bidderName = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown);
|
|
}
|
|
|
|
if (logGmTrade)
|
|
{
|
|
ObjectGuid ownerGuid = ObjectGuid.Create(HighGuid.Player, auction.owner);
|
|
string ownerName;
|
|
if (!ObjectManager.GetPlayerNameByGUID(ownerGuid, out ownerName))
|
|
ownerName = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown);
|
|
|
|
uint ownerAccId = ObjectManager.GetPlayerAccountIdByGUID(ownerGuid);
|
|
|
|
Log.outCommand(bidderAccId, "GM {0} (Account: {1}) won item in auction: {2} (Entry: {3} Count: {4}) and pay money: {5}. Original owner {6} (Account: {7})",
|
|
bidderName, bidderAccId, item.GetTemplate().GetName(), item.GetEntry(), item.GetCount(), auction.bid, ownerName, ownerAccId);
|
|
}
|
|
|
|
// receiver exist
|
|
if (bidder || bidderAccId != 0)
|
|
{
|
|
// 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 = DB.Characters.GetPreparedStatement(CharStatements.UPD_ITEM_OWNER);
|
|
stmt.AddValue(0, auction.bidder);
|
|
stmt.AddValue(1, item.GetGUID().GetCounter());
|
|
trans.Append(stmt);
|
|
|
|
if (bidder)
|
|
{
|
|
bidder.GetSession().SendAuctionWonNotification(auction, item);
|
|
// FIXME: for offline player need also
|
|
bidder.UpdateCriteria(CriteriaTypes.WonAuctions, 1);
|
|
}
|
|
|
|
new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.Won), AuctionEntry.BuildAuctionMailBody(auction.owner, auction.bid, auction.buyout, 0, 0))
|
|
.AddItem(item)
|
|
.SendMailTo(trans, new MailReceiver(bidder, auction.bidder), new MailSender(auction), MailCheckMask.Copied);
|
|
}
|
|
else
|
|
{
|
|
// bidder doesn't exist, delete the item
|
|
Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow, true);
|
|
}
|
|
}
|
|
|
|
public void SendAuctionSalePendingMail(AuctionEntry auction, SQLTransaction trans)
|
|
{
|
|
ObjectGuid owner_guid = ObjectGuid.Create(HighGuid.Player, auction.owner);
|
|
Player owner = Global.ObjAccessor.FindPlayer(owner_guid);
|
|
uint owner_accId = ObjectManager.GetPlayerAccountIdByGUID(owner_guid);
|
|
// owner exist (online or offline)
|
|
if (owner || owner_accId != 0)
|
|
new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.SalePending), AuctionEntry.BuildAuctionMailBody(auction.bidder, auction.bid, auction.buyout, auction.deposit, auction.GetAuctionCut()))
|
|
.SendMailTo(trans, new MailReceiver(owner, auction.owner), new MailSender(auction), MailCheckMask.Copied);
|
|
}
|
|
|
|
//call this method to send mail to auction owner, when auction is successful, it does not clear ram
|
|
public void SendAuctionSuccessfulMail(AuctionEntry auction, SQLTransaction trans)
|
|
{
|
|
ObjectGuid owner_guid = ObjectGuid.Create(HighGuid.Player, auction.owner);
|
|
Player owner = Global.ObjAccessor.FindPlayer(owner_guid);
|
|
uint owner_accId = ObjectManager.GetPlayerAccountIdByGUID(owner_guid);
|
|
Item item = GetAItem(auction.itemGUIDLow);
|
|
|
|
// owner exist
|
|
if (owner || owner_accId != 0)
|
|
{
|
|
uint profit = auction.bid + auction.deposit - auction.GetAuctionCut();
|
|
|
|
//FIXME: what do if owner offline
|
|
if (owner && item)
|
|
{
|
|
owner.UpdateCriteria(CriteriaTypes.GoldEarnedByAuctions, profit);
|
|
owner.UpdateCriteria(CriteriaTypes.HighestAuctionSold, auction.bid);
|
|
// send auction owner notification, bidder must be current!
|
|
owner.GetSession().SendAuctionClosedNotification(auction, WorldConfig.GetIntValue(WorldCfg.MailDeliveryDelay), true, item);
|
|
}
|
|
|
|
new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.Successful), AuctionEntry.BuildAuctionMailBody(auction.bidder, auction.bid, auction.buyout, auction.deposit, auction.GetAuctionCut()))
|
|
.AddMoney(profit)
|
|
.SendMailTo(trans, new MailReceiver(owner, auction.owner), new MailSender(auction), MailCheckMask.Copied, WorldConfig.GetUIntValue(WorldCfg.MailDeliveryDelay));
|
|
}
|
|
}
|
|
|
|
//does not clear ram
|
|
public void 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 = Global.ObjAccessor.FindPlayer(owner_guid);
|
|
uint owner_accId = ObjectManager.GetPlayerAccountIdByGUID(owner_guid);
|
|
// owner exist
|
|
if (owner || owner_accId != 0)
|
|
{
|
|
if (owner)
|
|
owner.GetSession().SendAuctionClosedNotification(auction, 0f, false, item);
|
|
|
|
new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.Expired), AuctionEntry.BuildAuctionMailBody(0, 0, auction.buyout, auction.deposit, 0))
|
|
.AddItem(item)
|
|
.SendMailTo(trans, new MailReceiver(owner, auction.owner), new MailSender(auction), MailCheckMask.Copied, 0);
|
|
}
|
|
else
|
|
{
|
|
// owner doesn't exist, delete the item
|
|
Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow, true);
|
|
}
|
|
}
|
|
|
|
//this function sends mail to old bidder
|
|
public void SendAuctionOutbiddedMail(AuctionEntry auction, ulong newPrice, Player newBidder, SQLTransaction trans)
|
|
{
|
|
ObjectGuid oldBidder_guid = ObjectGuid.Create(HighGuid.Player, auction.bidder);
|
|
Player oldBidder = Global.ObjAccessor.FindPlayer(oldBidder_guid);
|
|
|
|
uint oldBidder_accId = 0;
|
|
if (oldBidder == null)
|
|
oldBidder_accId = ObjectManager.GetPlayerAccountIdByGUID(oldBidder_guid);
|
|
|
|
Item item = GetAItem(auction.itemGUIDLow);
|
|
|
|
// old bidder exist
|
|
if (oldBidder || oldBidder_accId != 0)
|
|
{
|
|
if (oldBidder && item)
|
|
oldBidder.GetSession().SendAuctionOutBidNotification(auction, item);
|
|
|
|
new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.Outbidded), AuctionEntry.BuildAuctionMailBody(auction.owner, auction.bid, auction.buyout, auction.deposit, auction.GetAuctionCut()))
|
|
.AddMoney(auction.bid)
|
|
.SendMailTo(trans, new MailReceiver(oldBidder, auction.bidder), new MailSender(auction), MailCheckMask.Copied);
|
|
}
|
|
}
|
|
|
|
//this function sends mail, when auction is cancelled to old bidder
|
|
public void SendAuctionCancelledToBidderMail(AuctionEntry auction, SQLTransaction trans)
|
|
{
|
|
ObjectGuid bidder_guid = ObjectGuid.Create(HighGuid.Player, auction.bidder);
|
|
Player bidder = Global.ObjAccessor.FindPlayer(bidder_guid);
|
|
|
|
uint bidder_accId = 0;
|
|
if (!bidder)
|
|
bidder_accId = ObjectManager.GetPlayerAccountIdByGUID(bidder_guid);
|
|
|
|
// bidder exist
|
|
if (bidder || bidder_accId != 0)
|
|
new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.CancelledToBidder), AuctionEntry.BuildAuctionMailBody(auction.owner, auction.bid, auction.buyout, auction.deposit, 0))
|
|
.AddMoney(auction.bid)
|
|
.SendMailTo(trans, new MailReceiver(bidder, auction.bidder), new MailSender(auction), MailCheckMask.Copied);
|
|
}
|
|
|
|
public void LoadAuctionItems()
|
|
{
|
|
uint oldMSTime = Time.GetMSTime();
|
|
|
|
// need to clear in case we are reloading
|
|
mAitems.Clear();
|
|
|
|
// data needs to be at first place for Item.LoadFromDB
|
|
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_AUCTION_ITEMS);
|
|
SQLResult result = DB.Characters.Query(stmt);
|
|
|
|
if (result.IsEmpty())
|
|
{
|
|
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 auction items. DB table `auctionhouse` or `item_instance` is empty!");
|
|
return;
|
|
}
|
|
|
|
uint count = 0;
|
|
do
|
|
{
|
|
ulong itemGuid = result.Read<ulong>(0);
|
|
uint itemEntry = result.Read<uint>(1);
|
|
|
|
ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemEntry);
|
|
if (proto == null)
|
|
{
|
|
Log.outError(LogFilter.Server, "AuctionHouseMgr:LoadAuctionItems: Unknown item (GUID: {0} item entry: {1}) in auction, skipped.", itemGuid, itemEntry);
|
|
continue;
|
|
}
|
|
|
|
Item item = Bag.NewItemOrBag(proto);
|
|
if (!item.LoadFromDB(itemGuid, ObjectGuid.Empty, result.GetFields(), itemEntry))
|
|
continue;
|
|
|
|
AddAItem(item);
|
|
++count;
|
|
}
|
|
while (result.NextRow());
|
|
|
|
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} auction items in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
|
|
}
|
|
|
|
public void LoadAuctions()
|
|
{
|
|
uint oldMSTime = Time.GetMSTime();
|
|
|
|
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_AUCTIONS);
|
|
SQLResult result = DB.Characters.Query(stmt);
|
|
|
|
if (result.IsEmpty())
|
|
{
|
|
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 auctions. DB table `auctionhouse` is empty.");
|
|
return;
|
|
}
|
|
|
|
uint count = 0;
|
|
SQLTransaction trans = new SQLTransaction();
|
|
do
|
|
{
|
|
AuctionEntry aItem = new AuctionEntry();
|
|
if (!aItem.LoadFromDB(result.GetFields()))
|
|
{
|
|
aItem.DeleteFromDB(trans);
|
|
continue;
|
|
}
|
|
|
|
GetAuctionsMap(aItem.factionTemplateId).AddAuction(aItem);
|
|
++count;
|
|
} while (result.NextRow());
|
|
|
|
DB.Characters.CommitTransaction(trans);
|
|
|
|
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} auctions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
|
|
|
|
}
|
|
|
|
public void AddAItem(Item it)
|
|
{
|
|
Contract.Assert(it);
|
|
Contract.Assert(!mAitems.ContainsKey(it.GetGUID().GetCounter()));
|
|
mAitems[it.GetGUID().GetCounter()] = it;
|
|
}
|
|
|
|
public bool RemoveAItem(ulong id, bool deleteItem = false)
|
|
{
|
|
var item = mAitems.LookupByKey(id);
|
|
if (item == null)
|
|
return false;
|
|
|
|
if (deleteItem)
|
|
{
|
|
item.FSetState(ItemUpdateState.Removed);
|
|
item.SaveToDB(null);
|
|
}
|
|
|
|
mAitems.Remove(id);
|
|
return true;
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
mHordeAuctions.Update();
|
|
mAllianceAuctions.Update();
|
|
mNeutralAuctions.Update();
|
|
}
|
|
|
|
public AuctionHouseRecord GetAuctionHouseEntry(uint factionTemplateId)
|
|
{
|
|
uint houseId = 0;
|
|
return GetAuctionHouseEntry(factionTemplateId, ref houseId);
|
|
}
|
|
|
|
public AuctionHouseRecord GetAuctionHouseEntry(uint factionTemplateId, ref uint houseId)
|
|
{
|
|
uint houseid = 7; // goblin auction house
|
|
|
|
if (!WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionAuction))
|
|
{
|
|
// 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
|
|
{
|
|
FactionTemplateRecord u_entry = CliDB.FactionTemplateStorage.LookupByKey(factionTemplateId);
|
|
if (u_entry == null)
|
|
houseid = 7; // goblin auction house
|
|
else if ((u_entry.Mask & (int)FactionMasks.Alliance) != 0)
|
|
houseid = 1; // human auction house
|
|
else if ((u_entry.Mask & (int)FactionMasks.Horde) != 0)
|
|
houseid = 6; // orc auction house
|
|
else
|
|
houseid = 7; // goblin auction house
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
houseId = houseid;
|
|
|
|
return CliDB.AuctionHouseStorage.LookupByKey(houseid);
|
|
}
|
|
|
|
public Item GetAItem(ulong id)
|
|
{
|
|
return mAitems.LookupByKey(id);
|
|
}
|
|
|
|
AuctionHouseObject mHordeAuctions = new AuctionHouseObject();
|
|
AuctionHouseObject mAllianceAuctions = new AuctionHouseObject();
|
|
AuctionHouseObject mNeutralAuctions = new AuctionHouseObject();
|
|
|
|
Dictionary<ulong, Item> mAitems = new Dictionary<ulong, Item>();
|
|
}
|
|
|
|
public class AuctionHouseObject
|
|
{
|
|
public void AddAuction(AuctionEntry auction)
|
|
{
|
|
Contract.Assert(auction != null);
|
|
|
|
AuctionsMap[auction.Id] = auction;
|
|
Global.ScriptMgr.OnAuctionAdd(this, auction);
|
|
}
|
|
|
|
public bool RemoveAuction(AuctionEntry auction)
|
|
{
|
|
bool wasInMap = AuctionsMap.Remove(auction.Id) ? true : false;
|
|
|
|
Global.ScriptMgr.OnAuctionRemove(this, auction);
|
|
|
|
// we need to delete the entry, it is not referenced any more
|
|
auction = null;
|
|
|
|
return wasInMap;
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
long curTime = Global.WorldMgr.GetGameTime();
|
|
// Handle expired auctions
|
|
|
|
// If storage is empty, no need to update. next == NULL in this case.
|
|
if (AuctionsMap.Empty())
|
|
return;
|
|
|
|
SQLTransaction trans = new SQLTransaction();
|
|
|
|
foreach (var auction in AuctionsMap.Values)
|
|
{
|
|
// 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)
|
|
{
|
|
Global.AuctionMgr.SendAuctionExpiredMail(auction, trans);
|
|
Global.ScriptMgr.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
|
|
Global.AuctionMgr.SendAuctionSuccessfulMail(auction, trans);
|
|
Global.AuctionMgr.SendAuctionWonMail(auction, trans);
|
|
Global.ScriptMgr.OnAuctionSuccessful(this, auction);
|
|
}
|
|
|
|
// In any case clear the auction
|
|
auction.DeleteFromDB(trans);
|
|
|
|
Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow);
|
|
RemoveAuction(auction);
|
|
}
|
|
|
|
// Run DB changes
|
|
DB.Characters.CommitTransaction(trans);
|
|
}
|
|
|
|
public void BuildListBidderItems(AuctionListBidderItemsResult packet, Player player, ref uint totalcount)
|
|
{
|
|
foreach (var Aentry in AuctionsMap.Values)
|
|
{
|
|
if (Aentry != null && Aentry.bidder == player.GetGUID().GetCounter())
|
|
{
|
|
Aentry.BuildAuctionInfo(packet.Items, false);
|
|
++totalcount;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void BuildListOwnerItems(AuctionListOwnerItemsResult packet, Player player, ref uint totalcount)
|
|
{
|
|
foreach (var Aentry in AuctionsMap.Values)
|
|
{
|
|
if (Aentry != null && Aentry.owner == player.GetGUID().GetCounter())
|
|
{
|
|
Aentry.BuildAuctionInfo(packet.Items, false);
|
|
++totalcount;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void BuildListAuctionItems(AuctionListItemsResult packet, Player player, string searchedname, uint listfrom, byte levelmin, byte levelmax, bool usable, Optional<AuctionSearchFilters> filters, uint quality)
|
|
{
|
|
long curTime = Global.WorldMgr.GetGameTime();
|
|
|
|
foreach (var Aentry in AuctionsMap.Values)
|
|
{
|
|
// Skip expired auctions
|
|
if (Aentry.expire_time < curTime)
|
|
continue;
|
|
|
|
Item item = Global.AuctionMgr.GetAItem(Aentry.itemGUIDLow);
|
|
if (!item)
|
|
continue;
|
|
|
|
ItemTemplate proto = item.GetTemplate();
|
|
if (filters.HasValue)
|
|
{
|
|
// if we dont want any class filters, Optional is not initialized
|
|
// if we dont want this class included, SubclassMask is set to FILTER_SKIP_CLASS
|
|
// if we want this class and did not specify and subclasses, its set to FILTER_SKIP_SUBCLASS
|
|
// otherwise full restrictions apply
|
|
if (filters.Value.Classes[(int)proto.GetClass()].SubclassMask == AuctionSearchFilters.FilterType.SkipClass)
|
|
continue;
|
|
|
|
if (filters.Value.Classes[(int)proto.GetClass()].SubclassMask != AuctionSearchFilters.FilterType.SkipSubclass)
|
|
{
|
|
if (!Convert.ToBoolean((int)filters.Value.Classes[(int)proto.GetClass()].SubclassMask & (1u << (int)proto.GetSubClass())))
|
|
continue;
|
|
|
|
if (!Convert.ToBoolean(filters.Value.Classes[(int)proto.GetClass()].InvTypes[(int)proto.GetSubClass()] & (1u << (int)proto.GetInventoryType())))
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (quality != 0xffffffff && (uint)proto.GetQuality() != quality)
|
|
continue;
|
|
|
|
if (levelmin != 0 && (proto.GetBaseRequiredLevel() < levelmin || (levelmax != 0x00 && proto.GetBaseRequiredLevel() > levelmax)))
|
|
continue;
|
|
|
|
if (usable && player.CanUseItem(item) != InventoryResult.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 (!string.IsNullOrEmpty(searchedname))
|
|
{
|
|
string name = proto.GetName(player.GetSession().GetSessionDbcLocale());
|
|
if (string.IsNullOrEmpty(name))
|
|
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
|
|
int propRefID = item.GetItemRandomPropertyId();
|
|
if (propRefID != 0)
|
|
{
|
|
string suffix = null;
|
|
// 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
|
|
if (propRefID < 0)
|
|
{
|
|
ItemRandomSuffixRecord itemRandSuffix = CliDB.ItemRandomSuffixStorage.LookupByKey(-propRefID);
|
|
if (itemRandSuffix != null)
|
|
suffix = itemRandSuffix.Name[player.GetSession().GetSessionDbcLocale()];
|
|
}
|
|
else
|
|
{
|
|
ItemRandomPropertiesRecord itemRandProp = CliDB.ItemRandomPropertiesStorage.LookupByKey(propRefID);
|
|
if (itemRandProp != null)
|
|
suffix = itemRandProp.Name[player.GetSession().GetSessionDbcLocale()];
|
|
}
|
|
|
|
// dbc local name
|
|
if (!string.IsNullOrEmpty(suffix))
|
|
{
|
|
// Append the suffix (ie: of the Monkey) to the name using localization
|
|
// or default enUS if localization is invalid
|
|
name += ' ' + suffix;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the item if no search term or if entered search term was found
|
|
if (packet.Items.Count < 50 && packet.TotalCount >= listfrom)
|
|
Aentry.BuildAuctionInfo(packet.Items, true);
|
|
|
|
++packet.TotalCount;
|
|
}
|
|
}
|
|
|
|
public AuctionEntry GetAuction(uint id)
|
|
{
|
|
return AuctionsMap.LookupByKey(id);
|
|
}
|
|
|
|
Dictionary<uint, AuctionEntry> AuctionsMap = new Dictionary<uint, AuctionEntry>();
|
|
}
|
|
|
|
public class AuctionEntry
|
|
{
|
|
public void BuildAuctionInfo(List<AuctionItem> items, bool listAuctionItems)
|
|
{
|
|
Item item = Global.AuctionMgr.GetAItem(itemGUIDLow);
|
|
if (!item)
|
|
{
|
|
Log.outError(LogFilter.Server, "AuctionEntry:BuildAuctionInfo: Auction {0} has a non-existent item: {1}", Id, itemGUIDLow);
|
|
return;
|
|
}
|
|
AuctionItem auctionItem = new AuctionItem();
|
|
|
|
auctionItem.AuctionItemID = (int)Id;
|
|
auctionItem.Item = new ItemInstance(item);
|
|
auctionItem.BuyoutPrice = buyout;
|
|
auctionItem.CensorBidInfo = false;
|
|
auctionItem.CensorServerSideInfo = listAuctionItems;
|
|
auctionItem.Charges = item.GetSpellCharges();
|
|
auctionItem.Count = (int)item.GetCount();
|
|
auctionItem.DeleteReason = 0; // Always 0 ?
|
|
auctionItem.DurationLeft = (int)((expire_time - Time.UnixTime) * Time.InMilliseconds);
|
|
auctionItem.EndTime = (uint)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, ObjectManager.GetPlayerAccountIdByGUID(auctionItem.Owner));
|
|
auctionItem.MinIncrement = bidder != 0 ? GetAuctionOutBid() : 0;
|
|
auctionItem.Bidder = bidder != 0 ? ObjectGuid.Create(HighGuid.Player, bidder) : ObjectGuid.Empty;
|
|
auctionItem.BidAmount = bidder != 0 ? bid : 0;
|
|
|
|
for (EnchantmentSlot c = 0; c < EnchantmentSlot.MaxInspected; c++)
|
|
{
|
|
if (item.GetEnchantmentId(c) == 0)
|
|
continue;
|
|
|
|
auctionItem.Enchantments.Add(new ItemEnchantData((int)item.GetEnchantmentId(c), item.GetEnchantmentDuration(c), (int)item.GetEnchantmentCharges(c), (byte)c));
|
|
}
|
|
|
|
byte i = 0;
|
|
foreach (ItemDynamicFieldGems gemData in item.GetGems())
|
|
{
|
|
if (gemData.ItemId != 0)
|
|
{
|
|
ItemGemData gem = new ItemGemData();
|
|
gem.Slot = i;
|
|
gem.Item = new ItemInstance(gemData);
|
|
auctionItem.Gems.Add(gem);
|
|
}
|
|
++i;
|
|
}
|
|
|
|
items.Add(auctionItem);
|
|
}
|
|
|
|
public uint GetAuctionCut()
|
|
{
|
|
int cut = (int)(MathFunctions.CalculatePct(bid, auctionHouseEntry.ConsignmentRate) * WorldConfig.GetFloatValue(WorldCfg.RateAuctionCut));
|
|
return (uint)Math.Max(cut, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// the sum of outbid is (1% from current bid)*5, if bid is very small, it is 1c
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public uint GetAuctionOutBid()
|
|
{
|
|
uint outbid = MathFunctions.CalculatePct(bid, 5);
|
|
return outbid != 0 ? outbid : 1;
|
|
}
|
|
|
|
public void DeleteFromDB(SQLTransaction trans)
|
|
{
|
|
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_AUCTION);
|
|
stmt.AddValue(0, Id);
|
|
trans.Append(stmt);
|
|
}
|
|
|
|
public void SaveToDB(SQLTransaction trans)
|
|
{
|
|
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_AUCTION);
|
|
stmt.AddValue(0, Id);
|
|
stmt.AddValue(1, auctioneer);
|
|
stmt.AddValue(2, itemGUIDLow);
|
|
stmt.AddValue(3, owner);
|
|
stmt.AddValue(4, buyout);
|
|
stmt.AddValue(5, expire_time);
|
|
stmt.AddValue(6, bidder);
|
|
stmt.AddValue(7, bid);
|
|
stmt.AddValue(8, startbid);
|
|
stmt.AddValue(9, deposit);
|
|
trans.Append(stmt);
|
|
}
|
|
|
|
public bool LoadFromDB(SQLFields fields)
|
|
{
|
|
Id = fields.Read<uint>(0);
|
|
auctioneer = fields.Read<ulong>(1);
|
|
itemGUIDLow = fields.Read<ulong>(2);
|
|
itemEntry = fields.Read<uint>(3);
|
|
itemCount = fields.Read<uint>(4);
|
|
owner = fields.Read<ulong>(5);
|
|
buyout = fields.Read<uint>(6);
|
|
expire_time = fields.Read<uint>(7);
|
|
bidder = fields.Read<ulong>(8);
|
|
bid = fields.Read<uint>(9);
|
|
startbid = fields.Read<uint>(10);
|
|
deposit = fields.Read<uint>(11);
|
|
|
|
CreatureData auctioneerData = Global.ObjectMgr.GetCreatureData(auctioneer);
|
|
if (auctioneerData == null)
|
|
{
|
|
Log.outError(LogFilter.Server, "Auction {0} has not a existing auctioneer (GUID : {1})", Id, auctioneer);
|
|
return false;
|
|
}
|
|
|
|
CreatureTemplate auctioneerInfo = Global.ObjectMgr.GetCreatureTemplate(auctioneerData.id);
|
|
if (auctioneerInfo == null)
|
|
{
|
|
Log.outError(LogFilter.Server, "Auction {0} has not a existing auctioneer (GUID : {1} Entry: {2})", Id, auctioneer, auctioneerData.id);
|
|
return false;
|
|
}
|
|
|
|
factionTemplateId = auctioneerInfo.Faction;
|
|
auctionHouseEntry = Global.AuctionMgr.GetAuctionHouseEntry(factionTemplateId, ref houseId);
|
|
if (auctionHouseEntry == null)
|
|
{
|
|
Log.outError(LogFilter.Server, "Auction {0} has auctioneer (GUID : {1} Entry: {2}) with wrong faction {3}", 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 (!Global.AuctionMgr.GetAItem(itemGUIDLow))
|
|
{
|
|
Log.outError(LogFilter.Server, "Auction {0} has not a existing item : {1}", Id, itemGUIDLow);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool LoadFromFieldList(SQLFields 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.Read<uint>(0);
|
|
auctioneer = fields.Read<uint>(1);
|
|
itemGUIDLow = fields.Read<uint>(2);
|
|
itemEntry = fields.Read<uint>(3);
|
|
itemCount = fields.Read<uint>(4);
|
|
owner = fields.Read<uint>(5);
|
|
buyout = fields.Read<uint>(6);
|
|
expire_time = fields.Read<uint>(7);
|
|
bidder = fields.Read<uint>(8);
|
|
bid = fields.Read<uint>(9);
|
|
startbid = fields.Read<uint>(10);
|
|
deposit = fields.Read<uint>(11);
|
|
|
|
CreatureData auctioneerData = Global.ObjectMgr.GetCreatureData(auctioneer);
|
|
if (auctioneerData == null)
|
|
{
|
|
Log.outError(LogFilter.Server, "AuctionEntry:LoadFromFieldList() - Auction {0} has not a existing auctioneer (GUID : {1})", Id, auctioneer);
|
|
return false;
|
|
}
|
|
|
|
CreatureTemplate auctioneerInfo = Global.ObjectMgr.GetCreatureTemplate(auctioneerData.id);
|
|
if (auctioneerInfo == null)
|
|
{
|
|
Log.outError(LogFilter.Server, "AuctionEntry:LoadFromFieldList() - Auction {0} has not a existing auctioneer (GUID : {1} Entry: {2})", Id, auctioneer, auctioneerData.id);
|
|
return false;
|
|
}
|
|
|
|
factionTemplateId = auctioneerInfo.Faction;
|
|
auctionHouseEntry = Global.AuctionMgr.GetAuctionHouseEntry(factionTemplateId);
|
|
|
|
if (auctionHouseEntry == null)
|
|
{
|
|
Log.outError(LogFilter.Server, "AuctionEntry:LoadFromFieldList() - Auction {0} has auctioneer (GUID : {1} Entry: {2}) with wrong faction {3}", Id, auctioneer, auctioneerData.id, factionTemplateId);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public string BuildAuctionMailSubject(MailAuctionAnswers response)
|
|
{
|
|
return string.Format("{0}:0:{1}:{2}:{3}", itemEntry, response, Id, itemCount);
|
|
}
|
|
|
|
public static string BuildAuctionMailBody(ulong lowGuid, uint bid, uint buyout, uint deposit, uint cut)
|
|
{
|
|
return string.Format("{0}:{1}:{2}:{3}:{4}", lowGuid, bid, buyout, deposit, cut);
|
|
}
|
|
|
|
// helpers
|
|
public uint GetHouseId() { return houseId; }
|
|
public uint GetHouseFaction() { return auctionHouseEntry.FactionID; }
|
|
|
|
public uint Id;
|
|
public ulong auctioneer; // creature low guid
|
|
public ulong itemGUIDLow;
|
|
public uint itemEntry;
|
|
public uint itemCount;
|
|
public ulong owner;
|
|
public uint startbid; //maybe useless
|
|
public uint bid;
|
|
public uint buyout;
|
|
public long expire_time;
|
|
public ulong bidder;
|
|
public uint deposit; //deposit can be calculated only when creating auction
|
|
public uint etime;
|
|
uint houseId;
|
|
public AuctionHouseRecord auctionHouseEntry; // in AuctionHouse.dbc
|
|
public uint factionTemplateId;
|
|
}
|
|
|
|
public class AuctionSearchFilters
|
|
{
|
|
public enum FilterType : uint
|
|
{
|
|
SkipClass = 0,
|
|
SkipSubclass = 0xFFFFFFFF,
|
|
SkipInvtype = 0xFFFFFFFF
|
|
}
|
|
|
|
public Array<SubclassFilter> Classes = new Array<SubclassFilter>((int)ItemClass.Max);
|
|
|
|
public class SubclassFilter
|
|
{
|
|
public FilterType SubclassMask = FilterType.SkipClass;
|
|
public Array<uint> InvTypes = new Array<uint>(ItemConst.MaxItemSubclassTotal);
|
|
}
|
|
}
|
|
}
|