diff --git a/Source/Framework/Constants/AuctionConst.cs b/Source/Framework/Constants/AuctionConst.cs
index d812faad9..c10f1db22 100644
--- a/Source/Framework/Constants/AuctionConst.cs
+++ b/Source/Framework/Constants/AuctionConst.cs
@@ -13,38 +13,86 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
- */
+ */
namespace Framework.Constants
{
- public enum AuctionError
+ public enum AuctionResult
{
Ok = 0,
Inventory = 1,
DatabaseError = 2,
- NotEnoughtMoney = 3,
+ NotEnoughMoney = 3,
ItemNotFound = 4,
HigherBid = 5,
BidIncrement = 7,
BidOwn = 10,
- RestrictedAccount = 13
+ RestrictedAccountTrial = 13,
+ HasRestriction = 17,
+ AuctionHouseBusy = 18,
+ AuctionHouseUnavailable = 19,
+ CommodityPurchaseFailed = 21,
+ ItemHasQuote = 23
}
- public enum AuctionAction
+ public enum AuctionCommand
{
SellItem = 0,
Cancel = 1,
PlaceBid = 2
}
- public enum MailAuctionAnswers
+ public enum AuctionMailType
{
- Outbidded = 0,
+ Outbid = 0,
Won = 1,
- Successful = 2,
+ Sold = 2,
Expired = 3,
- CancelledToBidder = 4,
- Canceled = 5,
- SalePending = 6
+ Removed = 4,
+ Cancelled = 5,
+ Invoice = 6
+ }
+
+ public enum AuctionHouseResultLimits
+ {
+ Browse = 500,
+ Items = 50
+ }
+
+ public enum AuctionHouseFilterMask
+ {
+ None = 0x0,
+ UncollectedOnly = 0x1,
+ UsableOnly = 0x2,
+ UpgradesOnly = 0x4,
+ ExactMatch = 0x8,
+ PoorQuality = 0x10,
+ CommonQuality = 0x20,
+ UncommonQuality = 0x40,
+ RareQuality = 0x80,
+ EpicQuality = 0x100,
+ LegendaryQuality = 0x200,
+ ArtifactQuality = 0x400,
+ }
+
+ public enum AuctionHouseSortOrder
+ {
+ Price = 0,
+ Name = 1,
+ Level = 2,
+ Bid = 3,
+ Buyout = 4
+ }
+
+ public enum AuctionHouseBrowseMode
+ {
+ Search = 0,
+ SpecificKeys = 1
+ }
+
+ public enum AuctionHouseListType
+ {
+ Commodities = 1,
+ Items = 2
}
}
diff --git a/Source/Framework/Constants/Network/Opcodes.cs b/Source/Framework/Constants/Network/Opcodes.cs
index 95042e421..6ad3971e3 100644
--- a/Source/Framework/Constants/Network/Opcodes.cs
+++ b/Source/Framework/Constants/Network/Opcodes.cs
@@ -47,7 +47,7 @@ namespace Framework.Constants
AuctionListBidderItems = 0x34d4,
AuctionListItemsByBucketKey = 0x34d1,
AuctionListItemsByItemId = 0x34d2,
- AuctionListItemsByItemKeys = 0x34d5,
+ AuctionListBucketsByBucketKeys = 0x34d5,
AuctionListOwnerItems = 0x34d3,
AuctionPlaceBid = 0x34cf,
AuctionRemoveItem = 0x34cd,
@@ -813,11 +813,11 @@ namespace Framework.Constants
AttackSwingLandedLog = 0x273a,
AuctionClosedNotification = 0x2731,
AuctionCommandResult = 0x272e,
- AuctionCommodityPriceUpdate = 0x28c3,
+ AuctionCommodityQuote = 0x28c3,
+ AuctionFavoriteItems = 0x28cc,
AuctionHelloResponse = 0x272c,
AuctionListBidderItemsResult = 0x28c2,
- AuctionListBucketItemsResult = 0x28bf,
- AuctionListFavoriteItemsResult = 0x28cc,
+ AuctionListBucketsResult = 0x28bf,
AuctionListItemsResult = 0x28c0,
AuctionListOwnerItemsResult = 0x28c1,
AuctionOutbidNotification = 0x2730,
diff --git a/Source/Framework/Database/Databases/CharacterDatabase.cs b/Source/Framework/Database/Databases/CharacterDatabase.cs
index 05f4d68f5..9c1073745 100644
--- a/Source/Framework/Database/Databases/CharacterDatabase.cs
+++ b/Source/Framework/Database/Databases/CharacterDatabase.cs
@@ -138,6 +138,10 @@ namespace Framework.Database
PrepareStatement(CharStatements.SEL_CHARACTER_RANDOMBG, "SELECT guid FROM character_battleground_random WHERE guid = ?");
PrepareStatement(CharStatements.SEL_CHARACTER_BANNED, "SELECT guid FROM character_banned WHERE guid = ? AND active = 1");
PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUSREW, "SELECT quest FROM character_queststatus_rewarded WHERE guid = ? AND active = 1");
+ PrepareStatement(CharStatements.SEL_CHARACTER_FAVORITE_AUCTIONS, "SELECT `order`, itemId, itemLevel, battlePetSpeciesId, suffixItemNameDescriptionId FROM character_favorite_auctions WHERE guid = ? ORDER BY `order`");
+ PrepareStatement(CharStatements.INS_CHARACTER_FAVORITE_AUCTION, "INSERT INTO character_favorite_auctions (guid, `order`, itemId, itemLevel, battlePetSpeciesId, suffixItemNameDescriptionId) VALUE (?, ?, ?, ?, ?, ?)");
+ PrepareStatement(CharStatements.DEL_CHARACTER_FAVORITE_AUCTION, "DELETE FROM character_favorite_auctions WHERE guid = ? AND `order` = ?");
+ PrepareStatement(CharStatements.DEL_CHARACTER_FAVORITE_AUCTIONS_BY_CHAR, "DELETE FROM character_favorite_auctions WHERE guid = ?");
PrepareStatement(CharStatements.SEL_ACCOUNT_INSTANCELOCKTIMES, "SELECT instanceId, releaseTime FROM account_instance_times WHERE accountId = ?");
PrepareStatement(CharStatements.SEL_CHARACTER_ACTIONS_SPEC, "SELECT button, action, type FROM character_action WHERE guid = ? AND spec = ? ORDER BY button");
@@ -152,11 +156,17 @@ namespace Framework.Database
PrepareStatement(CharStatements.SEL_MAILITEMS_AZERITE_MILESTONE_POWER, "SELECT iamp.itemGuid, iamp.azeriteItemMilestonePowerId FROM item_instance_azerite_milestone_power iamp INNER JOIN mail_items mi ON iamp.itemGuid = mi.item_guid INNER JOIN mail m ON mi.mail_id = m.id WHERE m.receiver = ?");
PrepareStatement(CharStatements.SEL_MAILITEMS_AZERITE_UNLOCKED_ESSENCE, "SELECT iaue.itemGuid, iaue.azeriteEssenceId, iaue.`rank` FROM item_instance_azerite_unlocked_essence iaue INNER JOIN mail_items mi ON iaue.itemGuid = mi.item_guid INNER JOIN mail m ON mi.mail_id = m.id WHERE m.receiver = ?");
PrepareStatement(CharStatements.SEL_MAILITEMS_AZERITE_EMPOWERED, "SELECT iae.itemGuid, iae.azeritePowerId1, iae.azeritePowerId2, iae.azeritePowerId3, iae.azeritePowerId4, iae.azeritePowerId5 FROM item_instance_azerite_empowered iae INNER JOIN mail_items mi ON iae.itemGuid = mi.item_guid INNER JOIN mail m ON mi.mail_id = m.id WHERE m.receiver = ?");
- PrepareStatement(CharStatements.SEL_AUCTION_ITEMS, "SELECT " + SelectItemInstanceContent + " FROM auctionhouse ah JOIN item_instance ii ON ah.itemguid = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid");
- PrepareStatement(CharStatements.SEL_AUCTIONS, "SELECT id, auctioneerguid, itemguid, itemEntry, count, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit FROM auctionhouse ah INNER JOIN item_instance ii ON ii.guid = ah.itemguid");
- PrepareStatement(CharStatements.INS_AUCTION, "INSERT INTO auctionhouse (id, auctioneerguid, itemguid, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
- PrepareStatement(CharStatements.DEL_AUCTION, "DELETE FROM auctionhouse WHERE id = ?");
- PrepareStatement(CharStatements.UPD_AUCTION_BID, "UPDATE auctionhouse SET buyguid = ?, lastbid = ? WHERE id = ?");
+ PrepareStatement(CharStatements.SEL_AUCTION_ITEMS, "SELECT " + SelectItemInstanceContent + ", ii.owner_guid, ai.auctionId FROM auction_items ai INNER JOIN item_instance ii ON ai.itemGuid = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid");
+ PrepareStatement(CharStatements.SEL_AUCTIONS, "SELECT id, auctionHouseId, owner, bidder, minBid, buyoutOrUnitPrice, deposit, bidAmount, startTime, endTime FROM auctionhouse");
+ PrepareStatement(CharStatements.INS_AUCTION_ITEMS, "INSERT INTO auction_items (auctionId, itemGuid) VALUES (?, ?)");
+ PrepareStatement(CharStatements.DEL_AUCTION_ITEMS_BY_ITEM, "DELETE FROM auction_items WHERE itemGuid = ?");
+ PrepareStatement(CharStatements.SEL_AUCTION_BIDDERS, "SELECT auctionId, playerGuid FROM auction_bidders");
+ PrepareStatement(CharStatements.INS_AUCTION_BIDDER, "INSERT INTO auction_bidders (auctionId, playerGuid) VALUES (?, ?)");
+ PrepareStatement(CharStatements.DEL_AUCTION_BIDDER_BY_PLAYER, "DELETE FROM auction_bidders WHERE playerGuid = ?");
+ PrepareStatement(CharStatements.INS_AUCTION, "INSERT INTO auctionhouse (id, auctionHouseId, owner, bidder, minBid, buyoutOrUnitPrice, deposit, bidAmount, startTime, endTime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+ PrepareStatement(CharStatements.DEL_AUCTION, "DELETE a, ab, ai FROM auctionhouse a LEFT JOIN auction_items ai ON a.id = ai.auctionId LEFT JOIN auction_bidders ab ON a.id = ab.auctionId WHERE a.id = ?");
+ PrepareStatement(CharStatements.UPD_AUCTION_BID, "UPDATE auctionhouse SET bidder = ?, bidAmount = ? WHERE id = ?");
+ PrepareStatement(CharStatements.UPD_AUCTION_EXPIRATION, "UPDATE auctionhouse SET endTime = ? WHERE id = ?");
PrepareStatement(CharStatements.INS_MAIL, "INSERT INTO mail(id, messageType, stationery, mailTemplateId, sender, receiver, subject, body, has_items, expire_time, deliver_time, money, cod, checked) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
PrepareStatement(CharStatements.DEL_MAIL_BY_ID, "DELETE FROM mail WHERE id = ?");
PrepareStatement(CharStatements.INS_MAIL_ITEM, "INSERT INTO mail_items(mail_id, item_guid, receiver) VALUES (?, ?, ?)");
@@ -535,7 +545,7 @@ namespace Framework.Database
PrepareStatement(CharStatements.DEL_CHAR_AURA_FROZEN, "DELETE FROM character_aura WHERE spell = 9454 AND guid = ?");
PrepareStatement(CharStatements.SEL_CHAR_INVENTORY_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item WHERE itemEntry = ?");
PrepareStatement(CharStatements.SEL_MAIL_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM mail_items mi INNER JOIN item_instance ii ON ii.guid = mi.item_guid WHERE itemEntry = ?");
- PrepareStatement(CharStatements.SEL_AUCTIONHOUSE_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM auctionhouse ah INNER JOIN item_instance ii ON ii.guid = ah.itemguid WHERE itemEntry = ?");
+ PrepareStatement(CharStatements.SEL_AUCTIONHOUSE_COUNT_ITEM, "SELECT COUNT(*) FROM auction_items ai INNER JOIN item_instance ii ON ii.guid = ai.itemGuid WHERE ii.itemEntry = ?");
PrepareStatement(CharStatements.SEL_GUILD_BANK_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM guild_bank_item gbi INNER JOIN item_instance ii ON ii.guid = gbi.item_guid WHERE itemEntry = ?");
PrepareStatement(CharStatements.SEL_CHAR_INVENTORY_ITEM_BY_ENTRY, "SELECT ci.item, cb.slot AS bag, ci.slot, ci.guid, c.account, c.name FROM characters c " +
"INNER JOIN character_inventory ci ON ci.guid = c.guid " +
@@ -544,7 +554,7 @@ namespace Framework.Database
PrepareStatement(CharStatements.SEL_MAIL_ITEMS_BY_ENTRY, "SELECT mi.item_guid, m.sender, m.receiver, cs.account, cs.name, cr.account, cr.name " +
"FROM mail m INNER JOIN mail_items mi ON mi.mail_id = m.id INNER JOIN item_instance ii ON ii.guid = mi.item_guid " +
"INNER JOIN characters cs ON cs.guid = m.sender INNER JOIN characters cr ON cr.guid = m.receiver WHERE ii.itemEntry = ? LIMIT ?");
- PrepareStatement(CharStatements.SEL_AUCTIONHOUSE_ITEM_BY_ENTRY, "SELECT ah.itemguid, ah.itemowner, c.account, c.name FROM auctionhouse ah INNER JOIN characters c ON c.guid = ah.itemowner INNER JOIN item_instance ii ON ii.guid = ah.itemguid WHERE ii.itemEntry = ? LIMIT ?");
+ PrepareStatement(CharStatements.SEL_AUCTIONHOUSE_ITEM_BY_ENTRY, "SELECT ai.itemGuid, c.guid, c.account, c.name FROM auctionhouse ah INNER JOIN auction_items ai ON ah.id = ai.auctionId INNER JOIN characters c ON c.guid = ah.owner INNER JOIN item_instance ii ON ii.guid = ai.itemGuid WHERE ii.itemEntry = ? LIMIT ?");
PrepareStatement(CharStatements.SEL_GUILD_BANK_ITEM_BY_ENTRY, "SELECT gi.item_guid, gi.guildid, g.name FROM guild_bank_item gi INNER JOIN guild g ON g.guildid = gi.guildid INNER JOIN item_instance ii ON ii.guid = gi.item_guid WHERE ii.itemEntry = ? LIMIT ?");
PrepareStatement(CharStatements.DEL_CHAR_ACHIEVEMENT, "DELETE FROM character_achievement WHERE guid = ?");
PrepareStatement(CharStatements.DEL_CHAR_ACHIEVEMENT_PROGRESS, "DELETE FROM character_achievement_progress WHERE guid = ?");
@@ -863,6 +873,10 @@ namespace Framework.Database
SEL_CHARACTER_RANDOMBG,
SEL_CHARACTER_BANNED,
SEL_CHARACTER_QUESTSTATUSREW,
+ SEL_CHARACTER_FAVORITE_AUCTIONS,
+ INS_CHARACTER_FAVORITE_AUCTION,
+ DEL_CHARACTER_FAVORITE_AUCTION,
+ DEL_CHARACTER_FAVORITE_AUCTIONS_BY_CHAR,
SEL_ACCOUNT_INSTANCELOCKTIMES,
SEL_MAILITEMS,
SEL_MAILITEMS_ARTIFACT,
@@ -874,7 +888,13 @@ namespace Framework.Database
INS_AUCTION,
DEL_AUCTION,
UPD_AUCTION_BID,
+ UPD_AUCTION_EXPIRATION,
SEL_AUCTIONS,
+ INS_AUCTION_ITEMS,
+ DEL_AUCTION_ITEMS_BY_ITEM,
+ SEL_AUCTION_BIDDERS,
+ INS_AUCTION_BIDDER,
+ DEL_AUCTION_BIDDER_BY_PLAYER,
INS_MAIL,
DEL_MAIL_BY_ID,
INS_MAIL_ITEM,
diff --git a/Source/Framework/Database/SQLTransaction.cs b/Source/Framework/Database/SQLTransaction.cs
index 003b84a46..6e441ea6f 100644
--- a/Source/Framework/Database/SQLTransaction.cs
+++ b/Source/Framework/Database/SQLTransaction.cs
@@ -53,7 +53,7 @@ namespace Framework.Database
m_trans = trans;
}
- public bool Execute(MySqlBase mySqlBase)
+ public virtual bool Execute(MySqlBase mySqlBase)
{
MySqlErrorCode errorCode = TryExecute(mySqlBase);
if (errorCode == MySqlErrorCode.None)
@@ -87,7 +87,7 @@ namespace Framework.Database
{
public TransactionWithResultTask(SQLTransaction trans) : base(trans) { }
- public new bool Execute(MySqlBase mySqlBase)
+ public override bool Execute(MySqlBase mySqlBase)
{
MySqlErrorCode errorCode = TryExecute(mySqlBase);
if (errorCode == MySqlErrorCode.None)
diff --git a/Source/Framework/Dynamic/OptionalType.cs b/Source/Framework/Dynamic/OptionalType.cs
index cf123ef59..b992d28c9 100644
--- a/Source/Framework/Dynamic/OptionalType.cs
+++ b/Source/Framework/Dynamic/OptionalType.cs
@@ -44,11 +44,6 @@ namespace Framework.Dynamic
Value = default(T);
}
- public static implicit operator Optional (T value)
- {
- return value;
- }
-
public static explicit operator T(Optional value)
{
return (T)value;
diff --git a/Source/Game/Achievements/CriteriaHandler.cs b/Source/Game/Achievements/CriteriaHandler.cs
index 3264da607..5ed50a6bd 100644
--- a/Source/Game/Achievements/CriteriaHandler.cs
+++ b/Source/Game/Achievements/CriteriaHandler.cs
@@ -2210,8 +2210,8 @@ namespace Game.Achievements
return false;
case CriteriaAdditionalCondition.ItemModifiedAppearance: // 200
{
- Tuple hasAppearance = referencePlayer.GetSession().GetCollectionMgr().HasItemAppearance(reqValue);
- if (!hasAppearance.Item1 || hasAppearance.Item2)
+ var hasAppearance = referencePlayer.GetSession().GetCollectionMgr().HasItemAppearance(reqValue);
+ if (!hasAppearance.PermAppearance || hasAppearance.TempAppearance)
return false;
break;
}
diff --git a/Source/Game/AuctionHouse/AuctionManager.cs b/Source/Game/AuctionHouse/AuctionManager.cs
index 7f5d75c9c..0c7260349 100644
--- a/Source/Game/AuctionHouse/AuctionManager.cs
+++ b/Source/Game/AuctionHouse/AuctionManager.cs
@@ -25,14 +25,41 @@ using Game.Network.Packets;
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
+using System.Linq;
+using Framework.Networking;
+using Framework.IO;
+using System.Collections;
namespace Game
{
public class AuctionManager : Singleton
{
const int AH_MINIMUM_DEPOSIT = 100;
+ const int MIN_AUCTION_TIME = 12 * Time.Hour;
- AuctionManager() { }
+ AuctionHouseObject mHordeAuctions;
+ AuctionHouseObject mAllianceAuctions;
+ AuctionHouseObject mNeutralAuctions;
+ AuctionHouseObject mGoblinAuctions;
+
+ Dictionary _pendingAuctionsByPlayer = new Dictionary();
+
+ Dictionary _itemsByGuid = new Dictionary();
+
+ uint _replicateIdGenerator;
+
+ Dictionary _playerThrottleObjects = new Dictionary();
+ DateTime _playerThrottleObjectsCleanupTime;
+
+ AuctionManager()
+ {
+ mHordeAuctions = new AuctionHouseObject(6);
+ mAllianceAuctions = new AuctionHouseObject(2);
+ mNeutralAuctions = new AuctionHouseObject(1);
+ mGoblinAuctions = new AuctionHouseObject(7);
+ _replicateIdGenerator = 0;
+ _playerThrottleObjectsCleanupTime = GameTime.GetGameTimeSteadyPoint() + TimeSpan.FromHours(1);
+ }
public AuctionHouseObject GetAuctionsMap(uint factionTemplateId)
{
@@ -43,230 +70,104 @@ namespace Game
FactionTemplateRecord uEntry = CliDB.FactionTemplateStorage.LookupByKey(factionTemplateId);
if (uEntry == null)
return mNeutralAuctions;
- else if ((uEntry.FactionGroup & (int)FactionMasks.Alliance) != 0)
+ else if (uEntry.FactionGroup.HasAnyFlag((byte)FactionMasks.Alliance))
return mAllianceAuctions;
- else if ((uEntry.FactionGroup & (int)FactionMasks.Horde) != 0)
+ else if (uEntry.FactionGroup.HasAnyFlag((byte)FactionMasks.Horde))
return mHordeAuctions;
else
return mNeutralAuctions;
}
- public ulong GetAuctionDeposit(AuctionHouseRecord entry, uint time, Item pItem, uint count)
+ public AuctionHouseObject GetAuctionsById(uint auctionHouseId)
{
- 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);
- ulong deposit = (ulong)(((multiplier * MSV * count / 3) * timeHr * 3) * WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit));
-
- Log.outDebug(LogFilter.Auctionhouse, $"MSV: {MSV}");
- Log.outDebug(LogFilter.Auctionhouse, $"Items: {count}");
- Log.outDebug(LogFilter.Auctionhouse, $"Multiplier: {multiplier}");
- Log.outDebug(LogFilter.Auctionhouse, $"Deposit: {deposit}");
-
- if (deposit < AH_MINIMUM_DEPOSIT * WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit))
- return AH_MINIMUM_DEPOSIT * (uint)WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit);
- else
- return deposit;
+ switch (auctionHouseId)
+ {
+ case 1:
+ return mNeutralAuctions;
+ case 2:
+ return mAllianceAuctions;
+ case 6:
+ return mHordeAuctions;
+ case 7:
+ return mGoblinAuctions;
+ default:
+ break;
+ }
+ return mNeutralAuctions;
}
- public void SendAuctionWonMail(AuctionEntry auction, SQLTransaction trans)
+ public Item GetAItem(ObjectGuid itemGuid)
{
- Item item = GetAItem(auction.itemGUIDLow);
- if (!item)
- return;
-
- uint bidderAccId;
- ObjectGuid bidderGuid = ObjectGuid.Create(HighGuid.Player, auction.bidder);
- Player bidder = Global.ObjAccessor.FindPlayer(bidderGuid);
- // data for gm.log
- string bidderName = "";
- bool logGmTrade;
-
- if (bidder)
- {
- bidderAccId = bidder.GetSession().GetAccountId();
- bidderName = bidder.GetName();
- logGmTrade = bidder.GetSession().HasPermission(RBACPermissions.LogGmTrade);
- }
- else
- {
- bidderAccId = Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(bidderGuid);
- logGmTrade = Global.AccountMgr.HasPermission(bidderAccId, RBACPermissions.LogGmTrade, Global.WorldMgr.GetRealm().Id.Realm);
-
- if (logGmTrade && !Global.CharacterCacheStorage.GetCharacterNameByGuid(bidderGuid, out bidderName))
- bidderName = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown);
- }
-
- if (logGmTrade)
- {
- ObjectGuid ownerGuid = ObjectGuid.Create(HighGuid.Player, auction.owner);
- string ownerName;
- if (!Global.CharacterCacheStorage.GetCharacterNameByGuid(ownerGuid, out ownerName))
- ownerName = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown);
-
- uint ownerAccId = Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(ownerGuid);
-
- Log.outCommand(bidderAccId, $"GM {bidderName} (Account: {bidderAccId}) won item in auction: {item.GetTemplate().GetName()} (Entry: {item.GetEntry()} Count: {item.GetCount()}) and pay money: {auction.bid}. Original owner {ownerName} (Account: {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);
- }
+ return _itemsByGuid.LookupByKey(itemGuid);
}
- public void SendAuctionSalePendingMail(AuctionEntry auction, SQLTransaction trans)
+ public ulong GetCommodityAuctionDeposit(ItemTemplate item, TimeSpan time, uint quantity)
{
- ObjectGuid owner_guid = ObjectGuid.Create(HighGuid.Player, auction.owner);
- Player owner = Global.ObjAccessor.FindPlayer(owner_guid);
- uint owner_accId = Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(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);
+ uint sellPrice = item.GetSellPrice();
+ return (ulong)((Math.Ceiling(Math.Floor(Math.Max(0.15 * quantity * sellPrice, 100.0)) / MoneyConstants.Silver) * MoneyConstants.Silver) * (time.Minutes / (MIN_AUCTION_TIME / Time.Minute)));
}
- //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)
+ public ulong GetItemAuctionDeposit(Player player, Item item, TimeSpan time)
{
- ObjectGuid owner_guid = ObjectGuid.Create(HighGuid.Player, auction.owner);
- Player owner = Global.ObjAccessor.FindPlayer(owner_guid);
- uint owner_accId = Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(owner_guid);
- Item item = GetAItem(auction.itemGUIDLow);
-
- // owner exist
- if (owner || owner_accId != 0)
- {
- ulong 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));
- }
+ uint sellPrice = item.GetSellPrice(player);
+ return (ulong)((Math.Ceiling(Math.Floor(Math.Max(sellPrice * 0.15, 100.0)) / MoneyConstants.Silver) * MoneyConstants.Silver) * (time.Minutes / (MIN_AUCTION_TIME / Time.Minute)));
}
- //does not clear ram
- public void SendAuctionExpiredMail(AuctionEntry auction, SQLTransaction trans)
+ public string BuildItemAuctionMailSubject(AuctionMailType type, AuctionPosting auction)
{
- //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 = Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(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);
- }
+ return BuildAuctionMailSubject(auction.Items[0].GetEntry(), type, auction.Id, auction.GetTotalItemCount(),
+ auction.Items[0].GetModifier(ItemModifier.BattlePetSpeciesId), auction.Items[0].GetContext(), auction.Items[0].m_itemData.BonusListIDs);
}
- //this function sends mail to old bidder
- public void SendAuctionOutbiddedMail(AuctionEntry auction, ulong newPrice, Player newBidder, SQLTransaction trans)
+ public string BuildCommodityAuctionMailSubject(AuctionMailType type, uint itemId, uint itemCount)
{
- 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 = Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(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);
- }
+ return BuildAuctionMailSubject(itemId, type, 0, itemCount, 0, ItemContext.None, null);
}
- //this function sends mail, when auction is cancelled to old bidder
- public void SendAuctionCancelledToBidderMail(AuctionEntry auction, SQLTransaction trans)
+ public string BuildAuctionMailSubject(uint itemId, AuctionMailType type, uint auctionId, uint itemCount, uint battlePetSpeciesId, ItemContext context, List bonusListIds)
{
- ObjectGuid bidder_guid = ObjectGuid.Create(HighGuid.Player, auction.bidder);
- Player bidder = Global.ObjAccessor.FindPlayer(bidder_guid);
+ string str = $"{itemId}:0:{(uint)type}:{auctionId}:{itemCount}:{battlePetSpeciesId}:0:0:0:0:{(uint)context}:{bonusListIds.Count}";
- uint bidder_accId = 0;
- if (!bidder)
- bidder_accId = Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(bidder_guid);
+ foreach (var bonusListId in bonusListIds)
+ str += ':' + bonusListId;
- // 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);
+ return str;
}
- public void LoadAuctionItems()
+ public string BuildAuctionWonMailBody(ObjectGuid guid, ulong bid, ulong buyout)
+ {
+ return $"{guid.ToString()}:{bid}:{buyout}:0";
+ }
+
+ public string BuildAuctionSoldMailBody(ObjectGuid guid, ulong bid, ulong buyout, uint deposit, ulong consignment)
+ {
+ return $"{guid.ToString()}:{bid}:{buyout}:{deposit}:{consignment}:0";
+ }
+
+ public string BuildAuctionInvoiceMailBody(ObjectGuid guid, ulong bid, ulong buyout, uint deposit, ulong consignment, uint moneyDelay, uint eta)
+ {
+ return $"{guid.ToString()}:{bid}:{buyout}:{deposit}:{consignment}:{moneyDelay}:{eta}:0";
+ }
+
+ public void LoadAuctions()
{
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);
+ _itemsByGuid.Clear();
+ SQLResult result = DB.Characters.Query(DB.Characters.GetPreparedStatement(CharStatements.SEL_AUCTION_ITEMS));
if (result.IsEmpty())
{
- Log.outInfo(LogFilter.ServerLoading, "Loaded 0 auction items. DB table `auctionhouse` or `item_instance` is empty!");
+ Log.outInfo(LogFilter.ServerLoading, "Loaded 0 auctions. DB table `auctionhouse` is empty.");
return;
}
+ // data needs to be at first place for Item.LoadFromDB
uint count = 0;
+ MultiMap itemsByAuction = new MultiMap();
+ MultiMap biddersByAuction = new MultiMap();
+
do
{
ulong itemGuid = result.Read(0);
@@ -275,66 +176,110 @@ namespace Game
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);
+ Log.outError(LogFilter.Misc, $"AuctionHouseMgr.LoadAuctionItems: Unknown item (GUID: {itemGuid} item entry: #{itemEntry}) in auction, skipped.");
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()))
+ Item item = Item.NewItemOrBag(proto);
+ if (!item.LoadFromDB(itemGuid, ObjectGuid.Create(HighGuid.Player, result.Read(43)), result.GetFields(), itemEntry))
{
- aItem.DeleteFromDB(trans);
+ item.Dispose();
continue;
}
- GetAuctionsMap(aItem.factionTemplateId).AddAuction(aItem);
+ uint auctionId = result.Read(44);
+ itemsByAuction.Add(auctionId, item);
+
++count;
} while (result.NextRow());
- DB.Characters.CommitTransaction(trans);
+ Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} auction items in {Time.GetMSTimeDiffToNow(oldMSTime)} ms");
- Log.outInfo(LogFilter.ServerLoading, "Loaded {0} auctions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
+ oldMSTime = Time.GetMSTime();
+ count = 0;
+ result = DB.Characters.Query(DB.Characters.GetPreparedStatement(CharStatements.SEL_AUCTION_BIDDERS));
+ if (!result.IsEmpty())
+ {
+ do
+ {
+ biddersByAuction.Add(result.Read(0), ObjectGuid.Create(HighGuid.Player, result.Read(1)));
+
+ } while (result.NextRow());
+ }
+
+ Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} auction bidders in {Time.GetMSTimeDiffToNow(oldMSTime)} ms");
+
+ oldMSTime = Time.GetMSTime();
+ count = 0;
+
+ result = DB.Characters.Query(DB.Characters.GetPreparedStatement(CharStatements.SEL_AUCTIONS));
+ if (!result.IsEmpty())
+ {
+ SQLTransaction trans = new SQLTransaction();
+ do
+ {
+ AuctionPosting auction = new AuctionPosting();
+ auction.Id = result.Read(0);
+ uint auctionHouseId = result.Read(1);
+
+ AuctionHouseObject auctionHouse = GetAuctionsById(auctionHouseId);
+ if (auctionHouse == null)
+ {
+ Log.outError(LogFilter.Misc, $"Auction {auction.Id} has wrong auctionHouseId {auctionHouseId}");
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_AUCTION);
+ stmt.AddValue(0, auction.Id);
+ trans.Append(stmt);
+ continue;
+ }
+
+ if (!itemsByAuction.ContainsKey(auction.Id))
+ {
+ Log.outError(LogFilter.Misc, $"Auction {auction.Id} has no items");
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_AUCTION);
+ stmt.AddValue(0, auction.Id);
+ trans.Append(stmt);
+ continue;
+ }
+
+ auction.Items = itemsByAuction[auction.Id];
+ auction.Owner = ObjectGuid.Create(HighGuid.Player, result.Read(2));
+ auction.OwnerAccount = ObjectGuid.Create(HighGuid.WowAccount, Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(auction.Owner));
+ ulong bidder = result.Read(3);
+ if (bidder != 0)
+ auction.Bidder = ObjectGuid.Create(HighGuid.Player, bidder);
+
+ auction.MinBid = result.Read(4);
+ auction.BuyoutOrUnitPrice = result.Read(5);
+ auction.Deposit = result.Read(6);
+ auction.BidAmount = result.Read(7);
+ auction.StartTime = Time.UnixTimeToDateTime(result.Read(8));
+ auction.EndTime = Time.UnixTimeToDateTime(result.Read(9));
+
+ if (biddersByAuction.ContainsKey(auction.Id))
+ auction.BidderHistory = biddersByAuction[auction.Id];
+
+ auctionHouse.AddAuction(null, auction);
+
+ ++count;
+ } while (result.NextRow());
+
+ DB.Characters.CommitTransaction(trans);
+ }
+
+ Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} auctions in {Time.GetMSTimeDiffToNow(oldMSTime)} ms");
}
- public void AddAItem(Item it)
+ public void AddAItem(Item item)
{
- Cypher.Assert(it);
- Cypher.Assert(!mAitems.ContainsKey(it.GetGUID().GetCounter()));
- mAitems[it.GetGUID().GetCounter()] = it;
+ Cypher.Assert(item);
+ Cypher.Assert(!_itemsByGuid.ContainsKey(item.GetGUID()));
+ _itemsByGuid[item.GetGUID()] = item;
}
- public bool RemoveAItem(ulong id, bool deleteItem = false)
+ public bool RemoveAItem(ObjectGuid guid, bool deleteItem = false)
{
- var item = mAitems.LookupByKey(id);
+ var item = _itemsByGuid.LookupByKey(guid);
if (item == null)
return false;
@@ -344,15 +289,170 @@ namespace Game
item.SaveToDB(null);
}
- mAitems.Remove(id);
+ _itemsByGuid.Remove(guid);
return true;
}
+ public bool PendingAuctionAdd(Player player, uint auctionHouseId, uint auctionId, ulong deposit)
+ {
+ if (!_pendingAuctionsByPlayer.ContainsKey(player.GetGUID()))
+ _pendingAuctionsByPlayer[player.GetGUID()] = new PlayerPendingAuctions();
+
+
+ var pendingAuction = _pendingAuctionsByPlayer[player.GetGUID()];
+ // Get deposit so far
+ ulong totalDeposit = 0;
+ foreach (PendingAuctionInfo thisAuction in pendingAuction.Auctions)
+ totalDeposit += thisAuction.Deposit;
+
+ // Add this deposit
+ totalDeposit += deposit;
+
+ if (!player.HasEnoughMoney(totalDeposit))
+ return false;
+
+ pendingAuction.Auctions.Add(new PendingAuctionInfo(auctionId, auctionHouseId, deposit));
+ return true;
+ }
+
+ public int PendingAuctionCount(Player player)
+ {
+ var itr = _pendingAuctionsByPlayer.LookupByKey(player.GetGUID());
+ if (itr != null)
+ return itr.Auctions.Count;
+
+ return 0;
+ }
+
+ public void PendingAuctionProcess(Player player)
+ {
+ var playerPendingAuctions = _pendingAuctionsByPlayer.LookupByKey(player.GetGUID());
+ if (playerPendingAuctions == null)
+ return;
+
+ ulong totaldeposit = 0;
+ var auctionIndex = 0;
+ for (; auctionIndex < playerPendingAuctions.Auctions.Count; ++auctionIndex)
+ {
+ var pendingAuction = playerPendingAuctions.Auctions[auctionIndex];
+ if (!player.HasEnoughMoney(totaldeposit + pendingAuction.Deposit))
+ break;
+
+ totaldeposit += pendingAuction.Deposit;
+ }
+
+ // expire auctions we cannot afford
+ if (auctionIndex < playerPendingAuctions.Auctions.Count)
+ {
+ SQLTransaction trans = new SQLTransaction();
+
+ do
+ {
+ PendingAuctionInfo pendingAuction = playerPendingAuctions.Auctions[auctionIndex];
+ AuctionPosting auction = GetAuctionsById(pendingAuction.AuctionHouseId).GetAuction(pendingAuction.AuctionId);
+ if (auction != null)
+ auction.EndTime = GameTime.GetGameTimeSystemPoint();
+
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_AUCTION_EXPIRATION);
+ stmt.AddValue(0, (uint)GameTime.GetGameTime());
+ stmt.AddValue(1, pendingAuction.AuctionId);
+ trans.Append(stmt);
+ ++auctionIndex;
+ } while (auctionIndex < playerPendingAuctions.Auctions.Count);
+
+ DB.Characters.CommitTransaction(trans);
+ }
+
+ _pendingAuctionsByPlayer.Remove(player.GetGUID());
+ player.ModifyMoney(-(long)totaldeposit);
+ }
+
+ void UpdatePendingAuctions()
+ {
+ foreach (var pair in _pendingAuctionsByPlayer)
+ {
+ ObjectGuid playerGUID = pair.Key;
+ Player player = Global.ObjAccessor.FindConnectedPlayer(playerGUID);
+ if (player != null)
+ {
+ // Check if there were auctions since last update process if not
+ if (PendingAuctionCount(player) == pair.Value.LastAuctionsSize)
+ PendingAuctionProcess(player);
+ else
+ _pendingAuctionsByPlayer[playerGUID].LastAuctionsSize = PendingAuctionCount(player);
+ }
+ else
+ {
+ // Expire any auctions that we couldn't get a deposit for
+ Log.outWarn(LogFilter.Auctionhouse, $"Player {playerGUID.ToString()} was offline, unable to retrieve deposit!");
+
+ SQLTransaction trans = new SQLTransaction();
+ foreach (PendingAuctionInfo pendingAuction in pair.Value.Auctions)
+ {
+ AuctionPosting auction = GetAuctionsById(pendingAuction.AuctionHouseId).GetAuction(pendingAuction.AuctionId);
+ if (auction != null)
+ auction.EndTime = GameTime.GetGameTimeSystemPoint();
+
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_AUCTION_EXPIRATION);
+ stmt.AddValue(0, (uint)GameTime.GetGameTime());
+ stmt.AddValue(1, pendingAuction.AuctionId);
+ trans.Append(stmt);
+ }
+ DB.Characters.CommitTransaction(trans);
+ _pendingAuctionsByPlayer.Remove(playerGUID);
+ }
+ }
+ }
+
public void Update()
{
mHordeAuctions.Update();
mAllianceAuctions.Update();
mNeutralAuctions.Update();
+ mGoblinAuctions.Update();
+
+ DateTime now = GameTime.GetGameTimeSteadyPoint();
+ if (now >= _playerThrottleObjectsCleanupTime)
+ {
+ foreach (var pair in _playerThrottleObjects.ToList())
+ {
+ if (pair.Value.PeriodEnd < now)
+ _playerThrottleObjects.Remove(pair.Key);
+ }
+
+ _playerThrottleObjectsCleanupTime = now + TimeSpan.FromHours(1);
+ }
+ }
+
+ public uint GenerateReplicationId()
+ {
+ return ++_replicateIdGenerator;
+ }
+
+ public AuctionThrottleResult CheckThrottle(Player player, AuctionCommand command = AuctionCommand.SellItem)
+ {
+ DateTime now = GameTime.GetGameTimeSteadyPoint();
+
+ if (!_playerThrottleObjects.ContainsKey(player.GetGUID()))
+ _playerThrottleObjects[player.GetGUID()] = new PlayerThrottleObject();
+
+ var throttleObject = _playerThrottleObjects[player.GetGUID()];
+ if (now > throttleObject.PeriodEnd)
+ {
+ throttleObject.PeriodEnd = now + TimeSpan.FromMinutes(1);
+ throttleObject.QueriesRemaining = 100;
+ }
+
+ if (throttleObject.QueriesRemaining == 0)
+ {
+ player.GetSession().SendAuctionCommandResult(0, command, AuctionResult.AuctionHouseBusy, throttleObject.PeriodEnd - now);
+ return new AuctionThrottleResult(TimeSpan.Zero, true);
+ }
+
+ if ((--throttleObject.QueriesRemaining) == 0)
+ return new AuctionThrottleResult(throttleObject.PeriodEnd - now, false);
+ else
+ return new AuctionThrottleResult(TimeSpan.FromMilliseconds(WorldConfig.GetIntValue(WorldCfg.AuctionSearchDelay)), false);
}
public AuctionHouseRecord GetAuctionHouseEntry(uint factionTemplateId)
@@ -372,11 +472,11 @@ namespace Game
// but no easy way convert creature faction to player race faction for specific city
switch (factionTemplateId)
{
- case 120:
- houseid = 7;
+ case 120:
+ houseid = 7;
break; // booty bay, Blackwater Auction House
- case 474:
- houseid = 7;
+ case 474:
+ houseid = 7;
break; // gadgetzan, Blackwater Auction House
case 855:
houseid = 7;
@@ -402,398 +502,1479 @@ namespace Game
return CliDB.AuctionHouseStorage.LookupByKey(houseid);
}
- public Item GetAItem(ulong id)
+ class PendingAuctionInfo
{
- return mAitems.LookupByKey(id);
+ public uint AuctionId;
+ public uint AuctionHouseId;
+ public ulong Deposit;
+
+ public PendingAuctionInfo(uint auctionId, uint auctionHouseId, ulong deposit)
+ {
+ AuctionId = auctionId;
+ AuctionHouseId = auctionHouseId;
+ Deposit = deposit;
+ }
}
- AuctionHouseObject mHordeAuctions = new AuctionHouseObject();
- AuctionHouseObject mAllianceAuctions = new AuctionHouseObject();
- AuctionHouseObject mNeutralAuctions = new AuctionHouseObject();
+ class PlayerPendingAuctions
+ {
+ public List Auctions = new List();
+ public int LastAuctionsSize;
+ }
- Dictionary mAitems = new Dictionary();
+ class PlayerThrottleObject
+ {
+ public DateTime PeriodEnd;
+ public byte QueriesRemaining = 100;
+ }
}
public class AuctionHouseObject
{
- public void AddAuction(AuctionEntry auction)
+ public AuctionHouseObject(uint auctionHouseId)
{
- Cypher.Assert(auction != null);
+ _auctionHouse = CliDB.AuctionHouseStorage.LookupByKey(auctionHouseId);
+ }
+
+ public uint GetAuctionHouseId()
+ {
+ return _auctionHouse.Id;
+ }
+
+ public AuctionPosting GetAuction(uint auctionId)
+ {
+ return _itemsByAuctionId.LookupByKey(auctionId);
+ }
+
+ public void AddAuction(SQLTransaction trans, AuctionPosting auction)
+ {
+ AuctionsBucketKey key = AuctionsBucketKey.ForItem(auction.Items[0]);
+
+ AuctionsBucketData bucket = _buckets.LookupByKey(key);
+ if (bucket == null)
+ {
+ // we don't have any item for this key yet, create new bucket
+ bucket = new AuctionsBucketData();
+ bucket.Key = key;
+
+ ItemTemplate itemTemplate = auction.Items[0].GetTemplate();
+ bucket.ItemClass = (byte)itemTemplate.GetClass();
+ bucket.ItemSubClass = (byte)itemTemplate.GetSubClass();
+ bucket.InventoryType = (byte)itemTemplate.GetInventoryType();
+ bucket.RequiredLevel = (byte)auction.Items[0].GetRequiredLevel();
+ for (LocaleConstant locale = LocaleConstant.enUS; locale < LocaleConstant.Total; ++locale)
+ {
+ if (locale == LocaleConstant.None)
+ continue;
+
+ bucket.FullName[(int)locale] = auction.Items[0].GetName(locale);
+ }
+
+ _buckets.Add(key, bucket);
+ }
+
+ // update cache fields
+ ulong priceToDisplay = auction.BuyoutOrUnitPrice != 0 ? auction.BuyoutOrUnitPrice : auction.BidAmount;
+ if (bucket.MinPrice == 0 || priceToDisplay < bucket.MinPrice)
+ bucket.MinPrice = priceToDisplay;
+
+ var itemModifiedAppearance = auction.Items[0].GetItemModifiedAppearance();
+ if (itemModifiedAppearance != null)
+ {
+ int index = 0;
+ for (var i = 0; i < bucket.ItemModifiedAppearanceId.Length; ++i)
+ {
+ if (bucket.ItemModifiedAppearanceId[i].Id == itemModifiedAppearance.Id)
+ {
+ index = i;
+ break;
+ }
+ }
+
+ bucket.ItemModifiedAppearanceId[index] = (itemModifiedAppearance.Id, bucket.ItemModifiedAppearanceId[index].Item2 + 1);
+ }
+
+ uint quality;
+
+ if (auction.Items[0].GetModifier(ItemModifier.BattlePetSpeciesId) == 0)
+ quality = (byte)auction.Items[0].GetQuality();
+ else
+ {
+ quality = (auction.Items[0].GetModifier(ItemModifier.BattlePetBreedData) >> 24) & 0xFF;
+ foreach (Item item in auction.Items)
+ {
+ byte battlePetLevel = (byte)item.GetModifier(ItemModifier.BattlePetLevel);
+ if (bucket.MinBattlePetLevel == 0)
+ bucket.MinBattlePetLevel = battlePetLevel;
+ else if (bucket.MinBattlePetLevel > battlePetLevel)
+ bucket.MinBattlePetLevel = battlePetLevel;
+
+ bucket.MaxBattlePetLevel = Math.Max(bucket.MaxBattlePetLevel, battlePetLevel);
+ }
+ }
+
+ bucket.QualityMask |= (AuctionHouseFilterMask)(1 << ((int)quality + 4));
+ ++bucket.QualityCounts[quality];
+
+ if (trans != null)
+ {
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_AUCTION);
+ stmt.AddValue(0, auction.Id);
+ stmt.AddValue(1, _auctionHouse.Id);
+ stmt.AddValue(2, auction.Owner.GetCounter());
+ stmt.AddValue(3, ObjectGuid.Empty.GetCounter());
+ stmt.AddValue(4, auction.MinBid);
+ stmt.AddValue(5, auction.BuyoutOrUnitPrice);
+ stmt.AddValue(6, auction.Deposit);
+ stmt.AddValue(7, auction.BidAmount);
+ stmt.AddValue(8, (uint)Time.DateTimeToUnixTime(auction.StartTime));
+ stmt.AddValue(9, (uint)Time.DateTimeToUnixTime(auction.EndTime));
+ trans.Append(stmt);
+
+ foreach (Item item in auction.Items)
+ {
+ stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_AUCTION_ITEMS);
+ stmt.AddValue(0, auction.Id);
+ stmt.AddValue(1, item.GetGUID().GetCounter());
+ trans.Append(stmt);
+ }
+ }
+
+ foreach (Item item in auction.Items)
+ Global.AuctionHouseMgr.AddAItem(item);
+
+ auction.Bucket = bucket;
+ _playerOwnedAuctions.Add(auction.Owner, auction.Id);
+ foreach (ObjectGuid bidder in auction.BidderHistory)
+ _playerBidderAuctions.Add(bidder, auction.Id);
+
+ _itemsByAuctionId[auction.Id] = auction;
+
+ AuctionPosting.Sorter insertSorter = new AuctionPosting.Sorter(LocaleConstant.enUS, new AuctionSortDef[] { new AuctionSortDef(AuctionHouseSortOrder.Price, false) }, 1);
+ var auctionIndex = bucket.Auctions.BinarySearch(auction, insertSorter);
+ if (auctionIndex < 0)
+ auctionIndex = ~auctionIndex;
+ bucket.Auctions.Insert(auctionIndex, auction);
- AuctionsMap[auction.Id] = auction;
Global.ScriptMgr.OnAuctionAdd(this, auction);
}
- public bool RemoveAuction(AuctionEntry auction)
+ public void RemoveAuction(SQLTransaction trans, AuctionPosting auction, AuctionPosting auctionPosting = null)
{
+ AuctionsBucketData bucket = auction.Bucket;
+
+ bucket.Auctions.RemoveAll(auct => auct.Id == auction.Id);
+ if (!bucket.Auctions.Empty())
+ {
+ // update cache fields
+ ulong priceToDisplay = auction.BuyoutOrUnitPrice != 0 ? auction.BuyoutOrUnitPrice : auction.BidAmount;
+ if (bucket.MinPrice == priceToDisplay)
+ {
+ bucket.MinPrice = ulong.MaxValue;
+ foreach (AuctionPosting remainingAuction in bucket.Auctions)
+ bucket.MinPrice = Math.Min(bucket.MinPrice, remainingAuction.BuyoutOrUnitPrice != 0 ? remainingAuction.BuyoutOrUnitPrice : remainingAuction.BidAmount);
+ }
+
+ var itemModifiedAppearance = auction.Items[0].GetItemModifiedAppearance();
+ if (itemModifiedAppearance != null)
+ {
+ int index = -1;
+ for (var i = 0; i < bucket.ItemModifiedAppearanceId.Length; ++i)
+ {
+ if (bucket.ItemModifiedAppearanceId[i].Item1 == itemModifiedAppearance.Id)
+ {
+ index = i;
+ break;
+ }
+ }
+
+ if (index != -1)
+ if (--bucket.ItemModifiedAppearanceId[index].Count == 0)
+ bucket.ItemModifiedAppearanceId[index].Id = 0;
+ }
+
+ uint quality;
+
+ if (auction.Items[0].GetModifier(ItemModifier.BattlePetSpeciesId) == 0)
+ {
+ quality = (uint)auction.Items[0].GetQuality();
+ }
+ else
+ {
+ quality = (auction.Items[0].GetModifier(ItemModifier.BattlePetBreedData) >> 24) & 0xFF;
+ bucket.MinBattlePetLevel = 0;
+ bucket.MaxBattlePetLevel = 0;
+ foreach (AuctionPosting remainingAuction in bucket.Auctions)
+ {
+ foreach (Item item in remainingAuction.Items)
+ {
+ byte battlePetLevel = (byte)item.GetModifier(ItemModifier.BattlePetLevel);
+ if (bucket.MinBattlePetLevel == 0)
+ bucket.MinBattlePetLevel = battlePetLevel;
+ else if (bucket.MinBattlePetLevel > battlePetLevel)
+ bucket.MinBattlePetLevel = battlePetLevel;
+
+ bucket.MaxBattlePetLevel = Math.Max(bucket.MaxBattlePetLevel, battlePetLevel);
+ }
+ }
+ }
+
+ if (--bucket.QualityCounts[quality] == 0)
+ bucket.QualityMask &= (AuctionHouseFilterMask)(~(1 << ((int)quality + 4)));
+ }
+ else
+ _buckets.Remove(bucket.Key);
+
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_AUCTION);
+ stmt.AddValue(0, auction.Id);
+ trans.Append(stmt);
+
+ foreach (Item item in auction.Items)
+ Global.AuctionHouseMgr.RemoveAItem(item.GetGUID());
+
Global.ScriptMgr.OnAuctionRemove(this, auction);
- return AuctionsMap.TryRemove(auction.Id, out _);
+
+ _playerOwnedAuctions.Remove(auction.Owner, auction.Id);
+ foreach (ObjectGuid bidder in auction.BidderHistory)
+ _playerBidderAuctions.Remove(bidder, auction.Id);
+
+ _itemsByAuctionId.Remove(auction.Id);
}
public void Update()
{
- long curTime = GameTime.GetGameTime();
- // Handle expired auctions
+ DateTime curTime = GameTime.GetGameTimeSystemPoint();
+ DateTime curTimeSteady = GameTime.GetGameTimeSteadyPoint();
+ ///- Handle expired auctions
- // If storage is empty, no need to update. next == NULL in this case.
- if (AuctionsMap.Empty())
+ // Clear expired throttled players
+ foreach (var key in _replicateThrottleMap.Keys.ToList())
+ if (_replicateThrottleMap[key].NextAllowedReplication <= curTimeSteady)
+ _replicateThrottleMap.Remove(key);
+
+ foreach (var key in _commodityQuotes.Keys.ToList())
+ if (_commodityQuotes[key].ValidTo < curTimeSteady)
+ _commodityQuotes.Remove(key);
+
+ if (_itemsByAuctionId.Empty())
return;
SQLTransaction trans = new SQLTransaction();
- foreach (var auction in AuctionsMap.Values)
+ foreach (var auction in _itemsByAuctionId.Values)
{
- // filter auctions expired on next update
- if (auction.expire_time > curTime + 60)
+ ///- filter auctions expired on next update
+ if (auction.EndTime > curTime.AddMinutes(1))
continue;
- // Either cancel the auction if there was no bidder
- if (auction.bidder == 0 && auction.bid == 0)
+ ///- Either cancel the auction if there was no bidder
+ if (auction.Bidder.IsEmpty())
{
- Global.AuctionMgr.SendAuctionExpiredMail(auction, trans);
+ SendAuctionExpired(auction, trans);
Global.ScriptMgr.OnAuctionExpire(this, auction);
}
- // Or perform the transaction
+ ///- 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);
+ SendAuctionWon(auction, null, trans);
+ SendAuctionSold(auction, null, trans);
Global.ScriptMgr.OnAuctionSuccessful(this, auction);
}
- // In any case clear the auction
- auction.DeleteFromDB(trans);
-
- Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow);
- RemoveAuction(auction);
+ ///- In any case clear the auction
+ RemoveAuction(trans, auction);
}
// Run DB changes
DB.Characters.CommitTransaction(trans);
}
- public void BuildListBidderItems(AuctionListBidderItemsResult packet, Player player)
+ public void BuildListBuckets(AuctionListBucketsResult listBucketsResult, Player player, string name, byte minLevel, byte maxLevel, AuctionHouseFilterMask filters, Optional classFilters,
+ byte[] knownPetBits, int knownPetBitsCount, byte maxKnownPetLevel, uint offset, AuctionSortDef[] sorts, int sortCount)
{
- foreach (var Aentry in AuctionsMap.Values)
+ List knownAppearanceIds = new List();
+ BitArray knownPetSpecies = new BitArray(knownPetBits);
+ // prepare uncollected filter for more efficient searches
+ if (filters.HasFlag(AuctionHouseFilterMask.UncollectedOnly))
{
- if (Aentry != null && Aentry.bidder == player.GetGUID().GetCounter())
- Aentry.BuildAuctionInfo(packet.Items, false);
+ knownAppearanceIds = player.GetSession().GetCollectionMgr().GetAppearanceIds();
+ //todo fix me
+ //if (knownPetSpecies.size() < CliDB.BattlePetSpeciesStorage.GetNumRows())
+ //knownPetSpecies.resize(CliDB.BattlePetSpeciesStorage.GetNumRows());
}
- }
- public void BuildListOwnerItems(AuctionListOwnerItemsResult packet, Player player)
- {
- foreach (var Aentry in AuctionsMap.Values)
+ var sorter = new AuctionsBucketData.Sorter(player.GetSession().GetSessionDbcLocale(), sorts, sortCount);
+ var builder = new AuctionsResultBuilder(offset, sorter, AuctionHouseResultLimits.Browse);
+
+ foreach (var bucket in _buckets)
{
- if (Aentry != null && Aentry.owner == player.GetGUID().GetCounter())
- Aentry.BuildAuctionInfo(packet.Items, false);
- }
- }
+ AuctionsBucketData bucketData = bucket.Value;
+ if (!name.IsEmpty())
+ {
+ if (filters.HasFlag(AuctionHouseFilterMask.ExactMatch))
+ {
+ if (bucketData.FullName[(int)player.GetSession().GetSessionDbcLocale()] != name)
+ continue;
+ }
+ else
+ {
+ if (!bucketData.FullName[(int)player.GetSession().GetSessionDbcLocale()].Contains(name))
+ continue;
+ }
+ }
- public void BuildListAuctionItems(AuctionListItemsResult packet, Player player, string searchedname, uint listfrom, byte levelmin, byte levelmax, AuctionHouseFilterMask filters, Optional classFilters)
- {
- long curTime = GameTime.GetGameTime();
-
- foreach (var Aentry in AuctionsMap.Values)
- {
- // Skip expired auctions
- if (Aentry.expire_time < curTime)
+ if (minLevel != 0 && bucketData.RequiredLevel < minLevel)
continue;
- Item item = Global.AuctionMgr.GetAItem(Aentry.itemGUIDLow);
- if (!item)
+ if (maxLevel != 0 && bucketData.RequiredLevel > maxLevel)
+ continue;
+
+ if (!filters.HasFlag(bucketData.QualityMask))
continue;
- ItemTemplate proto = item.GetTemplate();
if (classFilters.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 (classFilters.Value.Classes[(int)proto.GetClass()].SubclassMask == AuctionSearchClassFilters.FilterType.SkipClass)
+ if (classFilters.Value.Classes[bucketData.ItemClass].SubclassMask == AuctionSearchClassFilters.FilterType.SkipClass)
continue;
- if (classFilters.Value.Classes[(int)proto.GetClass()].SubclassMask != AuctionSearchClassFilters.FilterType.SkipSubclass)
+ if (classFilters.Value.Classes[bucketData.ItemClass].SubclassMask != AuctionSearchClassFilters.FilterType.SkipSubclass)
{
- if (!Convert.ToBoolean((int)classFilters.Value.Classes[(int)proto.GetClass()].SubclassMask & (1u << (int)proto.GetSubClass())))
+ if (!classFilters.Value.Classes[bucketData.ItemClass].SubclassMask.HasAnyFlag((AuctionSearchClassFilters.FilterType)(1 << bucketData.ItemSubClass)))
continue;
- if (!Convert.ToBoolean(classFilters.Value.Classes[(int)proto.GetClass()].InvTypes[(int)proto.GetSubClass()] & (1u << (int)proto.GetInventoryType())))
+ if (!classFilters.Value.Classes[bucketData.ItemClass].InvTypes[bucketData.ItemSubClass].HasAnyFlag(1u << bucketData.InventoryType))
continue;
}
}
- if (!filters.HasFlag((AuctionHouseFilterMask)(1 << ((int)proto.GetQuality() + 4))))
- continue;
-
- if (levelmin != 0 && (item.GetRequiredLevel() < levelmin || (levelmax != 0 && item.GetRequiredLevel() > levelmax)))
- continue;
-
- if (filters.HasFlag(AuctionHouseFilterMask.UsableOnly) && 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))
+ if (filters.HasFlag(AuctionHouseFilterMask.UncollectedOnly))
{
- string name = proto.GetName(player.GetSession().GetSessionDbcLocale());
- if (string.IsNullOrEmpty(name))
+ // appearances - by ItemAppearanceId, not ItemModifiedAppearanceId
+ if (bucketData.InventoryType != (byte)InventoryType.NonEquip && bucketData.InventoryType != (byte)InventoryType.Bag)
+ {
+ bool hasAll = true;
+ foreach (var bucketAppearance in bucketData.ItemModifiedAppearanceId)
+ {
+ var itemModifiedAppearance = CliDB.ItemModifiedAppearanceStorage.LookupByKey(bucketAppearance.Item1);
+ if (itemModifiedAppearance != null)
+ {
+ if (!knownAppearanceIds.Contains(itemModifiedAppearance.ItemAppearanceID))
+ {
+ hasAll = false;
+ break;
+ }
+ }
+ }
+
+ if (hasAll)
+ continue;
+ }
+ // caged pets
+ else if (bucket.Key.BattlePetSpeciesId != 0)
+ {
+ if (knownPetSpecies.Get(bucket.Key.BattlePetSpeciesId))
+ continue;
+ }
+ // toys
+ else if (Global.DB2Mgr.IsToyItem(bucket.Key.ItemId))
+ {
+ if (player.GetSession().GetCollectionMgr().HasToy(bucket.Key.ItemId))
+ continue;
+ }
+ // mounts
+ // recipes
+ // pet items
+ else if (bucketData.ItemClass == (int)ItemClass.Consumable || bucketData.ItemClass == (int)ItemClass.Recipe || bucketData.ItemClass == (int)ItemClass.Miscellaneous)
+ {
+ ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(bucket.Key.ItemId);
+ if (itemTemplate.Effects.Count >= 2 && (itemTemplate.Effects[0].SpellID == 483 || itemTemplate.Effects[0].SpellID == 55884))
+ {
+ if (player.HasSpell((uint)itemTemplate.Effects[1].SpellID))
+ continue;
+
+ var battlePetSpecies = Global.SpellMgr.GetBattlePetSpecies((uint)itemTemplate.Effects[1].SpellID);
+ if (battlePetSpecies != null)
+ if (knownPetSpecies.Get((int)battlePetSpecies.Id))
+ continue;
+ }
+ }
+ }
+
+ if (filters.HasFlag(AuctionHouseFilterMask.UsableOnly))
+ {
+ if (bucketData.RequiredLevel != 0 && player.GetLevel() < bucketData.RequiredLevel)
+ continue;
+
+ if (player.CanUseItem(Global.ObjectMgr.GetItemTemplate(bucket.Key.ItemId), true) != InventoryResult.Ok)
+ continue;
+
+ // cannot learn caged pets whose level exceeds highest level of currently owned pet
+ if (bucketData.MinBattlePetLevel != 0 && bucketData.MinBattlePetLevel > maxKnownPetLevel)
continue;
}
- // 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);
+ // TODO: this one needs to access loot history to know highest item level for every inventory type
+ //if (filters.HasFlag(AuctionHouseFilterMask.UpgradesOnly))
+ //{
+ //}
- ++packet.TotalCount;
+ builder.AddItem(bucketData);
+ }
+
+ foreach (AuctionsBucketData resultBucket in builder.GetResultRange())
+ {
+ BucketInfo bucketInfo = new BucketInfo();
+ resultBucket.BuildBucketInfo(bucketInfo, player);
+ listBucketsResult.Buckets.Add(bucketInfo);
+ }
+
+ listBucketsResult.HasMoreResults = builder.HasMoreResults();
+ }
+
+ public void BuildListBuckets(AuctionListBucketsResult listBucketsResult, Player player, AuctionBucketKey[] keys, int keysCount, AuctionSortDef[] sorts, int sortCount)
+ {
+ List buckets = new List();
+ for (int i = 0; i < keysCount; ++i)
+ {
+ var bucketData = _buckets.LookupByKey(new AuctionsBucketKey(keys[i]));
+ if (bucketData != null)
+ buckets.Add(bucketData);
+ }
+
+ AuctionsBucketData.Sorter sorter = new AuctionsBucketData.Sorter(player.GetSession().GetSessionDbcLocale(), sorts, sortCount);
+ buckets.Sort(sorter);
+
+ foreach (AuctionsBucketData resultBucket in buckets)
+ {
+ BucketInfo bucketInfo = new BucketInfo();
+ resultBucket.BuildBucketInfo(bucketInfo, player);
+ listBucketsResult.Buckets.Add(bucketInfo);
+ }
+
+ listBucketsResult.HasMoreResults = false;
+ }
+
+ public void BuildListBidderItems(AuctionListBidderItemsResult listBidderItemsResult, Player player, uint offset, AuctionSortDef[] sorts, int sortCount)
+ {
+ // always full list
+ List auctions = new List();
+ foreach (var auctionId in _playerBidderAuctions.LookupByKey(player.GetGUID()))
+ {
+ AuctionPosting auction = _itemsByAuctionId.LookupByKey(auctionId);
+ if (auction != null)
+ auctions.Add(auction);
+ }
+
+ AuctionPosting.Sorter sorter = new AuctionPosting.Sorter(player.GetSession().GetSessionDbcLocale(), sorts, sortCount);
+ auctions.Sort(sorter);
+
+ foreach (var resultAuction in auctions)
+ {
+ AuctionItem auctionItem = new AuctionItem();
+ resultAuction.BuildAuctionItem(auctionItem, true, true, true, false);
+ listBidderItemsResult.Items.Add(auctionItem);
+ }
+
+ listBidderItemsResult.HasMoreResults = false;
+ }
+
+ public void BuildListAuctionItems(AuctionListItemsResult listItemsResult, Player player, AuctionsBucketKey bucketKey, uint offset, AuctionSortDef[] sorts, int sortCount)
+ {
+ listItemsResult.TotalCount = 0;
+ AuctionsBucketData bucket = _buckets.LookupByKey(bucketKey);
+ if (bucket != null)
+ {
+ var sorter = new AuctionPosting.Sorter(player.GetSession().GetSessionDbcLocale(), sorts, sortCount);
+ var builder = new AuctionsResultBuilder(offset, sorter, AuctionHouseResultLimits.Items);
+
+ foreach (var auction in bucket.Auctions)
+ {
+ builder.AddItem(auction);
+ foreach (Item item in auction.Items)
+ listItemsResult.TotalCount += item.GetCount();
+ }
+
+ foreach (AuctionPosting resultAuction in builder.GetResultRange())
+ {
+ AuctionItem auctionItem = new AuctionItem();
+ resultAuction.BuildAuctionItem(auctionItem, false, false, resultAuction.OwnerAccount != player.GetSession().GetAccountGUID(), resultAuction.Bidder.IsEmpty());
+ listItemsResult.Items.Add(auctionItem);
+ }
+
+ listItemsResult.HasMoreResults = builder.HasMoreResults();
}
}
- public AuctionEntry GetAuction(uint id)
+ public void BuildListAuctionItems(AuctionListItemsResult listItemsResult, Player player, uint itemId, uint offset, AuctionSortDef[] sorts, int sortCount)
{
- return AuctionsMap.LookupByKey(id);
+ var sorter = new AuctionPosting.Sorter(player.GetSession().GetSessionDbcLocale(), sorts, sortCount);
+ var builder = new AuctionsResultBuilder(offset, sorter, AuctionHouseResultLimits.Items);
+
+ listItemsResult.TotalCount = 0;
+ var bucketData = _buckets.LookupByKey(new AuctionsBucketKey(itemId, 0, 0, 0));
+ if (bucketData != null)
+ {
+ foreach (AuctionPosting auction in bucketData.Auctions)
+ {
+ builder.AddItem(auction);
+ foreach (Item item in auction.Items)
+ listItemsResult.TotalCount += item.GetCount();
+ }
+ }
+
+ foreach (AuctionPosting resultAuction in builder.GetResultRange())
+ {
+ AuctionItem auctionItem = new AuctionItem();
+ resultAuction.BuildAuctionItem(auctionItem, false, true, resultAuction.OwnerAccount != player.GetSession().GetAccountGUID(),
+ resultAuction.Bidder.IsEmpty());
+
+ listItemsResult.Items.Add(auctionItem);
+ }
+
+ listItemsResult.HasMoreResults = builder.HasMoreResults();
}
- ConcurrentDictionary AuctionsMap = new ConcurrentDictionary();
+ public void BuildListOwnerItems(AuctionListOwnerItemsResult listOwnerItemsResult, Player player, uint offset, AuctionSortDef[] sorts, int sortCount)
+ {
+ // always full list
+ List auctions = new List();
+ foreach (var auctionId in _playerOwnedAuctions.LookupByKey(player.GetGUID()))
+ {
+ AuctionPosting auction = _itemsByAuctionId.LookupByKey(auctionId);
+ if (auction != null)
+ auctions.Add(auction);
+ }
+
+ AuctionPosting.Sorter sorter = new AuctionPosting.Sorter(player.GetSession().GetSessionDbcLocale(), sorts, sortCount);
+ auctions.Sort(sorter);
+
+ foreach (var resultAuction in auctions)
+ {
+ AuctionItem auctionItem = new AuctionItem();
+ resultAuction.BuildAuctionItem(auctionItem, true, true, false, false);
+ listOwnerItemsResult.Items.Add(auctionItem);
+ }
+
+ listOwnerItemsResult.HasMoreResults = false;
+ }
+
+ public void BuildReplicate(AuctionReplicateResponse replicateResponse, Player player, uint global, uint cursor, uint tombstone, uint count)
+ {
+ DateTime curTime = GameTime.GetGameTimeSteadyPoint();
+
+ var throttleData = _replicateThrottleMap.LookupByKey(player.GetGUID());
+ if (throttleData == null)
+ {
+ throttleData = new PlayerReplicateThrottleData();
+ throttleData.NextAllowedReplication = curTime + TimeSpan.FromSeconds(WorldConfig.GetIntValue(WorldCfg.AuctionGetallDelay));
+ throttleData.Global = Global.AuctionHouseMgr.GenerateReplicationId();
+ }
+ else
+ {
+ if (throttleData.Global != global || throttleData.Cursor != cursor || throttleData.Tombstone != tombstone)
+ return;
+
+ if (!throttleData.IsReplicationInProgress() && throttleData.NextAllowedReplication > curTime)
+ return;
+ }
+
+ if (_itemsByAuctionId.Empty() || count == 0)
+ return;
+
+ var keyIndex = _itemsByAuctionId.IndexOfKey(cursor);
+ foreach (var pair in _itemsByAuctionId.Skip(keyIndex))
+ {
+ AuctionItem auctionItem = new AuctionItem();
+ pair.Value.BuildAuctionItem(auctionItem, false, true, true, pair.Value.Bidder.IsEmpty());
+ replicateResponse.Items.Add(auctionItem);
+ if (--count == 0)
+ break;
+ }
+
+ replicateResponse.ChangeNumberGlobal = throttleData.Global;
+ replicateResponse.ChangeNumberCursor = throttleData.Cursor = !replicateResponse.Items.Empty() ? replicateResponse.Items.Last().AuctionID : 0;
+ replicateResponse.ChangeNumberTombstone = throttleData.Tombstone = count == 0 ? _itemsByAuctionId.First().Key : 0;
+ _replicateThrottleMap[player.GetGUID()] = throttleData;
+ }
+
+ public ulong CalcualteAuctionHouseCut(ulong bidAmount)
+ {
+ return (ulong)Math.Max((long)(MathFunctions.CalculatePct(bidAmount, _auctionHouse.ConsignmentRate) * WorldConfig.GetFloatValue(WorldCfg.RateAuctionCut)), 0);
+ }
+
+ public CommodityQuote CreateCommodityQuote(Player player, uint itemId, uint quantity)
+ {
+ var bucketData = _buckets.LookupByKey(AuctionsBucketKey.ForCommodity(itemId));
+ if (bucketData == null)
+ return null;
+
+ ulong totalPrice = 0;
+ uint remainingQuantity = quantity;
+ foreach (AuctionPosting auction in bucketData.Auctions)
+ {
+ foreach (Item auctionItem in auction.Items)
+ {
+ if (auctionItem.GetCount() >= remainingQuantity)
+ {
+ totalPrice += auction.BuyoutOrUnitPrice * remainingQuantity;
+ remainingQuantity = 0;
+ break;
+ }
+
+ totalPrice += auction.BuyoutOrUnitPrice * auctionItem.GetCount();
+ remainingQuantity -= auctionItem.GetCount();
+ }
+ }
+
+ // not enough items on auction house
+ if (remainingQuantity != 0)
+ return null;
+
+ if (!player.HasEnoughMoney(totalPrice))
+ return null;
+
+ CommodityQuote quote = _commodityQuotes[player.GetGUID()];
+ quote.TotalPrice = totalPrice;
+ quote.Quantity = quantity;
+ quote.ValidTo = GameTime.GetGameTimeSteadyPoint() + TimeSpan.FromSeconds(30);
+ return quote;
+ }
+
+ public void CancelCommodityQuote(ObjectGuid guid)
+ {
+ _commodityQuotes.Remove(guid);
+ }
+
+ public bool BuyCommodity(SQLTransaction trans, Player player, uint itemId, uint quantity, TimeSpan delayForNextAction)
+ {
+ var bucketItr = _buckets.LookupByKey(AuctionsBucketKey.ForCommodity(itemId));
+ if (bucketItr == null)
+ {
+ player.GetSession().SendAuctionCommandResult(0, AuctionCommand.PlaceBid, AuctionResult.CommodityPurchaseFailed, delayForNextAction);
+ return false;
+ }
+
+ var quote = _commodityQuotes.LookupByKey(player.GetGUID());
+ if (quote == null)
+ {
+ player.GetSession().SendAuctionCommandResult(0, AuctionCommand.PlaceBid, AuctionResult.CommodityPurchaseFailed, delayForNextAction);
+ return false;
+ }
+
+ ulong totalPrice = 0;
+ uint remainingQuantity = quantity;
+ List auctions = new List();
+ for (var i = 0; i < bucketItr.Auctions.Count;)
+ {
+ AuctionPosting auction = bucketItr.Auctions[i++];
+ auctions.Add(auction);
+ foreach (Item auctionItem in auction.Items)
+ {
+ if (auctionItem.GetCount() >= remainingQuantity)
+ {
+ totalPrice += auction.BuyoutOrUnitPrice * remainingQuantity;
+ remainingQuantity = 0;
+ i = bucketItr.Auctions.Count;
+ break;
+ }
+
+ totalPrice += auction.BuyoutOrUnitPrice * auctionItem.GetCount();
+ remainingQuantity -= auctionItem.GetCount();
+ }
+ }
+
+ // not enough items on auction house
+ if (remainingQuantity != 0)
+ {
+ player.GetSession().SendAuctionCommandResult(0, AuctionCommand.PlaceBid, AuctionResult.CommodityPurchaseFailed, delayForNextAction);
+ return false;
+ }
+
+ // something was bought between creating quote and finalizing transaction
+ // but we allow lower price if new items were posted at lower price
+ if (totalPrice > quote.TotalPrice)
+ {
+ player.GetSession().SendAuctionCommandResult(0, AuctionCommand.PlaceBid, AuctionResult.CommodityPurchaseFailed, delayForNextAction);
+ return false;
+ }
+
+ if (!player.HasEnoughMoney(totalPrice))
+ {
+ player.GetSession().SendAuctionCommandResult(0, AuctionCommand.PlaceBid, AuctionResult.CommodityPurchaseFailed, delayForNextAction);
+ return false;
+ }
+
+ Optional uniqueSeller = new Optional();
+
+ // prepare items
+ List- items = new List
- ();
+ remainingQuantity = quantity;
+ List removedItemsFromAuction = new List();
+
+ for (var i = 0; i < bucketItr.Auctions.Count;)
+ {
+ AuctionPosting auction = bucketItr.Auctions[i++];
+ if (!uniqueSeller.HasValue)
+ uniqueSeller.Set(auction.Owner);
+ else if (uniqueSeller.Value != auction.Owner)
+ uniqueSeller.Set(ObjectGuid.Empty);
+
+ uint boughtFromAuction = 0;
+ int removedItems = 0;
+ foreach (Item auctionItem in auction.Items)
+ {
+ if (auctionItem.GetCount() >= remainingQuantity)
+ {
+ Item clonedItem = auctionItem.CloneItem(remainingQuantity, player);
+ if (!clonedItem)
+ {
+ player.GetSession().SendAuctionCommandResult(0, AuctionCommand.PlaceBid, AuctionResult.CommodityPurchaseFailed, delayForNextAction);
+ return false;
+ }
+
+ auctionItem.SetCount(auctionItem.GetCount() - remainingQuantity);
+ auctionItem.FSetState(ItemUpdateState.Changed);
+ auctionItem.SaveToDB(trans);
+ items.Add(clonedItem);
+ boughtFromAuction += remainingQuantity;
+ remainingQuantity = 0;
+ i = bucketItr.Auctions.Count;
+ break;
+ }
+
+ items.Add(auctionItem);
+ boughtFromAuction += auctionItem.GetCount();
+ remainingQuantity -= auctionItem.GetCount();
+ ++removedItems;
+ }
+
+ removedItemsFromAuction.Add(removedItems);
+
+ if (player.GetSession().HasPermission(RBACPermissions.LogGmTrade))
+ {
+ uint bidderAccId = player.GetSession().GetAccountId();
+ if (!Global.CharacterCacheStorage.GetCharacterNameByGuid(auction.Owner, out string ownerName))
+ ownerName = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown);
+
+ Log.outCommand(bidderAccId, $"GM {player.GetName()} (Account: {bidderAccId}) bought commodity in auction: {items[0].GetName(Global.WorldMgr.GetDefaultDbcLocale())} (Entry: {items[0].GetEntry()} " +
+ $"Count: {boughtFromAuction}) and pay money: { auction.BuyoutOrUnitPrice * boughtFromAuction}. Original owner {ownerName} (Account: {Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(auction.Owner)})");
+ }
+
+ ulong auctionHouseCut = CalcualteAuctionHouseCut(auction.BuyoutOrUnitPrice * boughtFromAuction);
+ ulong depositPart = Global.AuctionHouseMgr.GetCommodityAuctionDeposit(items[0].GetTemplate(), (auction.EndTime - auction.StartTime), boughtFromAuction);
+ ulong profit = auction.BuyoutOrUnitPrice * boughtFromAuction + depositPart - auctionHouseCut;
+
+ Player owner = Global.ObjAccessor.FindConnectedPlayer(auction.Owner);
+ if (owner != null)
+ {
+ owner.UpdateCriteria(CriteriaTypes.GoldEarnedByAuctions, profit);
+ owner.UpdateCriteria(CriteriaTypes.HighestAuctionSold, profit);
+ owner.GetSession().SendAuctionClosedNotification(auction, (float)WorldConfig.GetIntValue(WorldCfg.MailDeliveryDelay), true);
+ }
+
+ new MailDraft(Global.AuctionHouseMgr.BuildCommodityAuctionMailSubject(AuctionMailType.Sold, itemId, boughtFromAuction),
+ Global.AuctionHouseMgr.BuildAuctionSoldMailBody(player.GetGUID(), auction.BuyoutOrUnitPrice * boughtFromAuction, boughtFromAuction, (uint)depositPart, auctionHouseCut))
+ .AddMoney(profit)
+ .SendMailTo(trans, new MailReceiver(Global.ObjAccessor.FindConnectedPlayer(auction.Owner), auction.Owner), new MailSender(this), MailCheckMask.Copied, WorldConfig.GetUIntValue(WorldCfg.MailDeliveryDelay));
+ }
+
+ MailDraft mail = new MailDraft(Global.AuctionHouseMgr.BuildCommodityAuctionMailSubject(AuctionMailType.Won, itemId, quantity),
+ Global.AuctionHouseMgr.BuildAuctionWonMailBody(uniqueSeller.Value, totalPrice, quantity));
+
+ foreach (Item item in items)
+ {
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_AUCTION_ITEMS_BY_ITEM);
+ stmt.AddValue(0, item.GetGUID().GetCounter());
+ trans.Append(stmt);
+
+ item.SetOwnerGUID(player.GetGUID());
+ item.SaveToDB(trans);
+ mail.AddItem(item);
+ }
+
+ mail.SendMailTo(trans, player, new MailSender(this), MailCheckMask.Copied);
+
+ AuctionWonNotification packet = new AuctionWonNotification();
+ packet.Info.Initialize(auctions[0], items[0]);
+ player.SendPacket(packet);
+
+ for (int i = 0; i < auctions.Count; ++i)
+ {
+ if (removedItemsFromAuction[i] == auctions[i].Items.Count)
+ RemoveAuction(trans, auctions[i]); // bought all items
+ else if (removedItemsFromAuction[i] != 0)
+ {
+ var lastRemovedItemIndex = removedItemsFromAuction[i];
+ for (var c = 0; c != removedItemsFromAuction[i]; ++c)
+ {
+ Global.AuctionHouseMgr.RemoveAItem(auctions[i].Items[c].GetGUID());
+ }
+
+ auctions[i].Items.RemoveRange(0, lastRemovedItemIndex);
+ }
+ }
+
+ return true;
+ }
+
+ // this function notified old bidder that his bid is no longer highest
+ public void SendAuctionOutbid(AuctionPosting auction, ObjectGuid newBidder, ulong newBidAmount, SQLTransaction trans)
+ {
+ Player oldBidder = Global.ObjAccessor.FindConnectedPlayer(auction.Bidder);
+
+ // old bidder exist
+ if ((oldBidder || Global.CharacterCacheStorage.HasCharacterCacheEntry(auction.Bidder)))// && !sAuctionBotConfig.IsBotChar(auction.Bidder))
+ {
+ if (oldBidder)
+ {
+ AuctionOutbidNotification packet = new AuctionOutbidNotification();
+ packet.BidAmount = newBidAmount;
+ packet.MinIncrement = AuctionPosting.CalculateMinIncrement(newBidAmount);
+ packet.Info.AuctionID = auction.Id;
+ packet.Info.Bidder = newBidder;
+ packet.Info.Item = new ItemInstance(auction.Items[0]);
+ oldBidder.SendPacket(packet);
+ }
+
+ new MailDraft(Global.AuctionHouseMgr.BuildItemAuctionMailSubject(AuctionMailType.Outbid, auction), "")
+ .AddMoney(auction.BidAmount)
+ .SendMailTo(trans, new MailReceiver(oldBidder, auction.Bidder), new MailSender(this), MailCheckMask.Copied);
+ }
+ }
+
+ public void SendAuctionWon(AuctionPosting auction, Player bidder, SQLTransaction trans)
+ {
+ uint bidderAccId = 0;
+ if (!bidder)
+ bidder = Global.ObjAccessor.FindConnectedPlayer(auction.Bidder); // try lookup bidder when called from .Update
+
+ // 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 = Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(auction.Bidder);
+ logGmTrade = Global.AccountMgr.HasPermission(bidderAccId, RBACPermissions.LogGmTrade, Global.WorldMgr.GetRealmId().Realm);
+
+ if (logGmTrade && !Global.CharacterCacheStorage.GetCharacterNameByGuid(auction.Bidder, out bidderName))
+ bidderName = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown);
+ }
+
+ if (logGmTrade)
+ {
+ if (!Global.CharacterCacheStorage.GetCharacterNameByGuid(auction.Owner, out string ownerName))
+ ownerName = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown);
+
+ uint ownerAccId = Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(auction.Owner);
+
+ Log.outCommand(bidderAccId, $"GM {bidderName} (Account: {bidderAccId}) won item in auction: {auction.Items[0].GetName(Global.WorldMgr.GetDefaultDbcLocale())} (Entry: {auction.Items[0].GetEntry()}" +
+ $" Count: {auction.GetTotalItemCount()}) and pay money: {auction.BidAmount}. Original owner {ownerName} (Account: {ownerAccId})");
+ }
+
+ // receiver exist
+ if ((bidder != null || bidderAccId != 0))// && !sAuctionBotConfig.IsBotChar(auction.Bidder))
+ {
+ MailDraft mail = new MailDraft(Global.AuctionHouseMgr.BuildItemAuctionMailSubject(AuctionMailType.Won, auction),
+ Global.AuctionHouseMgr.BuildAuctionWonMailBody(auction.Owner, auction.BidAmount, auction.BuyoutOrUnitPrice));
+
+ // set owner to bidder (to prevent delete item with sender char deleting)
+ // owner in `data` will set at mail receive and item extracting
+ foreach (Item item in auction.Items)
+ {
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ITEM_OWNER);
+ stmt.AddValue(0, auction.Bidder.GetCounter());
+ stmt.AddValue(1, item.GetGUID().GetCounter());
+ trans.Append(stmt);
+
+ mail.AddItem(item);
+ }
+
+ if (bidder)
+ {
+ AuctionWonNotification packet = new AuctionWonNotification();
+ packet.Info.Initialize(auction, auction.Items[0]);
+ bidder.SendPacket(packet);
+
+ // FIXME: for offline player need also
+ bidder.UpdateCriteria(CriteriaTypes.WonAuctions, 1);
+ }
+
+ mail.SendMailTo(trans, new MailReceiver(bidder, auction.Bidder), new MailSender(this), MailCheckMask.Copied);
+ }
+ else
+ {
+ // bidder doesn't exist, delete the item
+ foreach (Item item in auction.Items)
+ Global.AuctionHouseMgr.RemoveAItem(item.GetGUID(), true);
+ }
+ }
+
+ //call this method to send mail to auction owner, when auction is successful, it does not clear ram
+ public void SendAuctionSold(AuctionPosting auction, Player owner, SQLTransaction trans)
+ {
+ if (!owner)
+ owner = Global.ObjAccessor.FindConnectedPlayer(auction.Owner);
+
+ // owner exist
+ if ((owner || Global.CharacterCacheStorage.HasCharacterCacheEntry(auction.Owner)))// && !sAuctionBotConfig.IsBotChar(auction.Owner))
+ {
+ ulong auctionHouseCut = CalcualteAuctionHouseCut(auction.BidAmount);
+ ulong profit = auction.BidAmount + auction.Deposit - auctionHouseCut;
+
+ //FIXME: what do if owner offline
+ if (owner)
+ {
+ owner.UpdateCriteria(CriteriaTypes.GoldEarnedByAuctions, profit);
+ owner.UpdateCriteria(CriteriaTypes.HighestAuctionSold, auction.BidAmount);
+ //send auction owner notification, bidder must be current!
+ owner.GetSession().SendAuctionClosedNotification(auction, (float)WorldConfig.GetIntValue(WorldCfg.MailDeliveryDelay), true);
+ }
+
+ new MailDraft(Global.AuctionHouseMgr.BuildItemAuctionMailSubject(AuctionMailType.Sold, auction),
+ Global.AuctionHouseMgr.BuildAuctionSoldMailBody(auction.Bidder, auction.BidAmount, auction.BuyoutOrUnitPrice, (uint)auction.Deposit, auctionHouseCut))
+ .AddMoney(profit)
+ .SendMailTo(trans, new MailReceiver(owner, auction.Owner), new MailSender(this), MailCheckMask.Copied, WorldConfig.GetUIntValue(WorldCfg.MailDeliveryDelay));
+ }
+ }
+
+ public void SendAuctionExpired(AuctionPosting auction, SQLTransaction trans)
+ {
+ Player owner = Global.ObjAccessor.FindConnectedPlayer(auction.Owner);
+ // owner exist
+ if ((owner || Global.CharacterCacheStorage.HasCharacterCacheEntry(auction.Owner)))// && !sAuctionBotConfig.IsBotChar(auction.Owner))
+ {
+ if (owner)
+ owner.GetSession().SendAuctionClosedNotification(auction, 0.0f, false);
+
+ MailDraft mail = new MailDraft(Global.AuctionHouseMgr.BuildItemAuctionMailSubject(AuctionMailType.Expired, auction), "");
+
+ foreach (Item item in auction.Items)
+ mail.AddItem(item);
+
+ mail.SendMailTo(trans, new MailReceiver(owner, auction.Owner), new MailSender(this), MailCheckMask.Copied, 0);
+ }
+ else
+ {
+ // owner doesn't exist, delete the item
+ foreach (Item item in auction.Items)
+ Global.AuctionHouseMgr.RemoveAItem(item.GetGUID(), true);
+ }
+ }
+
+ public void SendAuctionRemoved(AuctionPosting auction, Player owner, SQLTransaction trans)
+ {
+ MailDraft draft = new MailDraft(Global.AuctionHouseMgr.BuildItemAuctionMailSubject(AuctionMailType.Cancelled, auction), "");
+
+ foreach (Item item in auction.Items)
+ draft.AddItem(item);
+
+ draft.SendMailTo(trans, owner, new MailSender(this), MailCheckMask.Copied);
+ }
+
+ //this function sends mail, when auction is cancelled to old bidder
+ public void SendAuctionCancelledToBidder(AuctionPosting auction, SQLTransaction trans)
+ {
+ Player bidder = Global.ObjAccessor.FindConnectedPlayer(auction.Bidder);
+
+ // bidder exist
+ if ((bidder || Global.CharacterCacheStorage.HasCharacterCacheEntry(auction.Bidder)))// && !sAuctionBotConfig.IsBotChar(auction.Bidder))
+ new MailDraft(Global.AuctionHouseMgr.BuildItemAuctionMailSubject(AuctionMailType.Removed, auction), "")
+ .AddMoney(auction.BidAmount)
+ .SendMailTo(trans, new MailReceiver(bidder, auction.Bidder), new MailSender(this), MailCheckMask.Copied);
+ }
+
+ public void SendAuctionInvoice(AuctionPosting auction, Player owner, SQLTransaction trans)
+ {
+ if (!owner)
+ owner = Global.ObjAccessor.FindConnectedPlayer(auction.Owner);
+
+ // owner exist (online or offline)
+ if ((owner || Global.CharacterCacheStorage.HasCharacterCacheEntry(auction.Owner)))// && !sAuctionBotConfig.IsBotChar(auction.Owner))
+ {
+ ByteBuffer tempBuffer = new ByteBuffer();
+ tempBuffer.WritePackedTime(GameTime.GetGameTime() + WorldConfig.GetIntValue(WorldCfg.MailDeliveryDelay));
+ uint eta = tempBuffer.ReadUInt32();
+
+ new MailDraft(Global.AuctionHouseMgr.BuildItemAuctionMailSubject(AuctionMailType.Invoice, auction),
+ Global.AuctionHouseMgr.BuildAuctionInvoiceMailBody(auction.Bidder, auction.BidAmount, auction.BuyoutOrUnitPrice, (uint)auction.Deposit,
+ CalcualteAuctionHouseCut(auction.BidAmount), WorldConfig.GetUIntValue(WorldCfg.MailDeliveryDelay), eta))
+ .SendMailTo(trans, new MailReceiver(owner, auction.Owner), new MailSender(this), MailCheckMask.Copied);
+ }
+ }
+
+ class PlayerReplicateThrottleData
+ {
+ public uint Global;
+ public uint Cursor;
+ public uint Tombstone;
+ public DateTime NextAllowedReplication = DateTime.MinValue;
+
+ public bool IsReplicationInProgress() { return Cursor != Tombstone && Global != 0; }
+ }
+
+ AuctionHouseRecord _auctionHouse;
+
+ SortedList _itemsByAuctionId = new SortedList(); // ordered for replicate
+ Dictionary _soldItemsById = new Dictionary();
+ SortedDictionary _buckets = new SortedDictionary();// ordered for search by itemid only
+ Dictionary _commodityQuotes = new Dictionary();
+
+ MultiMap _playerOwnedAuctions = new MultiMap();
+ MultiMap _playerBidderAuctions = new MultiMap();
+
+ // Map of throttled players for GetAll, and throttle expiry time
+ // Stored here, rather than player object to maintain persistence after logout
+ Dictionary _replicateThrottleMap = new Dictionary();
}
- public class AuctionEntry
+ public class AuctionPosting
{
- public void BuildAuctionInfo(List 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.AuctionID = (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, Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(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 (SocketedGem gemData in item.m_itemData.Gems)
- {
- 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 ulong GetAuctionCut()
- {
- long cut = (long)(MathFunctions.CalculatePct(bid, auctionHouseEntry.ConsignmentRate) * WorldConfig.GetFloatValue(WorldCfg.RateAuctionCut));
- return (ulong)Math.Max(cut, 0);
- }
-
- ///
- /// the sum of outbid is (1% from current bid)*5, if bid is very small, it is 1c
- ///
- ///
- public ulong GetAuctionOutBid()
- {
- ulong 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(0);
- auctioneer = fields.Read(1);
- itemGUIDLow = fields.Read(2);
- itemEntry = fields.Read(3);
- itemCount = fields.Read(4);
- owner = fields.Read(5);
- buyout = fields.Read(6);
- expire_time = fields.Read(7);
- bidder = fields.Read(8);
- bid = fields.Read(9);
- startbid = fields.Read(10);
- deposit = fields.Read(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(0);
- auctioneer = fields.Read(1);
- itemGUIDLow = fields.Read(2);
- itemEntry = fields.Read(3);
- itemCount = fields.Read(4);
- owner = fields.Read(5);
- buyout = fields.Read(6);
- expire_time = fields.Read(7);
- bidder = fields.Read(8);
- bid = fields.Read(9);
- startbid = fields.Read(10);
- deposit = fields.Read(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 $"{itemEntry}:0:{(uint)response}:{Id}:{itemCount}";
- }
-
- public static string BuildAuctionMailBody(ulong lowGuid, ulong bid, ulong buyout, ulong deposit, ulong cut)
- {
- return $"{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 ulong startbid; //maybe useless
- public ulong bid;
- public ulong buyout;
- public long expire_time;
- public ulong bidder;
- public ulong deposit; //deposit can be calculated only when creating auction
- public uint etime;
- uint houseId;
- public AuctionHouseRecord auctionHouseEntry; // in AuctionHouse.dbc
- public uint factionTemplateId;
+ public AuctionsBucketData Bucket;
+
+ public List
- Items = new List
- ();
+ public ObjectGuid Owner;
+ public ObjectGuid OwnerAccount;
+ public ObjectGuid Bidder;
+ public ulong MinBid = 0;
+ public ulong BuyoutOrUnitPrice = 0;
+ public ulong Deposit = 0;
+ public ulong BidAmount = 0;
+ public DateTime StartTime = DateTime.MinValue;
+ public DateTime EndTime = DateTime.MinValue;
+
+ public List BidderHistory = new List();
+
+ public bool IsCommodity()
+ {
+ return Items.Count > 1 || Items[0].GetTemplate().GetMaxStackSize() > 1;
+ }
+
+ public uint GetTotalItemCount()
+ {
+ return (uint)Items.Sum(item => { return item.GetCount(); });
+ }
+
+ public void BuildAuctionItem(AuctionItem auctionItem, bool alwaysSendItem, bool sendKey, bool censorServerInfo, bool censorBidInfo)
+ {
+ // SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT, SMSG_AUCTION_LIST_ITEMS_RESULT (if not commodity), SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT, SMSG_AUCTION_REPLICATE_RESPONSE (if not commodity)
+ //auctionItem.Item - here to unify comment
+
+ // all (not optional<>)
+ auctionItem.Count = (int)GetTotalItemCount();
+ auctionItem.Flags = 0;
+ auctionItem.AuctionID = Id;
+ auctionItem.Owner = Owner;
+
+ // prices set when filled
+ if (IsCommodity())
+ {
+ if (alwaysSendItem)
+ {
+ auctionItem.Item.HasValue = true;
+ auctionItem.Item.Value = new ItemInstance(Items[0]);
+ }
+
+ auctionItem.UnitPrice.Set(BuyoutOrUnitPrice);
+ }
+ else
+ {
+ auctionItem.Item.HasValue = true;
+ auctionItem.Item.Value = new ItemInstance(Items[0]);
+ auctionItem.Charges = new[] { Items[0].GetSpellCharges(0), Items[0].GetSpellCharges(1), Items[0].GetSpellCharges(2), Items[0].GetSpellCharges(3), Items[0].GetSpellCharges(4) }.Max();
+ for (EnchantmentSlot enchantmentSlot = 0; enchantmentSlot < EnchantmentSlot.MaxInspected; enchantmentSlot++)
+ {
+ uint enchantId = Items[0].GetEnchantmentId(enchantmentSlot);
+ if (enchantId == 0)
+ continue;
+
+ auctionItem.Enchantments.Add(new ItemEnchantData(enchantId, Items[0].GetEnchantmentDuration(enchantmentSlot), Items[0].GetEnchantmentCharges(enchantmentSlot), (byte)enchantmentSlot));
+ }
+
+ for (byte i = 0; i < Items[0].m_itemData.Gems.Size(); ++i)
+ {
+ SocketedGem gemData = Items[0].m_itemData.Gems[i];
+ if (gemData.ItemId != 0)
+ {
+ ItemGemData gem = new ItemGemData();
+ gem.Slot = i;
+ gem.Item = new ItemInstance(gemData);
+ auctionItem.Gems.Add(gem);
+ }
+ }
+
+ if (MinBid != 0)
+ auctionItem.MinBid.Set(MinBid);
+
+ ulong minIncrement = CalculateMinIncrement();
+ if (minIncrement != 0)
+ auctionItem.MinIncrement.Set(minIncrement);
+
+ if (BuyoutOrUnitPrice != 0)
+ auctionItem.BuyoutPrice.Set(BuyoutOrUnitPrice);
+ }
+
+ // all (not optional<>)
+ auctionItem.DurationLeft = (int)Math.Max((EndTime - GameTime.GetGameTimeSystemPoint()).ToMilliseconds(), 0l);
+ auctionItem.DeleteReason = 0;
+
+ // SMSG_AUCTION_LIST_ITEMS_RESULT (only if owned)
+ auctionItem.CensorServerSideInfo = censorServerInfo;
+ auctionItem.ItemGuid = IsCommodity() ? ObjectGuid.Empty : Items[0].GetGUID();
+ auctionItem.OwnerAccountID = OwnerAccount;
+ auctionItem.EndTime = (uint)Time.DateTimeToUnixTime(EndTime);
+
+ // SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT, SMSG_AUCTION_LIST_ITEMS_RESULT (if has bid), SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT, SMSG_AUCTION_REPLICATE_RESPONSE (if has bid)
+ auctionItem.CensorBidInfo = censorBidInfo;
+ if (!Bidder.IsEmpty())
+ {
+ auctionItem.Bidder.Set(Bidder);
+ auctionItem.BidAmount.Set(BidAmount);
+ }
+
+ // SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT, SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT, SMSG_AUCTION_REPLICATE_RESPONSE (if commodity)
+ if (sendKey)
+ auctionItem.AuctionBucketKey.Set(new AuctionBucketKey(AuctionsBucketKey.ForItem(Items[0])));
+ }
+
+ public static ulong CalculateMinIncrement(ulong bidAmount)
+ {
+ return MathFunctions.CalculatePct(bidAmount / MoneyConstants.Silver, 5) * MoneyConstants.Silver;
+ }
+
+ public ulong CalculateMinIncrement() { return CalculateMinIncrement(BidAmount); }
+
+ public class Sorter : IComparer
+ {
+ public Sorter(LocaleConstant locale, AuctionSortDef[] sorts, int sortCount)
+ {
+ _locale = locale;
+ _sorts = sorts;
+ _sortCount = sortCount;
+ }
+
+ public int Compare(AuctionPosting left, AuctionPosting right)
+ {
+ for (var i = 0; i < _sortCount; ++i)
+ {
+ int ordering = CompareColumns(_sorts[i].SortOrder, left, right);
+ if (ordering != 0)
+ return (ordering < 0).CompareTo(!_sorts[i].ReverseSort);
+ }
+
+ // Auctions are processed in LIFO order
+ if (left.StartTime != right.StartTime)
+ return left.StartTime.CompareTo(right.StartTime);
+
+ return left.Id.CompareTo(right.Id);
+ }
+
+ int CompareColumns(AuctionHouseSortOrder column, AuctionPosting left, AuctionPosting right)
+ {
+ switch (column)
+ {
+ case AuctionHouseSortOrder.Price:
+ {
+ ulong leftPrice = left.BuyoutOrUnitPrice != 0 ? left.BuyoutOrUnitPrice : (left.BidAmount != 0 ? left.BidAmount : left.MinBid);
+ ulong rightPrice = right.BuyoutOrUnitPrice != 0 ? right.BuyoutOrUnitPrice : (right.BidAmount != 0 ? right.BidAmount : right.MinBid);
+ return (int)(leftPrice - rightPrice);
+ }
+ case AuctionHouseSortOrder.Name:
+ return left.Bucket.FullName[(int)_locale].CompareTo(right.Bucket.FullName[(int)_locale]);
+ case AuctionHouseSortOrder.Level:
+ {
+ int leftLevel = left.Items[0].GetModifier(ItemModifier.BattlePetSpeciesId) == 0 ? left.Items[0].GetRequiredLevel() : (int)left.Items[0].GetModifier(ItemModifier.BattlePetLevel);
+ int rightLevel = right.Items[0].GetModifier(ItemModifier.BattlePetSpeciesId) == 0 ? right.Items[0].GetRequiredLevel() : (int)right.Items[0].GetModifier(ItemModifier.BattlePetLevel);
+ return leftLevel - rightLevel;
+ }
+ case AuctionHouseSortOrder.Bid:
+ return (int)(left.BidAmount - right.BidAmount);
+ case AuctionHouseSortOrder.Buyout:
+ return (int)(left.BuyoutOrUnitPrice - right.BuyoutOrUnitPrice);
+ default:
+ break;
+ }
+
+ return 0;
+ }
+
+ LocaleConstant _locale;
+ AuctionSortDef[] _sorts;
+ int _sortCount;
+ }
+ }
+
+
+
+ public class AuctionsBucketData
+ {
+ public AuctionsBucketKey Key;
+
+ // filter helpers
+ public byte ItemClass;
+ public byte ItemSubClass;
+ public byte InventoryType;
+ public AuctionHouseFilterMask QualityMask;
+ public uint[] QualityCounts = new uint[(int)ItemQuality.Max];
+ public ulong MinPrice; // for sort
+ public (uint Id, uint Count)[] ItemModifiedAppearanceId = new (uint Id, uint Count)[4]; // for uncollected search
+ public byte RequiredLevel = 0; // for usable search
+ public byte MinBattlePetLevel = 0;
+ public byte MaxBattlePetLevel = 0;
+ public string[] FullName = new string[(int)LocaleConstant.Total];
+
+ public List Auctions = new List();
+
+ public void BuildBucketInfo(BucketInfo bucketInfo, Player player)
+ {
+ bucketInfo.Key = new AuctionBucketKey(Key);
+ bucketInfo.MinPrice = MinPrice;
+ bucketInfo.TotalQuantity = 0;
+
+ foreach (AuctionPosting auction in Auctions)
+ {
+ foreach (Item item in auction.Items)
+ {
+ bucketInfo.TotalQuantity += (int)item.GetCount();
+
+ if (Key.BattlePetSpeciesId != 0)
+ {
+ uint breedData = item.GetModifier(ItemModifier.BattlePetBreedData);
+ uint breedId = breedData & 0xFFFFFF;
+ byte quality = (byte)((breedData >> 24) & 0xFF);
+ byte level = (byte)(item.GetModifier(ItemModifier.BattlePetLevel));
+
+ bucketInfo.MaxBattlePetQuality.Set(bucketInfo.MaxBattlePetQuality.HasValue ? Math.Max(bucketInfo.MaxBattlePetQuality.Value, quality) : quality);
+ bucketInfo.MaxBattlePetLevel.Set(bucketInfo.MaxBattlePetLevel.HasValue ? Math.Max(bucketInfo.MaxBattlePetLevel.Value, level) : level);
+ bucketInfo.BattlePetBreedID.Set((byte)breedId);
+ }
+ }
+
+ bucketInfo.ContainsOwnerItem = bucketInfo.ContainsOwnerItem || auction.Owner == player.GetGUID();
+ }
+
+ bucketInfo.ContainsOnlyCollectedAppearances = true;
+ foreach (var appearance in ItemModifiedAppearanceId)
+ {
+ if (appearance.Item1 != 0)
+ {
+ bucketInfo.ItemModifiedAppearanceIDs.Add(appearance.Item1);
+ if (!player.GetSession().GetCollectionMgr().HasItemAppearance(appearance.Item1).PermAppearance)
+ bucketInfo.ContainsOnlyCollectedAppearances = false;
+ }
+ }
+ }
+
+ public class Sorter : IComparer
+ {
+ public Sorter(LocaleConstant locale, AuctionSortDef[] sorts, int sortCount)
+ {
+ _locale = locale;
+ _sorts = sorts;
+ _sortCount = sortCount;
+ }
+
+ public int Compare(AuctionsBucketData left, AuctionsBucketData right)
+ {
+ for (var i = 0; i < _sortCount; ++i)
+ {
+ int ordering = CompareColumns(_sorts[i].SortOrder, left, right);
+ if (ordering != 0)
+ return (ordering < 0).CompareTo(!_sorts[i].ReverseSort);
+ }
+
+ return left.Key != right.Key ? 1 : 0;
+ }
+
+ int CompareColumns(AuctionHouseSortOrder column, AuctionsBucketData left, AuctionsBucketData right)
+ {
+ switch (column)
+ {
+ case AuctionHouseSortOrder.Price:
+ case AuctionHouseSortOrder.Bid:
+ case AuctionHouseSortOrder.Buyout:
+ return (int)((long)left.MinPrice - (long)right.MinPrice);
+ case AuctionHouseSortOrder.Name:
+ return left.FullName[(int)_locale].CompareTo(right.FullName[(int)_locale]);
+ case AuctionHouseSortOrder.Level:
+ {
+ int leftLevel = left.MaxBattlePetLevel == 0 ? left.RequiredLevel : left.MaxBattlePetLevel;
+ int rightLevel = right.MaxBattlePetLevel == 0 ? right.RequiredLevel : right.MaxBattlePetLevel;
+ return leftLevel - rightLevel;
+ }
+ default:
+ break;
+ }
+
+ return 0;
+ }
+
+ LocaleConstant _locale;
+ AuctionSortDef[] _sorts;
+ int _sortCount;
+ }
+ }
+
+ public class CommodityQuote
+ {
+ public ulong TotalPrice;
+ public uint Quantity;
+ public DateTime ValidTo = DateTime.MinValue;
+ }
+
+ public class AuctionThrottleResult
+ {
+ public TimeSpan DelayUntilNext;
+ public bool Throttled;
+
+ public AuctionThrottleResult(TimeSpan delayUntilNext, bool throttled)
+ {
+ DelayUntilNext = delayUntilNext;
+ Throttled = throttled;
+ }
+ }
+
+ public class AuctionsBucketKey : IComparable
+ {
+ public uint ItemId { get; set; }
+ public ushort ItemLevel { get; set; }
+ public ushort BattlePetSpeciesId { get; set; }
+ public ushort SuffixItemNameDescriptionId { get; set; }
+
+ public AuctionsBucketKey(uint itemId, ushort itemLevel, ushort battlePetSpeciesId, ushort suffixItemNameDescriptionId)
+ {
+ ItemId = itemId;
+ ItemLevel = itemLevel;
+ BattlePetSpeciesId = battlePetSpeciesId;
+ SuffixItemNameDescriptionId = suffixItemNameDescriptionId;
+ }
+
+ public AuctionsBucketKey(AuctionBucketKey key)
+ {
+ ItemId = key.ItemID;
+ ItemLevel = key.ItemLevel;
+ BattlePetSpeciesId = (ushort)(key.BattlePetSpeciesID.HasValue ? key.BattlePetSpeciesID.Value : 0);
+ SuffixItemNameDescriptionId = (ushort)(key.SuffixItemNameDescriptionID.HasValue ? key.SuffixItemNameDescriptionID.Value : 0);
+ }
+
+ public int CompareTo(AuctionsBucketKey other)
+ {
+ return ItemId.CompareTo(other.ItemId);
+ }
+
+ public static bool operator ==(AuctionsBucketKey right, AuctionsBucketKey left)
+ {
+ return right.ItemId == left.ItemId
+ && right.ItemLevel == left.ItemLevel
+ && right.BattlePetSpeciesId == left.BattlePetSpeciesId
+ && right.SuffixItemNameDescriptionId == left.SuffixItemNameDescriptionId;
+ }
+ public static bool operator !=(AuctionsBucketKey right, AuctionsBucketKey left) { return !(right == left); }
+
+ public override bool Equals(object obj)
+ {
+ return base.Equals(obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return ItemId.GetHashCode() ^ ItemLevel.GetHashCode() ^ BattlePetSpeciesId.GetHashCode() ^ SuffixItemNameDescriptionId.GetHashCode();
+ }
+
+ public static AuctionsBucketKey ForItem(Item item)
+ {
+ ItemTemplate itemTemplate = item.GetTemplate();
+ if (itemTemplate.GetMaxStackSize() == 1)
+ {
+ return new AuctionsBucketKey(item.GetEntry(), (ushort)Item.GetItemLevel(itemTemplate, item.GetBonus(), 0, (uint)item.GetRequiredLevel(), 0, 0, 0, false, 0),
+ (ushort)item.GetModifier(ItemModifier.BattlePetSpeciesId), (ushort)item.GetBonus().Suffix);
+ }
+ else
+ return ForCommodity(item.GetEntry());
+ }
+
+ public static AuctionsBucketKey ForCommodity(uint itemId)
+ {
+ return new AuctionsBucketKey(itemId, 0, 0, 0);
+ }
}
public class AuctionSearchClassFilters
{
+ public SubclassFilter[] Classes = new SubclassFilter[(int)ItemClass.Max];
+
+ public AuctionSearchClassFilters()
+ {
+ for (var i = 0; i < (int)ItemClass.Max; ++i)
+ Classes[i] = new SubclassFilter();
+ }
+
+ public class SubclassFilter
+ {
+ public FilterType SubclassMask;
+ public uint[] InvTypes = new uint[ItemConst.MaxItemSubclassTotal];
+ }
+
public enum FilterType : uint
{
SkipClass = 0,
SkipSubclass = 0xFFFFFFFF,
SkipInvtype = 0xFFFFFFFF
}
+ }
- public Array Classes = new Array((int)ItemClass.Max);
+ class AuctionsResultBuilder
+ {
+ uint _offset;
+ IComparer _sorter;
+ AuctionHouseResultLimits _maxResults;
+ List _items = new List();
+ bool _hasMoreResults;
- public class SubclassFilter
+ public AuctionsResultBuilder(uint offset, IComparer sorter, AuctionHouseResultLimits maxResults)
{
- public FilterType SubclassMask = FilterType.SkipClass;
- public Array InvTypes = new Array(ItemConst.MaxItemSubclassTotal);
+ _offset = offset;
+ _sorter = sorter;
+ _maxResults = maxResults;
+ _hasMoreResults = false;
+ }
+
+ public void AddItem(T item)
+ {
+ var index = _items.BinarySearch(item, _sorter);
+ if (index < 0)
+ index = ~index;
+ _items.Insert(index, item);
+ if (_items.Count > (int)_maxResults + _offset)
+ {
+ _items.RemoveAt(_items.Count - 1);
+ _hasMoreResults = true;
+ }
+ }
+
+ public Span GetResultRange()
+ {
+ Span h = _items.ToArray();
+ return h.Slice((int)_offset);
+ }
+
+ public bool HasMoreResults()
+ {
+ return _hasMoreResults;
}
}
}
diff --git a/Source/Game/Chat/Commands/ReloadCommand.cs b/Source/Game/Chat/Commands/ReloadCommand.cs
index 177bc29c1..b1d4dea74 100644
--- a/Source/Game/Chat/Commands/ReloadCommand.cs
+++ b/Source/Game/Chat/Commands/ReloadCommand.cs
@@ -86,8 +86,7 @@ namespace Game.Chat
{
// Reload dynamic data tables from the database
Log.outInfo(LogFilter.Server, "Re-Loading Auctions...");
- Global.AuctionMgr.LoadAuctionItems();
- Global.AuctionMgr.LoadAuctions();
+ Global.AuctionHouseMgr.LoadAuctions();
handler.SendGlobalGMSysMessage("Auctions reloaded.");
return true;
}
diff --git a/Source/Game/Entities/Player/CollectionManager.cs b/Source/Game/Entities/Player/CollectionManager.cs
index 13729829e..44e4122b2 100644
--- a/Source/Game/Entities/Player/CollectionManager.cs
+++ b/Source/Game/Entities/Player/CollectionManager.cs
@@ -760,15 +760,15 @@ namespace Game.Entities
}
}
- public Tuple HasItemAppearance(uint itemModifiedAppearanceId)
+ public (bool PermAppearance, bool TempAppearance) HasItemAppearance(uint itemModifiedAppearanceId)
{
if (itemModifiedAppearanceId < _appearances.Count && _appearances.Get((int)itemModifiedAppearanceId))
- return Tuple.Create(true, false);
+ return (true, false);
if (_temporaryAppearances.ContainsKey(itemModifiedAppearanceId))
- return Tuple.Create(true, true);
+ return (true, true);
- return Tuple.Create(false, false);
+ return (false, false);
}
public List GetItemsProvidingTemporaryAppearance(uint itemModifiedAppearanceId)
@@ -776,6 +776,15 @@ namespace Game.Entities
return _temporaryAppearances.LookupByKey(itemModifiedAppearanceId);
}
+ public List GetAppearanceIds()
+ {
+ List appearances = new List();
+ foreach (int id in _appearances)
+ appearances.Add(CliDB.ItemModifiedAppearanceStorage.LookupByKey(id).ItemAppearanceID);
+
+ return appearances;
+ }
+
public void SetAppearanceIsFavorite(uint itemModifiedAppearanceId, bool apply)
{
var apperanceState = _favoriteAppearances.LookupByKey(itemModifiedAppearanceId);
diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs
index 53c43f6f2..640d614d8 100644
--- a/Source/Game/Entities/Player/Player.DB.cs
+++ b/Source/Game/Entities/Player/Player.DB.cs
@@ -4013,6 +4013,10 @@ namespace Game.Entities
stmt.AddValue(0, guid);
trans.Append(stmt);
+ stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_FAVORITE_AUCTIONS_BY_CHAR);
+ stmt.AddValue(0, guid);
+ trans.Append(stmt);
+
Corpse.DeleteFromDB(playerGuid, trans);
Garrison.DeleteFromDB(guid, trans);
diff --git a/Source/Game/Globals/Global.cs b/Source/Game/Globals/Global.cs
index 46a2d24d9..1e7e0ffa6 100644
--- a/Source/Game/Globals/Global.cs
+++ b/Source/Game/Globals/Global.cs
@@ -105,7 +105,7 @@ public static class Global
public static GameEventManager GameEventMgr { get { return GameEventManager.Instance; } }
public static CreatureTextManager CreatureTextMgr { get { return CreatureTextManager.Instance; } }
- public static AuctionManager AuctionMgr { get { return AuctionManager.Instance; } }
+ public static AuctionManager AuctionHouseMgr { get { return AuctionManager.Instance; } }
public static SpellManager SpellMgr { get { return SpellManager.Instance; } }
public static SupportManager SupportMgr { get { return SupportManager.Instance; } }
diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs
index 2c6002a34..fff450972 100644
--- a/Source/Game/Globals/ObjectManager.cs
+++ b/Source/Game/Globals/ObjectManager.cs
@@ -9275,7 +9275,8 @@ namespace Game
// Cleanup other tables from not existed guids ( >= hiItemGuid)
DB.Characters.Execute("DELETE FROM character_inventory WHERE item >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query
DB.Characters.Execute("DELETE FROM mail_items WHERE item_guid >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query
- DB.Characters.Execute("DELETE FROM auctionhouse WHERE itemguid >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query
+ DB.Characters.Execute("DELETE a, ab, ai FROM auctionhouse a LEFT JOIN auction_bidders ab ON ab.auctionId = a.id LEFT JOIN auction_items ai ON ai.auctionId = a.id WHERE ai.itemGuid >= '{0}'",
+ GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query
DB.Characters.Execute("DELETE FROM guild_bank_item WHERE item_guid >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query
result = DB.World.Query("SELECT MAX(guid) FROM transports");
diff --git a/Source/Game/Handlers/AuctionHandler.cs b/Source/Game/Handlers/AuctionHandler.cs
index 641410419..c7cd9e598 100644
--- a/Source/Game/Handlers/AuctionHandler.cs
+++ b/Source/Game/Handlers/AuctionHandler.cs
@@ -25,578 +25,23 @@ using Game.Network;
using Game.Network.Packets;
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Game
{
public partial class WorldSession
{
- [WorldPacketHandler(ClientOpcodes.AuctionHelloRequest)]
- void HandleAuctionHello(AuctionHelloRequest packet)
- {
- Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Guid, NPCFlags.Auctioneer, NPCFlags2.None);
- if (!unit)
- {
- Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionHelloOpcode - {0} not found or you can't interact with him.", packet.Guid.ToString());
- return;
- }
-
- // remove fake death
- if (GetPlayer().HasUnitState(UnitState.Died))
- GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
-
- SendAuctionHello(packet.Guid, unit);
- }
-
- public void SendAuctionHello(ObjectGuid guid, Creature unit)
- {
- if (GetPlayer().GetLevel() < WorldConfig.GetIntValue(WorldCfg.AuctionLevelReq))
- {
- SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.AuctionReq), WorldConfig.GetIntValue(WorldCfg.AuctionLevelReq));
- return;
- }
-
- AuctionHouseRecord ahEntry = Global.AuctionMgr.GetAuctionHouseEntry(unit.GetFaction());
- if (ahEntry == null)
- return;
-
- AuctionHelloResponse packet = new AuctionHelloResponse();
- packet.Guid = guid;
- packet.OpenForBusiness = false; // 3.3.3: 1 - AH enabled, 0 - AH disabled
- SendPacket(packet);
- }
-
- public void SendAuctionCommandResult(AuctionEntry auction, AuctionAction action, AuctionError errorCode, uint bidError = 0)
- {
- AuctionCommandResult auctionCommandResult = new AuctionCommandResult();
- auctionCommandResult.InitializeAuction(auction);
- auctionCommandResult.Command = (int)action;
- auctionCommandResult.ErrorCode = (int)errorCode;
- SendPacket(auctionCommandResult);
- }
-
- public void SendAuctionOutBidNotification(AuctionEntry auction, Item item)
- {
- AuctionOutbidNotification packet = new AuctionOutbidNotification();
- packet.BidAmount = auction.bid;
- packet.MinIncrement = auction.GetAuctionOutBid();
- packet.Info.Initialize(auction, item);
- SendPacket(packet);
- }
-
- public void SendAuctionClosedNotification(AuctionEntry auction, float mailDelay, bool sold, Item item)
- {
- AuctionClosedNotification packet = new AuctionClosedNotification();
- packet.Info.Initialize(auction, item);
- packet.ProceedsMailDelay = mailDelay;
- packet.Sold = sold;
- SendPacket(packet);
- }
-
- public void SendAuctionWonNotification(AuctionEntry auction, Item item)
- {
- AuctionWonNotification packet = new AuctionWonNotification();
- packet.Info.Initialize(auction, item);
- SendPacket(packet);
- }
-
- public void SendAuctionOwnerBidNotification(AuctionEntry auction, Item item)
- {
- AuctionOwnerBidNotification packet = new AuctionOwnerBidNotification();
- packet.Info.Initialize(auction, item);
- packet.Bidder = ObjectGuid.Create(HighGuid.Player, auction.bidder);
- packet.MinIncrement = auction.GetAuctionOutBid();
- SendPacket(packet);
- }
-
- [WorldPacketHandler(ClientOpcodes.AuctionSellItem)]
- void HandleAuctionSellItem(AuctionSellItem packet)
- {
- foreach (var aitem in packet.Items)
- if (aitem.Guid.IsEmpty() || aitem.UseCount == 0 || aitem.UseCount > 1000)
- return;
-
- if (packet.MinBid == 0 || packet.RunTime == 0)
- return;
-
- if (packet.MinBid > PlayerConst.MaxMoneyAmount || packet.BuyoutPrice > PlayerConst.MaxMoneyAmount)
- {
- Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionSellItem - Player {0} ({1}) attempted to sell item with higher price than max gold amount.", GetPlayer().GetName(), GetPlayer().GetGUID().ToString());
- SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError);
- return;
- }
-
- Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
- if (!creature)
- {
- Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionSellItem - {0} not found or you can't interact with him.", packet.Auctioneer.ToString());
- return;
- }
-
- uint houseId = 0;
- AuctionHouseRecord auctionHouseEntry = Global.AuctionMgr.GetAuctionHouseEntry(creature.GetFaction(), ref houseId);
- if (auctionHouseEntry == null)
- {
- Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionSellItem - {0} has wrong faction.", packet.Auctioneer.ToString());
- return;
- }
-
- packet.RunTime *= Time.Minute;
- switch (packet.RunTime)
- {
- case 1 * SharedConst.MinAuctionTime:
- case 2 * SharedConst.MinAuctionTime:
- case 4 * SharedConst.MinAuctionTime:
- break;
- default:
- return;
- }
-
- if (GetPlayer().HasUnitState(UnitState.Died))
- GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
-
- uint finalCount = 0;
- Item[] items = new Item[packet.Items.Count];
- for (var i = 0; i < packet.Items.Count; ++i)
- {
- items[i] = GetPlayer().GetItemByGuid(packet.Items[i].Guid);
- if (!items[i])
- {
- SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.ItemNotFound);
- return;
- }
-
- if (Global.AuctionMgr.GetAItem(items[i].GetGUID().GetCounter()) || !items[i].CanBeTraded() || items[i].IsNotEmptyBag() ||
- items[i].GetTemplate().GetFlags().HasAnyFlag(ItemFlags.Conjured) || items[i].m_itemData.Expiration != 0 ||
- items[i].GetCount() < packet.Items[i].UseCount)
- {
- SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError);
- return;
- }
-
- finalCount += packet.Items[i].UseCount;
- }
-
- if (packet.Items.Empty())
- {
- SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError);
- return;
- }
-
- if (finalCount == 0)
- {
- SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError);
- return;
- }
-
- // check if there are 2 identical guids, in this case user is most likely cheating
- for (int i = 0; i < packet.Items.Count; ++i)
- {
- for (int j = i + 1; j < packet.Items.Count; ++j)
- {
- if (packet.Items[i].Guid == packet.Items[j].Guid)
- {
- SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError);
- return;
- }
- if (items[i].GetEntry() != items[j].GetEntry())
- {
- SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.ItemNotFound);
- return;
- }
- }
- }
-
- for (var i = 0; i < packet.Items.Count; ++i)
- {
- if (items[i].GetMaxStackCount() < finalCount)
- {
- SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError);
- return;
- }
- }
-
- Item item = items[0];
-
- uint auctionTime = (uint)(packet.RunTime * WorldConfig.GetFloatValue(WorldCfg.RateAuctionTime));
- AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.GetFaction());
-
- ulong deposit = Global.AuctionMgr.GetAuctionDeposit(auctionHouseEntry, packet.RunTime, item, finalCount);
- if (!GetPlayer().HasEnoughMoney(deposit))
- {
- SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.NotEnoughMoney);
- return;
- }
-
- AuctionEntry AH = new AuctionEntry();
- SQLTransaction trans;
-
- if (WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionAuction))
- AH.auctioneer = 23442; //@TODO - HARDCODED DB GUID, BAD BAD BAD
- else
- AH.auctioneer = creature.GetSpawnId();
-
- // Required stack size of auction matches to current item stack size, just move item to auctionhouse
- if (packet.Items.Count == 1 && item.GetCount() == packet.Items[0].UseCount)
- {
- if (HasPermission(RBACPermissions.LogGmTrade))
- {
- Log.outCommand(GetAccountId(), "GM {0} (Account: {1}) create auction: {2} (Entry: {3} Count: {4})",
- GetPlayerName(), GetAccountId(), item.GetTemplate().GetName(), item.GetEntry(), item.GetCount());
- }
-
- AH.Id = Global.ObjectMgr.GenerateAuctionID();
- AH.itemGUIDLow = item.GetGUID().GetCounter();
- AH.itemEntry = item.GetEntry();
- AH.itemCount = item.GetCount();
- AH.owner = GetPlayer().GetGUID().GetCounter();
- AH.startbid = (uint)packet.MinBid;
- AH.bidder = 0;
- AH.bid = 0;
- AH.buyout = (uint)packet.BuyoutPrice;
- AH.expire_time = Time.UnixTime + auctionTime;
- AH.deposit = deposit;
- AH.etime = packet.RunTime;
- AH.auctionHouseEntry = auctionHouseEntry;
-
- Log.outInfo(LogFilter.Network, "CMSG_AUCTION_SELL_ITEM: {0} {1} is selling item {2} {3} to auctioneer {4} with count {5} with initial bid {6} with buyout {7} and with time {8} (in sec) in auctionhouse {9}",
- GetPlayer().GetGUID().ToString(), GetPlayer().GetName(), item.GetGUID().ToString(), item.GetTemplate().GetName(), AH.auctioneer, item.GetCount(), packet.MinBid, packet.BuyoutPrice, auctionTime, AH.GetHouseId());
- Global.AuctionMgr.AddAItem(item);
- auctionHouse.AddAuction(AH);
-
- GetPlayer().MoveItemFromInventory(item.GetBagSlot(), item.GetSlot(), true);
-
- trans = new SQLTransaction();
- item.DeleteFromInventoryDB(trans);
- item.SaveToDB(trans);
- AH.SaveToDB(trans);
- GetPlayer().SaveInventoryAndGoldToDB(trans);
- DB.Characters.CommitTransaction(trans);
-
- SendAuctionCommandResult(AH, AuctionAction.SellItem, AuctionError.Ok);
-
- GetPlayer().UpdateCriteria(CriteriaTypes.CreateAuction, 1);
- }
- else // Required stack size of auction does not match to current item stack size, clone item and set correct stack size
- {
- Item newItem = item.CloneItem(finalCount, GetPlayer());
- if (!newItem)
- {
- Log.outError(LogFilter.Network, "CMSG_AuctionAction.SellItem: Could not create clone of item {0}", item.GetEntry());
- SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError);
- return;
- }
-
- if (HasPermission(RBACPermissions.LogGmTrade))
- {
- Log.outCommand(GetAccountId(), "GM {0} (Account: {1}) create auction: {2} (Entry: {3} Count: {4})",
- GetPlayerName(), GetAccountId(), newItem.GetTemplate().GetName(), newItem.GetEntry(), newItem.GetCount());
- }
-
- AH.Id = Global.ObjectMgr.GenerateAuctionID();
- AH.itemGUIDLow = newItem.GetGUID().GetCounter();
- AH.itemEntry = newItem.GetEntry();
- AH.itemCount = newItem.GetCount();
- AH.owner = GetPlayer().GetGUID().GetCounter();
- AH.startbid = (uint)packet.MinBid;
- AH.bidder = 0;
- AH.bid = 0;
- AH.buyout = (uint)packet.BuyoutPrice;
- AH.expire_time = Time.UnixTime + auctionTime;
- AH.deposit = deposit;
- AH.etime = packet.RunTime;
- AH.auctionHouseEntry = auctionHouseEntry;
-
- Log.outInfo(LogFilter.Network, "CMSG_AuctionAction.SellItem: {0} {1} is selling {2} {3} to auctioneer {4} with count {5} with initial bid {6} with buyout {7} and with time {8} (in sec) in auctionhouse {9}",
- GetPlayer().GetGUID().ToString(), GetPlayer().GetName(), newItem.GetGUID().ToString(), newItem.GetTemplate().GetName(), AH.auctioneer, newItem.GetCount(), packet.MinBid, packet.BuyoutPrice, auctionTime, AH.GetHouseId());
- Global.AuctionMgr.AddAItem(newItem);
- auctionHouse.AddAuction(AH);
-
- for (var i = 0; i < packet.Items.Count; ++i)
- {
- Item item2 = items[i];
-
- // Item stack count equals required count, ready to delete item - cloned item will be used for auction
- if (item2.GetCount() == packet.Items[i].UseCount)
- {
- GetPlayer().MoveItemFromInventory(item2.GetBagSlot(), item2.GetSlot(), true);
-
- trans = new SQLTransaction();
- item2.DeleteFromInventoryDB(trans);
- item2.DeleteFromDB(trans);
- DB.Characters.CommitTransaction(trans);
- }
- else // Item stack count is bigger than required count, update item stack count and save to database - cloned item will be used for auction
- {
- item2.SetCount(item2.GetCount() - packet.Items[i].UseCount);
- item2.SetState(ItemUpdateState.Changed, GetPlayer());
- GetPlayer().ItemRemovedQuestCheck(item2.GetEntry(), packet.Items[i].UseCount);
- item2.SendUpdateToPlayer(GetPlayer());
-
- trans = new SQLTransaction();
- item2.SaveToDB(trans);
- DB.Characters.CommitTransaction(trans);
- }
- }
-
- trans = new SQLTransaction();
- newItem.SaveToDB(trans);
- AH.SaveToDB(trans);
- GetPlayer().SaveInventoryAndGoldToDB(trans);
- DB.Characters.CommitTransaction(trans);
-
- SendAuctionCommandResult(AH, AuctionAction.SellItem, AuctionError.Ok);
-
- GetPlayer().UpdateCriteria(CriteriaTypes.CreateAuction, 1);
- }
-
- GetPlayer().ModifyMoney(-(long)deposit);
- }
-
- [WorldPacketHandler(ClientOpcodes.AuctionPlaceBid)]
- void HandleAuctionPlaceBid(AuctionPlaceBid packet)
- {
- if (packet.AuctionID == 0 || packet.BidAmount == 0)
- return; // check for cheaters
-
- Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
- if (!creature)
- {
- Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionPlaceBid - {0} not found or you can't interact with him.", packet.Auctioneer.ToString());
- return;
- }
-
- // remove fake death
- if (GetPlayer().HasUnitState(UnitState.Died))
- GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
-
- AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.GetFaction());
-
- AuctionEntry auction = auctionHouse.GetAuction(packet.AuctionID);
- Player player = GetPlayer();
-
- if (auction == null || auction.owner == player.GetGUID().GetCounter())
- {
- //you cannot bid your own auction:
- SendAuctionCommandResult(null, AuctionAction.PlaceBid, AuctionError.BidOwn);
- return;
- }
-
- // impossible have online own another character (use this for speedup check in case online owner)
- ObjectGuid ownerGuid = ObjectGuid.Create(HighGuid.Player, auction.owner);
- Player auction_owner = Global.ObjAccessor.FindPlayer(ownerGuid);
- if (!auction_owner && Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(ownerGuid) == player.GetSession().GetAccountId())
- {
- //you cannot bid your another character auction:
- SendAuctionCommandResult(null, AuctionAction.PlaceBid, AuctionError.BidOwn);
- return;
- }
-
- // cheating
- if (packet.BidAmount <= auction.bid || packet.BidAmount < auction.startbid)
- return;
-
- // price too low for next bid if not buyout
- if ((packet.BidAmount < auction.buyout || auction.buyout == 0) && packet.BidAmount < auction.bid + auction.GetAuctionOutBid())
- {
- // client already test it but just in case ...
- SendAuctionCommandResult(auction, AuctionAction.PlaceBid, AuctionError.HigherBid);
- return;
- }
-
- if (!player.HasEnoughMoney(packet.BidAmount))
- {
- // client already test it but just in case ...
- SendAuctionCommandResult(auction, AuctionAction.PlaceBid, AuctionError.NotEnoughMoney);
- return;
- }
-
- SQLTransaction trans = new SQLTransaction();
- if (packet.BidAmount < auction.buyout || auction.buyout == 0)
- {
- if (auction.bidder > 0)
- {
- if (auction.bidder == player.GetGUID().GetCounter())
- player.ModifyMoney(-(long)(packet.BidAmount - auction.bid));
- else
- {
- // mail to last bidder and return money
- Global.AuctionMgr.SendAuctionOutbiddedMail(auction, packet.BidAmount, GetPlayer(), trans);
- player.ModifyMoney(-(long)packet.BidAmount);
- }
- }
- else
- player.ModifyMoney(-(long)packet.BidAmount);
-
- auction.bidder = player.GetGUID().GetCounter();
- auction.bid = (uint)packet.BidAmount;
- GetPlayer().UpdateCriteria(CriteriaTypes.HighestAuctionBid, packet.BidAmount);
-
- PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_AUCTION_BID);
- stmt.AddValue(0, auction.bidder);
- stmt.AddValue(1, auction.bid);
- stmt.AddValue(2, auction.Id);
- trans.Append(stmt);
-
- SendAuctionCommandResult(auction, AuctionAction.PlaceBid, AuctionError.Ok);
-
- // Not sure if we must send this now.
- Player owner = Global.ObjAccessor.FindConnectedPlayer(ObjectGuid.Create(HighGuid.Player, auction.owner));
- Item item = Global.AuctionMgr.GetAItem(auction.itemGUIDLow);
- if (owner && item)
- owner.GetSession().SendAuctionOwnerBidNotification(auction, item);
- }
- else
- {
- //buyout:
- if (player.GetGUID().GetCounter() == auction.bidder)
- player.ModifyMoney(-(long)(auction.buyout - auction.bid));
- else
- {
- player.ModifyMoney(-(long)auction.buyout);
- if (auction.bidder != 0) //buyout for bidded auction ..
- Global.AuctionMgr.SendAuctionOutbiddedMail(auction, auction.buyout, GetPlayer(), trans);
- }
- auction.bidder = player.GetGUID().GetCounter();
- auction.bid = auction.buyout;
- GetPlayer().UpdateCriteria(CriteriaTypes.HighestAuctionBid, auction.buyout);
-
- SendAuctionCommandResult(auction, AuctionAction.PlaceBid, AuctionError.Ok);
-
- //- Mails must be under transaction control too to prevent data loss
- Global.AuctionMgr.SendAuctionSalePendingMail(auction, trans);
- Global.AuctionMgr.SendAuctionSuccessfulMail(auction, trans);
- Global.AuctionMgr.SendAuctionWonMail(auction, trans);
-
- auction.DeleteFromDB(trans);
-
- Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow);
- auctionHouse.RemoveAuction(auction);
- }
-
- player.SaveInventoryAndGoldToDB(trans);
- DB.Characters.CommitTransaction(trans);
- }
-
- [WorldPacketHandler(ClientOpcodes.AuctionRemoveItem)]
- void HandleAuctionRemoveItem(AuctionRemoveItem packet)
- {
- Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
- if (!creature)
- {
- Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionRemoveItem - {0} not found or you can't interact with him.", packet.Auctioneer.ToString());
- return;
- }
-
- // remove fake death
- if (GetPlayer().HasUnitState(UnitState.Died))
- GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
-
- AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.GetFaction());
-
- AuctionEntry auction = auctionHouse.GetAuction((uint)packet.AuctionID);
- Player player = GetPlayer();
-
- SQLTransaction trans = new SQLTransaction();
- if (auction != null && auction.owner == player.GetGUID().GetCounter())
- {
- Item pItem = Global.AuctionMgr.GetAItem(auction.itemGUIDLow);
- if (pItem)
- {
- if (auction.bidder > 0) // If we have a bidder, we have to send him the money he paid
- {
- ulong auctionCut = auction.GetAuctionCut();
- if (!player.HasEnoughMoney(auctionCut)) //player doesn't have enough money, maybe message needed
- return;
- Global.AuctionMgr.SendAuctionCancelledToBidderMail(auction, trans);
- player.ModifyMoney(-(long)auctionCut);
- }
-
- // item will deleted or added to received mail list
- new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.Canceled), AuctionEntry.BuildAuctionMailBody(0, 0, auction.buyout, auction.deposit, 0))
- .AddItem(pItem)
- .SendMailTo(trans, new MailReceiver(player), new MailSender(auction), MailCheckMask.Copied);
- }
- else
- {
- Log.outError(LogFilter.Network, "Auction id: {0} got non existing item (item guid : {1})!", auction.Id, auction.itemGUIDLow);
- SendAuctionCommandResult(null, AuctionAction.Cancel, AuctionError.DatabaseError);
- return;
- }
- }
- else
- {
- SendAuctionCommandResult(null, AuctionAction.Cancel, AuctionError.DatabaseError);
- //this code isn't possible ... maybe there should be assert
- Log.outError(LogFilter.Network, "CHEATER: {0} tried to cancel auction (id: {1}) of another player or auction is null", player.GetGUID().ToString(), packet.AuctionID);
- return;
- }
-
- //inform player, that auction is removed
- SendAuctionCommandResult(auction, AuctionAction.Cancel, AuctionError.Ok);
-
- // Now remove the auction
- player.SaveInventoryAndGoldToDB(trans);
- auction.DeleteFromDB(trans);
- DB.Characters.CommitTransaction(trans);
-
- Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow);
- auctionHouse.RemoveAuction(auction);
- }
-
- [WorldPacketHandler(ClientOpcodes.AuctionListBidderItems)]
- void HandleAuctionListBidderItems(AuctionListBidderItems packet)
- {
- Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
- if (!creature)
- {
- Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionListBidderItems - {0} not found or you can't interact with him.", packet.Auctioneer.ToString());
- return;
- }
-
- // remove fake death
- if (GetPlayer().HasUnitState(UnitState.Died))
- GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
-
- AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.GetFaction());
-
- AuctionListBidderItemsResult result = new AuctionListBidderItemsResult();
-
- Player player = GetPlayer();
- auctionHouse.BuildListBidderItems(result, player);
- result.DesiredDelay = 300;
- SendPacket(result);
- }
-
- [WorldPacketHandler(ClientOpcodes.AuctionListOwnerItems)]
- void HandleAuctionListOwnerItems(AuctionListOwnerItems packet)
- {
- Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
- if (!creature)
- {
- Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionListOwnerItems - {0} not found or you can't interact with him.", packet.Auctioneer.ToString());
- return;
- }
-
- // remove fake death
- if (GetPlayer().HasUnitState(UnitState.Died))
- GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
-
- AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.GetFaction());
-
- AuctionListOwnerItemsResult result = new AuctionListOwnerItemsResult();
-
- auctionHouse.BuildListOwnerItems(result, GetPlayer());
- result.DesiredDelay = 300;
- SendPacket(result);
- }
-
[WorldPacketHandler(ClientOpcodes.AuctionBrowseQuery)]
- void HandleAuctionListItems(AuctionBrowseQuery browseQuery)
+ void HandleAuctionBrowseQuery(AuctionBrowseQuery browseQuery)
{
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
+
Creature creature = GetPlayer().GetNPCIfCanInteractWith(browseQuery.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
- if (!creature)
+ if (creature == null)
{
- Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionListItems - {0} not found or you can't interact with him.", browseQuery.Auctioneer.ToString());
+ Log.outError(LogFilter.Network, $"WORLD: HandleAuctionListItems - {browseQuery.Auctioneer.ToString()} not found or you can't interact with him.");
return;
}
@@ -604,11 +49,13 @@ namespace Game
if (GetPlayer().HasUnitState(UnitState.Died))
GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
- AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.GetFaction());
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ Log.outDebug(LogFilter.Auctionhouse, $"Auctionhouse search ({browseQuery.Auctioneer.ToString()}), searchedname: {browseQuery.Name}, levelmin: {browseQuery.MinLevel}, levelmax: {browseQuery.MaxLevel}, filters: {browseQuery.Filters}");
Optional classFilters = new Optional();
- AuctionListItemsResult result = new AuctionListItemsResult();
+ AuctionListBucketsResult listBucketsResult = new AuctionListBucketsResult();
if (!browseQuery.ItemClassFilters.Empty())
{
classFilters.HasValue = true;
@@ -619,9 +66,12 @@ namespace Game
{
foreach (var subClassFilter in classFilter.SubClassFilters)
{
- classFilters.Value.Classes[classFilter.ItemClass].SubclassMask |= (AuctionSearchClassFilters.FilterType)(1 << subClassFilter.ItemSubclass);
- if (subClassFilter.ItemSubclass < ItemConst.MaxItemSubclassTotal)
- classFilters.Value.Classes[classFilter.ItemClass].InvTypes[subClassFilter.ItemSubclass] = subClassFilter.InvTypeMask;
+ if (classFilter.ItemClass < (int)ItemClass.Max)
+ {
+ classFilters.Value.Classes[classFilter.ItemClass].SubclassMask |= (AuctionSearchClassFilters.FilterType)(1 << subClassFilter.ItemSubclass);
+ if (subClassFilter.ItemSubclass < ItemConst.MaxItemSubclassTotal)
+ classFilters.Value.Classes[classFilter.ItemClass].InvTypes[subClassFilter.ItemSubclass] = subClassFilter.InvTypeMask;
+ }
}
}
else
@@ -629,40 +79,932 @@ namespace Game
}
}
- auctionHouse.BuildListAuctionItems(result, GetPlayer(), browseQuery.Name.ToLower(), browseQuery.Offset, browseQuery.MinLevel, browseQuery.MaxLevel, browseQuery.Filters, classFilters);
+ auctionHouse.BuildListBuckets(listBucketsResult, _player, browseQuery.Name, browseQuery.MinLevel, browseQuery.MaxLevel, browseQuery.Filters, classFilters,
+ browseQuery.KnownPets, browseQuery.KnownPets.Count, (byte)browseQuery.MaxPetLevel, browseQuery.Offset, browseQuery.Sorts, browseQuery.Sorts.Count);
- result.DesiredDelay = WorldConfig.GetUIntValue(WorldCfg.AuctionSearchDelay);
- SendPacket(result);
+ listBucketsResult.BrowseMode = AuctionHouseBrowseMode.Search;
+ listBucketsResult.DesiredDelay = (uint)throttle.DelayUntilNext.TotalSeconds;
+ SendPacket(listBucketsResult);
}
- [WorldPacketHandler(ClientOpcodes.AuctionReplicateItems)]
- void HandleReplicateItems(AuctionReplicateItems packet)
+ [WorldPacketHandler(ClientOpcodes.AuctionCancelCommoditiesPurchase)]
+ void HandleAuctionCancelCommoditiesPurchase(AuctionCancelCommoditiesPurchase cancelCommoditiesPurchase)
{
- /* Creature* creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER);
- if (!creature)
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player, AuctionCommand.PlaceBid);
+ if (throttle.Throttled)
+ return;
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(cancelCommoditiesPurchase.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (creature == null)
{
- TC_LOG_DEBUG("network", "WORLD: HandleReplicateItems - {0} not found or you can't interact with him.", packet.Auctioneer.ToString().c_str());
+ Log.outError(LogFilter.Network, $"WORLD: HandleAuctionListItems - {cancelCommoditiesPurchase.Auctioneer.ToString()} not found or you can't interact with him.");
return;
}
// remove fake death
- if (GetPlayer().HasUnitState(UNIT_STATE_DIED))
- GetPlayer().RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
- AuctionHouseObject* auctionHouse = sAuctionMgr.GetAuctionsMap(creature.getFaction());
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+ auctionHouse.CancelCommodityQuote(_player.GetGUID());
+ }
- WorldPackets::AuctionHouse::AuctionReplicateResponse response;
+ [WorldPacketHandler(ClientOpcodes.AuctionConfirmCommoditiesPurchase)]
+ void HandleAuctionConfirmCommoditiesPurchase(AuctionConfirmCommoditiesPurchase confirmCommoditiesPurchase)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player, AuctionCommand.PlaceBid);
+ if (throttle.Throttled)
+ return;
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(confirmCommoditiesPurchase.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (creature == null)
+ {
+ Log.outError(LogFilter.Network, $"WORLD: HandleAuctionListItems - {confirmCommoditiesPurchase.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ SQLTransaction trans = new SQLTransaction();
+ if (auctionHouse.BuyCommodity(trans, _player, (uint)confirmCommoditiesPurchase.ItemID, confirmCommoditiesPurchase.Quantity, throttle.DelayUntilNext))
+ {
+ AddTransactionCallback(DB.Characters.AsyncCommitTransaction(trans)).AfterComplete(success =>
+ {
+ if (GetPlayer() && GetPlayer().GetGUID() == _player.GetGUID())
+ {
+ if (success)
+ {
+ GetPlayer().UpdateCriteria(CriteriaTypes.WonAuctions, 1);
+ SendAuctionCommandResult(0, AuctionCommand.PlaceBid, AuctionResult.Ok, throttle.DelayUntilNext);
+ }
+ else
+ SendAuctionCommandResult(0, AuctionCommand.PlaceBid, AuctionResult.CommodityPurchaseFailed, throttle.DelayUntilNext);
+ }
+ });
+ }
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionHelloRequest)]
+ void HandleAuctionHello(AuctionHelloRequest hello)
+ {
+ Creature unit = GetPlayer().GetNPCIfCanInteractWith(hello.Guid, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (!unit)
+ {
+ Log.outDebug(LogFilter.Network, $"WORLD: HandleAuctionHelloOpcode - {hello.Guid.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ SendAuctionHello(hello.Guid, unit);
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionListBidderItems)]
+ void HandleAuctionListBidderItems(AuctionListBidderItems listBidderItems)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(listBidderItems.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (!creature)
+ {
+ Log.outDebug(LogFilter.Network, $"WORLD: HandleAuctionListBidderItems - {listBidderItems.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ AuctionListBidderItemsResult result = new AuctionListBidderItemsResult();
+
+ Player player = GetPlayer();
+ auctionHouse.BuildListBidderItems(result, player, listBidderItems.Offset, listBidderItems.Sorts, listBidderItems.Sorts.Count);
+ result.DesiredDelay = (uint)throttle.DelayUntilNext.TotalSeconds;
+ SendPacket(result);
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionListBucketsByBucketKeys)]
+ void HandleAuctionListBucketsByBucketKeys(AuctionListBucketsByBucketKeys listBucketsByBucketKeys)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(listBucketsByBucketKeys.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (creature == null)
+ {
+ Log.outError(LogFilter.Network, $"WORLD: HandleAuctionListItems - {listBucketsByBucketKeys.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ AuctionListBucketsResult listBucketsResult = new AuctionListBucketsResult();
+
+ auctionHouse.BuildListBuckets(listBucketsResult, _player,
+ listBucketsByBucketKeys.BucketKeys, listBucketsByBucketKeys.BucketKeys.Count,
+ listBucketsByBucketKeys.Sorts, listBucketsByBucketKeys.Sorts.Count);
+
+ listBucketsResult.BrowseMode = AuctionHouseBrowseMode.SpecificKeys;
+ listBucketsResult.DesiredDelay = (uint)throttle.DelayUntilNext.TotalSeconds;
+ SendPacket(listBucketsResult);
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionListItemsByBucketKey)]
+ void HandleAuctionListItemsByBucketKey(AuctionListItemsByBucketKey listItemsByBucketKey)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(listItemsByBucketKey.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (creature == null)
+ {
+ Log.outError(LogFilter.Network, $"WORLD: HandleAuctionListItemsByBucketKey - {listItemsByBucketKey.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ AuctionListItemsResult listItemsResult = new AuctionListItemsResult();
+ listItemsResult.DesiredDelay = (uint)throttle.DelayUntilNext.TotalSeconds;
+ listItemsResult.BucketKey = listItemsByBucketKey.BucketKey;
+ ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(listItemsByBucketKey.BucketKey.ItemID);
+ listItemsResult.ListType = itemTemplate != null && itemTemplate.GetMaxStackSize() > 1 ? AuctionHouseListType.Commodities : AuctionHouseListType.Items;
+
+ auctionHouse.BuildListAuctionItems(listItemsResult, _player, new AuctionsBucketKey(listItemsByBucketKey.BucketKey), listItemsByBucketKey.Offset,
+ listItemsByBucketKey.Sorts, listItemsByBucketKey.Sorts.Count);
+
+ SendPacket(listItemsResult);
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionListItemsByItemId)]
+ void HandleAuctionListItemsByItemID(AuctionListItemsByItemID listItemsByItemID)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(listItemsByItemID.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (creature == null)
+ {
+ Log.outError(LogFilter.Network, $"WORLD: HandleAuctionListItemsByItemID - {listItemsByItemID.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ AuctionListItemsResult listItemsResult = new AuctionListItemsResult();
+ listItemsResult.DesiredDelay = (uint)throttle.DelayUntilNext.TotalSeconds;
+ listItemsResult.BucketKey.ItemID = listItemsByItemID.ItemID;
+ ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(listItemsByItemID.ItemID);
+ listItemsResult.ListType = itemTemplate != null && itemTemplate.GetMaxStackSize() > 1 ? AuctionHouseListType.Commodities : AuctionHouseListType.Items;
+
+ auctionHouse.BuildListAuctionItems(listItemsResult, _player, listItemsByItemID.ItemID, listItemsByItemID.Offset,
+ listItemsByItemID.Sorts, listItemsByItemID.Sorts.Count);
+
+ SendPacket(listItemsResult);
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionListOwnerItems)]
+ void HandleAuctionListOwnerItems(AuctionListOwnerItems listOwnerItems)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(listOwnerItems.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (!creature)
+ {
+ Log.outDebug(LogFilter.Network, $"WORLD: HandleAuctionListOwnerItems - {listOwnerItems.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ AuctionListOwnerItemsResult result = new AuctionListOwnerItemsResult();
+
+ auctionHouse.BuildListOwnerItems(result, _player, listOwnerItems.Offset, listOwnerItems.Sorts, listOwnerItems.Sorts.Count);
+ result.DesiredDelay = (uint)throttle.DelayUntilNext.TotalSeconds;
+ SendPacket(result);
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionPlaceBid)]
+ void HandleAuctionPlaceBid(AuctionPlaceBid placeBid)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player, AuctionCommand.PlaceBid);
+ if (throttle.Throttled)
+ return;
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(placeBid.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (!creature)
+ {
+ Log.outDebug(LogFilter.Network, $"WORLD: HandleAuctionPlaceBid - {placeBid.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // auction house does not deal with copper
+ if ((placeBid.BidAmount % MoneyConstants.Silver) != 0)
+ {
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand.PlaceBid, AuctionResult.BidIncrement, throttle.DelayUntilNext);
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ AuctionPosting auction = auctionHouse.GetAuction(placeBid.AuctionID);
+ if (auction == null || auction.IsCommodity())
+ {
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand.PlaceBid, AuctionResult.ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ Player player = GetPlayer();
+
+ // check auction owner - cannot buy own auctions
+ if (auction.Owner == player.GetGUID() || auction.OwnerAccount == GetAccountGUID())
+ {
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand.PlaceBid, AuctionResult.BidOwn, throttle.DelayUntilNext);
+ return;
+ }
+
+ bool canBid = auction.MinBid != 0;
+ bool canBuyout = auction.BuyoutOrUnitPrice != 0;
+
+ // buyout attempt with wrong amount
+ if (!canBid && placeBid.BidAmount != auction.BuyoutOrUnitPrice)
+ {
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand.PlaceBid, AuctionResult.BidIncrement, throttle.DelayUntilNext);
+ return;
+ }
+
+ ulong minBid = auction.BidAmount != 0 ? auction.BidAmount + auction.CalculateMinIncrement() : auction.MinBid;
+ if (canBid && placeBid.BidAmount < minBid)
+ {
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand.PlaceBid, AuctionResult.HigherBid, throttle.DelayUntilNext);
+ return;
+ }
+
+ SQLTransaction trans = new SQLTransaction();
+ ulong priceToPay = placeBid.BidAmount;
+ if (!auction.Bidder.IsEmpty())
+ {
+ // return money to previous bidder
+ if (auction.Bidder != player.GetGUID())
+ auctionHouse.SendAuctionOutbid(auction, player.GetGUID(), placeBid.BidAmount, trans);
+ else
+ priceToPay = placeBid.BidAmount - auction.BidAmount;
+ }
+
+ // check money
+ if (!player.HasEnoughMoney(priceToPay))
+ {
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand.PlaceBid, AuctionResult.NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
+
+ player.ModifyMoney(-(long)priceToPay);
+ auction.Bidder = player.GetGUID();
+ auction.BidAmount = placeBid.BidAmount;
+
+ if (canBuyout && placeBid.BidAmount == auction.BuyoutOrUnitPrice)
+ {
+ // buyout
+ auctionHouse.SendAuctionWon(auction, player, trans);
+ auctionHouse.SendAuctionSold(auction, null, trans);
+
+ auctionHouse.RemoveAuction(trans, auction);
+ }
+ else
+ {
+ // place bid
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_AUCTION_BID);
+ stmt.AddValue(0, auction.Bidder.GetCounter());
+ stmt.AddValue(1, auction.BidAmount);
+ stmt.AddValue(2, auction.Id);
+ trans.Append(stmt);
+
+ auction.BidderHistory.Add(player.GetGUID());
+ if (auction.BidderHistory.Contains(player.GetGUID()))
+ {
+ stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_AUCTION_BIDDER);
+ stmt.AddValue(0, auction.Id);
+ stmt.AddValue(1, player.GetGUID().GetCounter());
+ trans.Append(stmt);
+ }
+
+ // Not sure if we must send this now.
+ Player owner = Global.ObjAccessor.FindConnectedPlayer(auction.Owner);
+ if (owner != null)
+ owner.GetSession().SendAuctionOwnerBidNotification(auction);
+ }
+
+ player.SaveInventoryAndGoldToDB(trans);
+ AddTransactionCallback(DB.Characters.AsyncCommitTransaction(trans)).AfterComplete(success =>
+ {
+ if (GetPlayer() && GetPlayer().GetGUID() == _player.GetGUID())
+ {
+ if (success)
+ {
+ GetPlayer().UpdateCriteria(CriteriaTypes.HighestAuctionBid, placeBid.BidAmount);
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand.PlaceBid, AuctionResult.Ok, throttle.DelayUntilNext);
+ }
+ else
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand.PlaceBid, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ }
+ });
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionRemoveItem)]
+ void HandleAuctionRemoveItem(AuctionRemoveItem removeItem)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player, AuctionCommand.Cancel);
+ if (throttle.Throttled)
+ return;
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(removeItem.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (!creature)
+ {
+ Log.outDebug(LogFilter.Network, $"WORLD: HandleAuctionRemoveItem - {removeItem.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ AuctionPosting auction = auctionHouse.GetAuction(removeItem.AuctionID);
+ Player player = GetPlayer();
+
+ SQLTransaction trans = new SQLTransaction();
+ if (auction != null && auction.Owner == player.GetGUID())
+ {
+ if (auction.Bidder.IsEmpty()) // If we have a bidder, we have to send him the money he paid
+ {
+ ulong cancelCost = MathFunctions.CalculatePct(auction.BidAmount, 5u);
+ if (!player.HasEnoughMoney(cancelCost)) //player doesn't have enough money
+ {
+ SendAuctionCommandResult(0, AuctionCommand.Cancel, AuctionResult.NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
+ auctionHouse.SendAuctionCancelledToBidder(auction, trans);
+ player.ModifyMoney(-(long)cancelCost);
+ }
+
+ auctionHouse.SendAuctionRemoved(auction, player, trans);
+ }
+ else
+ {
+ SendAuctionCommandResult(0, AuctionCommand.Cancel, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ //this code isn't possible ... maybe there should be assert
+ Log.outError(LogFilter.Network, $"CHEATER: {player.GetGUID().ToString()} tried to cancel auction (id: {removeItem.AuctionID}) of another player or auction is null");
+ return;
+ }
+
+ // client bug - instead of removing auction in the UI, it only substracts 1 from visible count
+ uint auctionIdForClient = auction.IsCommodity() ? 0 : auction.Id;
+
+ // Now remove the auction
+ player.SaveInventoryAndGoldToDB(trans);
+ auctionHouse.RemoveAuction(trans, auction);
+ AddTransactionCallback(DB.Characters.AsyncCommitTransaction(trans)).AfterComplete(success =>
+ {
+ if (GetPlayer() && GetPlayer().GetGUID() == _player.GetGUID())
+ {
+ if (success)
+ SendAuctionCommandResult(auctionIdForClient, AuctionCommand.Cancel, AuctionResult.Ok, throttle.DelayUntilNext); //inform player, that auction is removed
+ else
+ SendAuctionCommandResult(0, AuctionCommand.Cancel, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ }
+ });
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionReplicateItems)]
+ void HandleReplicateItems(AuctionReplicateItems replicateItems)
+ {
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(replicateItems.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (!creature)
+ {
+ Log.outError(LogFilter.Network, $"WORLD: HandleReplicateItems - {replicateItems.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
- auctionHouse.BuildReplicate(response, GetPlayer(), packet.ChangeNumberGlobal, packet.ChangeNumberCursor, packet.ChangeNumberTombstone, packet.Count);
- */
- //@todo implement this properly
AuctionReplicateResponse response = new AuctionReplicateResponse();
- response.ChangeNumberCursor = packet.ChangeNumberCursor;
- response.ChangeNumberGlobal = packet.ChangeNumberGlobal;
- response.ChangeNumberTombstone = packet.ChangeNumberTombstone;
+
+ auctionHouse.BuildReplicate(response, GetPlayer(), replicateItems.ChangeNumberGlobal, replicateItems.ChangeNumberCursor, replicateItems.ChangeNumberTombstone, replicateItems.Count);
+
response.DesiredDelay = WorldConfig.GetUIntValue(WorldCfg.AuctionSearchDelay) * 5;
response.Result = 0;
+
SendPacket(response);
}
+
+ [WorldPacketHandler(ClientOpcodes.AuctionSellCommodity)]
+ void HandleAuctionSellCommodity(AuctionSellCommodity sellCommodity)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player, AuctionCommand.SellItem);
+ if (throttle.Throttled)
+ return;
+
+ if (sellCommodity.UnitPrice == 0 || sellCommodity.UnitPrice > PlayerConst.MaxMoneyAmount)
+ {
+ Log.outError(LogFilter.Network, $"WORLD: HandleAuctionSellItem - Player {_player.GetName()} ({_player.GetGUID().ToString()}) attempted to sell item with invalid price.");
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ // auction house does not deal with copper
+ if ((sellCommodity.UnitPrice % MoneyConstants.Silver) != 0)
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(sellCommodity.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (creature == null)
+ {
+ Log.outError(LogFilter.Network, $"WORLD: HandleAuctionListItems - {sellCommodity.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ uint houseId = 0;
+ AuctionHouseRecord auctionHouseEntry = Global.AuctionHouseMgr.GetAuctionHouseEntry(creature.GetFaction(), ref houseId);
+ if (auctionHouseEntry == null)
+ {
+ Log.outError(LogFilter.Network, $"WORLD: HandleAuctionSellItem - Unit ({sellCommodity.Auctioneer.ToString()}) has wrong faction.");
+ return;
+ }
+
+ switch (sellCommodity.RunTime)
+ {
+ case 1 * SharedConst.MinAuctionTime / Time.Minute:
+ case 2 * SharedConst.MinAuctionTime / Time.Minute:
+ case 4 * SharedConst.MinAuctionTime / Time.Minute:
+ break;
+ default:
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.AuctionHouseBusy, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ // find all items for sale
+ ulong totalCount = 0;
+ Dictionary items2 = new Dictionary();
+
+ foreach (var itemForSale in sellCommodity.Items)
+ {
+ Item item = _player.GetItemByGuid(itemForSale.Guid);
+ if (!item)
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (item.GetTemplate().GetMaxStackSize() == 1)
+ {
+ // not commodity, must use different packet
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ // verify that all items belong to the same bucket
+ if (!items2.Empty() && AuctionsBucketKey.ForItem(item) != AuctionsBucketKey.ForItem(items2.FirstOrDefault().Value.Item1))
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (Global.AuctionHouseMgr.GetAItem(item.GetGUID()) || !item.CanBeTraded() || item.IsNotEmptyBag() ||
+ item.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.Conjured) || item.m_itemData.Expiration != 0 ||
+ item.GetCount() < itemForSale.UseCount)
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ var soldItem = items2.LookupByKey(item.GetGUID());
+ soldItem.Item = item;
+ soldItem.UseCount += itemForSale.UseCount;
+ items2[item.GetGUID()] = soldItem;
+ if (item.GetCount() < soldItem.UseCount)
+ {
+ // check that we have enough of this item to sell
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ totalCount += itemForSale.UseCount;
+ }
+
+ if (totalCount == 0)
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ TimeSpan auctionTime = TimeSpan.FromSeconds((long)TimeSpan.FromMinutes(sellCommodity.RunTime).TotalSeconds * WorldConfig.GetFloatValue(WorldCfg.RateAuctionTime));
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ ulong deposit = Global.AuctionHouseMgr.GetCommodityAuctionDeposit(items2.FirstOrDefault().Value.Item.GetTemplate(), TimeSpan.FromMinutes(sellCommodity.RunTime), (uint)totalCount);
+ if (!_player.HasEnoughMoney(deposit))
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
+
+ uint auctionId = Global.ObjectMgr.GenerateAuctionID();
+ AuctionPosting auction = new AuctionPosting();
+ auction.Id = auctionId;
+ auction.Owner = _player.GetGUID();
+ auction.OwnerAccount = GetAccountGUID();
+ auction.BuyoutOrUnitPrice = sellCommodity.UnitPrice;
+ auction.Deposit = deposit;
+ auction.StartTime = GameTime.GetGameTimeSystemPoint();
+ auction.EndTime = auction.StartTime + auctionTime;
+
+ // keep track of what was cloned to undo/modify counts later
+ Dictionary
- clones = new Dictionary
- ();
+ foreach (var pair in items2)
+ {
+ Item itemForSale;
+ if (pair.Value.Item1.GetCount() != pair.Value.Item2)
+ {
+ itemForSale = pair.Value.Item1.CloneItem((uint)pair.Value.Item2, _player);
+ if (itemForSale == null)
+ {
+ Log.outError(LogFilter.Network, $"CMSG_AUCTION_SELL_COMMODITY: Could not create clone of item {pair.Value.Item1.GetEntry()}");
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ clones.Add(pair.Value.Item1, itemForSale);
+ }
+ }
+
+ if (!Global.AuctionHouseMgr.PendingAuctionAdd(_player, auctionHouse.GetAuctionHouseId(), auction.Id, auction.Deposit))
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
+
+ /*TC_LOG_INFO("network", "CMSG_AUCTION_SELL_COMMODITY: %s %s is selling item %s %s to auctioneer %s with count " UI64FMTD " with with unit price " UI64FMTD " and with time %u (in sec) in auctionhouse %u",
+ _player.GetGUID().ToString(), _player.GetName(), items2.begin().second.first.GetNameForLocaleIdx(sWorld.GetDefaultDbcLocale()),
+ ([&items2]()
+ {
+ std.stringstream ss;
+ auto itr = items2.begin();
+ ss << (itr++).first.ToString();
+ for (; itr != items2.end(); ++itr)
+ ss << ',' << itr.first.ToString();
+ return ss.str();
+ } ()),
+ creature.GetGUID().ToString(), totalCount, sellCommodity.UnitPrice, uint32(auctionTime.count()), auctionHouse.GetAuctionHouseId());*/
+
+ if (HasPermission(RBACPermissions.LogGmTrade))
+ {
+ Item logItem = items2.First().Value.Item1;
+ Log.outCommand(GetAccountId(), $"GM {GetPlayerName()} (Account: {GetAccountId()}) create auction: {logItem.GetName(Global.WorldMgr.GetDefaultDbcLocale())} (Entry: {logItem.GetEntry()} Count: {totalCount})");
+ }
+
+ SQLTransaction trans = new SQLTransaction();
+
+ foreach (var pair in items2)
+ {
+ Item itemForSale = pair.Value.Item1;
+ var cloneItr = clones.LookupByKey(pair.Value.Item1);
+ if (cloneItr != null)
+ {
+ Item original = itemForSale;
+ original.SetCount(original.GetCount() - (uint)pair.Value.Item2);
+ original.SetState(ItemUpdateState.Changed, _player);
+ _player.ItemRemovedQuestCheck(original.GetEntry(), (uint)pair.Value.Item2);
+ original.SaveToDB(trans);
+
+ itemForSale = cloneItr;
+ }
+ else
+ {
+ _player.MoveItemFromInventory(itemForSale.GetBagSlot(), itemForSale.GetSlot(), true);
+ itemForSale.DeleteFromInventoryDB(trans);
+ }
+
+ itemForSale.SaveToDB(trans);
+ auction.Items.Add(itemForSale);
+ }
+
+ auctionHouse.AddAuction(trans, auction);
+ _player.SaveInventoryAndGoldToDB(trans);
+
+ AddTransactionCallback(DB.Characters.AsyncCommitTransaction(trans)).AfterComplete(success =>
+ {
+ if (GetPlayer() && GetPlayer().GetGUID() == _player.GetGUID())
+ {
+ if (success)
+ {
+ GetPlayer().UpdateCriteria(CriteriaTypes.CreateAuction, 1);
+ SendAuctionCommandResult(auctionId, AuctionCommand.SellItem, AuctionResult.Ok, throttle.DelayUntilNext);
+ }
+ else
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ }
+ });
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionSellItem)]
+ void HandleAuctionSellItem(AuctionSellItem sellItem)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player, AuctionCommand.SellItem);
+ if (throttle.Throttled)
+ return;
+
+ if (sellItem.Items.Count != 1 || sellItem.Items[0].UseCount != 1)
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (sellItem.MinBid == 0 && sellItem.BuyoutPrice == 0)
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (sellItem.MinBid > PlayerConst.MaxMoneyAmount || sellItem.BuyoutPrice > PlayerConst.MaxMoneyAmount)
+ {
+ Log.outError(LogFilter.Network, $"WORLD: HandleAuctionSellItem - Player {_player.GetName()} ({_player.GetGUID().ToString()}) attempted to sell item with higher price than max gold amount.");
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.Inventory, throttle.DelayUntilNext, InventoryResult.TooMuchGold);
+ return;
+ }
+
+ // auction house does not deal with copper
+ if ((sellItem.MinBid % MoneyConstants.Silver) != 0 || (sellItem.BuyoutPrice % MoneyConstants.Silver) != 0)
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(sellItem.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (!creature)
+ {
+ Log.outError(LogFilter.Network, "WORLD: HandleAuctionSellItem - Unit (%s) not found or you can't interact with him.", sellItem.Auctioneer.ToString());
+ return;
+ }
+
+ uint houseId = 0;
+ AuctionHouseRecord auctionHouseEntry = Global.AuctionHouseMgr.GetAuctionHouseEntry(creature.GetFaction(), ref houseId);
+ if (auctionHouseEntry == null)
+ {
+ Log.outError(LogFilter.Network, "WORLD: HandleAuctionSellItem - Unit (%s) has wrong faction.", sellItem.Auctioneer.ToString());
+ return;
+ }
+
+ switch (sellItem.RunTime)
+ {
+ case 1 * SharedConst.MinAuctionTime / Time.Minute:
+ case 2 * SharedConst.MinAuctionTime / Time.Minute:
+ case 4 * SharedConst.MinAuctionTime / Time.Minute:
+ break;
+ default:
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.AuctionHouseBusy, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ Item item = _player.GetItemByGuid(sellItem.Items[0].Guid);
+ if (!item)
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (item.GetTemplate().GetMaxStackSize() > 1)
+ {
+ // commodity, must use different packet
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (Global.AuctionHouseMgr.GetAItem(item.GetGUID()) || !item.CanBeTraded() || item.IsNotEmptyBag() ||
+ item.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.Conjured) || item.m_itemData.Expiration != 0 ||
+ item.GetCount() != 1)
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ TimeSpan auctionTime = TimeSpan.FromSeconds((long)(TimeSpan.FromMinutes(sellItem.RunTime).TotalSeconds * WorldConfig.GetFloatValue(WorldCfg.RateAuctionTime)));
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ ulong deposit = Global.AuctionHouseMgr.GetItemAuctionDeposit(_player, item, TimeSpan.FromMinutes(sellItem.RunTime));
+ if (!_player.HasEnoughMoney(deposit))
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
+
+ uint auctionId = Global.ObjectMgr.GenerateAuctionID();
+
+ AuctionPosting auction = new AuctionPosting();
+ auction.Id = auctionId;
+ auction.Owner = _player.GetGUID();
+ auction.OwnerAccount = GetAccountGUID();
+ auction.MinBid = sellItem.MinBid;
+ auction.BuyoutOrUnitPrice = sellItem.BuyoutPrice;
+ auction.Deposit = deposit;
+ auction.BidAmount = sellItem.MinBid;
+ auction.StartTime = GameTime.GetGameTimeSystemPoint();
+ auction.EndTime = auction.StartTime + auctionTime;
+
+ if (HasPermission(RBACPermissions.LogGmTrade))
+ Log.outCommand(GetAccountId(), $"GM {GetPlayerName()} (Account: {GetAccountId()}) create auction: {item.GetTemplate().GetName()} (Entry: {item.GetEntry()} Count: {item.GetCount()})");
+
+ auction.Items.Add(item);
+
+ Log.outInfo(LogFilter.Network, $"CMSG_AuctionAction.SellItem: {_player.GetGUID().ToString()} {_player.GetName()} is selling item {item.GetGUID().ToString()} {item.GetTemplate().GetName()} " +
+ $"to auctioneer {creature.GetGUID().ToString()} with count {item.GetCount()} with initial bid {sellItem.MinBid} with buyout {sellItem.BuyoutPrice} and with time {auctionTime.TotalSeconds} " +
+ $"(in sec) in auctionhouse {auctionHouse.GetAuctionHouseId()}");
+
+ // Add to pending auctions, or fail with insufficient funds error
+ if (!Global.AuctionHouseMgr.PendingAuctionAdd(_player, auctionHouse.GetAuctionHouseId(), auctionId, auction.Deposit))
+ {
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
+
+ _player.MoveItemFromInventory(item.GetBagSlot(), item.GetSlot(), true);
+
+ SQLTransaction trans = new SQLTransaction();
+ item.DeleteFromInventoryDB(trans);
+ item.SaveToDB(trans);
+
+ auctionHouse.AddAuction(trans, auction);
+ _player.SaveInventoryAndGoldToDB(trans);
+ AddTransactionCallback(DB.Characters.AsyncCommitTransaction(trans)).AfterComplete(success =>
+ {
+ if (GetPlayer() && GetPlayer().GetGUID() == _player.GetGUID())
+ {
+ if (success)
+ {
+ GetPlayer().UpdateCriteria(CriteriaTypes.CreateAuction, 1);
+ SendAuctionCommandResult(auctionId, AuctionCommand.SellItem, AuctionResult.Ok, throttle.DelayUntilNext);
+ }
+ else
+ SendAuctionCommandResult(0, AuctionCommand.SellItem, AuctionResult.DatabaseError, throttle.DelayUntilNext);
+ }
+ });
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionSetFavoriteItem)]
+ void HandleAuctionSetFavoriteItem(AuctionSetFavoriteItem setFavoriteItem)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
+
+ SQLTransaction trans = new SQLTransaction();
+
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_FAVORITE_AUCTION);
+ stmt.AddValue(0, _player.GetGUID().GetCounter());
+ stmt.AddValue(1, setFavoriteItem.Item.Order);
+ trans.Append(stmt);
+
+ if (!setFavoriteItem.IsNotFavorite)
+ {
+ stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_FAVORITE_AUCTION);
+ stmt.AddValue(0, _player.GetGUID().GetCounter());
+ stmt.AddValue(1, setFavoriteItem.Item.Order);
+ stmt.AddValue(2, setFavoriteItem.Item.ItemID);
+ stmt.AddValue(3, setFavoriteItem.Item.ItemLevel);
+ stmt.AddValue(4, setFavoriteItem.Item.BattlePetSpeciesID);
+ stmt.AddValue(5, setFavoriteItem.Item.SuffixItemNameDescriptionID);
+ trans.Append(stmt);
+ }
+
+ DB.Characters.CommitTransaction(trans);
+ }
+
+ [WorldPacketHandler(ClientOpcodes.AuctionStartCommoditiesPurchase)]
+ void HandleAuctionStartCommoditiesPurchase(AuctionStartCommoditiesPurchase startCommoditiesPurchase)
+ {
+ AuctionThrottleResult throttle = Global.AuctionHouseMgr.CheckThrottle(_player, AuctionCommand.PlaceBid);
+ if (throttle.Throttled)
+ return;
+
+ Creature creature = GetPlayer().GetNPCIfCanInteractWith(startCommoditiesPurchase.Auctioneer, NPCFlags.Auctioneer, NPCFlags2.None);
+ if (!creature)
+ {
+ Log.outError(LogFilter.Network, "WORLD: HandleAuctionStartCommoditiesPurchase - {startCommoditiesPurchase.Auctioneer.ToString()} not found or you can't interact with him.");
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer().HasUnitState(UnitState.Died))
+ GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
+
+ AuctionHouseObject auctionHouse = Global.AuctionHouseMgr.GetAuctionsMap(creature.GetFaction());
+
+ AuctionCommodityQuote auctionCommodityQuote = new AuctionCommodityQuote();
+
+ CommodityQuote quote = auctionHouse.CreateCommodityQuote(_player, (uint)startCommoditiesPurchase.ItemID, startCommoditiesPurchase.Quantity);
+ if (quote != null)
+ {
+ auctionCommodityQuote.TotalPrice.Set(quote.TotalPrice);
+ auctionCommodityQuote.Quantity.Set(quote.Quantity);
+ auctionCommodityQuote.QuoteDuration.Set((int)(quote.ValidTo - GameTime.GetGameTimeSteadyPoint()).TotalMilliseconds);
+ }
+
+ auctionCommodityQuote.DesiredDelay = (uint)throttle.DelayUntilNext.TotalSeconds;
+
+ SendPacket(auctionCommodityQuote);
+ }
+
+ public void SendAuctionHello(ObjectGuid guid, Creature unit)
+ {
+ if (GetPlayer().GetLevel() < WorldConfig.GetIntValue(WorldCfg.AuctionLevelReq))
+ {
+ SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.AuctionReq), WorldConfig.GetIntValue(WorldCfg.AuctionLevelReq));
+ return;
+ }
+
+ AuctionHouseRecord ahEntry = Global.AuctionHouseMgr.GetAuctionHouseEntry(unit.GetFaction());
+ if (ahEntry == null)
+ return;
+
+ AuctionHelloResponse auctionHelloResponse = new AuctionHelloResponse();
+ auctionHelloResponse.Guid = guid;
+ auctionHelloResponse.OpenForBusiness = true;
+ SendPacket(auctionHelloResponse);
+ }
+
+ public void SendAuctionCommandResult(uint auctionId, AuctionCommand command, AuctionResult errorCode, TimeSpan delayForNextAction, InventoryResult bagError = 0)
+ {
+ AuctionCommandResult auctionCommandResult = new AuctionCommandResult();
+ auctionCommandResult.AuctionID = auctionId;
+ auctionCommandResult.Command = (int)command;
+ auctionCommandResult.ErrorCode = (int)errorCode;
+ auctionCommandResult.BagResult = (int)bagError;
+ auctionCommandResult.DesiredDelay = (uint)delayForNextAction.TotalSeconds;
+ SendPacket(auctionCommandResult);
+ }
+
+ public void SendAuctionClosedNotification(AuctionPosting auction, float mailDelay, bool sold)
+ {
+ AuctionClosedNotification packet = new AuctionClosedNotification();
+ packet.Info.Initialize(auction);
+ packet.ProceedsMailDelay = mailDelay;
+ packet.Sold = sold;
+ SendPacket(packet);
+ }
+
+ public void SendAuctionOwnerBidNotification(AuctionPosting auction)
+ {
+ AuctionOwnerBidNotification packet = new AuctionOwnerBidNotification();
+ packet.Info.Initialize(auction);
+ packet.Bidder = auction.Bidder;
+ packet.MinIncrement = auction.CalculateMinIncrement();
+ SendPacket(packet);
+ }
}
}
diff --git a/Source/Game/Handlers/CharacterHandler.cs b/Source/Game/Handlers/CharacterHandler.cs
index 6884d1371..84f74e533 100644
--- a/Source/Game/Handlers/CharacterHandler.cs
+++ b/Source/Game/Handlers/CharacterHandler.cs
@@ -788,6 +788,29 @@ namespace Game
// Place character in world (and load zone) before some object loading
pCurrChar.LoadCorpse(holder.GetResult(PlayerLoginQueryLoad.CorpseLocation));
+ stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_FAVORITE_AUCTIONS);
+ stmt.AddValue(0, pCurrChar.GetGUID().GetCounter());
+ GetQueryProcessor().AddCallback(DB.Characters.AsyncQuery(stmt)).WithCallback(favoriteAuctionResult =>
+ {
+ AuctionFavoriteItems favoriteItems = new AuctionFavoriteItems();
+ if (!favoriteAuctionResult.IsEmpty())
+ {
+ do
+ {
+ AuctionFavoriteInfo item = new AuctionFavoriteInfo();
+ item.Order = favoriteAuctionResult.Read(0);
+ item.ItemID = favoriteAuctionResult.Read(1);
+ item.ItemLevel = favoriteAuctionResult.Read(2);
+ item.BattlePetSpeciesID = favoriteAuctionResult.Read(3);
+ item.SuffixItemNameDescriptionID = favoriteAuctionResult.Read(4);
+ favoriteItems.Items.Add(item);
+
+ } while (favoriteAuctionResult.NextRow());
+
+ }
+ SendPacket(favoriteItems);
+ });
+
// setting Ghost+speed if dead
if (pCurrChar.GetDeathState() != DeathState.Alive)
{
diff --git a/Source/Game/Mails/Mail.cs b/Source/Game/Mails/Mail.cs
index a30c0ca7c..571222fea 100644
--- a/Source/Game/Mails/Mail.cs
+++ b/Source/Game/Mails/Mail.cs
@@ -90,7 +90,15 @@ namespace Game.Mails
m_receiver = receiver;
m_receiver_lowguid = receiver_lowguid;
- // ASSERT(!receiver || receiver.GetGUID().GetCounter() == receiver_lowguid);
+ Cypher.Assert(!receiver || receiver.GetGUID().GetCounter() == receiver_lowguid);
+ }
+
+ public MailReceiver(Player receiver, ObjectGuid receiverGuid)
+ {
+ m_receiver = receiver;
+ m_receiver_lowguid = receiverGuid.GetCounter();
+
+ Cypher.Assert(!receiver || receiver.GetGUID() == receiverGuid);
}
public Player GetPlayer() { return m_receiver; }
@@ -140,10 +148,11 @@ namespace Game.Mails
m_senderId = (uint)sender.EventId;
m_stationery = MailStationery.Default;
}
- public MailSender(AuctionEntry sender)
+
+ public MailSender(AuctionHouseObject sender)
{
m_messageType = MailMessageType.Auction;
- m_senderId = sender.GetHouseId();
+ m_senderId = sender.GetAuctionHouseId();
m_stationery = MailStationery.Auction;
}
diff --git a/Source/Game/Mails/MailDraft.cs b/Source/Game/Mails/MailDraft.cs
index 46bcc5a28..f3f827cc0 100644
--- a/Source/Game/Mails/MailDraft.cs
+++ b/Source/Game/Mails/MailDraft.cs
@@ -137,7 +137,7 @@ namespace Game.Mails
public void SendMailTo(SQLTransaction trans, MailReceiver receiver, MailSender sender, MailCheckMask checkMask = MailCheckMask.None, uint deliver_delay = 0)
{
Player pReceiver = receiver.GetPlayer(); // can be NULL
- Player pSender = Global.ObjAccessor.FindPlayer(ObjectGuid.Create(HighGuid.Player, sender.GetSenderId()));
+ Player pSender = sender.GetMailMessageType() == MailMessageType.Normal ? Global.ObjAccessor.FindPlayer(ObjectGuid.Create(HighGuid.Player, sender.GetSenderId())) : null;
if (pReceiver != null)
PrepareItems(pReceiver, trans); // generate mail template items
diff --git a/Source/Game/Network/Packets/AuctionHousePackets.cs b/Source/Game/Network/Packets/AuctionHousePackets.cs
index 484e3d584..8269ea89d 100644
--- a/Source/Game/Network/Packets/AuctionHousePackets.cs
+++ b/Source/Game/Network/Packets/AuctionHousePackets.cs
@@ -41,7 +41,7 @@ namespace Game.Network.Packets
public override void Read()
{
Auctioneer = _worldPacket.ReadPackedGuid();
- Offset = _worldPacket.ReadUInt8();
+ Offset = _worldPacket.ReadUInt32();
MinLevel = _worldPacket.ReadUInt8();
MaxLevel = _worldPacket.ReadUInt8();
Filters = (AuctionHouseFilterMask)_worldPacket.ReadUInt32();
@@ -88,10 +88,10 @@ namespace Game.Network.Packets
class AuctionConfirmCommoditiesPurchase : ClientPacket
{
- ObjectGuid Auctioneer;
- int ItemID;
- uint Quantity;
- Optional TaintedBy;
+ public ObjectGuid Auctioneer;
+ public int ItemID;
+ public uint Quantity;
+ public Optional TaintedBy;
public AuctionConfirmCommoditiesPurchase(WorldPacket packet) : base(packet) { }
@@ -149,6 +149,34 @@ namespace Game.Network.Packets
}
}
+ class AuctionListBucketsByBucketKeys : ClientPacket
+ {
+ public ObjectGuid Auctioneer;
+ public Optional TaintedBy;
+ public Array BucketKeys = new Array(100);
+ public Array Sorts = new Array(2);
+
+ public AuctionListBucketsByBucketKeys(WorldPacket packet) : base(packet) { }
+
+ public override void Read()
+ {
+ Auctioneer = _worldPacket.ReadPackedGuid();
+ TaintedBy.HasValue = _worldPacket.HasBit();
+
+ uint bucketKeysCount = _worldPacket.ReadBits(7);
+ uint sortCount = _worldPacket.ReadBits(2);
+
+ for (var i = 0; i < sortCount; ++i)
+ Sorts[i] = new AuctionSortDef(_worldPacket);
+
+ if (TaintedBy.HasValue)
+ TaintedBy.Value.Read(_worldPacket);
+
+ for (var i = 0; i < bucketKeysCount; ++i)
+ BucketKeys[i] = new AuctionBucketKey(_worldPacket);
+ }
+ }
+
class AuctionListItemsByBucketKey : ClientPacket
{
public ObjectGuid Auctioneer;
@@ -168,12 +196,10 @@ namespace Game.Network.Packets
TaintedBy.HasValue = _worldPacket.HasBit();
uint sortCount = _worldPacket.ReadBits(2);
-
for (var i = 0; i < sortCount; ++i)
Sorts[i] = new AuctionSortDef(_worldPacket);
- BucketKey = new AuctionBucketKey();
- BucketKey.Read(_worldPacket);
+ BucketKey = new AuctionBucketKey(_worldPacket);
if (TaintedBy.HasValue)
TaintedBy.Value.Read(_worldPacket);
@@ -183,7 +209,7 @@ namespace Game.Network.Packets
class AuctionListItemsByItemID : ClientPacket
{
public ObjectGuid Auctioneer;
- public int ItemID;
+ public uint ItemID;
public int SuffixItemNameDescriptionID;
public uint Offset;
public Optional TaintedBy;
@@ -194,37 +220,13 @@ namespace Game.Network.Packets
public override void Read()
{
Auctioneer = _worldPacket.ReadPackedGuid();
- ItemID = _worldPacket.ReadInt32();
+ ItemID = _worldPacket.ReadUInt32();
SuffixItemNameDescriptionID = _worldPacket.ReadInt32();
Offset = _worldPacket.ReadUInt32();
- TaintedBy.HasValue = _worldPacket.HasBit();
-
- uint sortCount = _worldPacket.ReadBits(2);
- for (var i = 0; i < sortCount; ++i)
- Sorts[i] = new AuctionSortDef(_worldPacket);
-
- if (TaintedBy.HasValue)
- TaintedBy.Value.Read(_worldPacket);
- }
- }
-
- class AuctionListItemsByItemKeys : ClientPacket
- {
- public ObjectGuid Auctioneer;
- public Optional TaintedBy;
- public Array BucketKeys = new Array(100);
- public Array Sorts = new Array(2);
-
- public AuctionListItemsByItemKeys(WorldPacket packet) : base(packet) { }
-
- public override void Read()
- {
- Auctioneer = _worldPacket.ReadPackedGuid();
if (_worldPacket.HasBit())
TaintedBy.HasValue = true;
- uint bucketKeyCount = _worldPacket.ReadBits(7);
uint sortCount = _worldPacket.ReadBits(2);
for (var i = 0; i < sortCount; ++i)
@@ -232,12 +234,6 @@ namespace Game.Network.Packets
if (TaintedBy.HasValue)
TaintedBy.Value.Read(_worldPacket);
-
- for (var i = 0; i < bucketKeyCount; ++i)
- {
- BucketKeys[i] = new AuctionBucketKey();
- BucketKeys[i].Read(_worldPacket);
- }
}
}
@@ -290,7 +286,7 @@ namespace Game.Network.Packets
class AuctionRemoveItem : ClientPacket
{
public ObjectGuid Auctioneer;
- public int AuctionID = 0;
+ public uint AuctionID;
public Optional TaintedBy;
public AuctionRemoveItem(WorldPacket packet) : base(packet) { }
@@ -298,7 +294,7 @@ namespace Game.Network.Packets
public override void Read()
{
Auctioneer = _worldPacket.ReadPackedGuid();
- AuctionID = _worldPacket.ReadInt32();
+ AuctionID = _worldPacket.ReadUInt32();
TaintedBy.HasValue = _worldPacket.HasBit();
if (TaintedBy.HasValue)
@@ -364,7 +360,7 @@ namespace Game.Network.Packets
public ulong MinBid;
public uint RunTime;
public Optional TaintedBy;
- public Array Items = new Array(64);
+ public Array Items = new Array(1);
public AuctionSellItem(WorldPacket packet) : base(packet) { }
@@ -451,24 +447,6 @@ namespace Game.Network.Packets
public AuctionCommandResult() : base(ServerOpcodes.AuctionCommandResult) { }
- /**
- * @fn void WorldPackets::AuctionHousePackets::AuctionCommandResult::InitializeAuction(AuctionEntry* auction);
- *
- * @brief Initialize the following fields: AuctionId, Bid, AuctionOutBid, Bidder
- *
- * @param auction The relevant auction object
- */
- public void InitializeAuction(AuctionEntry auction)
- {
- if (auction != null)
- {
- AuctionID = auction.Id;
- Money = auction.bid == auction.buyout ? 0 : auction.bid;
- MinIncrement = auction.bid == auction.buyout ? 0 : auction.GetAuctionOutBid();
- Guid = ObjectGuid.Create(HighGuid.Player, auction.bidder);
- }
- }
-
public override void Write()
{
_worldPacket.WriteUInt32(AuctionID);
@@ -482,7 +460,7 @@ namespace Game.Network.Packets
}
}
- class AuctionCommodityPriceUpdate : ServerPacket
+ class AuctionCommodityQuote : ServerPacket
{
public Optional TotalPrice;
public Optional Quantity;
@@ -490,7 +468,7 @@ namespace Game.Network.Packets
public int Unknown830;
public uint DesiredDelay;
- public AuctionCommodityPriceUpdate() : base(ServerOpcodes.AuctionCommodityPriceUpdate) { }
+ public AuctionCommodityQuote() : base(ServerOpcodes.AuctionCommodityQuote) { }
public override void Write()
{
@@ -546,16 +524,16 @@ namespace Game.Network.Packets
}
}
- class AuctionListBucketItemsResult : ServerPacket
+ public class AuctionListBucketsResult : ServerPacket
{
public List Buckets = new List();
public uint DesiredDelay;
public int Unknown830_0;
public int Unknown830_1;
- public int BrowseMode;
+ public AuctionHouseBrowseMode BrowseMode;
public bool HasMoreResults;
- public AuctionListBucketItemsResult() : base(ServerOpcodes.AuctionListBucketItemsResult) { }
+ public AuctionListBucketsResult() : base(ServerOpcodes.AuctionListBucketsResult) { }
public override void Write()
{
@@ -563,7 +541,7 @@ namespace Game.Network.Packets
_worldPacket.WriteUInt32(DesiredDelay);
_worldPacket.WriteInt32(Unknown830_0);
_worldPacket.WriteInt32(Unknown830_1);
- _worldPacket.WriteBits(BrowseMode, 1);
+ _worldPacket.WriteBits((int)BrowseMode, 1);
_worldPacket.WriteBit(HasMoreResults);
_worldPacket.FlushBits();
@@ -572,12 +550,12 @@ namespace Game.Network.Packets
}
}
- class AuctionListFavoriteItemsResult : ServerPacket
+ class AuctionFavoriteItems : ServerPacket
{
public uint DesiredDelay;
public List Items = new List();
- public AuctionListFavoriteItemsResult() : base(ServerOpcodes.AuctionListFavoriteItemsResult) { }
+ public AuctionFavoriteItems() : base(ServerOpcodes.AuctionFavoriteItems) { }
public override void Write()
{
@@ -596,9 +574,9 @@ namespace Game.Network.Packets
public uint Unknown830;
public uint TotalCount;
public uint DesiredDelay;
- public uint ListType;
+ public AuctionHouseListType ListType;
public bool HasMoreResults;
- public AuctionBucketKey BucketKey;
+ public AuctionBucketKey BucketKey = new AuctionBucketKey();
public AuctionListItemsResult() : base(ServerOpcodes.AuctionListItemsResult) { }
@@ -608,7 +586,7 @@ namespace Game.Network.Packets
_worldPacket.WriteUInt32(Unknown830);
_worldPacket.WriteUInt32(TotalCount);
_worldPacket.WriteUInt32(DesiredDelay);
- _worldPacket.WriteBits(ListType, 2);
+ _worldPacket.WriteBits((int)ListType, 2);
_worldPacket.WriteBit(HasMoreResults);
_worldPacket.FlushBits();
@@ -673,7 +651,7 @@ namespace Game.Network.Packets
}
}
- class AuctionReplicateResponse : ServerPacket
+ public class AuctionReplicateResponse : ServerPacket
{
public uint ChangeNumberCursor;
public uint ChangeNumberGlobal;
@@ -718,7 +696,21 @@ namespace Game.Network.Packets
public Optional BattlePetSpeciesID = new Optional();
public Optional SuffixItemNameDescriptionID = new Optional();
- public void Read(WorldPacket data)
+ public AuctionBucketKey() { }
+
+ public AuctionBucketKey(AuctionsBucketKey key)
+ {
+ ItemID = key.ItemId;
+ ItemLevel = key.ItemLevel;
+
+ if (key.BattlePetSpeciesId != 0)
+ BattlePetSpeciesID.Set(key.BattlePetSpeciesId);
+
+ if (key.SuffixItemNameDescriptionId != 0)
+ SuffixItemNameDescriptionID.Set(key.SuffixItemNameDescriptionId);
+ }
+
+ public AuctionBucketKey(WorldPacket data)
{
data.ResetBitPos();
ItemID = data.ReadBits(20);
@@ -735,7 +727,7 @@ namespace Game.Network.Packets
BattlePetSpeciesID.Set(data.ReadUInt16());
if (SuffixItemNameDescriptionID.HasValue)
- SuffixItemNameDescriptionID = data.ReadUInt16();
+ SuffixItemNameDescriptionID.Set(data.ReadUInt16());
}
public void Write(WorldPacket data)
@@ -786,6 +778,12 @@ namespace Game.Network.Packets
public AuctionHouseSortOrder SortOrder;
public bool ReverseSort;
+ public AuctionSortDef(AuctionHouseSortOrder sortOrder, bool reverseSort)
+ {
+ SortOrder = sortOrder;
+ ReverseSort = reverseSort;
+ }
+
public AuctionSortDef(WorldPacket data)
{
data.ResetBitPos();
@@ -812,7 +810,7 @@ namespace Game.Network.Packets
public uint ItemID;
public uint ItemLevel;
public uint BattlePetSpeciesID;
- public uint ItemSuffix;
+ public uint SuffixItemNameDescriptionID;
public AuctionFavoriteInfo(WorldPacket data)
{
@@ -820,7 +818,7 @@ namespace Game.Network.Packets
ItemID = data.ReadUInt32();
ItemLevel = data.ReadUInt32();
BattlePetSpeciesID = data.ReadUInt32();
- ItemSuffix = data.ReadUInt32();
+ SuffixItemNameDescriptionID = data.ReadUInt32();
}
public void Write(WorldPacket data)
@@ -829,7 +827,7 @@ namespace Game.Network.Packets
data.WriteUInt32(ItemID);
data.WriteUInt32(ItemLevel);
data.WriteUInt32(BattlePetSpeciesID);
- data.WriteUInt32(ItemSuffix);
+ data.WriteUInt32(SuffixItemNameDescriptionID);
}
}
@@ -839,11 +837,11 @@ namespace Game.Network.Packets
public ulong BidAmount;
public ItemInstance Item;
- public void Initialize(AuctionEntry auction, Item item)
+ public void Initialize(AuctionPosting auction)
{
AuctionID = auction.Id;
- Item = new ItemInstance(item);
- BidAmount = auction.bid;
+ Item = new ItemInstance(auction.Items[0]);
+ BidAmount = auction.BidAmount;
}
public void Write(WorldPacket data)
@@ -859,7 +857,7 @@ namespace Game.Network.Packets
public AuctionBucketKey Key;
public int TotalQuantity;
public ulong MinPrice;
- public List ItemModifiedAppearanceIDs = new List();
+ public List ItemModifiedAppearanceIDs = new List();
public Optional MaxBattlePetQuality;
public Optional MaxBattlePetLevel;
public Optional BattlePetBreedID;
@@ -903,7 +901,7 @@ namespace Game.Network.Packets
public int Charges;
public List Enchantments = new List();
public int Flags;
- public int AuctionID;
+ public uint AuctionID;
public ObjectGuid Owner;
public Optional MinBid;
public Optional MinIncrement;
@@ -947,7 +945,7 @@ namespace Game.Network.Packets
data.WriteInt32(Count);
data.WriteInt32(Charges);
data.WriteInt32(Flags);
- data.WriteInt32(AuctionID);
+ data.WriteUInt32(AuctionID);
data.WritePackedGuid(Owner);
data.WriteInt32(DurationLeft);
data.WriteUInt8(DeleteReason);
@@ -993,20 +991,20 @@ namespace Game.Network.Packets
struct AuctionBidderNotification
{
- public int AuctionID;
+ public uint AuctionID;
public ObjectGuid Bidder;
public ItemInstance Item;
- public void Initialize(AuctionEntry auction, Item item)
+ public void Initialize(AuctionPosting auction, Item item)
{
- AuctionID = (int)auction.Id;
+ AuctionID = auction.Id;
Item = new ItemInstance(item);
- Bidder = ObjectGuid.Create(HighGuid.Player, auction.bidder);
+ Bidder = auction.Bidder;
}
public void Write(WorldPacket data)
{
- data.WriteInt32(AuctionID);
+ data.WriteUInt32(AuctionID);
data.WritePackedGuid(Bidder);
Item.Write(data);
}
diff --git a/Source/Game/Network/Packets/ItemPackets.cs b/Source/Game/Network/Packets/ItemPackets.cs
index b221dd30c..48047a1c2 100644
--- a/Source/Game/Network/Packets/ItemPackets.cs
+++ b/Source/Game/Network/Packets/ItemPackets.cs
@@ -883,7 +883,7 @@ namespace Game.Network.Packets
public class ItemEnchantData
{
- public ItemEnchantData(int id, uint expiration, int charges, byte slot)
+ public ItemEnchantData(uint id, uint expiration, int charges, byte slot)
{
ID = id;
Expiration = expiration;
@@ -893,13 +893,13 @@ namespace Game.Network.Packets
public void Write(WorldPacket data)
{
- data.WriteInt32(ID);
+ data.WriteUInt32(ID);
data.WriteUInt32(Expiration);
data.WriteInt32(Charges);
data.WriteUInt8(Slot);
}
- public int ID;
+ public uint ID;
public uint Expiration;
public int Charges;
public byte Slot;
diff --git a/Source/Game/Network/Packets/MailPackets.cs b/Source/Game/Network/Packets/MailPackets.cs
index 018c386da..94b092b1b 100644
--- a/Source/Game/Network/Packets/MailPackets.cs
+++ b/Source/Game/Network/Packets/MailPackets.cs
@@ -326,7 +326,7 @@ namespace Game.Network.Packets
if (item.GetEnchantmentId(slot) == 0)
continue;
- Enchants.Add(new ItemEnchantData((int)item.GetEnchantmentId(slot), item.GetEnchantmentDuration(slot), (int)item.GetEnchantmentCharges(slot), (byte)slot));
+ Enchants.Add(new ItemEnchantData(item.GetEnchantmentId(slot), item.GetEnchantmentDuration(slot), (int)item.GetEnchantmentCharges(slot), (byte)slot));
}
byte i = 0;
diff --git a/Source/Game/Scripting/CoreScripts.cs b/Source/Game/Scripting/CoreScripts.cs
index ed977cc66..e008b6f64 100644
--- a/Source/Game/Scripting/CoreScripts.cs
+++ b/Source/Game/Scripting/CoreScripts.cs
@@ -482,16 +482,16 @@ namespace Game.Scripting
}
// Called when an auction is added to an auction house.
- public virtual void OnAuctionAdd(AuctionHouseObject ah, AuctionEntry entry) { }
+ public virtual void OnAuctionAdd(AuctionHouseObject ah, AuctionPosting auction) { }
// Called when an auction is removed from an auction house.
- public virtual void OnAuctionRemove(AuctionHouseObject ah, AuctionEntry entry) { }
+ public virtual void OnAuctionRemove(AuctionHouseObject ah, AuctionPosting auction) { }
// Called when an auction was succesfully completed.
- public virtual void OnAuctionSuccessful(AuctionHouseObject ah, AuctionEntry entry) { }
+ public virtual void OnAuctionSuccessful(AuctionHouseObject ah, AuctionPosting auction) { }
// Called when an auction expires.
- public virtual void OnAuctionExpire(AuctionHouseObject ah, AuctionEntry entry) { }
+ public virtual void OnAuctionExpire(AuctionHouseObject ah, AuctionPosting auction) { }
}
public class ConditionScript : ScriptObject
diff --git a/Source/Game/Scripting/ScriptManager.cs b/Source/Game/Scripting/ScriptManager.cs
index 82e0edf7b..83b88e0b9 100644
--- a/Source/Game/Scripting/ScriptManager.cs
+++ b/Source/Game/Scripting/ScriptManager.cs
@@ -975,29 +975,29 @@ namespace Game.Scripting
}
// AuctionHouseScript
- public void OnAuctionAdd(AuctionHouseObject ah, AuctionEntry entry)
+ public void OnAuctionAdd(AuctionHouseObject ah, AuctionPosting auction)
{
Cypher.Assert(ah != null);
- Cypher.Assert(entry != null);
- ForEach(p => p.OnAuctionAdd(ah, entry));
+ Cypher.Assert(auction != null);
+ ForEach(p => p.OnAuctionAdd(ah, auction));
}
- public void OnAuctionRemove(AuctionHouseObject ah, AuctionEntry entry)
+ public void OnAuctionRemove(AuctionHouseObject ah, AuctionPosting auction)
{
Cypher.Assert(ah != null);
- Cypher.Assert(entry != null);
- ForEach(p => p.OnAuctionRemove(ah, entry));
+ Cypher.Assert(auction != null);
+ ForEach(p => p.OnAuctionRemove(ah, auction));
}
- public void OnAuctionSuccessful(AuctionHouseObject ah, AuctionEntry entry)
+ public void OnAuctionSuccessful(AuctionHouseObject ah, AuctionPosting auction)
{
Cypher.Assert(ah != null);
- Cypher.Assert(entry != null);
- ForEach(p => p.OnAuctionSuccessful(ah, entry));
+ Cypher.Assert(auction != null);
+ ForEach(p => p.OnAuctionSuccessful(ah, auction));
}
- public void OnAuctionExpire(AuctionHouseObject ah, AuctionEntry entry)
+ public void OnAuctionExpire(AuctionHouseObject ah, AuctionPosting auction)
{
Cypher.Assert(ah != null);
- Cypher.Assert(entry != null);
- ForEach(p => p.OnAuctionExpire(ah, entry));
+ Cypher.Assert(auction != null);
+ ForEach(p => p.OnAuctionExpire(ah, auction));
}
// ConditionScript
diff --git a/Source/Game/Server/WorldManager.cs b/Source/Game/Server/WorldManager.cs
index cfff3c9fd..3929c694d 100644
--- a/Source/Game/Server/WorldManager.cs
+++ b/Source/Game/Server/WorldManager.cs
@@ -691,12 +691,13 @@ namespace Game
Log.outInfo(LogFilter.ServerLoading, "Loading Completed Achievements...");
Global.AchievementMgr.LoadCompletedAchievements();
- // Load dynamic data tables from the database
- Log.outInfo(LogFilter.ServerLoading, "Loading Item Auctions...");
- Global.AuctionMgr.LoadAuctionItems();
+ // Load before guilds and arena teams
+ Log.outInfo(LogFilter.ServerLoading, "Loading character cache store...");
+ Global.CharacterCacheStorage.LoadCharacterCacheStorage();
+ // Load dynamic data tables from the database
Log.outInfo(LogFilter.ServerLoading, "Loading Auctions...");
- Global.AuctionMgr.LoadAuctions();
+ Global.AuctionHouseMgr.LoadAuctions();
if (WorldConfig.GetBoolValue(WorldCfg.BlackmarketEnabled))
{
@@ -707,10 +708,6 @@ namespace Game
Global.BlackMarketMgr.LoadAuctions();
}
- // Load before guilds and arena teams
- Log.outInfo(LogFilter.ServerLoading, "Loading character cache store...");
- Global.CharacterCacheStorage.LoadCharacterCacheStorage();
-
Log.outInfo(LogFilter.ServerLoading, "Loading Guild rewards...");
Global.GuildMgr.LoadGuildRewards();
@@ -1184,7 +1181,7 @@ namespace Game
}
// Handle expired auctions
- Global.AuctionMgr.Update();
+ Global.AuctionHouseMgr.Update();
}
if (m_timers[WorldTimers.AuctionsPending].Passed())
diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql
index 5e8d47955..23240d7d9 100644
--- a/sql/base/characters_database.sql
+++ b/sql/base/characters_database.sql
@@ -158,6 +158,53 @@ LOCK TABLES `arena_team_member` WRITE;
/*!40000 ALTER TABLE `arena_team_member` ENABLE KEYS */;
UNLOCK TABLES;
+--
+-- Table structure for table `auction_bidders`
+--
+
+DROP TABLE IF EXISTS `auction_bidders`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `auction_bidders` (
+ `auctionId` int unsigned NOT NULL,
+ `playerGuid` bigint unsigned NOT NULL,
+ PRIMARY KEY (`auctionId`,`playerGuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `auction_bidders`
+--
+
+LOCK TABLES `auction_bidders` WRITE;
+/*!40000 ALTER TABLE `auction_bidders` DISABLE KEYS */;
+/*!40000 ALTER TABLE `auction_bidders` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `auction_items`
+--
+
+DROP TABLE IF EXISTS `auction_items`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `auction_items` (
+ `auctionId` int unsigned NOT NULL,
+ `itemGuid` bigint unsigned NOT NULL,
+ PRIMARY KEY (`auctionId`,`itemGuid`),
+ UNIQUE KEY `idx_itemGuid` (`itemGuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `auction_items`
+--
+
+LOCK TABLES `auction_items` WRITE;
+/*!40000 ALTER TABLE `auction_items` DISABLE KEYS */;
+/*!40000 ALTER TABLE `auction_items` ENABLE KEYS */;
+UNLOCK TABLES;
+
--
-- Table structure for table `auctionhouse`
--
@@ -167,17 +214,16 @@ DROP TABLE IF EXISTS `auctionhouse`;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `auctionhouse` (
`id` int(10) unsigned NOT NULL DEFAULT '0',
- `auctioneerguid` bigint(20) unsigned NOT NULL DEFAULT '0',
- `itemguid` bigint(20) unsigned NOT NULL DEFAULT '0',
- `itemowner` bigint(20) unsigned NOT NULL DEFAULT '0',
- `buyoutprice` bigint(20) unsigned NOT NULL DEFAULT '0',
- `time` int(10) unsigned NOT NULL DEFAULT '0',
- `buyguid` bigint(20) unsigned NOT NULL DEFAULT '0',
- `lastbid` bigint(20) unsigned NOT NULL DEFAULT '0',
- `startbid` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `auctionHouseId` int(10) unsigned NOT NULL DEFAULT '0',
+ `owner` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `bidder` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `minBid` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `buyoutOrUnitPrice` bigint(20) unsigned NOT NULL DEFAULT '0',
`deposit` bigint(20) unsigned NOT NULL DEFAULT '0',
- PRIMARY KEY (`id`),
- UNIQUE KEY `item_guid` (`itemguid`)
+ `bidAmount` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `startTime` int(10) unsigned NOT NULL DEFAULT '0',
+ `endTime` int(10) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
@@ -733,6 +779,33 @@ LOCK TABLES `character_equipmentsets` WRITE;
/*!40000 ALTER TABLE `character_equipmentsets` ENABLE KEYS */;
UNLOCK TABLES;
+--
+-- Table structure for table `character_favorite_auctions`
+--
+
+DROP TABLE IF EXISTS `character_favorite_auctions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `character_favorite_auctions` (
+ `guid` bigint(20) unsigned NOT NULL,
+ `order` int(10) unsigned NOT NULL DEFAULT '0',
+ `itemId` int(10) unsigned NOT NULL DEFAULT '0',
+ `itemLevel` int(10) unsigned NOT NULL DEFAULT '0',
+ `battlePetSpeciesId` int(10) unsigned NOT NULL DEFAULT '0',
+ `suffixItemNameDescriptionId` int(10) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`guid`,`order`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `character_favorite_auctions`
+--
+
+LOCK TABLES `character_favorite_auctions` WRITE;
+/*!40000 ALTER TABLE `character_favorite_auctions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `character_favorite_auctions` ENABLE KEYS */;
+UNLOCK TABLES;
+
--
-- Table structure for table `character_fishingsteps`
--
@@ -3696,7 +3769,9 @@ INSERT INTO `updates` VALUES
('2019_11_22_00_characters.sql','95DFA71DBD75542C098CD86E9C0051C9690902F0','RELEASED','2019-11-20 15:10:12',0),
('2019_11_30_00_characters.sql','D0678E62B651AECA60C2DD6989BF80BD999AD12B','RELEASED','2019-11-29 22:42:01',0),
('2019_12_05_00_characters.sql','EA381C9634A5646A3168F15DF4E06A708A622762','RELEASED','2019-12-05 20:56:58',0),
-('2020_02_17_00_characters.sql','E1519A81D35F19B48B3C75A83A270CB4BA0B84F2','RELEASED','2020-02-17 21:55:17',0);
+('2020_02_17_00_characters.sql','E1519A81D35F19B48B3C75A83A270CB4BA0B84F2','RELEASED','2020-02-17 21:55:17',0),
+('2020_04_20_00_characters.sql','977B5E0C894E0A7E80B2A9626F17CA636A69BD22','RELEASED','2020-04-20 19:08:18',0),
+('2020_04_24_00_characters.sql','85E2E0395A9457A53D73A9E0A7BB39B7E4C429BF','RELEASED','2020-04-24 22:04:59',0);
/*!40000 ALTER TABLE `updates` ENABLE KEYS */;
UNLOCK TABLES;
diff --git a/sql/updates/characters/master/2020_04_20_00_characters.sql b/sql/updates/characters/master/2020_04_20_00_characters.sql
new file mode 100644
index 000000000..689f2ea4b
--- /dev/null
+++ b/sql/updates/characters/master/2020_04_20_00_characters.sql
@@ -0,0 +1,46 @@
+ALTER TABLE `auctionhouse` ADD `auctionHouseId` int(10) unsigned NOT NULL DEFAULT '0' AFTER `id`;
+ALTER TABLE `auctionhouse` DROP `auctioneerguid`;
+
+-- temporarily mark all auctions as coming from neutral AH (not goblin one) and expired
+UPDATE `auctionhouse` SET `auctionHouseId`=1, `time`=UNIX_TIMESTAMP();
+
+DROP TABLE IF EXISTS `auction_bidders`;
+CREATE TABLE `auction_bidders` (
+ `auctionId` int unsigned NOT NULL,
+ `playerGuid` bigint unsigned NOT NULL,
+ PRIMARY KEY (`auctionId`,`playerGuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+INSERT INTO `auction_bidders` SELECT `id`, `buyguid` FROM `auctionhouse`;
+
+DROP TABLE IF EXISTS `auction_items`;
+CREATE TABLE `auction_items` (
+ `auctionId` int unsigned NOT NULL,
+ `itemGuid` bigint unsigned NOT NULL,
+ PRIMARY KEY (`auctionId`,`itemGuid`),
+ UNIQUE KEY `idx_itemGuid` (`itemGuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `character_favorite_auctions`;
+CREATE TABLE `character_favorite_auctions` (
+ `guid` bigint(20) unsigned NOT NULL,
+ `order` int(10) unsigned NOT NULL DEFAULT '0',
+ `itemId` int(10) unsigned NOT NULL DEFAULT '0',
+ `itemLevel` int(10) unsigned NOT NULL DEFAULT '0',
+ `battlePetSpeciesId` int(10) unsigned NOT NULL DEFAULT '0',
+ `suffixItemNameDescriptionId` int(10) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`guid`,`order`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+INSERT INTO `auction_items` SELECT `id`, `itemguid` FROM `auctionhouse`;
+
+ALTER TABLE `auctionhouse` DROP INDEX `item_guid`;
+ALTER TABLE `auctionhouse` DROP `itemguid`;
+ALTER TABLE `auctionhouse` CHANGE `itemowner` `owner` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `auctionHouseId`;
+ALTER TABLE `auctionhouse` CHANGE `buyguid` `bidder` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `owner`;
+ALTER TABLE `auctionhouse` CHANGE `startbid` `minBid` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `bidder`;
+ALTER TABLE `auctionhouse` CHANGE `buyoutprice` `buyoutOrUnitPrice` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `lastbid`;
+ALTER TABLE `auctionhouse` MODIFY `deposit` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `buyoutOrUnitPrice`;
+ALTER TABLE `auctionhouse` CHANGE `lastbid` `bidAmount` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `deposit`;
+ALTER TABLE `auctionhouse` ADD `startTime` int(10) unsigned NOT NULL DEFAULT '0' AFTER `bidAmount`;
+ALTER TABLE `auctionhouse` CHANGE `time` `endTime` int(10) unsigned NOT NULL DEFAULT '0' AFTER `startTime`;