aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/Utilities/Util.cpp185
-rw-r--r--src/common/Utilities/Util.h12
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.cpp24
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.h10
-rw-r--r--src/server/game/AuctionHouse/AuctionHouseMgr.cpp1932
-rw-r--r--src/server/game/AuctionHouse/AuctionHouseMgr.h372
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBot.cpp47
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBot.h9
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp148
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h8
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp84
-rw-r--r--src/server/game/AuctionHouseBot/AuctionHouseBotSeller.h5
-rw-r--r--src/server/game/Entities/Player/CollectionMgr.cpp13
-rw-r--r--src/server/game/Entities/Player/CollectionMgr.h2
-rw-r--r--src/server/game/Entities/Player/Player.cpp4
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp3
-rw-r--r--src/server/game/Handlers/AuctionHouseHandler.cpp1178
-rw-r--r--src/server/game/Handlers/CharacterHandler.cpp28
-rw-r--r--src/server/game/Mails/Mail.cpp11
-rw-r--r--src/server/game/Mails/Mail.h5
-rw-r--r--src/server/game/Scripting/ScriptMgr.cpp24
-rw-r--r--src/server/game/Scripting/ScriptMgr.h18
-rw-r--r--src/server/game/Server/Packets/AuctionHousePackets.cpp98
-rw-r--r--src/server/game/Server/Packets/AuctionHousePackets.h70
-rw-r--r--src/server/game/Server/Protocol/Opcodes.cpp44
-rw-r--r--src/server/game/Server/Protocol/Opcodes.h8
-rw-r--r--src/server/game/Server/WorldSession.h58
-rw-r--r--src/server/game/World/World.cpp11
-rw-r--r--src/server/scripts/Commands/cs_reload.cpp1
29 files changed, 3072 insertions, 1340 deletions
diff --git a/src/common/Utilities/Util.cpp b/src/common/Utilities/Util.cpp
index 743fb7e4543..1776815a247 100644
--- a/src/common/Utilities/Util.cpp
+++ b/src/common/Utilities/Util.cpp
@@ -372,7 +372,190 @@ bool WStrToUtf8(std::wstring const& wstr, std::string& utf8str)
return true;
}
-typedef wchar_t const* const* wstrlist;
+std::wstring wstrCaseAccentInsensitiveParse(std::wstring const& wstr, LocaleConstant locale)
+{
+ std::wstring result;
+ result.reserve(wstr.length() * 2);
+
+ switch (locale)
+ {
+ case LOCALE_frFR:
+ for (wchar_t wchar : wstr)
+ {
+ wchar = wcharToLower(wchar);
+ switch (wchar)
+ {
+ case 0x00A0: // NO-BREAK SPACE
+ result += L' ';
+ break;
+ case 0x00AB: // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ case 0x00BB: // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ result += L'"';
+ break;
+ case 0x00E7: // LATIN SMALL LETTER C WITH CEDILLA
+ result += L'c';
+ break;
+ case 0x00E8: // LATIN SMALL LETTER E WITH GRAVE
+ case 0x00E9: // LATIN SMALL LETTER E WITH ACUTE
+ case 0x00EA: // LATIN SMALL LETTER E WITH CIRCUMFLEX
+ case 0x00EB: // LATIN SMALL LETTER E WITH DIAERESIS
+ result += L'e';
+ break;
+ case 0x00EE: // LATIN SMALL LETTER I WITH CIRCUMFLEX
+ case 0x00EF: // LATIN SMALL LETTER I WITH DIAERESIS
+ result += L'i';
+ break;
+ case 0x00F2: // LATIN SMALL LETTER O WITH GRAVE
+ case 0x00F3: // LATIN SMALL LETTER O WITH ACUTE
+ case 0x00F4: // LATIN SMALL LETTER O WITH CIRCUMFLEX
+ case 0x00F6: // LATIN SMALL LETTER O WITH DIAERESIS
+ result += L'o';
+ break;
+ case 0x00F9: // LATIN SMALL LETTER U WITH GRAVE
+ case 0x00FA: // LATIN SMALL LETTER U WITH ACUTE
+ case 0x00FB: // LATIN SMALL LETTER U WITH CIRCUMFLEX
+ case 0x00FC: // LATIN SMALL LETTER U WITH DIAERESIS
+ result += L'u';
+ break;
+ case 0x0153: // LATIN SMALL LIGATURE OE
+ result += L'o';
+ result += L'e';
+ break;
+ case 0x2013: // EN DASH
+ result += L'-';
+ break;
+ case 0x2018: // LEFT SINGLE QUOTATION MARK
+ case 0x2019: // RIGHT SINGLE QUOTATION MARK
+ result += L'\'';
+ break;
+ default:
+ result += wchar;
+ break;
+ }
+ }
+ break;
+ case LOCALE_deDE:
+ for (wchar_t wchar : wstr)
+ {
+ wchar = wcharToLower(wchar);
+ if (wchar == 0x00DF) // LATIN SMALL LETTER SHARP S
+ {
+ result += L's';
+ result += L's';
+ }
+ else
+ result += wchar;
+ }
+ break;
+ case LOCALE_esES:
+ case LOCALE_esMX:
+ case LOCALE_itIT:
+ for (wchar_t wchar : wstr)
+ {
+ wchar = wcharToLower(wchar);
+ switch (wchar)
+ {
+ case 0x00E1: // LATIN SMALL LETTER A WITH ACUTE
+ result += L'a';
+ break;
+ case 0x00E9: // LATIN SMALL LETTER E WITH ACUTE
+ result += L'e';
+ break;
+ case 0x00ED: // LATIN SMALL LETTER I WITH ACUTE
+ result += L'i';
+ break;
+ case 0x00F1: // LATIN SMALL LETTER N WITH TILDE
+ result += L'n';
+ break;
+ case 0x00F3: // LATIN SMALL LETTER O WITH ACUTE
+ result += L'o';
+ break;
+ case 0x00FA: // LATIN SMALL LETTER U WITH ACUTE
+ case 0x00FC: // LATIN SMALL LETTER U WITH DIAERESIS
+ result += L'u';
+ break;
+ default:
+ result += wchar;
+ break;
+ }
+ }
+ break;
+ case LOCALE_ruRU:
+ for (wchar_t wchar : wstr)
+ {
+ wchar = wcharToLower(wchar);
+ switch (wchar)
+ {
+ case 0x451: // CYRILLIC SMALL LETTER IO
+ result += wchar_t(0x435);
+ break;
+ case 0x2013: // EN DASH
+ result += L'-';
+ break;
+ default:
+ result += wchar;
+ break;
+ }
+ }
+ break;
+ case LOCALE_ptBR:
+ for (wchar_t wchar : wstr)
+ {
+ wchar = wcharToLower(wchar);
+ switch (wchar)
+ {
+ case 0x00E0: // LATIN SMALL LETTER A WITH GRAVE
+ case 0x00E1: // LATIN SMALL LETTER A WITH ACUTE
+ case 0x00E2: // LATIN SMALL LETTER A WITH CIRCUMFLEX
+ case 0x00E3: // LATIN SMALL LETTER A WITH TILDE
+ case 0x00E4: // LATIN SMALL LETTER A WITH DIAERESIS
+ result += L'a';
+ break;
+ case 0x00E7: // LATIN SMALL LETTER C WITH CEDILLA
+ result += L'c';
+ break;
+ case 0x00E8: // LATIN SMALL LETTER E WITH GRAVE
+ case 0x00E9: // LATIN SMALL LETTER E WITH ACUTE
+ case 0x00EA: // LATIN SMALL LETTER E WITH CIRCUMFLEX
+ case 0x00EB: // LATIN SMALL LETTER E WITH DIAERESIS
+ result += L'e';
+ break;
+ case 0x00EC: // LATIN SMALL LETTER I WITH GRAVE
+ case 0x00ED: // LATIN SMALL LETTER I WITH ACUTE
+ case 0x00EE: // LATIN SMALL LETTER I WITH CIRCUMFLEX
+ case 0x00EF: // LATIN SMALL LETTER I WITH DIAERESIS
+ result += L'i';
+ break;
+ case 0x00F1: // LATIN SMALL LETTER N WITH TILDE
+ result += L'n';
+ break;
+ case 0x00F2: // LATIN SMALL LETTER O WITH GRAVE
+ case 0x00F3: // LATIN SMALL LETTER O WITH ACUTE
+ case 0x00F4: // LATIN SMALL LETTER O WITH CIRCUMFLEX
+ case 0x00F5: // LATIN SMALL LETTER O WITH TILDE
+ case 0x00F6: // LATIN SMALL LETTER O WITH DIAERESIS
+ result += L'o';
+ break;
+ case 0x00F9: // LATIN SMALL LETTER U WITH GRAVE
+ case 0x00FA: // LATIN SMALL LETTER U WITH ACUTE
+ case 0x00FB: // LATIN SMALL LETTER U WITH CIRCUMFLEX
+ case 0x00FC: // LATIN SMALL LETTER U WITH DIAERESIS
+ result += L'u';
+ break;
+ default:
+ result += wchar;
+ break;
+ }
+ }
+ break;
+ default:
+ result = wstr;
+ wstrToLower(result);
+ break;
+ }
+
+ return result;
+}
void wstrToUpper(std::wstring& str)
{
diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h
index e47be23c95a..20520cb2068 100644
--- a/src/common/Utilities/Util.h
+++ b/src/common/Utilities/Util.h
@@ -23,6 +23,8 @@
#include <string>
#include <vector>
+enum LocaleConstant : uint8;
+
class TC_COMMON_API Tokenizer
{
public:
@@ -247,6 +249,10 @@ inline wchar_t wcharToUpper(wchar_t wchar)
return wchar_t(uint16(wchar)-0x0020);
if (wchar == 0x0451) // CYRILLIC SMALL LETTER IO
return wchar_t(0x0401);
+ if (wchar == 0x0153) // LATIN SMALL LIGATURE OE
+ return wchar_t(0x0152);
+ if (wchar == 0x00FF) // LATIN SMALL LETTER Y WITH DIAERESIS
+ return wchar_t(0x0178);
return wchar;
}
@@ -273,12 +279,18 @@ inline wchar_t wcharToLower(wchar_t wchar)
return wchar_t(0x00DF);
if (wchar == 0x0401) // CYRILLIC CAPITAL LETTER IO
return wchar_t(0x0451);
+ if (wchar == 0x0152) // LATIN CAPITAL LIGATURE OE
+ return wchar_t(0x0153);
+ if (wchar == 0x0178) // LATIN CAPITAL LETTER Y WITH DIAERESIS
+ return wchar_t(0x00FF);
if (wchar >= 0x0410 && wchar <= 0x042F) // CYRILLIC CAPITAL LETTER A - CYRILLIC CAPITAL LETTER YA
return wchar_t(uint16(wchar)+0x0020);
return wchar;
}
+TC_COMMON_API std::wstring wstrCaseAccentInsensitiveParse(std::wstring const& wstr, LocaleConstant locale);
+
TC_COMMON_API void wstrToUpper(std::wstring& str);
TC_COMMON_API void wstrToLower(std::wstring& str);
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp
index 72798a0847a..13f25a9d4bd 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp
@@ -141,6 +141,10 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_SEL_CHARACTER_RANDOMBG, "SELECT guid FROM character_battleground_random WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_BANNED, "SELECT guid FROM character_banned WHERE guid = ? AND active = 1", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUSREW, "SELECT quest FROM character_queststatus_rewarded WHERE guid = ? AND active = 1", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_SEL_CHARACTER_FAVORITE_AUCTIONS, "SELECT `order`, itemId, itemLevel, battlePetSpeciesId, suffixItemNameDescriptionId FROM character_favorite_auctions WHERE guid = ? ORDER BY `order`", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_INS_CHARACTER_FAVORITE_AUCTION, "INSERT INTO character_favorite_auctions (guid, `order`, itemId, itemLevel, battlePetSpeciesId, suffixItemNameDescriptionId) VALUE (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_CHARACTER_FAVORITE_AUCTION, "DELETE FROM character_favorite_auctions WHERE guid = ? AND `order` = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_CHARACTER_FAVORITE_AUCTIONS_BY_CHAR, "DELETE FROM character_favorite_auctions WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_ACCOUNT_INSTANCELOCKTIMES, "SELECT instanceId, releaseTime FROM account_instance_times WHERE accountId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC, "SELECT button, action, type FROM character_action WHERE guid = ? AND spec = ? ORDER BY button", CONNECTION_ASYNC);
@@ -155,11 +159,17 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_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 = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_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 = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_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 = ?", CONNECTION_SYNCH);
- PrepareStatement(CHAR_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", CONNECTION_SYNCH);
- PrepareStatement(CHAR_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", CONNECTION_SYNCH);
- PrepareStatement(CHAR_INS_AUCTION, "INSERT INTO auctionhouse (id, auctioneerguid, itemguid, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
- PrepareStatement(CHAR_DEL_AUCTION, "DELETE FROM auctionhouse WHERE id = ?", CONNECTION_ASYNC);
- PrepareStatement(CHAR_UPD_AUCTION_BID, "UPDATE auctionhouse SET buyguid = ?, lastbid = ? WHERE id = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_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", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_SEL_AUCTIONS, "SELECT id, auctionHouseId, owner, bidder, minBid, buyoutOrUnitPrice, deposit, bidAmount, startTime, endTime FROM auctionhouse", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_INS_AUCTION_ITEMS, "INSERT INTO auction_items (auctionId, itemGuid) VALUES (?, ?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_AUCTION_ITEMS_BY_ITEM, "DELETE FROM auction_items WHERE itemGuid = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_SEL_AUCTION_BIDDERS, "SELECT auctionId, playerGuid FROM auction_bidders", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_INS_AUCTION_BIDDER, "INSERT INTO auction_bidders (auctionId, playerGuid) VALUES (?, ?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_AUCTION_BIDDER_BY_PLAYER, "DELETE FROM auction_bidders WHERE playerGuid = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_INS_AUCTION, "INSERT INTO auctionhouse (id, auctionHouseId, owner, bidder, minBid, buyoutOrUnitPrice, deposit, bidAmount, startTime, endTime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_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 = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_UPD_AUCTION_BID, "UPDATE auctionhouse SET bidder = ?, bidAmount = ? WHERE id = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_UPD_AUCTION_EXPIRATION, "UPDATE auctionhouse SET endTime = ? WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_INS_MAIL, "INSERT INTO mail(id, messageType, stationery, mailTemplateId, sender, receiver, subject, body, has_items, expire_time, deliver_time, money, cod, checked) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_MAIL_BY_ID, "DELETE FROM mail WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_MAIL_ITEM, "INSERT INTO mail_items(mail_id, item_guid, receiver) VALUES (?, ?, ?)", CONNECTION_ASYNC);
@@ -542,7 +552,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_DEL_CHAR_AURA_FROZEN, "DELETE FROM character_aura WHERE spell = 9454 AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_INVENTORY_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item WHERE itemEntry = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_MAIL_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM mail_items mi INNER JOIN item_instance ii ON ii.guid = mi.item_guid WHERE itemEntry = ?", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_AUCTIONHOUSE_COUNT_ITEM,"SELECT COUNT(itemEntry) FROM auctionhouse ah INNER JOIN item_instance ii ON ii.guid = ah.itemguid WHERE itemEntry = ?", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_SEL_AUCTIONHOUSE_COUNT_ITEM,"SELECT COUNT(*) FROM auction_items ai INNER JOIN item_instance ii ON ii.guid = ai.itemGuid WHERE ii.itemEntry = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_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 = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_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 "
@@ -551,7 +561,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_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 ?", CONNECTION_SYNCH);
- PrepareStatement(CHAR_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 ?", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_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 ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_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 ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_CHAR_ACHIEVEMENT, "DELETE FROM character_achievement WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS, "DELETE FROM character_achievement_progress WHERE guid = ?", CONNECTION_ASYNC);
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h
index 230a901bc74..8e1f12416b0 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.h
+++ b/src/server/database/Database/Implementation/CharacterDatabase.h
@@ -114,6 +114,10 @@ enum CharacterDatabaseStatements : uint32
CHAR_SEL_CHARACTER_RANDOMBG,
CHAR_SEL_CHARACTER_BANNED,
CHAR_SEL_CHARACTER_QUESTSTATUSREW,
+ CHAR_SEL_CHARACTER_FAVORITE_AUCTIONS,
+ CHAR_INS_CHARACTER_FAVORITE_AUCTION,
+ CHAR_DEL_CHARACTER_FAVORITE_AUCTION,
+ CHAR_DEL_CHARACTER_FAVORITE_AUCTIONS_BY_CHAR,
CHAR_SEL_ACCOUNT_INSTANCELOCKTIMES,
CHAR_SEL_MAILITEMS,
CHAR_SEL_MAILITEMS_ARTIFACT,
@@ -125,7 +129,13 @@ enum CharacterDatabaseStatements : uint32
CHAR_INS_AUCTION,
CHAR_DEL_AUCTION,
CHAR_UPD_AUCTION_BID,
+ CHAR_UPD_AUCTION_EXPIRATION,
CHAR_SEL_AUCTIONS,
+ CHAR_INS_AUCTION_ITEMS,
+ CHAR_DEL_AUCTION_ITEMS_BY_ITEM,
+ CHAR_SEL_AUCTION_BIDDERS,
+ CHAR_INS_AUCTION_BIDDER,
+ CHAR_DEL_AUCTION_BIDDER_BY_PLAYER,
CHAR_INS_MAIL,
CHAR_DEL_MAIL_BY_ID,
CHAR_INS_MAIL_ITEM,
diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp
index 347dfc4a9fb..ca8691bb8ec 100644
--- a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp
+++ b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp
@@ -22,7 +22,9 @@
#include "Bag.h"
#include "DB2Stores.h"
#include "CharacterCache.h"
+#include "CollectionMgr.h"
#include "Common.h"
+#include "Containers.h"
#include "DatabaseEnv.h"
#include "GameTime.h"
#include "Language.h"
@@ -33,9 +35,12 @@
#include "Player.h"
#include "Realm.h"
#include "ScriptMgr.h"
+#include "SpellMgr.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
+#include <boost/dynamic_bitset.hpp>
+#include <numeric>
#include <sstream>
#include <vector>
@@ -44,352 +49,614 @@ enum eAuctionHouse
AH_MINIMUM_DEPOSIT = 100
};
-AuctionHouseMgr::AuctionHouseMgr() { }
-
-AuctionHouseMgr::~AuctionHouseMgr()
+AuctionsBucketKey::AuctionsBucketKey(WorldPackets::AuctionHouse::AuctionBucketKey const& key) :
+ ItemId(key.ItemID), ItemLevel(key.ItemLevel), BattlePetSpeciesId(key.BattlePetSpeciesID.value_or(0)),
+ SuffixItemNameDescriptionId(key.SuffixItemNameDescriptionID.value_or(0))
{
- for (ItemMap::iterator itr = mAitems.begin(); itr != mAitems.end(); ++itr)
- delete itr->second;
}
-AuctionHouseMgr* AuctionHouseMgr::instance()
+std::size_t AuctionsBucketKey::Hash(AuctionsBucketKey const& key)
{
- static AuctionHouseMgr instance;
- return &instance;
+ std::size_t hashVal = 0;
+ Trinity::hash_combine(hashVal, std::hash<uint32>()(key.ItemId));
+ Trinity::hash_combine(hashVal, std::hash<uint16>()(key.ItemLevel));
+ Trinity::hash_combine(hashVal, std::hash<uint16>()(key.BattlePetSpeciesId));
+ Trinity::hash_combine(hashVal, std::hash<uint16>()(key.SuffixItemNameDescriptionId));
+ return hashVal;
}
-AuctionHouseObject* AuctionHouseMgr::GetAuctionsMap(uint32 factionTemplateId)
+AuctionsBucketKey AuctionsBucketKey::ForItem(Item* item)
{
- if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
- return &mNeutralAuctions;
-
- // teams have linked auction houses
- FactionTemplateEntry const* uEntry = sFactionTemplateStore.LookupEntry(factionTemplateId);
- if (!uEntry)
- return &mNeutralAuctions;
- else if (uEntry->FactionGroup & FACTION_MASK_ALLIANCE)
- return &mAllianceAuctions;
- else if (uEntry->FactionGroup & FACTION_MASK_HORDE)
- return &mHordeAuctions;
+ ItemTemplate const* itemTemplate = item->GetTemplate();
+ if (itemTemplate->GetMaxStackSize() == 1)
+ {
+ return
+ {
+ item->GetEntry(),
+ uint16(Item::GetItemLevel(itemTemplate, *item->GetBonus(), 0, item->GetRequiredLevel(), 0, 0, 0, false, 0)),
+ uint16(item->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID)),
+ uint16(item->GetBonus()->Suffix)
+ };
+ }
else
- return &mNeutralAuctions;
+ return ForCommodity(item->GetEntry());
}
-uint64 AuctionHouseMgr::GetAuctionDeposit(AuctionHouseEntry const* entry, uint32 time, Item* pItem, uint32 count)
+AuctionsBucketKey AuctionsBucketKey::ForCommodity(uint32 itemId)
{
- uint32 MSV = pItem->GetTemplate()->GetSellPrice();
+ return { itemId, 0, 0, 0 };
+}
- if (MSV <= 0)
- return AH_MINIMUM_DEPOSIT * sWorld->getRate(RATE_AUCTION_DEPOSIT);
+bool operator<(AuctionsBucketKey const& left, AuctionsBucketKey const& right)
+{
+ if (left.ItemId < right.ItemId)
+ return true;
+ if (left.ItemId > right.ItemId)
+ return false;
+ if (left.ItemLevel < right.ItemLevel)
+ return true;
+ if (left.ItemLevel > right.ItemLevel)
+ return false;
+ if (left.BattlePetSpeciesId < right.BattlePetSpeciesId)
+ return true;
+ if (left.BattlePetSpeciesId > right.BattlePetSpeciesId)
+ return false;
+ if (left.SuffixItemNameDescriptionId < right.SuffixItemNameDescriptionId)
+ return true;
+ //if (left.SuffixItemNameDescriptionId > right.SuffixItemNameDescriptionId)
+ // return false;
- float multiplier = CalculatePct(float(entry->DepositRate), 3);
- uint32 timeHr = (((time / 60) / 60) / 12);
- uint64 deposit = uint64(MSV * multiplier * sWorld->getRate(RATE_AUCTION_DEPOSIT));
- float remainderbase = float(MSV * multiplier * sWorld->getRate(RATE_AUCTION_DEPOSIT)) - deposit;
+ // everything equal
+ return false;
+}
- deposit *= timeHr * count;
+void AuctionsBucketData::BuildBucketInfo(WorldPackets::AuctionHouse::BucketInfo* bucketInfo, Player* player) const
+{
+ bucketInfo->Key = Key;
+ bucketInfo->MinPrice = MinPrice;
+ bucketInfo->TotalQuantity = 0;
- int i = count;
- while (i > 0 && (remainderbase * i) != uint32(remainderbase * i))
- i--;
+ for (AuctionPosting const* auction : Auctions)
+ {
+ for (Item* item : auction->Items)
+ {
+ bucketInfo->TotalQuantity += item->GetCount();
- if (i)
- deposit += remainderbase * i * timeHr;
+ if (Key.BattlePetSpeciesId)
+ {
+ uint32 breedData = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA);
+ uint32 breedId = breedData & 0xFFFFFF;
+ uint8 quality = uint8((breedData >> 24) & 0xFF);
+ uint8 level = uint8(item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL));
+
+ bucketInfo->MaxBattlePetQuality = bucketInfo->MaxBattlePetQuality ? std::max(*bucketInfo->MaxBattlePetQuality, quality) : quality;
+ bucketInfo->MaxBattlePetLevel = bucketInfo->MaxBattlePetLevel ? std::max(*bucketInfo->MaxBattlePetLevel, level) : level;
+ bucketInfo->BattlePetBreedID = breedId;
+ }
+ }
- TC_LOG_DEBUG("auctionHouse", "MSV: %u", MSV);
- TC_LOG_DEBUG("auctionHouse", "Items: %u", count);
- TC_LOG_DEBUG("auctionHouse", "Multiplier: %f", multiplier);
- TC_LOG_DEBUG("auctionHouse", "Deposit: " UI64FMTD, deposit);
- TC_LOG_DEBUG("auctionHouse", "Deposit rm: %f", remainderbase * count);
+ bucketInfo->ContainsOwnerItem = bucketInfo->ContainsOwnerItem || auction->Owner == player->GetGUID();
+ }
- if (deposit < AH_MINIMUM_DEPOSIT * sWorld->getRate(RATE_AUCTION_DEPOSIT))
- return AH_MINIMUM_DEPOSIT * sWorld->getRate(RATE_AUCTION_DEPOSIT);
- else
- return deposit;
+ bucketInfo->ContainsOnlyCollectedAppearances = true;
+ for (std::pair<uint32, uint32> appearance : ItemModifiedAppearanceId)
+ {
+ if (appearance.first)
+ {
+ bucketInfo->ItemModifiedAppearanceIDs.push_back(appearance.first);
+ if (!player->GetSession()->GetCollectionMgr()->HasItemAppearance(appearance.first).first)
+ bucketInfo->ContainsOnlyCollectedAppearances = false;
+ }
+ }
}
-//does not clear ram
-void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry* auction, CharacterDatabaseTransaction& trans)
+bool AuctionPosting::IsCommodity() const
{
- Item* item = GetAItem(auction->itemGUIDLow);
- if (!item)
- return;
+ return Items.size() > 1 || Items[0]->GetTemplate()->GetMaxStackSize() > 1;
+}
- uint32 bidderAccId = 0;
- ObjectGuid bidderGuid = ObjectGuid::Create<HighGuid::Player>(auction->bidder);
- Player* bidder = ObjectAccessor::FindConnectedPlayer(bidderGuid);
- // data for gm.log
- std::string bidderName;
- bool logGmTrade = false;
+uint32 AuctionPosting::GetTotalItemCount() const
+{
+ return std::accumulate(Items.begin(), Items.end(), 0u, [](uint32 totalCount, Item* item)
+ {
+ return totalCount + item->GetCount();
+ });
+}
- if (bidder)
+void AuctionPosting::BuildAuctionItem(WorldPackets::AuctionHouse::AuctionItem* auctionItem,
+ bool alwaysSendItem, bool sendKey, bool censorServerInfo, bool censorBidInfo) const
+{
+ // 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 = int32(GetTotalItemCount());
+ auctionItem->Flags = 0;
+ auctionItem->AuctionID = Id;
+ auctionItem->Owner = Owner;
+
+ // prices set when filled
+ if (IsCommodity())
{
- bidderAccId = bidder->GetSession()->GetAccountId();
- bidderName = bidder->GetName();
- logGmTrade = bidder->GetSession()->HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE);
+ if (alwaysSendItem)
+ {
+ auctionItem->Item.emplace();
+ auctionItem->Item->Initialize(Items[0]);
+ }
+
+ auctionItem->UnitPrice = BuyoutOrUnitPrice;
}
else
{
- bidderAccId = sCharacterCache->GetCharacterAccountIdByGuid(bidderGuid);
- logGmTrade = AccountMgr::HasPermission(bidderAccId, rbac::RBAC_PERM_LOG_GM_TRADE, realm.Id.Realm);
+ auctionItem->Item.emplace();
+ auctionItem->Item->Initialize(Items[0]);
+ auctionItem->Charges = std::max({ Items[0]->GetSpellCharges(0), Items[0]->GetSpellCharges(1), Items[0]->GetSpellCharges(2), Items[0]->GetSpellCharges(3), Items[0]->GetSpellCharges(4) });
+ for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; i++)
+ {
+ uint32 enchantId = Items[0]->GetEnchantmentId(EnchantmentSlot(i));
+ if (!enchantId)
+ continue;
- if (logGmTrade && !sCharacterCache->GetCharacterNameByGuid(bidderGuid, bidderName))
- bidderName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
+ auctionItem->Enchantments.emplace_back(enchantId, Items[0]->GetEnchantmentDuration(EnchantmentSlot(i)), Items[0]->GetEnchantmentCharges(EnchantmentSlot(i)), i);
+ }
+
+ for (uint8 i = 0; i < Items[0]->m_itemData->Gems.size(); ++i)
+ {
+ UF::SocketedGem const* gemData = &Items[0]->m_itemData->Gems[i];
+ if (gemData->ItemID)
+ {
+ WorldPackets::Item::ItemGemData gem;
+ gem.Slot = i;
+ gem.Item.Initialize(gemData);
+ auctionItem->Gems.push_back(gem);
+ }
+ }
+
+ if (MinBid)
+ auctionItem->MinBid = MinBid;
+
+ if (uint64 minIncrement = CalculateMinIncrement())
+ auctionItem->MinIncrement = minIncrement;
+
+ if (BuyoutOrUnitPrice)
+ auctionItem->BuyoutPrice = BuyoutOrUnitPrice;
}
- if (logGmTrade)
+ // all (not optional<>)
+ auctionItem->DurationLeft = uint32(std::max(std::chrono::duration_cast<Milliseconds>(EndTime - GameTime::GetGameTimeSystemPoint()).count(), Milliseconds::zero().count()));
+ 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 = uint32(std::chrono::system_clock::to_time_t(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())
{
- ObjectGuid ownerGuid = ObjectGuid::Create<HighGuid::Player>(auction->owner);
- std::string ownerName;
- if (!sCharacterCache->GetCharacterNameByGuid(ownerGuid, ownerName))
- ownerName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
+ auctionItem->Bidder = Bidder;
+ auctionItem->BidAmount = BidAmount;
+ }
- uint32 ownerAccId = sCharacterCache->GetCharacterAccountIdByGuid(ownerGuid);
+ // SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT, SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT, SMSG_AUCTION_REPLICATE_RESPONSE (if commodity)
+ if (sendKey)
+ auctionItem->AuctionBucketKey.emplace(AuctionsBucketKey::ForItem(Items[0]));
+}
- sLog->outCommand(bidderAccId, "GM %s (Account: %u) won item in auction: %s (Entry: %u Count: %u) and pay money: " UI64FMTD ". Original owner %s (Account: %u)",
- bidderName.c_str(), bidderAccId, item->GetTemplate()->GetDefaultLocaleName(), item->GetEntry(), item->GetCount(), auction->bid, ownerName.c_str(), ownerAccId);
+uint64 AuctionPosting::CalculateMinIncrement(uint64 bidAmount)
+{
+ return CalculatePct(bidAmount / SILVER /*ignore copper*/, 5) * SILVER;
+}
+
+class AuctionsBucketData::Sorter
+{
+public:
+ Sorter(LocaleConstant locale, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount)
+ : _locale(locale), _sorts(sorts), _sortCount(sortCount) { }
+
+ bool operator()(AuctionsBucketData const* left, AuctionsBucketData const* right) const
+ {
+ for (std::size_t i = 0; i < _sortCount; ++i)
+ {
+ int32 ordering = CompareColumns(_sorts[i].SortOrder, left, right);
+ if (ordering != 0)
+ return (ordering < 0) == !_sorts[i].ReverseSort;
+ }
+
+ return left->Key < right->Key;
}
- // receiver exist
- if ((bidder || bidderAccId) && !sAuctionBotConfig->IsBotChar(auction->bidder))
+private:
+ int32 CompareColumns(AuctionHouseSortOrder column, AuctionsBucketData const* left, AuctionsBucketData const* right) const
{
- // set owner to bidder (to prevent delete item with sender char deleting)
- // owner in `data` will set at mail receive and item extracting
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_OWNER);
- stmt->setUInt64(0, auction->bidder);
- stmt->setUInt64(1, item->GetGUID().GetCounter());
- trans->Append(stmt);
+ switch (column)
+ {
+ case AuctionHouseSortOrder::Price:
+ case AuctionHouseSortOrder::Bid:
+ case AuctionHouseSortOrder::Buyout:
+ return int64(left->MinPrice) - int64(right->MinPrice);
+ case AuctionHouseSortOrder::Name:
+ return left->FullName[_locale].compare(right->FullName[_locale]);
+ case AuctionHouseSortOrder::Level:
+ {
+ int32 leftLevel = !left->MaxBattlePetLevel ? left->RequiredLevel : left->MaxBattlePetLevel;
+ int32 rightLevel = !right->MaxBattlePetLevel ? right->RequiredLevel : right->MaxBattlePetLevel;
+ return leftLevel - rightLevel;
+ }
+ default:
+ break;
+ }
- if (bidder)
+ return 0;
+ }
+
+ LocaleConstant _locale;
+ WorldPackets::AuctionHouse::AuctionSortDef const* _sorts;
+ std::size_t _sortCount;
+};
+
+class AuctionPosting::Sorter
+{
+public:
+ Sorter(LocaleConstant locale, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount)
+ : _locale(locale), _sorts(sorts), _sortCount(sortCount) { }
+
+ bool operator()(AuctionPosting const* left, AuctionPosting const* right) const
+ {
+ for (std::size_t i = 0; i < _sortCount; ++i)
{
- bidder->GetSession()->SendAuctionWonNotification(auction, item);
- // FIXME: for offline player need also
- bidder->UpdateCriteria(CRITERIA_TYPE_WON_AUCTIONS, 1);
+ int32 ordering = CompareColumns(_sorts[i].SortOrder, left, right);
+ if (ordering != 0)
+ return (ordering < 0) == !_sorts[i].ReverseSort;
}
- MailDraft(auction->BuildAuctionMailSubject(AUCTION_WON), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, 0, 0))
- .AddItem(item)
- .SendMailTo(trans, MailReceiver(bidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
+ // Auctions are processed in LIFO order
+ if (left->StartTime != right->StartTime)
+ return left->StartTime > right->StartTime;
+
+ return left->Id > right->Id;
}
- else
+
+private:
+ int32 CompareColumns(AuctionHouseSortOrder column, AuctionPosting const* left, AuctionPosting const* right) const
{
- // bidder doesn't exist, delete the item
- sAuctionMgr->RemoveAItem(auction->itemGUIDLow, true);
+ switch (column)
+ {
+ case AuctionHouseSortOrder::Price:
+ {
+ int64 leftPrice = left->BuyoutOrUnitPrice ? left->BuyoutOrUnitPrice : (left->BidAmount ? left->BidAmount : left->MinBid);
+ int64 rightPrice = right->BuyoutOrUnitPrice ? right->BuyoutOrUnitPrice : (right->BidAmount ? right->BidAmount : right->MinBid);
+ return leftPrice - rightPrice;
+ }
+ case AuctionHouseSortOrder::Name:
+ return left->Bucket->FullName[_locale].compare(right->Bucket->FullName[_locale]);
+ case AuctionHouseSortOrder::Level:
+ {
+ int32 leftLevel = !left->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID)
+ ? left->Items[0]->GetRequiredLevel()
+ : left->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
+ int32 rightLevel = !right->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID)
+ ? right->Items[0]->GetRequiredLevel()
+ : right->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
+ return leftLevel - rightLevel;
+ }
+ case AuctionHouseSortOrder::Bid:
+ return int64(left->BidAmount) - int64(right->BidAmount);
+ case AuctionHouseSortOrder::Buyout:
+ return int64(left->BuyoutOrUnitPrice) - int64(right->BuyoutOrUnitPrice);
+ default:
+ break;
+ }
+
+ return 0;
}
-}
-void AuctionHouseMgr::SendAuctionSalePendingMail(AuctionEntry* auction, CharacterDatabaseTransaction& trans)
-{
- ObjectGuid owner_guid = ObjectGuid::Create<HighGuid::Player>(auction->owner);
- Player* owner = ObjectAccessor::FindConnectedPlayer(owner_guid);
- uint32 owner_accId = sCharacterCache->GetCharacterAccountIdByGuid(owner_guid);
- // owner exist (online or offline)
- if ((owner || owner_accId) && !sAuctionBotConfig->IsBotChar(auction->owner))
- MailDraft(auction->BuildAuctionMailSubject(AUCTION_SALE_PENDING), AuctionEntry::BuildAuctionMailBody(auction->bidder, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut()))
- .SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED);
-}
+ LocaleConstant _locale;
+ WorldPackets::AuctionHouse::AuctionSortDef const* _sorts;
+ std::size_t _sortCount;
+};
-//call this method to send mail to auction owner, when auction is successful, it does not clear ram
-void AuctionHouseMgr::SendAuctionSuccessfulMail(AuctionEntry* auction, CharacterDatabaseTransaction& trans)
+template<typename T>
+class AuctionsResultBuilder
{
- ObjectGuid owner_guid = ObjectGuid::Create<HighGuid::Player>(auction->owner);
- Player* owner = ObjectAccessor::FindConnectedPlayer(owner_guid);
- uint32 owner_accId = sCharacterCache->GetCharacterAccountIdByGuid(owner_guid);
- Item* item = GetAItem(auction->itemGUIDLow);
+public:
+ using Sorter = typename T::Sorter;
- // owner exist
- if ((owner || owner_accId) && !sAuctionBotConfig->IsBotChar(auction->owner))
+ AuctionsResultBuilder(uint32 offset, LocaleConstant locale, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount, AuctionHouseResultLimits maxResults)
+ : _offset(offset), _sorter(locale, sorts, sortCount), _maxResults(AsUnderlyingType(maxResults)), _hasMoreResults(false)
{
- uint64 profit = auction->bid + auction->deposit - auction->GetAuctionCut();
+ _items.reserve(_maxResults + offset + 1);
+ }
- //FIXME: what do if owner offline
- if (owner && item)
+ void AddItem(T const* item)
+ {
+ auto where = std::lower_bound(_items.begin(), _items.end(), item, std::cref(_sorter));
+
+ _items.insert(where, item);
+ if (_items.size() > _maxResults + _offset)
{
- owner->UpdateCriteria(CRITERIA_TYPE_GOLD_EARNED_BY_AUCTIONS, profit);
- owner->UpdateCriteria(CRITERIA_TYPE_HIGHEST_AUCTION_SOLD, auction->bid);
- //send auction owner notification, bidder must be current!
- owner->GetSession()->SendAuctionClosedNotification(auction, (float)sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), true, item);
+ _items.pop_back();
+ _hasMoreResults = true;
}
+ }
- MailDraft(auction->BuildAuctionMailSubject(AUCTION_SUCCESSFUL), AuctionEntry::BuildAuctionMailBody(auction->bidder, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut()))
- .AddMoney(profit)
- .SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED, sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY));
+ Trinity::IteratorPair<typename std::vector<T const*>::const_iterator> GetResultRange() const
+ {
+ return std::make_pair(_items.begin() + _offset, _items.end());
}
+
+ bool HasMoreResults() const
+ {
+ return _hasMoreResults;
+ }
+
+private:
+ uint32 _offset;
+ Sorter _sorter;
+ std::size_t _maxResults;
+ std::vector<T const*> _items;
+ bool _hasMoreResults;
+};
+
+AuctionHouseMgr::AuctionHouseMgr() : mHordeAuctions(6), mAllianceAuctions(2), mNeutralAuctions(1), mGoblinAuctions(7), _replicateIdGenerator(0)
+{
+ _playerThrottleObjectsCleanupTime = GameTime::GetGameTimeSteadyPoint() + Hours(1);
}
-//does not clear ram
-void AuctionHouseMgr::SendAuctionExpiredMail(AuctionEntry* auction, CharacterDatabaseTransaction& trans)
+AuctionHouseMgr::~AuctionHouseMgr()
{
- //return an item in auction to its owner by mail
- Item* item = GetAItem(auction->itemGUIDLow);
- if (!item)
- return;
+ for (std::pair<ObjectGuid const, Item*>& itemPair : _itemsByGuid)
+ delete itemPair.second;
+}
- ObjectGuid owner_guid = ObjectGuid::Create<HighGuid::Player>(auction->owner);
- Player* owner = ObjectAccessor::FindConnectedPlayer(owner_guid);
- uint32 owner_accId = sCharacterCache->GetCharacterAccountIdByGuid(owner_guid);
- // owner exist
- if ((owner || owner_accId) && !sAuctionBotConfig->IsBotChar(auction->owner))
- {
- if (owner)
- owner->GetSession()->SendAuctionClosedNotification(auction, 0.0f, false, item);
+AuctionHouseMgr* AuctionHouseMgr::instance()
+{
+ static AuctionHouseMgr instance;
+ return &instance;
+}
- MailDraft(auction->BuildAuctionMailSubject(AUCTION_EXPIRED), AuctionEntry::BuildAuctionMailBody(0, 0, auction->buyout, auction->deposit, 0))
- .AddItem(item)
- .SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED, 0);
- }
+AuctionHouseObject* AuctionHouseMgr::GetAuctionsMap(uint32 factionTemplateId)
+{
+ if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
+ return &mNeutralAuctions;
+
+ // teams have linked auction houses
+ FactionTemplateEntry const* uEntry = sFactionTemplateStore.LookupEntry(factionTemplateId);
+ if (!uEntry)
+ return &mNeutralAuctions;
+ else if (uEntry->FactionGroup & FACTION_MASK_ALLIANCE)
+ return &mAllianceAuctions;
+ else if (uEntry->FactionGroup & FACTION_MASK_HORDE)
+ return &mHordeAuctions;
else
+ return &mNeutralAuctions;
+}
+
+AuctionHouseObject* AuctionHouseMgr::GetAuctionsById(uint32 auctionHouseId)
+{
+ switch (auctionHouseId)
{
- // owner doesn't exist, delete the item
- sAuctionMgr->RemoveAItem(auction->itemGUIDLow, true);
+ case 1:
+ return &mNeutralAuctions;
+ case 2:
+ return &mAllianceAuctions;
+ case 6:
+ return &mHordeAuctions;
+ case 7:
+ return &mGoblinAuctions;
+ default:
+ break;
}
+ return &mNeutralAuctions;
}
-//this function sends mail to old bidder
-void AuctionHouseMgr::SendAuctionOutbiddedMail(AuctionEntry* auction, uint64 /*newPrice*/, Player* /*newBidder*/, CharacterDatabaseTransaction& trans)
+Item* AuctionHouseMgr::GetAItem(ObjectGuid itemGuid)
{
- ObjectGuid oldBidder_guid = ObjectGuid::Create<HighGuid::Player>(auction->bidder);
- Player* oldBidder = ObjectAccessor::FindConnectedPlayer(oldBidder_guid);
+ return Trinity::Containers::MapGetValuePtr(_itemsByGuid, itemGuid);
+}
- uint32 oldBidder_accId = 0;
- if (!oldBidder)
- oldBidder_accId = sCharacterCache->GetCharacterAccountIdByGuid(oldBidder_guid);
+uint64 AuctionHouseMgr::GetCommodityAuctionDeposit(ItemTemplate const* item, Minutes time, uint32 quantity)
+{
+ uint32 sellPrice = item->GetSellPrice();
+ return uint64(std::ceil(std::floor(fmax(0.15 * quantity * sellPrice, 100.0)) / SILVER) * SILVER) * (time.count() / (MIN_AUCTION_TIME / MINUTE));
+}
- Item* item = GetAItem(auction->itemGUIDLow);
+uint64 AuctionHouseMgr::GetItemAuctionDeposit(Player* player, Item* item, Minutes time)
+{
+ uint32 sellPrice = item->GetSellPrice(player);
+ return uint64(std::ceil(std::floor(fmax(sellPrice * 0.15, 100.0)) / SILVER) * SILVER) * (time.count() / (MIN_AUCTION_TIME / MINUTE));
+}
- // old bidder exist
- if ((oldBidder || oldBidder_accId) && !sAuctionBotConfig->IsBotChar(auction->bidder))
- {
- if (oldBidder && item)
- oldBidder->GetSession()->SendAuctionOutBidNotification(auction, item);
+std::string AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType type, AuctionPosting const* auction)
+{
+ return BuildAuctionMailSubject(auction->Items[0]->GetEntry(), type, auction->Id, auction->GetTotalItemCount(),
+ auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID), auction->Items[0]->GetContext(), *auction->Items[0]->m_itemData->BonusListIDs);
+}
- MailDraft(auction->BuildAuctionMailSubject(AUCTION_OUTBIDDED), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut()))
- .AddMoney(auction->bid)
- .SendMailTo(trans, MailReceiver(oldBidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
- }
+std::string AuctionHouseMgr::BuildCommodityAuctionMailSubject(AuctionMailType type, uint32 itemId, uint32 itemCount)
+{
+ return BuildAuctionMailSubject(itemId, type, 0, itemCount, 0, ItemContext::NONE, {});
}
-//this function sends mail, when auction is cancelled to old bidder
-void AuctionHouseMgr::SendAuctionCancelledToBidderMail(AuctionEntry* auction, CharacterDatabaseTransaction& trans)
+std::string AuctionHouseMgr::BuildAuctionMailSubject(uint32 itemId, AuctionMailType type, uint32 auctionId, uint32 itemCount, uint32 battlePetSpeciesId,
+ ItemContext context, std::vector<int32> const& bonusListIds)
{
- ObjectGuid bidder_guid = ObjectGuid::Create<HighGuid::Player>(auction->bidder);
- Player* bidder = ObjectAccessor::FindConnectedPlayer(bidder_guid);
+ std::ostringstream strm;
+ strm
+ << itemId << ':'
+ << "0:" // OLD: itemRandomPropertiesId
+ << AsUnderlyingType(type) << ':'
+ << auctionId << ':'
+ << itemCount << ':'
+ << battlePetSpeciesId << ':'
+ << "0:"
+ << "0:"
+ << "0:"
+ << "0:"
+ << uint32(context) << ':'
+ << bonusListIds.size();
+
+ for (int32 bonusListId : bonusListIds)
+ strm << ':' << bonusListId;
+
+ return strm.str();
+}
- uint32 bidder_accId = 0;
+std::string AuctionHouseMgr::BuildAuctionWonMailBody(ObjectGuid guid, uint64 bid, uint64 buyout)
+{
+ return Trinity::StringFormat("%s:" UI64FMTD ":" UI64FMTD ":0", guid.ToString().c_str(), bid, buyout);
+}
- if (!bidder)
- bidder_accId = sCharacterCache->GetCharacterAccountIdByGuid(bidder_guid);
+std::string AuctionHouseMgr::BuildAuctionSoldMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment)
+{
+ return Trinity::StringFormat("%s:" UI64FMTD ":" UI64FMTD ":%u:" UI64FMTD ":0", guid.ToString().c_str(), bid, buyout, deposit, consignment);
+}
- // bidder exist
- if ((bidder || bidder_accId) && !sAuctionBotConfig->IsBotChar(auction->bidder))
- MailDraft(auction->BuildAuctionMailSubject(AUCTION_CANCELLED_TO_BIDDER), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, auction->deposit, 0))
- .AddMoney(auction->bid)
- .SendMailTo(trans, MailReceiver(bidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
+std::string AuctionHouseMgr::BuildAuctionInvoiceMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment, uint32 moneyDelay, uint32 eta)
+{
+ return Trinity::StringFormat("%s:" UI64FMTD ":" UI64FMTD ":%u:" UI64FMTD ":%u:%u:0", guid.ToString().c_str(), bid, buyout, deposit, consignment, moneyDelay, eta);
}
-void AuctionHouseMgr::LoadAuctionItems()
+void AuctionHouseMgr::LoadAuctions()
{
uint32 oldMSTime = getMSTime();
// need to clear in case we are reloading
- if (!mAitems.empty())
+ if (!_itemsByGuid.empty())
{
- for (ItemMap::iterator itr = mAitems.begin(); itr != mAitems.end(); ++itr)
- delete itr->second;
+ for (std::pair<ObjectGuid const, Item*>& itemPair : _itemsByGuid)
+ delete itemPair.second;
- mAitems.clear();
+ _itemsByGuid.clear();
}
// data needs to be at first place for Item::LoadFromDB
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTION_ITEMS);
- PreparedQueryResult result = CharacterDatabase.Query(stmt);
-
- if (!result)
- {
- TC_LOG_INFO("server.loading", ">> Loaded 0 auction items. DB table `auctionhouse` or `item_instance` is empty!");
- return;
- }
-
uint32 count = 0;
+ std::unordered_map<uint32, std::vector<Item*>> itemsByAuction;
+ std::unordered_map<uint32, GuidUnorderedSet> biddersByAuction;
- do
+ if (PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTION_ITEMS)))
{
- Field* fields = result->Fetch();
+ do
+ {
+ Field* fields = result->Fetch();
- ObjectGuid::LowType itemGuid = fields[0].GetUInt64();
- uint32 itemEntry = fields[1].GetUInt32();
+ ObjectGuid::LowType itemGuid = fields[0].GetUInt64();
+ uint32 itemEntry = fields[1].GetUInt32();
- ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry);
- if (!proto)
- {
- TC_LOG_ERROR("misc", "AuctionHouseMgr::LoadAuctionItems: Unknown item (GUID: " UI64FMTD " item entry: #%u) in auction, skipped.", itemGuid, itemEntry);
- continue;
- }
+ ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry);
+ if (!proto)
+ {
+ TC_LOG_ERROR("misc", "AuctionHouseMgr::LoadAuctionItems: Unknown item (GUID: " UI64FMTD " item entry: #%u) in auction, skipped.", itemGuid, itemEntry);
+ continue;
+ }
- Item* item = NewItemOrBag(proto);
- if (!item->LoadFromDB(itemGuid, ObjectGuid::Empty, fields, itemEntry))
- {
- delete item;
- continue;
- }
- AddAItem(item);
+ Item* item = NewItemOrBag(proto);
+ if (!item->LoadFromDB(itemGuid, ObjectGuid::Create<HighGuid::Player>(fields[43].GetUInt64()), fields, itemEntry))
+ {
+ delete item;
+ continue;
+ }
+ uint32 auctionId = fields[44].GetUInt32();
+ itemsByAuction[auctionId].push_back(item);
- ++count;
+ ++count;
+ } while (result->NextRow());
}
- while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded %u auction items in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
-}
-void AuctionHouseMgr::LoadAuctions()
-{
- uint32 oldMSTime = getMSTime();
+ oldMSTime = getMSTime();
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTIONS);
- PreparedQueryResult result = CharacterDatabase.Query(stmt);
+ count = 0;
- if (!result)
+ if (PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTION_BIDDERS)))
{
- TC_LOG_INFO("server.loading", ">> Loaded 0 auctions. DB table `auctionhouse` is empty.");
- return;
+ do
+ {
+ Field* fields = result->Fetch();
+ biddersByAuction[fields[0].GetUInt32()].insert(ObjectGuid::Create<HighGuid::Player>(fields[1].GetUInt64()));
+
+ } while (result->NextRow());
}
- uint32 count = 0;
+ TC_LOG_INFO("server.loading", ">> Loaded %u auction bidders in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
- CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
- do
- {
- Field* fields = result->Fetch();
+ oldMSTime = getMSTime();
+
+ count = 0;
- AuctionEntry* aItem = new AuctionEntry();
- if (!aItem->LoadFromDB(fields))
+ if (PreparedQueryResult result = CharacterDatabase.Query(CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTIONS)))
+ {
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ do
{
- aItem->DeleteFromDB(trans);
- delete aItem;
- continue;
- }
+ Field* fields = result->Fetch();
- GetAuctionsMap(aItem->factionTemplateId)->AddAuction(aItem);
- ++count;
- } while (result->NextRow());
+ AuctionPosting auction;
+ auction.Id = fields[0].GetUInt32();
+ uint32 auctionHouseId = fields[1].GetUInt32();
+ AuctionHouseObject* auctionHouse = GetAuctionsById(auctionHouseId);
+ if (!auctionHouse)
+ {
+ TC_LOG_ERROR("misc", "Auction %u has wrong auctionHouseId %u", auction.Id, auctionHouseId);
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION);
+ stmt->setUInt32(0, auction.Id);
+ trans->Append(stmt);
+ continue;
+ }
- CharacterDatabase.CommitTransaction(trans);
+ auto itemsItr = itemsByAuction.find(auction.Id);
+ if (itemsItr == itemsByAuction.end())
+ {
+ TC_LOG_ERROR("misc", "Auction %u has no items", auction.Id);
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION);
+ stmt->setUInt32(0, auction.Id);
+ trans->Append(stmt);
+ continue;
+ }
- TC_LOG_INFO("server.loading", ">> Loaded %u auctions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
+ auction.Items = std::move(itemsItr->second);
+ auction.Owner = ObjectGuid::Create<HighGuid::Player>(fields[2].GetUInt64());
+ auction.OwnerAccount = ObjectGuid::Create<HighGuid::WowAccount>(sCharacterCache->GetCharacterAccountIdByGuid(auction.Owner));
+ if (uint64 bidder = fields[3].GetUInt64())
+ auction.Bidder = ObjectGuid::Create<HighGuid::Player>(bidder);
+
+ auction.MinBid = fields[4].GetUInt64();
+ auction.BuyoutOrUnitPrice = fields[5].GetUInt64();
+ auction.Deposit = fields[6].GetUInt64();
+ auction.BidAmount = fields[7].GetUInt64();
+ auction.StartTime = std::chrono::system_clock::from_time_t(fields[8].GetUInt32());
+ auction.EndTime = std::chrono::system_clock::from_time_t(fields[9].GetUInt32());
+
+ auto biddersItr = biddersByAuction.find(auction.Id);
+ if (biddersItr != biddersByAuction.end())
+ auction.BidderHistory = std::move(biddersItr->second);
+
+ auctionHouse->AddAuction(nullptr, std::move(auction));
+
+ ++count;
+ } while (result->NextRow());
+ CharacterDatabase.CommitTransaction(trans);
+ }
+
+ TC_LOG_INFO("server.loading", ">> Loaded %u auctions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
}
-void AuctionHouseMgr::AddAItem(Item* it)
+void AuctionHouseMgr::AddAItem(Item* item)
{
- ASSERT(it);
- ASSERT(mAitems.count(it->GetGUID().GetCounter()) == 0);
- mAitems[it->GetGUID().GetCounter()] = it;
+ ASSERT(item);
+ ASSERT(_itemsByGuid.count(item->GetGUID()) == 0);
+ _itemsByGuid[item->GetGUID()] = item;
}
-bool AuctionHouseMgr::RemoveAItem(ObjectGuid::LowType id, bool deleteItem)
+bool AuctionHouseMgr::RemoveAItem(ObjectGuid id, bool deleteItem)
{
- ItemMap::iterator i = mAitems.find(id);
- if (i == mAitems.end())
+ auto i = _itemsByGuid.find(id);
+ if (i == _itemsByGuid.end())
return false;
if (deleteItem)
@@ -399,97 +666,92 @@ bool AuctionHouseMgr::RemoveAItem(ObjectGuid::LowType id, bool deleteItem)
i->second->SaveToDB(trans);
}
- mAitems.erase(i);
+ _itemsByGuid.erase(i);
return true;
}
-bool AuctionHouseMgr::PendingAuctionAdd(Player* player, AuctionEntry* aEntry)
+bool AuctionHouseMgr::PendingAuctionAdd(Player* player, uint32 auctionHouseId, uint32 auctionId, uint64 deposit)
{
- PlayerAuctions* thisAH;
- auto itr = pendingAuctionMap.find(player->GetGUID());
- if (itr != pendingAuctionMap.end())
+ auto itr = _pendingAuctionsByPlayer.find(player->GetGUID());
+ if (itr != _pendingAuctionsByPlayer.end())
{
- thisAH = itr->second.first;
-
// Get deposit so far
uint64 totalDeposit = 0;
- for (AuctionEntry const* thisAuction : *thisAH)
- totalDeposit += thisAuction->deposit;
+ for (PendingAuctionInfo const& thisAuction : itr->second.Auctions)
+ totalDeposit += thisAuction.Deposit;
// Add this deposit
- totalDeposit += aEntry->deposit;
+ totalDeposit += deposit;
if (!player->HasEnoughMoney(totalDeposit))
return false;
}
else
- {
- thisAH = new PlayerAuctions;
- pendingAuctionMap[player->GetGUID()] = AuctionPair(thisAH, 0);
- }
- thisAH->push_back(aEntry);
+ itr = _pendingAuctionsByPlayer.emplace(std::piecewise_construct, std::forward_as_tuple(player->GetGUID()), std::forward_as_tuple()).first;
+
+ itr->second.Auctions.push_back({ auctionId, auctionHouseId, deposit });
return true;
}
-uint32 AuctionHouseMgr::PendingAuctionCount(const Player* player) const
+std::size_t AuctionHouseMgr::PendingAuctionCount(Player const* player) const
{
- auto const itr = pendingAuctionMap.find(player->GetGUID());
- if (itr != pendingAuctionMap.end())
- return itr->second.first->size();
+ auto itr = _pendingAuctionsByPlayer.find(player->GetGUID());
+ if (itr != _pendingAuctionsByPlayer.end())
+ return itr->second.Auctions.size();
return 0;
}
void AuctionHouseMgr::PendingAuctionProcess(Player* player)
{
- auto iterMap = pendingAuctionMap.find(player->GetGUID());
- if (iterMap == pendingAuctionMap.end())
+ auto iterMap = _pendingAuctionsByPlayer.find(player->GetGUID());
+ if (iterMap == _pendingAuctionsByPlayer.end())
return;
- PlayerAuctions* thisAH = iterMap->second.first;
-
uint64 totaldeposit = 0;
- auto itrAH = thisAH->begin();
- for (; itrAH != thisAH->end(); ++itrAH)
+ auto itrAH = iterMap->second.Auctions.begin();
+ for (; itrAH != iterMap->second.Auctions.end(); ++itrAH)
{
- AuctionEntry* AH = (*itrAH);
- if (!player->HasEnoughMoney(totaldeposit + AH->deposit))
+ if (!player->HasEnoughMoney(totaldeposit + itrAH->Deposit))
break;
- totaldeposit += AH->deposit;
+ totaldeposit += itrAH->Deposit;
}
// expire auctions we cannot afford
- if (itrAH != thisAH->end())
+ if (itrAH != iterMap->second.Auctions.end())
{
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
do
{
- AuctionEntry* AH = (*itrAH);
- AH->expire_time = GameTime::GetGameTime();
- AH->DeleteFromDB(trans);
- AH->SaveToDB(trans);
+ PendingAuctionInfo const& pendingAuction = *itrAH;
+ if (AuctionPosting* auction = GetAuctionsById(pendingAuction.AuctionHouseId)->GetAuction(pendingAuction.AuctionId))
+ auction->EndTime = GameTime::GetGameTimeSystemPoint();
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_EXPIRATION);
+ stmt->setUInt32(0, uint32(GameTime::GetGameTime()));
+ stmt->setUInt32(1, pendingAuction.AuctionId);
+ trans->Append(stmt);
++itrAH;
- } while (itrAH != thisAH->end());
+ } while (itrAH != iterMap->second.Auctions.end());
CharacterDatabase.CommitTransaction(trans);
}
- pendingAuctionMap.erase(player->GetGUID());
- delete thisAH;
+ _pendingAuctionsByPlayer.erase(player->GetGUID());
player->ModifyMoney(-int64(totaldeposit));
}
void AuctionHouseMgr::UpdatePendingAuctions()
{
- for (auto itr = pendingAuctionMap.begin(); itr != pendingAuctionMap.end();)
+ for (auto itr = _pendingAuctionsByPlayer.begin(); itr != _pendingAuctionsByPlayer.end();)
{
ObjectGuid playerGUID = itr->first;
if (Player* player = ObjectAccessor::FindConnectedPlayer(playerGUID))
{
// Check if there were auctions since last update process if not
- if (PendingAuctionCount(player) == itr->second.second)
+ if (PendingAuctionCount(player) == itr->second.LastAuctionsSize)
{
++itr;
PendingAuctionProcess(player);
@@ -497,27 +759,27 @@ void AuctionHouseMgr::UpdatePendingAuctions()
else
{
++itr;
- pendingAuctionMap[playerGUID].second = PendingAuctionCount(player);
+ _pendingAuctionsByPlayer[playerGUID].LastAuctionsSize = PendingAuctionCount(player);
}
}
else
{
// Expire any auctions that we couldn't get a deposit for
TC_LOG_WARN("auctionHouse", "Player %s was offline, unable to retrieve deposit!", playerGUID.ToString().c_str());
- PlayerAuctions* thisAH = itr->second.first;
++itr;
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
- for (auto AHitr = thisAH->begin(); AHitr != thisAH->end();)
+ for (PendingAuctionInfo const& pendingAuction : itr->second.Auctions)
{
- AuctionEntry* AH = (*AHitr);
- ++AHitr;
- AH->expire_time = time(nullptr);
- AH->DeleteFromDB(trans);
- AH->SaveToDB(trans);
+ if (AuctionPosting* auction = GetAuctionsById(pendingAuction.AuctionHouseId)->GetAuction(pendingAuction.AuctionId))
+ auction->EndTime = GameTime::GetGameTimeSystemPoint();
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_EXPIRATION);
+ stmt->setUInt32(0, uint32(GameTime::GetGameTime()));
+ stmt->setUInt32(1, pendingAuction.AuctionId);
+ trans->Append(stmt);
}
CharacterDatabase.CommitTransaction(trans);
- pendingAuctionMap.erase(playerGUID);
- delete thisAH;
+ _pendingAuctionsByPlayer.erase(playerGUID);
}
}
}
@@ -527,6 +789,48 @@ void AuctionHouseMgr::Update()
mHordeAuctions.Update();
mAllianceAuctions.Update();
mNeutralAuctions.Update();
+ mGoblinAuctions.Update();
+
+ std::chrono::steady_clock::time_point now = GameTime::GetGameTimeSteadyPoint();
+ if (now >= _playerThrottleObjectsCleanupTime)
+ {
+ for (auto itr = _playerThrottleObjects.begin(); itr != _playerThrottleObjects.end();)
+ {
+ if (itr->second.PeriodEnd < now)
+ itr = _playerThrottleObjects.erase(itr);
+ else
+ ++itr;
+ }
+
+ _playerThrottleObjectsCleanupTime = now + Hours(1);
+ }
+}
+
+uint32 AuctionHouseMgr::GenerateReplicationId()
+{
+ return ++_replicateIdGenerator;
+}
+
+AuctionThrottleResult AuctionHouseMgr::CheckThrottle(Player* player, AuctionCommand command)
+{
+ std::chrono::steady_clock::time_point now = GameTime::GetGameTimeSteadyPoint();
+ auto itr = _playerThrottleObjects.emplace(std::piecewise_construct, std::forward_as_tuple(player->GetGUID()), std::forward_as_tuple());
+ if (itr.second || now > itr.first->second.PeriodEnd)
+ {
+ itr.first->second.PeriodEnd = now + Minutes(1);
+ itr.first->second.QueriesRemaining = 100;
+ }
+
+ if (!itr.first->second.QueriesRemaining)
+ {
+ player->GetSession()->SendAuctionCommandResult(0, command, AuctionResult::AuctionHouseBusy, std::chrono::duration_cast<Milliseconds>(itr.first->second.PeriodEnd - now));
+ return { {}, true };
+ }
+
+ if (!--itr.first->second.QueriesRemaining)
+ return { std::chrono::duration_cast<Milliseconds>(itr.first->second.PeriodEnd - now), false };
+ else
+ return { Milliseconds(sWorld->getIntConfig(CONFIG_AUCTION_SEARCH_DELAY)), false };
}
AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntry(uint32 factionTemplateId, uint32* houseId)
@@ -565,60 +869,262 @@ AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntry(uint32 factionTem
return sAuctionHouseStore.LookupEntry(houseid);
}
-void AuctionHouseObject::AddAuction(AuctionEntry* auction)
+AuctionHouseObject::AuctionHouseObject(uint32 auctionHouseId) : _auctionHouse(sAuctionHouseStore.AssertEntry(auctionHouseId))
{
- ASSERT(auction);
+}
- AuctionsMap[auction->Id] = auction;
- sScriptMgr->OnAuctionAdd(this, auction);
+AuctionHouseObject::~AuctionHouseObject() = default;
+
+uint32 AuctionHouseObject::GetAuctionHouseId() const
+{
+ return _auctionHouse->ID;
+}
+
+AuctionPosting* AuctionHouseObject::GetAuction(uint32 auctionId)
+{
+ return Trinity::Containers::MapGetValuePtr(_itemsByAuctionId, auctionId);
}
-bool AuctionHouseObject::RemoveAuction(AuctionEntry* auction)
+void AuctionHouseObject::AddAuction(CharacterDatabaseTransaction trans, AuctionPosting auction)
{
- bool wasInMap = AuctionsMap.erase(auction->Id) ? true : false;
+ AuctionsBucketKey key = AuctionsBucketKey::ForItem(auction.Items[0]);
+ AuctionsBucketData* bucket;
+ auto bucketItr = _buckets.find(key);
+ if (bucketItr == _buckets.end())
+ {
+ // we don't have any item for this key yet, create new bucket
+ bucketItr = _buckets.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple()).first;
+ bucket = &bucketItr->second;
+ bucket->Key = key;
+
+ ItemTemplate const* itemTemplate = auction.Items[0]->GetTemplate();
+ bucket->ItemClass = itemTemplate->GetClass();
+ bucket->ItemSubClass = itemTemplate->GetSubClass();
+ bucket->InventoryType = itemTemplate->GetInventoryType();
+ bucket->RequiredLevel = auction.Items[0]->GetRequiredLevel();
+ for (LocaleConstant locale = LOCALE_enUS; locale < TOTAL_LOCALES; locale = LocaleConstant(locale + 1))
+ {
+ if (locale == LOCALE_none)
+ continue;
+
+ std::wstring utf16name;
+ if (!Utf8toWStr(auction.Items[0]->GetNameForLocaleIdx(locale), utf16name))
+ continue;
+
+ bucket->FullName[locale] = wstrCaseAccentInsensitiveParse(utf16name, locale);
+ }
+ }
+ else
+ bucket = &bucketItr->second;
+
+ // update cache fields
+ uint64 priceToDisplay = auction.BuyoutOrUnitPrice ? auction.BuyoutOrUnitPrice : auction.BidAmount;
+ if (!bucket->MinPrice || priceToDisplay < bucket->MinPrice)
+ bucket->MinPrice = priceToDisplay;
+
+ if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = auction.Items[0]->GetItemModifiedAppearance())
+ {
+ auto itr = std::find_if(bucket->ItemModifiedAppearanceId.begin(), bucket->ItemModifiedAppearanceId.end(),
+ [itemModifiedAppearance](std::pair<uint32, uint32> const& appearance) { return appearance.first == itemModifiedAppearance->ID; });
+
+ if (itr == bucket->ItemModifiedAppearanceId.end())
+ itr = std::find_if(bucket->ItemModifiedAppearanceId.begin(), bucket->ItemModifiedAppearanceId.end(),
+ [](std::pair<uint32, uint32> const& appearance) { return appearance.first == 0; });
+
+ if (itr != bucket->ItemModifiedAppearanceId.end())
+ {
+ itr->first = itemModifiedAppearance->ID;
+ ++itr->second;
+ }
+ }
+
+ uint32 quality;
+
+ if (!auction.Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID))
+ {
+ quality = auction.Items[0]->GetQuality();
+ }
+ else
+ {
+ quality = (auction.Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF;
+ for (Item* item : auction.Items)
+ {
+ uint8 battlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
+ if (!bucket->MinBattlePetLevel)
+ bucket->MinBattlePetLevel = battlePetLevel;
+ else if (bucket->MinBattlePetLevel > battlePetLevel)
+ bucket->MinBattlePetLevel = battlePetLevel;
+
+ bucket->MaxBattlePetLevel = std::max<uint8>(bucket->MaxBattlePetLevel, battlePetLevel);
+ }
+ }
+
+ bucket->QualityMask |= static_cast<AuctionHouseFilterMask>(1 << (quality + 4));
+ ++bucket->QualityCounts[quality];
+
+ if (trans)
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION);
+ stmt->setUInt32(0, auction.Id);
+ stmt->setUInt32(1, _auctionHouse->ID);
+ stmt->setUInt64(2, auction.Owner.GetCounter());
+ stmt->setUInt64(3, ObjectGuid::Empty.GetCounter());
+ stmt->setUInt64(4, auction.MinBid);
+ stmt->setUInt64(5, auction.BuyoutOrUnitPrice);
+ stmt->setUInt64(6, auction.Deposit);
+ stmt->setUInt64(7, auction.BidAmount);
+ stmt->setUInt32(8, uint32(std::chrono::system_clock::to_time_t(auction.StartTime)));
+ stmt->setUInt32(9, uint32(std::chrono::system_clock::to_time_t(auction.EndTime)));
+ trans->Append(stmt);
+
+ for (Item* item : auction.Items)
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION_ITEMS);
+ stmt->setUInt32(0, auction.Id);
+ stmt->setUInt64(1, item->GetGUID().GetCounter());
+ trans->Append(stmt);
+ }
+ }
+
+ for (Item* item : auction.Items)
+ sAuctionMgr->AddAItem(item);
+
+ auction.Bucket = bucket;
+ _playerOwnedAuctions.emplace(auction.Owner, auction.Id);
+ for (ObjectGuid bidder : auction.BidderHistory)
+ _playerBidderAuctions.emplace(bidder, auction.Id);
+
+ AuctionPosting* addedAuction = &(_itemsByAuctionId[auction.Id] = std::move(auction));
+
+ WorldPackets::AuctionHouse::AuctionSortDef priceSort{ AuctionHouseSortOrder::Price, false };
+ AuctionPosting::Sorter insertSorter(LOCALE_enUS, &priceSort, 1);
+ bucket->Auctions.insert(std::lower_bound(bucket->Auctions.begin(), bucket->Auctions.end(), addedAuction, std::cref(insertSorter)), addedAuction);
+
+ sScriptMgr->OnAuctionAdd(this, addedAuction);
+}
+
+void AuctionHouseObject::RemoveAuction(CharacterDatabaseTransaction trans, AuctionPosting* auction, std::map<uint32, AuctionPosting>::iterator* auctionItr /*= nullptr*/)
+{
+ AuctionsBucketData* bucket = auction->Bucket;
+
+ bucket->Auctions.erase(std::remove(bucket->Auctions.begin(), bucket->Auctions.end(), auction), bucket->Auctions.end());
+ if (!bucket->Auctions.empty())
+ {
+ // update cache fields
+ uint64 priceToDisplay = auction->BuyoutOrUnitPrice ? auction->BuyoutOrUnitPrice : auction->BidAmount;
+ if (bucket->MinPrice == priceToDisplay)
+ {
+ bucket->MinPrice = std::numeric_limits<uint64>::max();
+ for (AuctionPosting const* remainingAuction : bucket->Auctions)
+ bucket->MinPrice = std::min(bucket->MinPrice, remainingAuction->BuyoutOrUnitPrice ? remainingAuction->BuyoutOrUnitPrice : remainingAuction->BidAmount);
+ }
+
+ if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = auction->Items[0]->GetItemModifiedAppearance())
+ {
+ auto itr = std::find_if(bucket->ItemModifiedAppearanceId.begin(), bucket->ItemModifiedAppearanceId.end(),
+ [itemModifiedAppearance](std::pair<uint32, uint32> const& appearance)
+ {
+ return appearance.first == itemModifiedAppearance->ID;
+ });
+
+ if (itr != bucket->ItemModifiedAppearanceId.end())
+ if (!--itr->second)
+ itr->first = 0;
+ }
+
+ uint32 quality;
+
+ if (!auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID))
+ {
+ quality = auction->Items[0]->GetQuality();
+ }
+ else
+ {
+ quality = (auction->Items[0]->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF;
+ bucket->MinBattlePetLevel = 0;
+ bucket->MaxBattlePetLevel = 0;
+ for (AuctionPosting const* remainingAuction : bucket->Auctions)
+ {
+ for (Item* item : remainingAuction->Items)
+ {
+ uint8 battlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
+ if (!bucket->MinBattlePetLevel)
+ bucket->MinBattlePetLevel = battlePetLevel;
+ else if (bucket->MinBattlePetLevel > battlePetLevel)
+ bucket->MinBattlePetLevel = battlePetLevel;
+
+ bucket->MaxBattlePetLevel = std::max<uint8>(bucket->MaxBattlePetLevel, battlePetLevel);
+ }
+ }
+ }
+
+ if (!--bucket->QualityCounts[quality])
+ bucket->QualityMask &= static_cast<AuctionHouseFilterMask>(~(1 << (quality + 4)));
+ }
+ else
+ _buckets.erase(bucket->Key);
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION);
+ stmt->setUInt32(0, auction->Id);
+ trans->Append(stmt);
+
+ for (Item* item : auction->Items)
+ sAuctionMgr->RemoveAItem(item->GetGUID());
sScriptMgr->OnAuctionRemove(this, auction);
- // we need to delete the entry, it is not referenced any more
- delete auction;
- return wasInMap;
+ Trinity::Containers::MultimapErasePair(_playerOwnedAuctions, auction->Owner, auction->Id);
+ for (ObjectGuid bidder : auction->BidderHistory)
+ Trinity::Containers::MultimapErasePair(_playerBidderAuctions, bidder, auction->Id);
+
+ if (auctionItr)
+ *auctionItr = _itemsByAuctionId.erase(*auctionItr);
+ else
+ _itemsByAuctionId.erase(auction->Id);
}
void AuctionHouseObject::Update()
{
- time_t curTime = GameTime::GetGameTime();
+ std::chrono::system_clock::time_point curTime = GameTime::GetGameTimeSystemPoint();
+ std::chrono::steady_clock::time_point curTimeSteady = GameTime::GetGameTimeSteadyPoint();
///- Handle expired auctions
- // If storage is empty, no need to update. next == NULL in this case.
- if (AuctionsMap.empty())
- return;
-
// Clear expired throttled players
- for (PlayerGetAllThrottleMap::const_iterator itr = GetAllThrottleMap.begin(); itr != GetAllThrottleMap.end();)
+ for (auto itr = _replicateThrottleMap.begin(); itr != _replicateThrottleMap.end();)
+ {
+ if (itr->second.NextAllowedReplication <= curTimeSteady)
+ itr = _replicateThrottleMap.erase(itr);
+ else
+ ++itr;
+ }
+
+ for (auto itr = _commodityQuotes.begin(); itr != _commodityQuotes.end();)
{
- if (itr->second.NextAllowedReplication <= curTime)
- itr = GetAllThrottleMap.erase(itr);
+ if (itr->second.ValidTo < curTimeSteady)
+ itr = _commodityQuotes.erase(itr);
else
++itr;
}
+ if (_itemsByAuctionId.empty())
+ return;
+
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
- for (AuctionEntryMap::iterator it = AuctionsMap.begin(); it != AuctionsMap.end();)
+ for (auto it = _itemsByAuctionId.begin(); it != _itemsByAuctionId.end();)
{
- // from auctionhousehandler.cpp, creates auction pointer & player pointer
- AuctionEntry* auction = it->second;
- // Increment iterator due to AuctionEntry deletion
- ++it;
-
+ AuctionPosting* auction = &it->second;
///- filter auctions expired on next update
- if (auction->expire_time > curTime + 60)
+ if (auction->EndTime > curTime + 1min)
+ {
+ ++it;
continue;
+ }
///- Either cancel the auction if there was no bidder
- if (auction->bidder == 0 && auction->bid == 0)
+ if (auction->Bidder.IsEmpty())
{
- sAuctionMgr->SendAuctionExpiredMail(auction, trans);
+ SendAuctionExpired(auction, trans);
sScriptMgr->OnAuctionExpire(this, auction);
}
///- Or perform the transaction
@@ -627,42 +1133,290 @@ void AuctionHouseObject::Update()
//we should send an "item sold" message if the seller is online
//we send the item to the winner
//we send the money to the seller
- sAuctionMgr->SendAuctionSuccessfulMail(auction, trans);
- sAuctionMgr->SendAuctionWonMail(auction, trans);
+ SendAuctionWon(auction, nullptr, trans);
+ SendAuctionSold(auction, nullptr, trans);
sScriptMgr->OnAuctionSuccessful(this, auction);
}
///- In any case clear the auction
- auction->DeleteFromDB(trans);
-
- sAuctionMgr->RemoveAItem(auction->itemGUIDLow);
- RemoveAuction(auction);
+ RemoveAuction(trans, auction, &it);
}
// Run DB changes
CharacterDatabase.CommitTransaction(trans);
}
-void AuctionHouseObject::BuildListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItemsResult& packet, Player* player)
+void AuctionHouseObject::BuildListBuckets(WorldPackets::AuctionHouse::AuctionListBucketsResult& listBucketsResult, Player* player,
+ std::wstring const& name, uint8 minLevel, uint8 maxLevel, EnumClassFlag<AuctionHouseFilterMask> filters, Optional<AuctionSearchClassFilters> const& classFilters,
+ uint8 const* knownPetBits, std::size_t knownPetBitsCount, uint8 maxKnownPetLevel, uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount)
{
- for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
+ std::unordered_set<uint32> knownAppearanceIds;
+ boost::dynamic_bitset<uint8> knownPetSpecies;
+ // prepare uncollected filter for more efficient searches
+ if (filters.HasFlag(AuctionHouseFilterMask::UncollectedOnly))
{
- AuctionEntry* Aentry = itr->second;
- if (Aentry && Aentry->bidder == player->GetGUID().GetCounter())
- itr->second->BuildAuctionInfo(packet.Items, false);
+ knownAppearanceIds = player->GetSession()->GetCollectionMgr()->GetAppearanceIds();
+ knownPetSpecies.init_from_block_range(knownPetBits, knownPetBits + knownPetBitsCount);
+ if (knownPetSpecies.size() < sBattlePetSpeciesStore.GetNumRows())
+ knownPetSpecies.resize(sBattlePetSpeciesStore.GetNumRows());
}
+
+ AuctionsResultBuilder<AuctionsBucketData> builder(offset, player->GetSession()->GetSessionDbcLocale(), sorts, sortCount, AuctionHouseResultLimits::Browse);
+
+ for (std::pair<AuctionsBucketKey const, AuctionsBucketData> const& bucket : _buckets)
+ {
+ AuctionsBucketData const* bucketData = &bucket.second;
+ if (!name.empty())
+ {
+ if (filters.HasFlag(AuctionHouseFilterMask::ExactMatch))
+ {
+ if (bucketData->FullName[player->GetSession()->GetSessionDbcLocale()] != name)
+ continue;
+ }
+ else
+ if (bucketData->FullName[player->GetSession()->GetSessionDbcLocale()].find(name) == std::wstring::npos)
+ continue;
+ }
+
+ if (minLevel && bucketData->RequiredLevel < minLevel)
+ continue;
+
+ if (maxLevel && bucketData->RequiredLevel > maxLevel)
+ continue;
+
+ if (!filters.HasFlag(bucketData->QualityMask))
+ continue;
+
+ if (classFilters)
+ {
+ // 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->Classes[bucketData->ItemClass].SubclassMask == AuctionSearchClassFilters::FILTER_SKIP_CLASS)
+ continue;
+
+ if (classFilters->Classes[bucketData->ItemClass].SubclassMask != AuctionSearchClassFilters::FILTER_SKIP_SUBCLASS)
+ {
+ if (!(classFilters->Classes[bucketData->ItemClass].SubclassMask & (1 << bucketData->ItemSubClass)))
+ continue;
+
+ if (!(classFilters->Classes[bucketData->ItemClass].InvTypes[bucketData->ItemSubClass] & (1 << bucketData->InventoryType)))
+ continue;
+ }
+ }
+
+ if (filters.HasFlag(AuctionHouseFilterMask::UncollectedOnly))
+ {
+ // appearances - by ItemAppearanceId, not ItemModifiedAppearanceId
+ if (bucketData->InventoryType != INVTYPE_NON_EQUIP && bucketData->InventoryType != INVTYPE_BAG)
+ {
+ bool hasAll = true;
+ for (std::pair<uint32, uint32> bucketAppearance : bucketData->ItemModifiedAppearanceId)
+ {
+ if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(bucketAppearance.first))
+ {
+ if (knownAppearanceIds.find(itemModifiedAppearance->ItemAppearanceID) == knownAppearanceIds.end())
+ {
+ hasAll = false;
+ break;
+ }
+ }
+ }
+
+ if (hasAll)
+ continue;
+ }
+ // caged pets
+ else if (bucket.first.BattlePetSpeciesId)
+ {
+ if (knownPetSpecies.test(bucket.first.BattlePetSpeciesId))
+ continue;
+ }
+ // toys
+ else if (sDB2Manager.IsToyItem(bucket.first.ItemId))
+ {
+ if (player->GetSession()->GetCollectionMgr()->HasToy(bucket.first.ItemId))
+ continue;
+ }
+ // mounts
+ // recipes
+ // pet items
+ else if (bucketData->ItemClass == ITEM_CLASS_CONSUMABLE || bucketData->ItemClass == ITEM_CLASS_RECIPE || bucketData->ItemClass == ITEM_CLASS_MISCELLANEOUS)
+ {
+ ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(bucket.first.ItemId));
+ if (itemTemplate->Effects.size() >= 2 && (itemTemplate->Effects[0]->SpellID == 483 || itemTemplate->Effects[0]->SpellID == 55884))
+ {
+ if (player->HasSpell(itemTemplate->Effects[1]->SpellID))
+ continue;
+
+ if (BattlePetSpeciesEntry const* battlePetSpecies = sSpellMgr->GetBattlePetSpecies(itemTemplate->Effects[1]->SpellID))
+ if (knownPetSpecies.test(battlePetSpecies->ID))
+ continue;
+ }
+ }
+ }
+
+ if (filters.HasFlag(AuctionHouseFilterMask::UsableOnly))
+ {
+ if (bucketData->RequiredLevel && player->getLevel() < bucketData->RequiredLevel)
+ continue;
+
+ if (player->CanUseItem(sObjectMgr->GetItemTemplate(bucket.first.ItemId), true) != EQUIP_ERR_OK)
+ continue;
+
+ // cannot learn caged pets whose level exceeds highest level of currently owned pet
+ if (bucketData->MinBattlePetLevel && bucketData->MinBattlePetLevel > maxKnownPetLevel)
+ continue;
+ }
+
+ // TODO: this one needs to access loot history to know highest item level for every inventory type
+ //if (filters.HasFlag(AuctionHouseFilterMask::UpgradesOnly))
+ //{
+ //}
+
+ builder.AddItem(bucketData);
+ }
+
+ for (AuctionsBucketData const* resultBucket : builder.GetResultRange())
+ {
+ listBucketsResult.Buckets.emplace_back();
+ WorldPackets::AuctionHouse::BucketInfo& bucketInfo = listBucketsResult.Buckets.back();
+ resultBucket->BuildBucketInfo(&bucketInfo, player);
+ }
+
+ listBucketsResult.HasMoreResults = builder.HasMoreResults();
}
-void AuctionHouseObject::BuildListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItemsResult& packet, Player* player)
+void AuctionHouseObject::BuildListBuckets(WorldPackets::AuctionHouse::AuctionListBucketsResult& listBucketsResult, Player* player,
+ WorldPackets::AuctionHouse::AuctionBucketKey const* keys, std::size_t keysCount,
+ WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount)
{
- for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
+ std::vector<AuctionsBucketData const*> buckets;
+ buckets.reserve(keysCount);
+ for (std::size_t i = 0; i < keysCount; ++i)
{
- AuctionEntry* Aentry = itr->second;
- if (Aentry && Aentry->owner == player->GetGUID().GetCounter())
- Aentry->BuildAuctionInfo(packet.Items, false);
+ auto bucketItr = _buckets.find(AuctionsBucketKey(keys[i]));
+ if (bucketItr != _buckets.end())
+ buckets.push_back(&bucketItr->second);
+ }
+
+ AuctionsBucketData::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts, sortCount);
+ std::sort(buckets.begin(), buckets.end(), std::cref(sorter));
+
+ for (AuctionsBucketData const* resultBucket : buckets)
+ {
+ listBucketsResult.Buckets.emplace_back();
+ WorldPackets::AuctionHouse::BucketInfo& bucketInfo = listBucketsResult.Buckets.back();
+ resultBucket->BuildBucketInfo(&bucketInfo, player);
+ }
+
+ listBucketsResult.HasMoreResults = false;
+
+}
+
+void AuctionHouseObject::BuildListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItemsResult& listBidderItemsResult, Player* player,
+ uint32 /*offset*/, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) const
+{
+ // always full list
+ std::vector<AuctionPosting const*> auctions;
+ for (auto const& auctionId : Trinity::Containers::MapEqualRange(_playerBidderAuctions, player->GetGUID()))
+ if (AuctionPosting const* auction = Trinity::Containers::MapGetValuePtr(_itemsByAuctionId, auctionId.second))
+ auctions.push_back(auction);
+
+ AuctionPosting::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts, sortCount);
+ std::sort(auctions.begin(), auctions.end(), std::cref(sorter));
+
+ for (AuctionPosting const* resultAuction : auctions)
+ {
+ listBidderItemsResult.Items.emplace_back();
+ WorldPackets::AuctionHouse::AuctionItem& auctionItem = listBidderItemsResult.Items.back();
+ resultAuction->BuildAuctionItem(&auctionItem, true, true, true, false);
}
+
+ listBidderItemsResult.HasMoreResults = false;
}
+void AuctionHouseObject::BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult& listItemsResult, Player* player, AuctionsBucketKey const& bucketKey,
+ uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) const
+{
+ listItemsResult.TotalCount = 0;
+ if (AuctionsBucketData const* bucket = Trinity::Containers::MapGetValuePtr(_buckets, bucketKey))
+ {
+ AuctionsResultBuilder<AuctionPosting> builder(offset, player->GetSession()->GetSessionDbcLocale(), sorts, sortCount, AuctionHouseResultLimits::Items);
+
+ for (AuctionPosting const* auction : bucket->Auctions)
+ {
+ builder.AddItem(auction);
+ for (Item* item : auction->Items)
+ listItemsResult.TotalCount += item->GetCount();
+ }
+
+ for (AuctionPosting const* resultAuction : builder.GetResultRange())
+ {
+ listItemsResult.Items.emplace_back();
+ WorldPackets::AuctionHouse::AuctionItem& auctionItem = listItemsResult.Items.back();
+ resultAuction->BuildAuctionItem(&auctionItem, false, false, resultAuction->OwnerAccount != player->GetSession()->GetAccountGUID(),
+ resultAuction->Bidder.IsEmpty());
+ }
+
+ listItemsResult.HasMoreResults = builder.HasMoreResults();
+ }
+}
+
+void AuctionHouseObject::BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult& listItemsResult, Player* player, uint32 itemId,
+ uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) const
+{
+ AuctionsResultBuilder<AuctionPosting> builder(offset, player->GetSession()->GetSessionDbcLocale(), sorts, sortCount, AuctionHouseResultLimits::Items);
+ auto itr = _buckets.lower_bound(AuctionsBucketKey(itemId, 0, 0, 0));
+ auto end = _buckets.lower_bound(AuctionsBucketKey(itemId + 1, 0, 0, 0));
+ listItemsResult.TotalCount = 0;
+ while (itr != end)
+ {
+ for (AuctionPosting const* auction : itr->second.Auctions)
+ {
+ builder.AddItem(auction);
+ for (Item* item : auction->Items)
+ listItemsResult.TotalCount += item->GetCount();
+ }
+
+ ++itr;
+ }
+
+ for (AuctionPosting const* resultAuction : builder.GetResultRange())
+ {
+ listItemsResult.Items.emplace_back();
+ WorldPackets::AuctionHouse::AuctionItem& auctionItem = listItemsResult.Items.back();
+ resultAuction->BuildAuctionItem(&auctionItem, false, true, resultAuction->OwnerAccount != player->GetSession()->GetAccountGUID(),
+ resultAuction->Bidder.IsEmpty());
+ }
+
+ listItemsResult.HasMoreResults = builder.HasMoreResults();
+}
+
+void AuctionHouseObject::BuildListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItemsResult& listOwnerItemsResult, Player* player,
+ uint32 /*offset*/, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount)
+{
+ // always full list
+ std::vector<AuctionPosting const*> auctions;
+ for (auto const& auctionId : Trinity::Containers::MapEqualRange(_playerOwnedAuctions, player->GetGUID()))
+ if (AuctionPosting const* auction = Trinity::Containers::MapGetValuePtr(_itemsByAuctionId, auctionId.second))
+ auctions.push_back(auction);
+
+ AuctionPosting::Sorter sorter(player->GetSession()->GetSessionDbcLocale(), sorts, sortCount);
+ std::sort(auctions.begin(), auctions.end(), std::cref(sorter));
+
+ for (AuctionPosting const* resultAuction : auctions)
+ {
+ listOwnerItemsResult.Items.emplace_back();
+ WorldPackets::AuctionHouse::AuctionItem& auctionItem = listOwnerItemsResult.Items.back();
+ resultAuction->BuildAuctionItem(&auctionItem, true, true, false, false);
+ }
+
+ listOwnerItemsResult.HasMoreResults = false;
+}
+
+/*
void AuctionHouseObject::BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult& packet, Player* player,
std::wstring const& searchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, EnumClassFlag<AuctionHouseFilterMask> filters,
Optional<AuctionSearchClassFilters> const& classFilters)
@@ -731,14 +1485,14 @@ void AuctionHouseObject::BuildListAuctionItems(WorldPackets::AuctionHouse::Aucti
++packet.TotalCount;
}
}
-
-void AuctionHouseObject::BuildReplicate(WorldPackets::AuctionHouse::AuctionReplicateResponse& auctionReplicateResult, Player* player,
+*/
+void AuctionHouseObject::BuildReplicate(WorldPackets::AuctionHouse::AuctionReplicateResponse& replicateResponse, Player* player,
uint32 global, uint32 cursor, uint32 tombstone, uint32 count)
{
- time_t curTime = GameTime::GetGameTime();
+ std::chrono::steady_clock::time_point curTime = GameTime::GetGameTimeSteadyPoint();
- auto throttleItr = GetAllThrottleMap.find(player->GetGUID());
- if (throttleItr != GetAllThrottleMap.end())
+ auto throttleItr = _replicateThrottleMap.find(player->GetGUID());
+ if (throttleItr != _replicateThrottleMap.end())
{
if (throttleItr->second.Global != global || throttleItr->second.Cursor != cursor || throttleItr->second.Tombstone != tombstone)
return;
@@ -748,187 +1502,447 @@ void AuctionHouseObject::BuildReplicate(WorldPackets::AuctionHouse::AuctionRepli
}
else
{
- throttleItr = GetAllThrottleMap.insert({ player->GetGUID(), PlayerGetAllThrottleData{} }).first;
- throttleItr->second.NextAllowedReplication = curTime + sWorld->getIntConfig(CONFIG_AUCTION_GETALL_DELAY);
- throttleItr->second.Global = uint32(curTime);
+ throttleItr = _replicateThrottleMap.emplace(player->GetGUID(), PlayerReplicateThrottleData{}).first;
+ throttleItr->second.NextAllowedReplication = curTime + Seconds(sWorld->getIntConfig(CONFIG_AUCTION_GETALL_DELAY));
+ throttleItr->second.Global = sAuctionMgr->GenerateReplicationId();
}
- if (AuctionsMap.empty() || !count)
+ if (_itemsByAuctionId.empty() || !count)
return;
- auto itr = AuctionsMap.upper_bound(cursor);
- for (; itr != AuctionsMap.end(); ++itr)
+ auto itr = _itemsByAuctionId.upper_bound(cursor);
+ for (; itr != _itemsByAuctionId.end(); ++itr)
{
- AuctionEntry* auction = itr->second;
- if (auction->expire_time < curTime)
- continue;
+ AuctionPosting const& auction = itr->second;
- Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow);
- if (!item)
- continue;
-
- auction->BuildAuctionInfo(auctionReplicateResult.Items, true, item);
+ replicateResponse.Items.emplace_back();
+ WorldPackets::AuctionHouse::AuctionItem& auctionItem = replicateResponse.Items.back();
+ auction.BuildAuctionItem(&auctionItem, false, true, true, auction.Bidder.IsEmpty());
if (!--count)
break;
}
- auctionReplicateResult.ChangeNumberGlobal = throttleItr->second.Global;
- auctionReplicateResult.ChangeNumberCursor = throttleItr->second.Cursor = !auctionReplicateResult.Items.empty() ? auctionReplicateResult.Items.back().AuctionID : 0;
- auctionReplicateResult.ChangeNumberTombstone = throttleItr->second.Tombstone = !count ? AuctionsMap.rbegin()->first : 0;
+ replicateResponse.ChangeNumberGlobal = throttleItr->second.Global;
+ replicateResponse.ChangeNumberCursor = throttleItr->second.Cursor = !replicateResponse.Items.empty() ? replicateResponse.Items.back().AuctionID : 0;
+ replicateResponse.ChangeNumberTombstone = throttleItr->second.Tombstone = !count ? _itemsByAuctionId.rbegin()->first : 0;
}
-//this function inserts to WorldPacket auction's data
-void AuctionEntry::BuildAuctionInfo(std::vector<WorldPackets::AuctionHouse::AuctionItem>& items, bool listAuctionItems, Item* sourceItem /*= nullptr*/) const
+uint64 AuctionHouseObject::CalcualteAuctionHouseCut(uint64 bidAmount) const
{
- Item* item = (sourceItem) ? sourceItem : sAuctionMgr->GetAItem(itemGUIDLow);
- if (!item)
- {
- TC_LOG_ERROR("misc", "AuctionEntry::BuildAuctionInfo: Auction %u has a non-existent item: " UI64FMTD, Id, itemGUIDLow);
- return;
- }
-
- WorldPackets::AuctionHouse::AuctionItem auctionItem;
-
- auctionItem.AuctionID = Id;
- auctionItem.Item.emplace();
- auctionItem.Item->Initialize(item);
- auctionItem.BuyoutPrice = buyout;
- auctionItem.CensorBidInfo = false;
- auctionItem.CensorServerSideInfo = listAuctionItems;
- auctionItem.Charges = item->GetSpellCharges();
- auctionItem.Count = item->GetCount();
- auctionItem.DeleteReason = 0; // Always 0 ?
- auctionItem.DurationLeft = (expire_time - time(nullptr)) * IN_MILLISECONDS;
- auctionItem.EndTime = expire_time;
- auctionItem.Flags = 0; // todo
- auctionItem.ItemGuid = item->GetGUID();
- auctionItem.MinBid = startbid;
- auctionItem.Owner = ObjectGuid::Create<HighGuid::Player>(owner);
- auctionItem.OwnerAccountID = ObjectGuid::Create<HighGuid::WowAccount>(sCharacterCache->GetCharacterAccountIdByGuid(auctionItem.Owner));
- auctionItem.MinIncrement = bidder ? GetAuctionOutBid() : 0;
- auctionItem.Bidder = bidder ? ObjectGuid::Create<HighGuid::Player>(bidder) : ObjectGuid::Empty;
- auctionItem.BidAmount = bidder ? bid : 0;
-
- for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; i++)
- {
- if (!item->GetEnchantmentId((EnchantmentSlot) i))
- continue;
+ return std::max(int64(CalculatePct(bidAmount, _auctionHouse->ConsignmentRate) * double(sWorld->getRate(RATE_AUCTION_CUT))), SI64LIT(0));
+}
- auctionItem.Enchantments.emplace_back(item->GetEnchantmentId((EnchantmentSlot) i), item->GetEnchantmentDuration((EnchantmentSlot) i), item->GetEnchantmentCharges((EnchantmentSlot) i), i);
- }
+CommodityQuote const* AuctionHouseObject::CreateCommodityQuote(Player* player, uint32 itemId, uint32 quantity)
+{
+ auto bucketItr = _buckets.find(AuctionsBucketKey::ForCommodity(itemId));
+ if (bucketItr == _buckets.end())
+ return nullptr;
- uint8 i = 0;
- for (UF::SocketedGem const& gemData : item->m_itemData->Gems)
+ uint64 totalPrice = 0;
+ uint32 remainingQuantity = quantity;
+ for (AuctionPosting const* auction : bucketItr->second.Auctions)
{
- if (gemData.ItemID)
+ for (Item* auctionItem : auction->Items)
{
- WorldPackets::Item::ItemGemData gem;
- gem.Slot = i;
- gem.Item.Initialize(&gemData);
- auctionItem.Gems.push_back(gem);
+ if (auctionItem->GetCount() >= remainingQuantity)
+ {
+ totalPrice += auction->BuyoutOrUnitPrice * remainingQuantity;
+ remainingQuantity = 0;
+ break;
+ }
+
+ totalPrice += auction->BuyoutOrUnitPrice * auctionItem->GetCount();
+ remainingQuantity -= auctionItem->GetCount();
}
- ++i;
}
- items.emplace_back(auctionItem);
-}
+ // not enough items on auction house
+ if (remainingQuantity)
+ return nullptr;
-uint64 AuctionEntry::GetAuctionCut() const
-{
- int64 cut = int64(CalculatePct(bid, auctionHouseEntry->ConsignmentRate) * sWorld->getRate(RATE_AUCTION_CUT));
- return std::max(cut, int64(0));
-}
+ if (!player->HasEnoughMoney(totalPrice))
+ return nullptr;
-/// the sum of outbid is (1% from current bid)*5, if bid is very small, it is 1c
-uint64 AuctionEntry::GetAuctionOutBid() const
-{
- uint64 outbid = CalculatePct(bid, 5);
- return outbid ? outbid : 1;
+ CommodityQuote* quote = &_commodityQuotes[player->GetGUID()];
+ quote->TotalPrice = totalPrice;
+ quote->Quantity = quantity;
+ quote->ValidTo = GameTime::GetGameTimeSteadyPoint() + 30s;
+ return quote;
}
-void AuctionEntry::DeleteFromDB(CharacterDatabaseTransaction& trans) const
+void AuctionHouseObject::CancelCommodityQuote(ObjectGuid guid)
{
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION);
- stmt->setUInt32(0, Id);
- trans->Append(stmt);
-}
-
-void AuctionEntry::SaveToDB(CharacterDatabaseTransaction& trans) const
-{
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION);
- stmt->setUInt32(0, Id);
- stmt->setUInt64(1, auctioneer);
- stmt->setUInt64(2, itemGUIDLow);
- stmt->setUInt64(3, owner);
- stmt->setUInt64(4, buyout);
- stmt->setUInt32(5, uint32(expire_time));
- stmt->setUInt64(6, bidder);
- stmt->setUInt64(7, bid);
- stmt->setUInt64(8, startbid);
- stmt->setUInt64(9, deposit);
- trans->Append(stmt);
+ _commodityQuotes.erase(guid);
}
-bool AuctionEntry::LoadFromDB(Field* fields)
+bool AuctionHouseObject::BuyCommodity(CharacterDatabaseTransaction trans, Player* player, uint32 itemId, uint32 quantity, Milliseconds delayForNextAction)
{
- Id = fields[0].GetUInt32();
- auctioneer = fields[1].GetUInt64();
- itemGUIDLow = fields[2].GetUInt64();
- itemEntry = fields[3].GetUInt32();
- itemCount = fields[4].GetUInt32();
- owner = fields[5].GetUInt64();
- buyout = fields[6].GetUInt64();
- expire_time = fields[7].GetUInt32();
- bidder = fields[8].GetUInt64();
- bid = fields[9].GetUInt64();
- startbid = fields[10].GetUInt64();
- deposit = fields[11].GetUInt64();
+ auto bucketItr = _buckets.find(AuctionsBucketKey::ForCommodity(itemId));
+ if (bucketItr == _buckets.end())
+ {
+ player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction);
+ return false;
+ }
- CreatureData const* auctioneerData = sObjectMgr->GetCreatureData(auctioneer);
- if (!auctioneerData)
+ auto quote = _commodityQuotes.find(player->GetGUID());
+ if (quote == _commodityQuotes.end())
{
- TC_LOG_ERROR("misc", "Auction %u has not a existing auctioneer (GUID : " UI64FMTD ")", Id, auctioneer);
+ player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction);
return false;
}
- CreatureTemplate const* auctioneerInfo = sObjectMgr->GetCreatureTemplate(auctioneerData->id);
- if (!auctioneerInfo)
+ std::shared_ptr<std::nullptr_t> removeQuote(nullptr, [this, quote](std::nullptr_t)
{
- TC_LOG_ERROR("misc", "Auction %u has not a existing auctioneer (GUID : " UI64FMTD " Entry: %u)", Id, auctioneer, auctioneerData->id);
+ _commodityQuotes.erase(quote);
+ });
+
+ uint64 totalPrice = 0;
+ uint32 remainingQuantity = quantity;
+ std::vector<AuctionPosting*> auctions;
+ for (auto auctionItr = bucketItr->second.Auctions.begin(); auctionItr != bucketItr->second.Auctions.end();)
+ {
+ AuctionPosting* auction = *auctionItr++;
+ auctions.push_back(auction);
+ for (Item* auctionItem : auction->Items)
+ {
+ if (auctionItem->GetCount() >= remainingQuantity)
+ {
+ totalPrice += auction->BuyoutOrUnitPrice * remainingQuantity;
+ remainingQuantity = 0;
+ auctionItr = bucketItr->second.Auctions.end();
+ break;
+ }
+
+ totalPrice += auction->BuyoutOrUnitPrice * auctionItem->GetCount();
+ remainingQuantity -= auctionItem->GetCount();
+ }
+ }
+
+ // not enough items on auction house
+ if (remainingQuantity)
+ {
+ player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction);
return false;
}
- factionTemplateId = auctioneerInfo->faction;
- auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(factionTemplateId, &houseId);
- if (!auctionHouseEntry)
+ // 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->second.TotalPrice)
{
- TC_LOG_ERROR("misc", "Auction %u has auctioneer (GUID : " UI64FMTD " Entry: %u) with wrong faction %u", Id, auctioneer, auctioneerData->id, factionTemplateId);
+ player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction);
return false;
}
- // check if sold item exists for guid
- // and itemEntry in fact (GetAItem will fail if problematic in result check in AuctionHouseMgr::LoadAuctionItems)
- if (!sAuctionMgr->GetAItem(itemGUIDLow))
+ if (!player->HasEnoughMoney(totalPrice))
{
- TC_LOG_ERROR("misc", "Auction %u has not a existing item : " UI64FMTD, Id, itemGUIDLow);
+ player->GetSession()->SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, delayForNextAction);
return false;
}
+
+ Optional<ObjectGuid> uniqueSeller;
+
+ // prepare items
+ std::vector<Item*> items;
+ remainingQuantity = quantity;
+ std::vector<std::size_t> removedItemsFromAuction;
+
+ for (auto auctionItr = bucketItr->second.Auctions.begin(); auctionItr != bucketItr->second.Auctions.end();)
+ {
+ AuctionPosting* auction = *auctionItr++;
+ if (!uniqueSeller)
+ uniqueSeller = auction->Owner;
+ else if (*uniqueSeller != auction->Owner)
+ uniqueSeller = ObjectGuid::Empty;
+
+ uint32 boughtFromAuction = 0;
+ std::size_t removedItems = 0;
+ for (Item* auctionItem : 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(ITEM_CHANGED);
+ auctionItem->SaveToDB(trans);
+ items.push_back(clonedItem);
+ boughtFromAuction += remainingQuantity;
+ remainingQuantity = 0;
+ auctionItr = bucketItr->second.Auctions.end();
+ break;
+ }
+
+ items.push_back(auctionItem);
+ boughtFromAuction += auctionItem->GetCount();
+ remainingQuantity -= auctionItem->GetCount();
+ ++removedItems;
+ }
+
+ removedItemsFromAuction.push_back(removedItems);
+
+ if (player->GetSession()->HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE))
+ {
+ uint32 bidderAccId = player->GetSession()->GetAccountId();
+ std::string ownerName;
+ if (!sCharacterCache->GetCharacterNameByGuid(auction->Owner, ownerName))
+ ownerName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
+
+ sLog->outCommand(bidderAccId, "GM %s (Account: %u) bought commodity in auction: %s (Entry: %u Count: %u) and pay money: " UI64FMTD ". Original owner %s (Account: %u)",
+ player->GetName().c_str(), bidderAccId, items[0]->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()).c_str(),
+ items[0]->GetEntry(), boughtFromAuction, auction->BuyoutOrUnitPrice * boughtFromAuction, ownerName.c_str(),
+ sCharacterCache->GetCharacterAccountIdByGuid(auction->Owner));
+ }
+
+ uint64 auctionHouseCut = CalcualteAuctionHouseCut(auction->BuyoutOrUnitPrice * boughtFromAuction);
+ uint64 depositPart = AuctionHouseMgr::GetCommodityAuctionDeposit(items[0]->GetTemplate(), std::chrono::duration_cast<Minutes>(auction->EndTime - auction->StartTime),
+ boughtFromAuction);
+ uint64 profit = auction->BuyoutOrUnitPrice * boughtFromAuction + depositPart - auctionHouseCut;
+
+ if (Player* owner = ObjectAccessor::FindConnectedPlayer(auction->Owner))
+ {
+ owner->UpdateCriteria(CRITERIA_TYPE_GOLD_EARNED_BY_AUCTIONS, profit);
+ owner->UpdateCriteria(CRITERIA_TYPE_HIGHEST_AUCTION_SOLD, profit);
+ owner->GetSession()->SendAuctionClosedNotification(auction, (float)sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), true);
+ }
+
+ MailDraft(AuctionHouseMgr::BuildCommodityAuctionMailSubject(AuctionMailType::Sold, itemId, boughtFromAuction),
+ AuctionHouseMgr::BuildAuctionSoldMailBody(player->GetGUID(), auction->BuyoutOrUnitPrice * boughtFromAuction, boughtFromAuction, depositPart, auctionHouseCut))
+ .AddMoney(profit)
+ .SendMailTo(trans, MailReceiver(ObjectAccessor::FindConnectedPlayer(auction->Owner), auction->Owner), this, MAIL_CHECK_MASK_COPIED, sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY));
+ }
+
+ MailDraft mail(AuctionHouseMgr::BuildCommodityAuctionMailSubject(AuctionMailType::Won, itemId, quantity),
+ AuctionHouseMgr::BuildAuctionWonMailBody(*uniqueSeller, totalPrice, quantity));
+
+ for (Item* item : items)
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION_ITEMS_BY_ITEM);
+ stmt->setUInt64(0, item->GetGUID().GetCounter());
+ trans->Append(stmt);
+
+ item->SetOwnerGUID(player->GetGUID());
+ item->SaveToDB(trans);
+ mail.AddItem(item);
+ }
+
+ mail.SendMailTo(trans, player, this, MAIL_CHECK_MASK_COPIED);
+
+ WorldPackets::AuctionHouse::AuctionWonNotification packet;
+ packet.Info.Initialize(auctions[0], items[0]);
+ player->SendDirectMessage(packet.Write());
+
+ for (std::size_t i = 0; i < auctions.size(); ++i)
+ {
+ if (removedItemsFromAuction[i] == auctions[i]->Items.size())
+ RemoveAuction(trans, auctions[i]); // bought all items
+ else if (removedItemsFromAuction[i])
+ {
+ auto lastRemovedItem = auctions[i]->Items.begin() + removedItemsFromAuction[i];
+ for (auto itr = auctions[i]->Items.begin(); itr != lastRemovedItem; ++itr)
+ sAuctionMgr->RemoveAItem((*itr)->GetGUID());
+
+ auctions[i]->Items.erase(auctions[i]->Items.begin(), lastRemovedItem);
+ }
+ }
+
return true;
}
-std::string AuctionEntry::BuildAuctionMailSubject(MailAuctionAnswers response) const
+// this function notified old bidder that his bid is no longer highest
+void AuctionHouseObject::SendAuctionOutbid(AuctionPosting const* auction, ObjectGuid newBidder, uint64 newBidAmount, CharacterDatabaseTransaction trans)
{
- std::ostringstream strm;
- strm << itemEntry << ":0:" << response << ':' << Id << ':' << itemCount;
- return strm.str();
+ Player* oldBidder = ObjectAccessor::FindConnectedPlayer(auction->Bidder);
+
+ // old bidder exist
+ if ((oldBidder || sCharacterCache->HasCharacterCacheEntry(auction->Bidder)) && !sAuctionBotConfig->IsBotChar(auction->Bidder))
+ {
+ if (oldBidder)
+ {
+ WorldPackets::AuctionHouse::AuctionOutbidNotification packet;
+ packet.BidAmount = newBidAmount;
+ packet.MinIncrement = AuctionPosting::CalculateMinIncrement(newBidAmount);
+ packet.Info.AuctionID = auction->Id;
+ packet.Info.Bidder = newBidder;
+ packet.Info.Item.Initialize(auction->Items[0]);
+ oldBidder->SendDirectMessage(packet.Write());
+ }
+
+ MailDraft(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Outbid, auction), "")
+ .AddMoney(auction->BidAmount)
+ .SendMailTo(trans, MailReceiver(oldBidder, auction->Bidder), this, MAIL_CHECK_MASK_COPIED);
+ }
}
-std::string AuctionEntry::BuildAuctionMailBody(uint64 lowGuid, uint64 bid, uint64 buyout, uint64 deposit, uint64 cut)
+void AuctionHouseObject::SendAuctionWon(AuctionPosting const* auction, Player* bidder, CharacterDatabaseTransaction trans)
{
- std::ostringstream strm;
- strm.width(16);
- strm << std::right << std::hex << ObjectGuid::Create<HighGuid::Player>(lowGuid).ToString(); // HIGHGUID_PLAYER always present, even for empty guids
- strm << std::dec << ':' << bid << ':' << buyout;
- strm << ':' << deposit << ':' << cut;
- return strm.str();
+ uint32 bidderAccId = 0;
+ if (!bidder)
+ bidder = ObjectAccessor::FindConnectedPlayer(auction->Bidder); // try lookup bidder when called from ::Update
+
+ // data for gm.log
+ std::string bidderName;
+ bool logGmTrade = false;
+
+ if (bidder)
+ {
+ bidderAccId = bidder->GetSession()->GetAccountId();
+ bidderName = bidder->GetName();
+ logGmTrade = bidder->GetSession()->HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE);
+ }
+ else
+ {
+ bidderAccId = sCharacterCache->GetCharacterAccountIdByGuid(auction->Bidder);
+ logGmTrade = AccountMgr::HasPermission(bidderAccId, rbac::RBAC_PERM_LOG_GM_TRADE, realm.Id.Realm);
+
+ if (logGmTrade && !sCharacterCache->GetCharacterNameByGuid(auction->Bidder, bidderName))
+ bidderName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
+ }
+
+ if (logGmTrade)
+ {
+ std::string ownerName;
+ if (!sCharacterCache->GetCharacterNameByGuid(auction->Owner, ownerName))
+ ownerName = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
+
+ uint32 ownerAccId = sCharacterCache->GetCharacterAccountIdByGuid(auction->Owner);
+
+ sLog->outCommand(bidderAccId, "GM %s (Account: %u) won item in auction: %s (Entry: %u Count: %u) and pay money: " UI64FMTD ". Original owner %s (Account: %u)",
+ bidderName.c_str(), bidderAccId, auction->Items[0]->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()).c_str(),
+ auction->Items[0]->GetEntry(), auction->GetTotalItemCount(), auction->BidAmount, ownerName.c_str(), ownerAccId);
+ }
+
+ // receiver exist
+ if ((bidder || bidderAccId) && !sAuctionBotConfig->IsBotChar(auction->Bidder))
+ {
+ MailDraft mail(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Won, auction),
+ 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
+ for (Item* item : auction->Items)
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_OWNER);
+ stmt->setUInt64(0, auction->Bidder.GetCounter());
+ stmt->setUInt64(1, item->GetGUID().GetCounter());
+ trans->Append(stmt);
+
+ mail.AddItem(item);
+ }
+
+ if (bidder)
+ {
+ WorldPackets::AuctionHouse::AuctionWonNotification packet;
+ packet.Info.Initialize(auction, auction->Items[0]);
+ bidder->SendDirectMessage(packet.Write());
+
+ // FIXME: for offline player need also
+ bidder->UpdateCriteria(CRITERIA_TYPE_WON_AUCTIONS, 1);
+ }
+
+ mail.SendMailTo(trans, MailReceiver(bidder, auction->Bidder), this, MAIL_CHECK_MASK_COPIED);
+ }
+ else
+ {
+ // bidder doesn't exist, delete the item
+ for (Item* item : auction->Items)
+ sAuctionMgr->RemoveAItem(item->GetGUID(), true);
+ }
+}
+
+//call this method to send mail to auction owner, when auction is successful, it does not clear ram
+void AuctionHouseObject::SendAuctionSold(AuctionPosting const* auction, Player* owner, CharacterDatabaseTransaction trans)
+{
+ if (!owner)
+ owner = ObjectAccessor::FindConnectedPlayer(auction->Owner);
+
+ // owner exist
+ if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner))
+ {
+ uint64 auctionHouseCut = CalcualteAuctionHouseCut(auction->BidAmount);
+ uint64 profit = auction->BidAmount + auction->Deposit - auctionHouseCut;
+
+ //FIXME: what do if owner offline
+ if (owner)
+ {
+ owner->UpdateCriteria(CRITERIA_TYPE_GOLD_EARNED_BY_AUCTIONS, profit);
+ owner->UpdateCriteria(CRITERIA_TYPE_HIGHEST_AUCTION_SOLD, auction->BidAmount);
+ //send auction owner notification, bidder must be current!
+ owner->GetSession()->SendAuctionClosedNotification(auction, (float)sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), true);
+ }
+
+ MailDraft(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Sold, auction),
+ AuctionHouseMgr::BuildAuctionSoldMailBody(auction->Bidder, auction->BidAmount, auction->BuyoutOrUnitPrice, auction->Deposit, auctionHouseCut))
+ .AddMoney(profit)
+ .SendMailTo(trans, MailReceiver(owner, auction->Owner), this, MAIL_CHECK_MASK_COPIED, sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY));
+ }
+}
+
+void AuctionHouseObject::SendAuctionExpired(AuctionPosting const* auction, CharacterDatabaseTransaction trans)
+{
+ Player* owner = ObjectAccessor::FindConnectedPlayer(auction->Owner);
+ // owner exist
+ if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner))
+ {
+ if (owner)
+ owner->GetSession()->SendAuctionClosedNotification(auction, 0.0f, false);
+
+ MailDraft mail(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Expired, auction), "");
+
+ for (Item* item : auction->Items)
+ mail.AddItem(item);
+
+ mail.SendMailTo(trans, MailReceiver(owner, auction->Owner), this, MAIL_CHECK_MASK_COPIED, 0);
+ }
+ else
+ {
+ // owner doesn't exist, delete the item
+ for (Item* item : auction->Items)
+ sAuctionMgr->RemoveAItem(item->GetGUID(), true);
+ }
+}
+
+void AuctionHouseObject::SendAuctionRemoved(AuctionPosting const* auction, Player* owner, CharacterDatabaseTransaction trans)
+{
+ MailDraft draft(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Cancelled, auction), "");
+
+ for (Item* item : auction->Items)
+ draft.AddItem(item);
+
+ draft.SendMailTo(trans, owner, this, MAIL_CHECK_MASK_COPIED);
+}
+
+//this function sends mail, when auction is cancelled to old bidder
+void AuctionHouseObject::SendAuctionCancelledToBidder(AuctionPosting const* auction, CharacterDatabaseTransaction trans)
+{
+ Player* bidder = ObjectAccessor::FindConnectedPlayer(auction->Bidder);
+
+ // bidder exist
+ if ((bidder || sCharacterCache->HasCharacterCacheEntry(auction->Bidder)) && !sAuctionBotConfig->IsBotChar(auction->Bidder))
+ MailDraft(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Removed, auction), "")
+ .AddMoney(auction->BidAmount)
+ .SendMailTo(trans, MailReceiver(bidder, auction->Bidder), this, MAIL_CHECK_MASK_COPIED);
+}
+
+void AuctionHouseObject::SendAuctionInvoice(AuctionPosting const* auction, Player* owner, CharacterDatabaseTransaction trans)
+{
+ if (!owner)
+ owner = ObjectAccessor::FindConnectedPlayer(auction->Owner);
+
+ // owner exist (online or offline)
+ if ((owner || sCharacterCache->HasCharacterCacheEntry(auction->Owner)) && !sAuctionBotConfig->IsBotChar(auction->Owner))
+ {
+ ByteBuffer tempBuffer;
+ tempBuffer.AppendPackedTime(GameTime::GetGameTime() + sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY));
+ uint32 eta = tempBuffer.read<uint32>();
+
+ MailDraft(AuctionHouseMgr::BuildItemAuctionMailSubject(AuctionMailType::Invoice, auction),
+ AuctionHouseMgr::BuildAuctionInvoiceMailBody(auction->Bidder, auction->BidAmount, auction->BuyoutOrUnitPrice, auction->Deposit,
+ CalcualteAuctionHouseCut(auction->BidAmount), sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY), eta))
+ .SendMailTo(trans, MailReceiver(owner, auction->Owner), this, MAIL_CHECK_MASK_COPIED);
+ }
}
diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.h b/src/server/game/AuctionHouse/AuctionHouseMgr.h
index cc692aed733..9381b90ce5a 100644
--- a/src/server/game/AuctionHouse/AuctionHouseMgr.h
+++ b/src/server/game/AuctionHouse/AuctionHouseMgr.h
@@ -20,6 +20,7 @@
#include "Define.h"
#include "DatabaseEnvFwd.h"
+#include "Duration.h"
#include "EnumClassFlag.h"
#include "ItemTemplate.h"
#include "ObjectGuid.h"
@@ -36,7 +37,11 @@ namespace WorldPackets
{
namespace AuctionHouse
{
+ struct AuctionBucketKey;
struct AuctionItem;
+ struct AuctionSortDef;
+ struct BucketInfo;
+ class AuctionListBucketsResult;
class AuctionListBidderItemsResult;
class AuctionListOwnerItemsResult;
class AuctionListItemsResult;
@@ -44,46 +49,55 @@ namespace WorldPackets
}
}
-#define MIN_AUCTION_TIME (12*HOUR)
+uint32 constexpr MIN_AUCTION_TIME = 12 * HOUR;
-enum AuctionError
+enum class AuctionResult : int8
{
- ERR_AUCTION_OK = 0,
- ERR_AUCTION_INVENTORY = 1,
- ERR_AUCTION_DATABASE_ERROR = 2,
- ERR_AUCTION_NOT_ENOUGH_MONEY = 3,
- ERR_AUCTION_ITEM_NOT_FOUND = 4,
- ERR_AUCTION_HIGHER_BID = 5,
- ERR_AUCTION_BID_INCREMENT = 7,
- ERR_AUCTION_BID_OWN = 10,
- ERR_AUCTION_RESTRICTED_ACCOUNT_TRIAL = 13,
- ERR_AUCTION_HAS_RESTRICTION = 17,
- ERR_AUCTION_HOUSE_BUSY = 18,
- ERR_AUCTION_HOUSE_UNAVAILABLE = 19,
- ERR_AUCTION_COMMODITY_PURCHASE_FAILED = 21,
- ERR_AUCTION_ITEM_HAS_QUOTE = 23
+ Ok = 0,
+ Inventory = 1,
+ DatabaseError = 2,
+ NotEnoughMoney = 3,
+ ItemNotFound = 4,
+ HigherBid = 5,
+ BidIncrement = 7,
+ BidOwn = 10,
+ RestrictedAccountTrial = 13,
+ HasRestriction = 17,
+ AuctionHouseBusy = 18,
+ AuctionHouseUnavailable = 19,
+ CommodityPurchaseFailed = 21,
+ ItemHasQuote = 23
};
-enum AuctionAction
+enum class AuctionCommand : int8
{
- AUCTION_SELL_ITEM = 0,
- AUCTION_CANCEL = 1,
- AUCTION_PLACE_BID = 2
+ SellItem = 0,
+ Cancel = 1,
+ PlaceBid = 2
};
-enum MailAuctionAnswers
+enum class AuctionMailType : int32
{
- AUCTION_OUTBIDDED = 0,
- AUCTION_WON = 1,
- AUCTION_SUCCESSFUL = 2,
- AUCTION_EXPIRED = 3,
- AUCTION_CANCELLED_TO_BIDDER = 4,
- AUCTION_CANCELED = 5,
- AUCTION_SALE_PENDING = 6
+ Outbid = 0,
+ Won = 1,
+ Sold = 2,
+ Expired = 3,
+ Removed = 4, // for bidder
+ Cancelled = 5, // for seller
+ Invoice = 6
};
+enum class AuctionHouseResultLimits : std::size_t
+{
+ Browse = 500,
+ Items = 50
+};
+
+constexpr std::size_t MAX_FAVORITE_AUCTIONS = 100;
+
enum class AuctionHouseFilterMask : uint32
{
+ None = 0x0,
UncollectedOnly = 0x1,
UsableOnly = 0x2,
UpgradesOnly = 0x4,
@@ -118,39 +132,6 @@ enum class AuctionHouseListType : uint8
Items = 2
};
-struct TC_GAME_API AuctionEntry
-{
- uint32 Id;
- ObjectGuid::LowType auctioneer; // creature low guid
- ObjectGuid::LowType itemGUIDLow;
- uint32 itemEntry;
- uint32 itemCount;
- ObjectGuid::LowType owner;
- uint64 startbid; //maybe useless
- uint64 bid;
- uint64 buyout;
- time_t expire_time;
- ObjectGuid::LowType bidder;
- uint64 deposit; //deposit can be calculated only when creating auction
- uint32 etime;
- uint32 houseId;
- AuctionHouseEntry const* auctionHouseEntry; // in AuctionHouse.dbc
- uint32 factionTemplateId;
-
- // helpers
- uint32 GetHouseId() const { return houseId; }
- uint32 GetHouseFaction() const { return auctionHouseEntry->FactionID; }
- uint64 GetAuctionCut() const;
- uint64 GetAuctionOutBid() const;
- void BuildAuctionInfo(std::vector<WorldPackets::AuctionHouse::AuctionItem>& items, bool listAuctionItems, Item* sourceItem = nullptr) const;
- void DeleteFromDB(CharacterDatabaseTransaction& trans) const;
- void SaveToDB(CharacterDatabaseTransaction& trans) const;
- bool LoadFromDB(Field* fields);
- std::string BuildAuctionMailSubject(MailAuctionAnswers response) const;
- static std::string BuildAuctionMailBody(uint64 lowGuid, uint64 bid, uint64 buyout, uint64 deposit, uint64 cut);
-
-};
-
struct AuctionSearchClassFilters
{
enum FilterType : uint32
@@ -169,62 +150,194 @@ struct AuctionSearchClassFilters
std::array<SubclassFilter, MAX_ITEM_CLASS> Classes = { };
};
-//this class is used as auctionhouse instance
-class TC_GAME_API AuctionHouseObject
+struct AuctionsBucketKey
{
- public:
- ~AuctionHouseObject()
+ AuctionsBucketKey() = default;
+ AuctionsBucketKey(uint32 itemId, uint16 itemLevel, uint16 battlePetSpeciesId, uint16 suffixItemNameDescriptionId)
+ : ItemId(itemId), ItemLevel(itemLevel), BattlePetSpeciesId(battlePetSpeciesId), SuffixItemNameDescriptionId(suffixItemNameDescriptionId) { }
+ AuctionsBucketKey(WorldPackets::AuctionHouse::AuctionBucketKey const& key);
+
+ uint32 ItemId;
+ uint16 ItemLevel;
+ uint16 BattlePetSpeciesId;
+ uint16 SuffixItemNameDescriptionId;
+
+ bool operator==(AuctionsBucketKey const& right) const
+ {
+ return ItemId == right.ItemId
+ && ItemLevel == right.ItemLevel
+ && BattlePetSpeciesId == right.BattlePetSpeciesId
+ && SuffixItemNameDescriptionId == right.SuffixItemNameDescriptionId;
+ }
+
+ bool operator!=(AuctionsBucketKey const& right) const
{
- for (AuctionEntryMap::iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
- delete itr->second;
+ return !(*this == right);
}
- typedef std::map<uint32, AuctionEntry*> AuctionEntryMap;
+ static std::size_t Hash(AuctionsBucketKey const& bucket);
+ static AuctionsBucketKey ForItem(Item* item);
+ static AuctionsBucketKey ForCommodity(uint32 itemId);
+};
+
+bool operator<(AuctionsBucketKey const& left, AuctionsBucketKey const& right);
- struct PlayerGetAllThrottleData
+namespace std
+{
+ template<>
+ struct hash<AuctionsBucketKey>
+ {
+ size_t operator()(AuctionsBucketKey const& key) const
+ {
+ return AuctionsBucketKey::Hash(key);
+ }
+ };
+}
+
+struct AuctionPosting;
+
+struct AuctionsBucketData
+{
+ AuctionsBucketKey Key;
+
+ // filter helpers
+ uint8 ItemClass = 0;
+ uint8 ItemSubClass = 0;
+ uint8 InventoryType = 0;
+ EnumClassFlag<AuctionHouseFilterMask> QualityMask = AuctionHouseFilterMask::None;
+ std::array<uint32, MAX_ITEM_QUALITY> QualityCounts = { };
+ uint64 MinPrice = 0; // for sort
+ std::array<std::pair<uint32, uint32>, 4> ItemModifiedAppearanceId = { }; // for uncollected search
+ uint8 RequiredLevel = 0; // for usable search
+ uint8 MinBattlePetLevel = 0;
+ uint8 MaxBattlePetLevel = 0;
+ std::array<std::wstring, TOTAL_LOCALES> FullName = { };
+
+ std::vector<AuctionPosting*> Auctions;
+
+ void BuildBucketInfo(WorldPackets::AuctionHouse::BucketInfo* bucketInfo, Player* player) const;
+
+ class Sorter;
+};
+
+// This structure represents the result of a single C_AuctionHouse.PostItem/PostCommodity call
+struct AuctionPosting
+{
+ uint32 Id = 0;
+ AuctionsBucketData* Bucket = nullptr;
+
+ std::vector<Item*> Items;
+ ObjectGuid Owner;
+ ObjectGuid OwnerAccount;
+ ObjectGuid Bidder;
+ uint64 MinBid = 0;
+ uint64 BuyoutOrUnitPrice = 0;
+ uint64 Deposit = 0;
+ uint64 BidAmount = 0;
+ std::chrono::system_clock::time_point StartTime = std::chrono::system_clock::time_point::min();
+ std::chrono::system_clock::time_point EndTime = std::chrono::system_clock::time_point::min();
+
+ GuidUnorderedSet BidderHistory;
+
+ bool IsCommodity() const;
+ uint32 GetTotalItemCount() const;
+ void BuildAuctionItem(WorldPackets::AuctionHouse::AuctionItem* auctionItem, bool alwaysSendItem, bool sendKey, bool censorServerInfo, bool censorBidInfo) const;
+ static uint64 CalculateMinIncrement(uint64 currentBid);
+ uint64 CalculateMinIncrement() const { return CalculateMinIncrement(BidAmount); }
+
+ class Sorter;
+};
+
+struct CommodityQuote
+{
+ uint64 TotalPrice = 0;
+ uint32 Quantity = 0;
+ std::chrono::steady_clock::time_point ValidTo = std::chrono::steady_clock::time_point::min();
+};
+
+struct AuctionThrottleResult
+{
+ Milliseconds DelayUntilNext;
+ bool Throttled;
+};
+
+//this class is used as auctionhouse instance
+class TC_GAME_API AuctionHouseObject
+{
+public:
+ explicit AuctionHouseObject(uint32 auctionHouseId);
+
+ ~AuctionHouseObject();
+
+ struct PlayerReplicateThrottleData
{
uint32 Global = 0;
uint32 Cursor = 0;
uint32 Tombstone = 0;
- time_t NextAllowedReplication = 0;
+ std::chrono::steady_clock::time_point NextAllowedReplication = std::chrono::steady_clock::time_point::min();
bool IsReplicationInProgress() const { return Cursor != Tombstone && Global != 0; }
};
- typedef std::unordered_map<ObjectGuid, PlayerGetAllThrottleData> PlayerGetAllThrottleMap;
-
- uint32 Getcount() const { return uint32(AuctionsMap.size()); }
+ uint32 GetAuctionHouseId() const;
- AuctionEntryMap::iterator GetAuctionsBegin() { return AuctionsMap.begin(); }
- AuctionEntryMap::iterator GetAuctionsEnd() { return AuctionsMap.end(); }
+ std::map<uint32, AuctionPosting>::iterator GetAuctionsBegin() { return _itemsByAuctionId.begin(); }
+ std::map<uint32, AuctionPosting>::iterator GetAuctionsEnd() { return _itemsByAuctionId.end(); }
- AuctionEntry* GetAuction(uint32 id) const
- {
- AuctionEntryMap::const_iterator itr = AuctionsMap.find(id);
- return itr != AuctionsMap.end() ? itr->second : NULL;
- }
+ AuctionPosting* GetAuction(uint32 auctionId);
- void AddAuction(AuctionEntry* auction);
+ void AddAuction(CharacterDatabaseTransaction trans, AuctionPosting auction);
- bool RemoveAuction(AuctionEntry* auction);
+ void RemoveAuction(CharacterDatabaseTransaction trans, AuctionPosting* auction, std::map<uint32, AuctionPosting>::iterator* auctionItr = nullptr);
void Update();
- void BuildListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItemsResult& packet, Player* player);
- void BuildListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItemsResult& packet, Player* player);
- void BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult& packet, Player* player,
- std::wstring const& searchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, EnumClassFlag<AuctionHouseFilterMask> filters,
- Optional<AuctionSearchClassFilters> const& classFilters);
- void BuildReplicate(WorldPackets::AuctionHouse::AuctionReplicateResponse& auctionReplicateResult, Player* player,
+ void BuildListBuckets(WorldPackets::AuctionHouse::AuctionListBucketsResult& listBucketsResult, Player* player,
+ std::wstring const& name, uint8 minLevel, uint8 maxLevel, EnumClassFlag<AuctionHouseFilterMask> filters, Optional<AuctionSearchClassFilters> const& classFilters,
+ uint8 const* knownPetBits, std::size_t knownPetBitsCount, uint8 maxKnownPetLevel,
+ uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount);
+ void BuildListBuckets(WorldPackets::AuctionHouse::AuctionListBucketsResult& listBucketsResult, Player* player,
+ WorldPackets::AuctionHouse::AuctionBucketKey const* keys, std::size_t keysCount,
+ WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount);
+ void BuildListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItemsResult& listBidderItemsResult, Player* player,
+ uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) const;
+ void BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult& listItemsResult, Player* player, AuctionsBucketKey const& bucketKey,
+ uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) const;
+ void BuildListAuctionItems(WorldPackets::AuctionHouse::AuctionListItemsResult& listItemsResult, Player* player, uint32 itemId,
+ uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount) const;
+ void BuildListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItemsResult& listOwnerItemsResult, Player* player,
+ uint32 offset, WorldPackets::AuctionHouse::AuctionSortDef const* sorts, std::size_t sortCount);
+ void BuildReplicate(WorldPackets::AuctionHouse::AuctionReplicateResponse& replicateResponse, Player* player,
uint32 global, uint32 cursor, uint32 tombstone, uint32 count);
- private:
- AuctionEntryMap AuctionsMap;
+ uint64 CalcualteAuctionHouseCut(uint64 bidAmount) const;
+
+ CommodityQuote const* CreateCommodityQuote(Player* player, uint32 itemId, uint32 quantity);
+ void CancelCommodityQuote(ObjectGuid guid);
+ bool BuyCommodity(CharacterDatabaseTransaction trans, Player* player, uint32 itemId, uint32 quantity, Milliseconds delayForNextAction);
+
+ void SendAuctionOutbid(AuctionPosting const* auction, ObjectGuid newBidder, uint64 newBidAmount, CharacterDatabaseTransaction trans);
+ void SendAuctionWon(AuctionPosting const* auction, Player* player, CharacterDatabaseTransaction trans);
+ void SendAuctionSold(AuctionPosting const* auction, Player* owner, CharacterDatabaseTransaction trans);
+ void SendAuctionExpired(AuctionPosting const* auction, CharacterDatabaseTransaction trans);
+ void SendAuctionRemoved(AuctionPosting const* auction, Player* owner, CharacterDatabaseTransaction trans);
+ void SendAuctionCancelledToBidder(AuctionPosting const* auction, CharacterDatabaseTransaction trans);
+ void SendAuctionInvoice(AuctionPosting const* auction, Player* owner, CharacterDatabaseTransaction trans);
+
+private:
+ AuctionHouseEntry const* _auctionHouse;
+
+ std::map<uint32, AuctionPosting> _itemsByAuctionId; // ordered for replicate
+ std::unordered_map<uint32, AuctionPosting> _soldItemsById;
+ std::map<AuctionsBucketKey, AuctionsBucketData> _buckets; // ordered for search by itemid only
+ std::unordered_map<ObjectGuid, CommodityQuote> _commodityQuotes;
+
+ std::unordered_multimap<ObjectGuid, uint32> _playerOwnedAuctions;
+ std::unordered_multimap<ObjectGuid, uint32> _playerBidderAuctions;
// Map of throttled players for GetAll, and throttle expiry time
// Stored here, rather than player object to maintain persistence after logout
- PlayerGetAllThrottleMap GetAllThrottleMap;
-
+ std::unordered_map<ObjectGuid, PlayerReplicateThrottleData> _replicateThrottleMap;
};
class TC_GAME_API AuctionHouseMgr
@@ -236,56 +349,73 @@ class TC_GAME_API AuctionHouseMgr
public:
static AuctionHouseMgr* instance();
- typedef std::unordered_map<ObjectGuid::LowType, Item*> ItemMap;
- typedef std::vector<AuctionEntry*> PlayerAuctions;
- typedef std::pair<PlayerAuctions*, uint32> AuctionPair;
-
AuctionHouseObject* GetAuctionsMap(uint32 factionTemplateId);
- AuctionHouseObject* GetBidsMap(uint32 factionTemplateId);
-
- Item* GetAItem(ObjectGuid::LowType id)
- {
- ItemMap::const_iterator itr = mAitems.find(id);
- if (itr != mAitems.end())
- return itr->second;
+ AuctionHouseObject* GetAuctionsById(uint32 auctionHouseId);
- return NULL;
- }
+ Item* GetAItem(ObjectGuid itemGuid);
- //auction messages
- void SendAuctionWonMail(AuctionEntry* auction, CharacterDatabaseTransaction& trans);
- void SendAuctionSalePendingMail(AuctionEntry* auction, CharacterDatabaseTransaction& trans);
- void SendAuctionSuccessfulMail(AuctionEntry* auction, CharacterDatabaseTransaction& trans);
- void SendAuctionExpiredMail(AuctionEntry* auction, CharacterDatabaseTransaction& trans);
- void SendAuctionOutbiddedMail(AuctionEntry* auction, uint64 newPrice, Player* newBidder, CharacterDatabaseTransaction& trans);
- void SendAuctionCancelledToBidderMail(AuctionEntry* auction, CharacterDatabaseTransaction& trans);
+ static std::string BuildItemAuctionMailSubject(AuctionMailType type, AuctionPosting const* auction);
+ static std::string BuildCommodityAuctionMailSubject(AuctionMailType type, uint32 itemId, uint32 itemCount);
+ static std::string BuildAuctionMailSubject(uint32 itemId, AuctionMailType type, uint32 auctionId, uint32 itemCount, uint32 battlePetSpeciesId,
+ ItemContext context, std::vector<int32> const& bonusListIds);
+ static std::string BuildAuctionWonMailBody(ObjectGuid guid, uint64 bid, uint64 buyout);
+ static std::string BuildAuctionSoldMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment);
+ static std::string BuildAuctionInvoiceMailBody(ObjectGuid guid, uint64 bid, uint64 buyout, uint32 deposit, uint64 consignment, uint32 moneyDelay, uint32 eta);
- static uint64 GetAuctionDeposit(AuctionHouseEntry const* entry, uint32 time, Item* pItem, uint32 count);
+ static uint64 GetCommodityAuctionDeposit(ItemTemplate const* item, Minutes time, uint32 quantity);
+ static uint64 GetItemAuctionDeposit(Player* player, Item* item, Minutes time);
static AuctionHouseEntry const* GetAuctionHouseEntry(uint32 factionTemplateId, uint32* houseId);
public:
- //load first auction items, because of check if item exists, when loading
- void LoadAuctionItems();
void LoadAuctions();
- void AddAItem(Item* it);
- bool RemoveAItem(ObjectGuid::LowType id, bool deleteItem = false);
- bool PendingAuctionAdd(Player* player, AuctionEntry* aEntry);
- uint32 PendingAuctionCount(const Player* player) const;
+ void AddAItem(Item* item);
+ bool RemoveAItem(ObjectGuid itemGuid, bool deleteItem = false);
+ bool PendingAuctionAdd(Player* player, uint32 auctionHouseId, uint32 auctionId, uint64 deposit);
+ std::size_t PendingAuctionCount(Player const* player) const;
void PendingAuctionProcess(Player* player);
void UpdatePendingAuctions();
void Update();
+ uint32 GenerateReplicationId();
+
+ AuctionThrottleResult CheckThrottle(Player* player, AuctionCommand command = AuctionCommand::SellItem);
+
private:
AuctionHouseObject mHordeAuctions;
AuctionHouseObject mAllianceAuctions;
AuctionHouseObject mNeutralAuctions;
+ AuctionHouseObject mGoblinAuctions;
+
+ struct PendingAuctionInfo
+ {
+ uint32 AuctionId = 0;
+ uint32 AuctionHouseId = 0;
+ uint64 Deposit = 0;
+ };
+
+ struct PlayerPendingAuctions
+ {
+ std::vector<PendingAuctionInfo> Auctions;
+ std::size_t LastAuctionsSize = 0;
+ };
+
+ struct PlayerThrottleObject
+ {
+ std::chrono::steady_clock::time_point PeriodEnd;
+ uint8 QueriesRemaining = 100;
+ };
+
+ std::unordered_map<ObjectGuid, PlayerPendingAuctions> _pendingAuctionsByPlayer;
+
+ std::unordered_map<ObjectGuid, Item*> _itemsByGuid;
- std::map<ObjectGuid, AuctionPair> pendingAuctionMap;
+ uint32 _replicateIdGenerator;
- ItemMap mAitems;
+ std::unordered_map<ObjectGuid, PlayerThrottleObject> _playerThrottleObjects;
+ std::chrono::steady_clock::time_point _playerThrottleObjectsCleanupTime;
};
#define sAuctionMgr AuctionHouseMgr::instance()
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp b/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp
index 7c756e7926f..f4c8aa5c320 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBot.cpp
@@ -26,6 +26,8 @@
#include "Log.h"
#include "World.h"
+constexpr uint32 AuctionHouseIds[MAX_AUCTION_HOUSE_TYPE] = { 1, 2, 6 };
+
AuctionBotConfig* AuctionBotConfig::instance()
{
static AuctionBotConfig instance;
@@ -67,7 +69,7 @@ bool AuctionBotConfig::Initialize()
{
do
{
- _AHBotCharacters.push_back((*result)[0].GetUInt64());
+ _AHBotCharacters.push_back(ObjectGuid::Create<HighGuid::Player>((*result)[0].GetUInt64()));
} while (result->NextRow());
TC_LOG_DEBUG("ahbot", "AuctionHouseBot found " UI64FMTD " characters", result->GetRowCount());
@@ -291,6 +293,11 @@ void AuctionBotConfig::GetConfigFromFile()
SetConfig(CONFIG_AHBOT_BIDPRICE_MAX, "AuctionHouseBot.BidPrice.Max", 0.9f);
}
+uint32 AuctionBotConfig::GetAuctionHouseId(AuctionHouseType houseType) const
+{
+ return AuctionHouseIds[houseType];
+}
+
char const* AuctionBotConfig::GetHouseTypeName(AuctionHouseType houseType)
{
static char const* names[MAX_AUCTION_HOUSE_TYPE] = { "Neutral", "Alliance", "Horde" };
@@ -298,35 +305,35 @@ char const* AuctionBotConfig::GetHouseTypeName(AuctionHouseType houseType)
}
// Picks a random character from the list of AHBot chars
-ObjectGuid::LowType AuctionBotConfig::GetRandChar() const
+ObjectGuid AuctionBotConfig::GetRandChar() const
{
if (_AHBotCharacters.empty())
- return ObjectGuid::LowType(0);
+ return ObjectGuid::Empty;
return Trinity::Containers::SelectRandomContainerElement(_AHBotCharacters);
}
// Picks a random AHBot character, but excludes a specific one. This is used
// to have another character than the auction owner place bids
-ObjectGuid::LowType AuctionBotConfig::GetRandCharExclude(ObjectGuid::LowType exclude) const
+ObjectGuid AuctionBotConfig::GetRandCharExclude(ObjectGuid exclude) const
{
if (_AHBotCharacters.empty())
- return ObjectGuid::LowType(0);
+ return ObjectGuid::Empty;
- std::vector<uint32> filteredCharacters;
+ std::vector<ObjectGuid> filteredCharacters;
filteredCharacters.reserve(_AHBotCharacters.size() - 1);
- for (uint32 charId : _AHBotCharacters)
+ for (ObjectGuid charId : _AHBotCharacters)
if (charId != exclude)
filteredCharacters.push_back(charId);
if (filteredCharacters.empty())
- return ObjectGuid::LowType(0);
+ return ObjectGuid::Empty;
return Trinity::Containers::SelectRandomContainerElement(filteredCharacters);
}
-bool AuctionBotConfig::IsBotChar(ObjectGuid::LowType characterID) const
+bool AuctionBotConfig::IsBotChar(ObjectGuid characterID) const
{
return !characterID || std::find(_AHBotCharacters.begin(), _AHBotCharacters.end(), characterID) != _AHBotCharacters.end();
}
@@ -460,19 +467,19 @@ void AuctionHouseBot::PrepareStatusInfos(AuctionHouseBotStatusInfo& statusInfo)
for (int j = 0; j < MAX_AUCTION_QUALITY; ++j)
statusInfo[i].QualityInfo[j] = 0;
- AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(AuctionHouseType(i));
- for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = auctionHouse->GetAuctionsBegin(); itr != auctionHouse->GetAuctionsEnd(); ++itr)
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsById(AuctionHouseIds[i]);
+ for (auto itr = auctionHouse->GetAuctionsBegin(); itr != auctionHouse->GetAuctionsEnd(); ++itr)
{
- AuctionEntry* auctionEntry = itr->second;
- if (Item* item = sAuctionMgr->GetAItem(auctionEntry->itemGUIDLow))
+ AuctionPosting const& auction = itr->second;
+ for (Item* item : auction.Items)
{
ItemTemplate const* prototype = item->GetTemplate();
- if (!auctionEntry->owner || sAuctionBotConfig->IsBotChar(auctionEntry->owner)) // Add only ahbot items
+ if (auction.Owner.IsEmpty() || sAuctionBotConfig->IsBotChar(auction.Owner)) // Add only ahbot items
{
if (prototype->GetQuality() < MAX_AUCTION_QUALITY)
++statusInfo[i].QualityInfo[prototype->GetQuality()];
- ++statusInfo[i].ItemsCount;
+ statusInfo[i].ItemsCount += item->GetCount();
}
}
}
@@ -483,11 +490,11 @@ void AuctionHouseBot::Rebuild(bool all)
{
for (uint32 i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i)
{
- AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(AuctionHouseType(i));
- for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = auctionHouse->GetAuctionsBegin(); itr != auctionHouse->GetAuctionsEnd(); ++itr)
- if (!itr->second->owner || sAuctionBotConfig->IsBotChar(itr->second->owner)) // ahbot auction
- if (all || itr->second->bid == 0) // expire now auction if no bid or forced
- itr->second->expire_time = GameTime::GetGameTime();
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsById(AuctionHouseIds[i]);
+ for (auto itr = auctionHouse->GetAuctionsBegin(); itr != auctionHouse->GetAuctionsEnd(); ++itr)
+ if (itr->second.Owner.IsEmpty() || sAuctionBotConfig->IsBotChar(itr->second.Owner)) // ahbot auction
+ if (all || itr->second.BidAmount == 0) // expire now auction if no bid or forced
+ itr->second.EndTime = GameTime::GetGameTimeSystemPoint();
}
}
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBot.h b/src/server/game/AuctionHouseBot/AuctionHouseBot.h
index 151c6eb67f7..75145c498e6 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBot.h
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBot.h
@@ -230,17 +230,18 @@ public:
uint32 GetItemPerCycleBoost() const { return _itemsPerCycleBoost; }
uint32 GetItemPerCycleNormal() const { return _itemsPerCycleNormal; }
- ObjectGuid::LowType GetRandChar() const;
- ObjectGuid::LowType GetRandCharExclude(ObjectGuid::LowType exclude) const;
- bool IsBotChar(ObjectGuid::LowType characterID) const;
+ ObjectGuid GetRandChar() const;
+ ObjectGuid GetRandCharExclude(ObjectGuid exclude) const;
+ bool IsBotChar(ObjectGuid characterID) const;
void Reload() { GetConfigFromFile(); }
+ uint32 GetAuctionHouseId(AuctionHouseType houseType) const;
static char const* GetHouseTypeName(AuctionHouseType houseType);
private:
std::string _AHBotIncludes;
std::string _AHBotExcludes;
- std::vector<ObjectGuid::LowType> _AHBotCharacters;
+ std::vector<ObjectGuid> _AHBotCharacters;
uint32 _itemsPerCycleBoost;
uint32 _itemsPerCycleNormal;
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp
index a00955e5bfa..bd6425f9dfd 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.cpp
@@ -100,38 +100,40 @@ uint32 AuctionBotBuyer::GetItemInformation(BuyerConfiguration& config)
time_t now = time(nullptr);
uint32 count = 0;
- AuctionHouseObject* house = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
- for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = house->GetAuctionsBegin(); itr != house->GetAuctionsEnd(); ++itr)
+ AuctionHouseObject* house = sAuctionMgr->GetAuctionsById(sAuctionBotConfig->GetAuctionHouseId(config.GetHouseType()));
+ for (auto itr = house->GetAuctionsBegin(); itr != house->GetAuctionsEnd(); ++itr)
{
- AuctionEntry* entry = itr->second;
+ AuctionPosting* entry = &itr->second;
- if (!entry->owner || sAuctionBotConfig->IsBotChar(entry->owner))
- continue; // Skip auctions owned by AHBot
+ if (entry->IsCommodity())
+ continue; // skip commodities, there can be thousands of items in a single auction. TODO: partial buys?
- Item* item = sAuctionMgr->GetAItem(entry->itemGUIDLow);
- if (!item)
- continue;
+ if (entry->Owner.IsEmpty() || sAuctionBotConfig->IsBotChar(entry->Owner))
+ continue; // Skip auctions owned by AHBot
- BuyerItemInfo& itemInfo = config.SameItemInfo[item->GetEntry()];
+ BuyerItemInfo& itemInfo = config.SameItemInfo[entry->Bucket->Key.ItemId];
// Update item entry's count and total bid prices
// This can be used later to determine the prices and chances to bid
- uint32 itemBidPrice = entry->startbid / item->GetCount();
- itemInfo.TotalBidPrice = itemInfo.TotalBidPrice + itemBidPrice;
- itemInfo.BidItemCount++;
+ if (entry->MinBid)
+ {
+ uint32 itemBidPrice = entry->MinBid;
+ itemInfo.TotalBidPrice = itemInfo.TotalBidPrice + itemBidPrice;
+ itemInfo.BidItemCount++;
- // Set minimum bid price
- if (!itemInfo.MinBidPrice)
- itemInfo.MinBidPrice = itemBidPrice;
- else
- itemBidPrice = std::min(itemInfo.MinBidPrice, itemBidPrice);
+ // Set minimum bid price
+ if (!itemInfo.MinBidPrice)
+ itemInfo.MinBidPrice = itemBidPrice;
+ else
+ itemInfo.MinBidPrice = std::min(itemInfo.MinBidPrice, itemBidPrice);
+ }
// Set minimum buyout price if item has buyout
- if (entry->buyout)
+ if (entry->BuyoutOrUnitPrice)
{
// Update item entry's count and total buyout prices
// This can be used later to determine the prices and chances to buyout
- uint32 itemBuyPrice = entry->buyout / item->GetCount();
+ uint32 itemBuyPrice = entry->BuyoutOrUnitPrice;
itemInfo.TotalBuyPrice = itemInfo.TotalBuyPrice + itemBuyPrice;
itemInfo.BuyItemCount++;
@@ -144,7 +146,7 @@ uint32 AuctionBotBuyer::GetItemInformation(BuyerConfiguration& config)
// Add/update EligibleItems if:
// * no bid
// * bid from player
- if (!entry->bid || entry->bidder)
+ if (!entry->BidAmount || !entry->Bidder.IsEmpty())
{
config.EligibleItems[entry->Id].LastExist = now;
config.EligibleItems[entry->Id].AuctionId = entry->Id;
@@ -158,13 +160,19 @@ uint32 AuctionBotBuyer::GetItemInformation(BuyerConfiguration& config)
}
// ahInfo can be NULL
-bool AuctionBotBuyer::RollBuyChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 /*bidPrice*/)
+bool AuctionBotBuyer::RollBuyChance(BuyerItemInfo const* ahInfo, AuctionPosting const* auction)
{
- if (!auction->buyout)
+ if (!auction->BuyoutOrUnitPrice)
return false;
- float itemBuyPrice = float(auction->buyout / item->GetCount());
- float itemPrice = float(item->GetTemplate()->GetSellPrice() ? item->GetTemplate()->GetSellPrice() : GetVendorPrice(item->GetTemplate()->GetQuality()));
+ Item const* item = auction->Items[0];
+ float itemBuyPrice = float(auction->BuyoutOrUnitPrice);
+ float itemPrice;
+ if (uint32 itemSellPrice = item->GetSellPrice(item->GetTemplate(), item->GetQuality(), item->GetItemLevel(item->GetTemplate(), *item->GetBonus(), 0, 0, 0, 0, 0, false, 0)))
+ itemPrice = float(itemSellPrice);
+ else
+ itemPrice = float(GetVendorPrice(item->GetQuality()));
+
// The AH cut needs to be added to the price, but we dont want a 100% chance to buy if the price is exactly AH default
itemPrice *= 1.4f;
@@ -173,7 +181,7 @@ bool AuctionBotBuyer::RollBuyChance(const BuyerItemInfo* ahInfo, const Item* ite
float chance = std::min(100.f, std::pow(100.f, 1.f + (1.f - itemBuyPrice / itemPrice) / sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_FACTOR)));
// If a player has bidded on item, have fifth of normal chance
- if (auction->bidder)
+ if (!auction->Bidder.IsEmpty())
chance = chance / 5.f;
if (ahInfo)
@@ -197,10 +205,19 @@ bool AuctionBotBuyer::RollBuyChance(const BuyerItemInfo* ahInfo, const Item* ite
}
// ahInfo can be NULL
-bool AuctionBotBuyer::RollBidChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 bidPrice)
+bool AuctionBotBuyer::RollBidChance(BuyerItemInfo const* ahInfo, AuctionPosting const* auction, uint32 bidPrice)
{
- float itemBidPrice = float(bidPrice / item->GetCount());
- float itemPrice = float(item->GetTemplate()->GetSellPrice() ? item->GetTemplate()->GetSellPrice() : GetVendorPrice(item->GetTemplate()->GetQuality()));
+ if (!auction->MinBid)
+ return false;
+
+ Item const* item = auction->Items[0];
+ float itemBidPrice = float(bidPrice);
+ float itemPrice;
+ if (uint32 itemSellPrice = item->GetSellPrice(item->GetTemplate(), item->GetQuality(), item->GetItemLevel(item->GetTemplate(), *item->GetBonus(), 0, 0, 0, 0, 0, false, 0)))
+ itemPrice = float(itemSellPrice);
+ else
+ itemPrice = float(GetVendorPrice(item->GetQuality()));
+
// The AH cut needs to be added to the price, but we dont want a 100% chance to buy if the price is exactly AH default
itemPrice *= 1.4f;
@@ -220,7 +237,7 @@ bool AuctionBotBuyer::RollBidChance(const BuyerItemInfo* ahInfo, const Item* ite
}
// If a player has bidded on item, have fifth of normal chance
- if (auction->bidder && !sAuctionBotConfig->IsBotChar(auction->bidder))
+ if (!auction->Bidder.IsEmpty() && !sAuctionBotConfig->IsBotChar(auction->Bidder))
chance = chance / 5.f;
// Add config weigh in for quality
@@ -254,7 +271,7 @@ void AuctionBotBuyer::PrepareListOfEntry(BuyerConfiguration& config)
void AuctionBotBuyer::BuyAndBidItems(BuyerConfiguration& config)
{
time_t now = time(nullptr);
- AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsById(sAuctionBotConfig->GetAuctionHouseId(config.GetHouseType()));
CheckEntryMap& items = config.EligibleItems;
// Max amount of items to buy or bid
@@ -270,7 +287,7 @@ void AuctionBotBuyer::BuyAndBidItems(BuyerConfiguration& config)
CheckEntryMap::iterator itr = items.begin();
while (cycles && itr != items.end())
{
- AuctionEntry* auction = auctionHouse->GetAuction(itr->second.AuctionId);
+ AuctionPosting* auction = auctionHouse->GetAuction(itr->second.AuctionId);
if (!auction)
{
TC_LOG_DEBUG("ahbot", "AHBot: Entry %u doesn't exists, perhaps bought already?", itr->second.AuctionId);
@@ -287,48 +304,40 @@ void AuctionBotBuyer::BuyAndBidItems(BuyerConfiguration& config)
continue;
}
- Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow);
- if (!item)
- {
- // auction item not accessible, possible auction in payment pending mode
- items.erase(itr++);
- continue;
- }
-
// price to bid if bidding
- uint32 bidPrice;
- if (auction->bid >= auction->startbid)
+ uint64 bidPrice;
+ if (auction->BidAmount)
{
// get bid price to outbid previous bidder
- bidPrice = auction->bid + auction->GetAuctionOutBid();
+ bidPrice = auction->BidAmount + auction->CalculateMinIncrement();
}
else
{
// no previous bidders - use starting bid
- bidPrice = auction->startbid;
+ bidPrice = auction->MinBid;
}
const BuyerItemInfo* ahInfo = nullptr;
- BuyerItemInfoMap::const_iterator sameItemItr = config.SameItemInfo.find(item->GetEntry());
+ BuyerItemInfoMap::const_iterator sameItemItr = config.SameItemInfo.find(auction->Bucket->Key.ItemId);
if (sameItemItr != config.SameItemInfo.end())
ahInfo = &sameItemItr->second;
TC_LOG_DEBUG("ahbot", "AHBot: Rolling for AHentry %u:", auction->Id);
// Roll buy and bid chances
- bool successBuy = RollBuyChance(ahInfo, item, auction, bidPrice);
- bool successBid = RollBidChance(ahInfo, item, auction, bidPrice);
+ bool successBuy = RollBuyChance(ahInfo, auction);
+ bool successBid = RollBidChance(ahInfo, auction, bidPrice);
// If roll bidding succesfully and bid price is above buyout -> buyout
// If roll for buying was successful but not for bid, buyout directly
// If roll bidding was also successful, buy the entry with 20% chance
// - Better bid than buy since the item is bought by bot if no player bids after
// Otherwise bid if roll for bid was successful
- if ((auction->buyout && successBid && bidPrice >= auction->buyout) ||
+ if ((auction->BuyoutOrUnitPrice && successBid && bidPrice >= auction->BuyoutOrUnitPrice) ||
(successBuy && (!successBid || urand(1, 5) == 1)))
BuyEntry(auction, auctionHouse); // buyout
else if (successBid)
- PlaceBidToEntry(auction, bidPrice); // bid
+ PlaceBidToEntry(auction, auctionHouse, bidPrice); // bid
itr->second.LastChecked = now;
--cycles;
@@ -386,55 +395,54 @@ uint32 AuctionBotBuyer::GetChanceMultiplier(uint32 quality)
}
// Buys the auction and does necessary actions to complete the buyout
-void AuctionBotBuyer::BuyEntry(AuctionEntry* auction, AuctionHouseObject* auctionHouse)
+void AuctionBotBuyer::BuyEntry(AuctionPosting* auction, AuctionHouseObject* auctionHouse)
{
- TC_LOG_DEBUG("ahbot", "AHBot: Entry %u bought at %.2fg", auction->Id, float(auction->buyout) / GOLD);
+ TC_LOG_DEBUG("ahbot", "AHBot: Entry %u bought at %.2fg", auction->Id, float(auction->BuyoutOrUnitPrice) / GOLD);
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ ObjectGuid newBidder = sAuctionBotConfig->GetRandCharExclude(auction->Owner);
+
// Send mail to previous bidder if any
- if (auction->bidder && !sAuctionBotConfig->IsBotChar(auction->bidder))
- sAuctionMgr->SendAuctionOutbiddedMail(auction, auction->buyout, NULL, trans);
+ if (!auction->Bidder.IsEmpty() && !sAuctionBotConfig->IsBotChar(auction->Bidder))
+ auctionHouse->SendAuctionOutbid(auction, newBidder, auction->BuyoutOrUnitPrice, trans);
// Set bot as bidder and set new bid amount
- auction->bidder = sAuctionBotConfig->GetRandCharExclude(auction->owner);
- auction->bid = auction->buyout;
+ auction->Bidder = newBidder;
+ auction->BidAmount = auction->BuyoutOrUnitPrice;
// Mails must be under transaction control too to prevent data loss
- sAuctionMgr->SendAuctionSalePendingMail(auction, trans);
- sAuctionMgr->SendAuctionSuccessfulMail(auction, trans);
- sAuctionMgr->SendAuctionWonMail(auction, trans);
+ auctionHouse->SendAuctionWon(auction, nullptr, trans);
+ auctionHouse->SendAuctionSold(auction, nullptr, trans);
- // Delete auction from DB
- auction->DeleteFromDB(trans);
-
- // Remove auction item and auction from memory
- sAuctionMgr->RemoveAItem(auction->itemGUIDLow);
- auctionHouse->RemoveAuction(auction);
+ // Remove auction
+ auctionHouse->RemoveAuction(trans, auction);
// Run SQLs
CharacterDatabase.CommitTransaction(trans);
}
// Bids on the auction and does the necessary actions for bidding
-void AuctionBotBuyer::PlaceBidToEntry(AuctionEntry* auction, uint32 bidPrice)
+void AuctionBotBuyer::PlaceBidToEntry(AuctionPosting* auction, AuctionHouseObject* auctionHouse, uint32 bidPrice)
{
TC_LOG_DEBUG("ahbot", "AHBot: Bid placed to entry %u, %.2fg", auction->Id, float(bidPrice) / GOLD);
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ ObjectGuid newBidder = sAuctionBotConfig->GetRandCharExclude(auction->Owner);
+
// Send mail to previous bidder if any
- if (auction->bidder && !sAuctionBotConfig->IsBotChar(auction->bidder))
- sAuctionMgr->SendAuctionOutbiddedMail(auction, bidPrice, NULL, trans);
+ if (!auction->Bidder.IsEmpty() && !sAuctionBotConfig->IsBotChar(auction->Bidder))
+ auctionHouse->SendAuctionOutbid(auction, newBidder, bidPrice, trans);
// Set bot as bidder and set new bid amount
- auction->bidder = sAuctionBotConfig->GetRandCharExclude(auction->owner);
- auction->bid = bidPrice;
+ auction->Bidder = newBidder;
+ auction->BidAmount = bidPrice;
// Update auction to DB
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_BID);
- stmt->setUInt64(0, auction->bidder);
- stmt->setUInt32(1, auction->bid);
+ stmt->setUInt64(0, auction->Bidder.GetCounter());
+ stmt->setUInt32(1, auction->BidAmount);
stmt->setUInt32(2, auction->Id);
trans->Append(stmt);
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h
index b8ba4465004..10087e4b624 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBotBuyer.h
@@ -86,10 +86,10 @@ private:
void LoadBuyerValues(BuyerConfiguration& config);
// ahInfo can be NULL
- bool RollBuyChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 bidPrice);
- bool RollBidChance(const BuyerItemInfo* ahInfo, const Item* item, const AuctionEntry* auction, uint32 bidPrice);
- void PlaceBidToEntry(AuctionEntry* auction, uint32 bidPrice);
- void BuyEntry(AuctionEntry* auction, AuctionHouseObject* auctionHouse);
+ bool RollBuyChance(BuyerItemInfo const* ahInfo, AuctionPosting const* auction);
+ bool RollBidChance(BuyerItemInfo const* ahInfo, AuctionPosting const* auction, uint32 bidPrice);
+ void PlaceBidToEntry(AuctionPosting* auction, AuctionHouseObject* auctionHouse, uint32 bidPrice);
+ void BuyEntry(AuctionPosting* auction, AuctionHouseObject* auctionHouse);
void PrepareListOfEntry(BuyerConfiguration& config);
uint32 GetItemInformation(BuyerConfiguration& config);
uint32 GetVendorPrice(uint32 quality);
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp b/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp
index 5109fe96cbd..44ee9fcc2a4 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp
@@ -19,6 +19,7 @@
#include "AuctionHouseMgr.h"
#include "DatabaseEnv.h"
#include "DB2Stores.h"
+#include "GameTime.h"
#include "Item.h"
#include "Log.h"
#include "ObjectMgr.h"
@@ -596,20 +597,14 @@ void AuctionBotSeller::LoadSellerValues(SellerConfiguration& config)
// Fill ItemInfos object with real content of AH.
uint32 AuctionBotSeller::SetStat(SellerConfiguration& config)
{
- AllItemsArray itemsSaved(MAX_AUCTION_QUALITY, std::vector<uint32>(MAX_ITEM_CLASS));
+ AllItemsArray itemsSaved;
- AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
- for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = auctionHouse->GetAuctionsBegin(); itr != auctionHouse->GetAuctionsEnd(); ++itr)
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsById(sAuctionBotConfig->GetAuctionHouseId(config.GetHouseType()));
+ for (auto itr = auctionHouse->GetAuctionsBegin(); itr != auctionHouse->GetAuctionsEnd(); ++itr)
{
- AuctionEntry* auctionEntry = itr->second;
- Item* item = sAuctionMgr->GetAItem(auctionEntry->itemGUIDLow);
- if (item)
- {
- ItemTemplate const* prototype = item->GetTemplate();
- if (prototype)
- if (!auctionEntry->owner || sAuctionBotConfig->IsBotChar(auctionEntry->owner)) // Add only ahbot items
- ++itemsSaved[prototype->GetQuality()][prototype->GetClass()];
- }
+ AuctionPosting const* auctionEntry = &itr->second;
+ if (auctionEntry->Owner.IsEmpty() || sAuctionBotConfig->IsBotChar(auctionEntry->Owner)) // Add only ahbot items
+ ++itemsSaved[auctionEntry->Items[0]->GetQuality()][auctionEntry->Bucket->ItemClass];
}
uint32 count = 0;
@@ -664,7 +659,7 @@ bool AuctionBotSeller::GetItemsToSell(SellerConfiguration& config, ItemsToSellAr
}
// Set items price. All important value are passed by address.
-void AuctionBotSeller::SetPricesOfItem(ItemTemplate const* itemProto, SellerConfiguration& config, uint32& buyp, uint32& bidp, uint32 stackCount)
+void AuctionBotSeller::SetPricesOfItem(ItemTemplate const* itemProto, SellerConfiguration& config, uint32& buyout, uint32& bid, uint32 stackCount)
{
uint32 classRatio = config.GetPriceRatioPerClass(ItemClass(itemProto->GetClass()));
uint32 qualityRatio = config.GetPriceRatioPerQuality(AuctionQuality(itemProto->GetQuality()));
@@ -696,14 +691,14 @@ void AuctionBotSeller::SetPricesOfItem(ItemTemplate const* itemProto, SellerConf
float basePriceFloat = (buyPrice * stackCount * priceRatio) / (itemProto->GetClass() == 6 ? 200.0f : static_cast<float>(itemProto->GetBuyCount())) / 100.0f;
float range = basePriceFloat * 0.04f;
- buyp = static_cast<uint32>(frand(basePriceFloat - range, basePriceFloat + range) + 0.5f);
- if (buyp == 0)
- buyp = 1;
+ buyout = (static_cast<uint32>(frand(basePriceFloat - range, basePriceFloat + range) + 0.5f) / SILVER) * SILVER;
+ if (buyout == 0)
+ buyout = SILVER;
float bidPercentage = frand(sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BIDPRICE_MIN), sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BIDPRICE_MAX));
- bidp = static_cast<uint32>(bidPercentage * buyp);
- if (bidp == 0)
- bidp = 1;
+ bid = (static_cast<uint32>(bidPercentage * buyout) / SILVER) * SILVER;
+ if (bid == 0)
+ bid = SILVER;
}
// Determines the stack size to use for the item
@@ -901,24 +896,10 @@ void AuctionBotSeller::AddNewAuctions(SellerConfiguration& config)
else
items = sAuctionBotConfig->GetItemPerCycleNormal();
- uint32 houseid = 0;
- uint64 auctioneer = 0;
- switch (config.GetHouseType())
- {
- case AUCTION_HOUSE_ALLIANCE:
- houseid = 1; auctioneer = 79707; break;
- case AUCTION_HOUSE_HORDE:
- houseid = 6; auctioneer = 4656; break;
- default:
- houseid = 7; auctioneer = 23442; break;
- }
-
- AuctionHouseEntry const* ahEntry = sAuctionHouseStore.LookupEntry(houseid);
-
- AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsById(sAuctionBotConfig->GetAuctionHouseId(config.GetHouseType()));
ItemsToSellArray itemsToSell;
- AllItemsArray allItems(MAX_AUCTION_QUALITY, std::vector<uint32>(MAX_ITEM_CLASS));
+ AllItemsArray allItems;
// Main loop
// getRandomArray will give what categories of items should be added (return true if there is at least 1 items missed)
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
@@ -983,27 +964,18 @@ void AuctionBotSeller::AddNewAuctions(SellerConfiguration& config)
break;
}
- AuctionEntry* auctionEntry = new AuctionEntry();
- auctionEntry->Id = sObjectMgr->GenerateAuctionID();
- auctionEntry->owner = sAuctionBotConfig->GetRandChar();
- auctionEntry->itemGUIDLow = item->GetGUID().GetCounter();
- auctionEntry->itemEntry = item->GetEntry();
- auctionEntry->startbid = bidPrice;
- auctionEntry->buyout = buyoutPrice;
- auctionEntry->auctioneer = auctioneer;
- auctionEntry->bidder = UI64LIT(0);
- auctionEntry->bid = 0;
- auctionEntry->deposit = sAuctionMgr->GetAuctionDeposit(ahEntry, etime, item, stackCount);
- auctionEntry->houseId = houseid;
- auctionEntry->auctionHouseEntry = ahEntry;
- auctionEntry->expire_time = time(NULL) + urand(config.GetMinTime(), config.GetMaxTime()) * HOUR;
-
- item->SaveToDB(trans);
- sAuctionMgr->AddAItem(item);
- auctionHouse->AddAuction(auctionEntry);
- auctionEntry->SaveToDB(trans);
-
- auctionHouse->AddAuction(auctionEntry);
+ AuctionPosting auction;
+ auction.Id = sObjectMgr->GenerateAuctionID();
+ auction.Items.push_back(item);
+ auction.Owner = sAuctionBotConfig->GetRandChar();
+ if (!auction.IsCommodity())
+ auction.MinBid = bidPrice;
+
+ auction.BuyoutOrUnitPrice = buyoutPrice;
+ auction.StartTime = GameTime::GetGameTimeSystemPoint();
+ auction.EndTime = auction.StartTime + Hours(urand(config.GetMinTime(), config.GetMaxTime()));
+
+ auctionHouse->AddAuction(trans, std::move(auction));
++count;
}
diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.h b/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.h
index 29312c975eb..ea82dd991f4 100644
--- a/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.h
+++ b/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.h
@@ -21,6 +21,7 @@
#include "Define.h"
#include "ItemTemplate.h"
#include "AuctionHouseBot.h"
+#include <array>
struct ItemToSell
{
@@ -29,7 +30,7 @@ struct ItemToSell
};
typedef std::vector<ItemToSell> ItemsToSellArray;
-typedef std::vector<std::vector<uint32>> AllItemsArray;
+typedef std::array<std::array<uint32, MAX_ITEM_CLASS>, MAX_ITEM_QUALITY> AllItemsArray;
struct SellerItemClassInfo
{
@@ -141,7 +142,7 @@ private:
void LoadSellerValues(SellerConfiguration& config);
uint32 SetStat(SellerConfiguration& config);
bool GetItemsToSell(SellerConfiguration& config, ItemsToSellArray& itemsToSellArray, AllItemsArray const& addedItem);
- void SetPricesOfItem(ItemTemplate const* itemProto, SellerConfiguration& config, uint32& buyp, uint32& bidp, uint32 stackcnt);
+ void SetPricesOfItem(ItemTemplate const* itemProto, SellerConfiguration& config, uint32& buyout, uint32& bid, uint32 stackcnt);
uint32 GetStackSizeForItem(ItemTemplate const* itemProto, SellerConfiguration& config) const;
void LoadItemsQuantity(SellerConfiguration& config);
static uint32 GetBuyModifier(ItemTemplate const* prototype);
diff --git a/src/server/game/Entities/Player/CollectionMgr.cpp b/src/server/game/Entities/Player/CollectionMgr.cpp
index ea5f32fd9f6..72c7693e6d3 100644
--- a/src/server/game/Entities/Player/CollectionMgr.cpp
+++ b/src/server/game/Entities/Player/CollectionMgr.cpp
@@ -851,6 +851,19 @@ std::unordered_set<ObjectGuid> CollectionMgr::GetItemsProvidingTemporaryAppearan
return std::unordered_set<ObjectGuid>();
}
+std::unordered_set<uint32> CollectionMgr::GetAppearanceIds() const
+{
+ std::unordered_set<uint32> appearances;
+ std::size_t id = _appearances->find_first();
+ while (id != boost::dynamic_bitset<uint32>::npos)
+ {
+ appearances.insert(sItemModifiedAppearanceStore.AssertEntry(id)->ItemAppearanceID);
+ id = _appearances->find_next(id);
+ }
+
+ return appearances;
+}
+
void CollectionMgr::SetAppearanceIsFavorite(uint32 itemModifiedAppearanceId, bool apply)
{
auto itr = _favoriteAppearances.find(itemModifiedAppearanceId);
diff --git a/src/server/game/Entities/Player/CollectionMgr.h b/src/server/game/Entities/Player/CollectionMgr.h
index 4a79b04fd99..1f3b9f7bd3f 100644
--- a/src/server/game/Entities/Player/CollectionMgr.h
+++ b/src/server/game/Entities/Player/CollectionMgr.h
@@ -132,6 +132,8 @@ public:
// returns pair<hasAppearance, isTemporary>
std::pair<bool, bool> HasItemAppearance(uint32 itemModifiedAppearanceId) const;
std::unordered_set<ObjectGuid> GetItemsProvidingTemporaryAppearance(uint32 itemModifiedAppearanceId) const;
+ // returns ItemAppearance::ID, not ItemModifiedAppearance::ID
+ std::unordered_set<uint32> GetAppearanceIds() const;
enum class FavoriteAppearanceState
{
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 76c67dd291d..3e60cf4f502 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -4181,6 +4181,10 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe
stmt->setUInt64(0, guid);
trans->Append(stmt);
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_FAVORITE_AUCTIONS_BY_CHAR);
+ stmt->setUInt64(0, guid);
+ trans->Append(stmt);
+
Corpse::DeleteFromDB(playerguid, trans);
Garrison::DeleteFromDB(guid, trans);
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index 72da2e1f580..abc2a7b9189 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -6816,7 +6816,8 @@ void ObjectMgr::SetHighestGuids()
// Cleanup other tables from nonexistent guids ( >= _hiItemGuid)
CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item >= '%u'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
CharacterDatabase.PExecute("DELETE FROM mail_items WHERE item_guid >= '%u'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
- CharacterDatabase.PExecute("DELETE FROM auctionhouse WHERE itemguid >= '%u'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
+ CharacterDatabase.PExecute("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 >= '%u'",
+ GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
CharacterDatabase.PExecute("DELETE FROM guild_bank_item WHERE item_guid >= '%u'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
result = WorldDatabase.Query("SELECT MAX(guid) FROM transports");
diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp
index 5fb0a62103b..593405627c2 100644
--- a/src/server/game/Handlers/AuctionHouseHandler.cpp
+++ b/src/server/game/Handlers/AuctionHouseHandler.cpp
@@ -22,6 +22,7 @@
#include "CharacterCache.h"
#include "Creature.h"
#include "DatabaseEnv.h"
+#include "GameTime.h"
#include "Item.h"
#include "Language.h"
#include "Log.h"
@@ -32,14 +33,18 @@
#include "Util.h"
#include "World.h"
#include "WorldPacket.h"
+#include <sstream>
-//void called when player click on auctioneer npc
-void WorldSession::HandleAuctionHelloOpcode(WorldPackets::AuctionHouse::AuctionHelloRequest& packet)
+void WorldSession::HandleAuctionBrowseQuery(WorldPackets::AuctionHouse::AuctionBrowseQuery& browseQuery)
{
- Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(packet.Guid, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
- if (!unit)
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
+
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(browseQuery.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ if (!creature)
{
- TC_LOG_DEBUG("network", "WORLD: HandleAuctionHelloOpcode - Unit (%s) not found or you can't interact with him.", packet.Guid.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionListItems - %s not found or you can't interact with him.", browseQuery.Auctioneer.ToString().c_str());
return;
}
@@ -47,344 +52,292 @@ void WorldSession::HandleAuctionHelloOpcode(WorldPackets::AuctionHouse::AuctionH
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- SendAuctionHello(packet.Guid, unit);
-}
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
-//this void causes that auction window is opened
-void WorldSession::SendAuctionHello(ObjectGuid guid, Creature* unit)
-{
- if (GetPlayer()->getLevel() < sWorld->getIntConfig(CONFIG_AUCTION_LEVEL_REQ))
- {
- SendNotification(GetTrinityString(LANG_AUCTION_REQ), sWorld->getIntConfig(CONFIG_AUCTION_LEVEL_REQ));
- return;
- }
+ TC_LOG_DEBUG("auctionHouse", "Auctionhouse search (%s), searchedname: %s, levelmin: %u, levelmax: %u, filters: %u",
+ browseQuery.Auctioneer.ToString().c_str(), browseQuery.Name.c_str(), browseQuery.MinLevel, browseQuery.MaxLevel, AsUnderlyingType(browseQuery.Filters));
- AuctionHouseEntry const* ahEntry = AuctionHouseMgr::GetAuctionHouseEntry(unit->getFaction(), nullptr);
- if (!ahEntry)
+ std::wstring name;
+ if (!Utf8toWStr(browseQuery.Name, name))
return;
- WorldPackets::AuctionHouse::AuctionHelloResponse auctionHelloResponse;
- auctionHelloResponse.Guid = guid;
- auctionHelloResponse.OpenForBusiness = false; // 3.3.3: 1 - AH enabled, 0 - AH disabled
- SendPacket(auctionHelloResponse.Write());
-}
+ Optional<AuctionSearchClassFilters> classFilters;
-void WorldSession::SendAuctionCommandResult(AuctionEntry* auction, uint32 action, uint32 errorCode, uint32 /*bidError = 0*/)
-{
- WorldPackets::AuctionHouse::AuctionCommandResult auctionCommandResult;
- auctionCommandResult.InitializeAuction(auction);
- auctionCommandResult.Command = action;
- auctionCommandResult.ErrorCode = errorCode;
- SendPacket(auctionCommandResult.Write());
-}
+ WorldPackets::AuctionHouse::AuctionListBucketsResult listBucketsResult;
+ if (!browseQuery.ItemClassFilters.empty())
+ {
+ classFilters = boost::in_place();
-void WorldSession::SendAuctionOutBidNotification(AuctionEntry const* auction, Item const* item)
-{
- WorldPackets::AuctionHouse::AuctionOutbidNotification packet;
- packet.BidAmount = auction->bid;
- packet.MinIncrement = auction->GetAuctionOutBid();
- packet.Info.Initialize(auction, item);
- SendPacket(packet.Write());
-}
+ for (auto const& classFilter : browseQuery.ItemClassFilters)
+ {
+ if (!classFilter.SubClassFilters.empty())
+ {
+ for (auto const& subClassFilter : classFilter.SubClassFilters)
+ {
+ if (classFilter.ItemClass < MAX_ITEM_CLASS)
+ {
+ classFilters->Classes[classFilter.ItemClass].SubclassMask |= 1 << subClassFilter.ItemSubclass;
+ if (subClassFilter.ItemSubclass < MAX_ITEM_SUBCLASS_TOTAL)
+ classFilters->Classes[classFilter.ItemClass].InvTypes[subClassFilter.ItemSubclass] = subClassFilter.InvTypeMask;
+ }
+ }
+ }
+ else
+ classFilters->Classes[classFilter.ItemClass].SubclassMask = AuctionSearchClassFilters::FILTER_SKIP_SUBCLASS;
+ }
+ }
-void WorldSession::SendAuctionClosedNotification(AuctionEntry const* auction, float mailDelay, bool sold, Item const* item)
-{
- WorldPackets::AuctionHouse::AuctionClosedNotification packet;
- packet.Info.Initialize(auction, item);
- packet.ProceedsMailDelay = mailDelay;
- packet.Sold = sold;
- SendPacket(packet.Write());
-}
+ auctionHouse->BuildListBuckets(listBucketsResult, _player,
+ name, browseQuery.MinLevel, browseQuery.MaxLevel, browseQuery.Filters, classFilters,
+ browseQuery.KnownPets.data(), browseQuery.KnownPets.size(), browseQuery.MaxPetLevel,
+ browseQuery.Offset, browseQuery.Sorts.data(), browseQuery.Sorts.size());
-void WorldSession::SendAuctionWonNotification(AuctionEntry const* auction, Item const* item)
-{
- WorldPackets::AuctionHouse::AuctionWonNotification packet;
- packet.Info.Initialize(auction, item);
- SendPacket(packet.Write());
+ listBucketsResult.BrowseMode = AuctionHouseBrowseMode::Search;
+ listBucketsResult.DesiredDelay = uint32(throttle.DelayUntilNext.count());
+ SendPacket(listBucketsResult.Write());
}
-void WorldSession::SendAuctionOwnerBidNotification(AuctionEntry const* auction, Item const* item)
+void WorldSession::HandleAuctionCancelCommoditiesPurchase(WorldPackets::AuctionHouse::AuctionCancelCommoditiesPurchase& cancelCommoditiesPurchase)
{
- WorldPackets::AuctionHouse::AuctionOwnerBidNotification packet;
- packet.Info.Initialize(auction, item);
- packet.Bidder = ObjectGuid::Create<HighGuid::Player>(auction->bidder);
- packet.MinIncrement = auction->GetAuctionOutBid();
- SendPacket(packet.Write());
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::PlaceBid);
+ if (throttle.Throttled)
+ return;
+
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(cancelCommoditiesPurchase.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ if (!creature)
+ {
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionCancelCommoditiesPurchase - %s not found or you can't interact with him.",
+ cancelCommoditiesPurchase.Auctioneer.ToString().c_str());
+ return;
+ }
+
+ // remove fake death
+ if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
+ GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
+
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
+ auctionHouse->CancelCommodityQuote(_player->GetGUID());
}
-//this void creates new auction and adds auction to some auctionhouse
-void WorldSession::HandleAuctionSellItem(WorldPackets::AuctionHouse::AuctionSellItem& packet)
+void WorldSession::HandleAuctionConfirmCommoditiesPurchase(WorldPackets::AuctionHouse::AuctionConfirmCommoditiesPurchase& confirmCommoditiesPurchase)
{
- for (auto const& item : packet.Items)
- if (!item.Guid || !item.UseCount || item.UseCount > 1000)
- return;
-
- if (!packet.MinBid || !packet.RunTime)
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::PlaceBid);
+ if (throttle.Throttled)
return;
- if (packet.MinBid > MAX_MONEY_AMOUNT || packet.BuyoutPrice > MAX_MONEY_AMOUNT)
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(confirmCommoditiesPurchase.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ if (!creature)
{
- TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Player %s (%s) attempted to sell item with higher price than max gold amount.", _player->GetName().c_str(), _player->GetGUID().ToString().c_str());
- SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR);
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionConfirmCommoditiesPurchase - %s not found or you can't interact with him.", confirmCommoditiesPurchase.Auctioneer.ToString().c_str());
return;
}
+ // remove fake death
+ if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
+ GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
+
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
- if (!creature)
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ if (auctionHouse->BuyCommodity(trans, _player, confirmCommoditiesPurchase.ItemID, confirmCommoditiesPurchase.Quantity, throttle.DelayUntilNext))
{
- TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) not found or you can't interact with him.", packet.Auctioneer.ToString().c_str());
- return;
+ AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete([this, buyerGuid = _player->GetGUID(), throttle](bool success)
+ {
+ if (GetPlayer() && GetPlayer()->GetGUID() == buyerGuid)
+ {
+ if (success)
+ {
+ GetPlayer()->UpdateCriteria(CRITERIA_TYPE_WON_AUCTIONS, 1);
+ SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::Ok, throttle.DelayUntilNext);
+ }
+ else
+ SendAuctionCommandResult(0, AuctionCommand::PlaceBid, AuctionResult::CommodityPurchaseFailed, throttle.DelayUntilNext);
+ }
+ });
}
+}
- uint32 houseId = 0;
- AuctionHouseEntry const* auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(creature->getFaction(), &houseId);
- if (!auctionHouseEntry)
+void WorldSession::HandleAuctionHelloOpcode(WorldPackets::AuctionHouse::AuctionHelloRequest& hello)
+{
+ Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(hello.Guid, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ if (!unit)
{
- TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) has wrong faction.", packet.Auctioneer.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionHelloOpcode - Unit (%s) not found or you can't interact with him.", hello.Guid.ToString().c_str());
return;
}
- packet.RunTime *= MINUTE;
+ // remove fake death
+ if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
+ GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
+
+ SendAuctionHello(hello.Guid, unit);
+}
+
+void WorldSession::HandleAuctionListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItems& listBidderItems)
+{
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
- switch (packet.RunTime)
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(listBidderItems.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ if (!creature)
{
- case 1 * MIN_AUCTION_TIME:
- case 2 * MIN_AUCTION_TIME:
- case 4 * MIN_AUCTION_TIME:
- break;
- default:
- return;
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionListBidderItems - %s not found or you can't interact with him.", listBidderItems.Auctioneer.ToString().c_str());
+ return;
}
+ // remove fake death
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- uint32 finalCount = 0;
- std::array<Item*, decltype(packet.Items)::max_capacity::value> items;
- items.fill(nullptr);
- for (std::size_t i = 0; i < packet.Items.size(); ++i)
- {
- items[i] = _player->GetItemByGuid(packet.Items[i].Guid);
-
- if (!items[i])
- {
- SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_ITEM_NOT_FOUND);
- return;
- }
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- if (sAuctionMgr->GetAItem(items[i]->GetGUID().GetCounter()) || !items[i]->CanBeTraded() || items[i]->IsNotEmptyBag() ||
- items[i]->GetTemplate()->GetFlags() & ITEM_FLAG_CONJURED || *items[i]->m_itemData->Expiration ||
- items[i]->GetCount() < packet.Items[i].UseCount)
- {
- SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR);
- return;
- }
+ WorldPackets::AuctionHouse::AuctionListBidderItemsResult result;
- finalCount += packet.Items[i].UseCount;
- }
+ Player* player = GetPlayer();
+ auctionHouse->BuildListBidderItems(result, player, listBidderItems.Offset, listBidderItems.Sorts.data(), listBidderItems.Sorts.size());
+ result.DesiredDelay = uint32(throttle.DelayUntilNext.count());
+ SendPacket(result.Write());
+}
- if (packet.Items.empty())
- {
- SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR);
+void WorldSession::HandleAuctionListBucketsByBucketKeys(WorldPackets::AuctionHouse::AuctionListBucketsByBucketKeys& listBucketsByBucketKeys)
+{
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player);
+ if (throttle.Throttled)
return;
- }
- if (!finalCount)
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(listBucketsByBucketKeys.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ if (!creature)
{
- SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR);
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionListBucketsByBucketKeys - %s not found or you can't interact with him.",
+ listBucketsByBucketKeys.Auctioneer.ToString().c_str());
return;
}
- // check if there are 2 identical guids, in this case user is most likely cheating
- for (std::size_t i = 0; i < packet.Items.size() - 1; ++i)
- {
- for (std::size_t j = i + 1; j < packet.Items.size(); ++j)
- {
- if (packet.Items[i].Guid == packet.Items[j].Guid)
- {
- SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR);
- return;
- }
- if (items[i]->GetEntry() != items[j]->GetEntry())
- {
- SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_ITEM_NOT_FOUND);
- return;
- }
- }
- }
+ // remove fake death
+ if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
+ GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- for (std::size_t i = 0; i < packet.Items.size(); ++i)
- {
- if (items[i]->GetMaxStackCount() < finalCount)
- {
- SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR);
- return;
- }
- }
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- Item* item = items[0];
+ WorldPackets::AuctionHouse::AuctionListBucketsResult listBucketsResult;
- uint32 auctionTime = uint32(packet.RunTime * sWorld->getRate(RATE_AUCTION_TIME));
- AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
+ auctionHouse->BuildListBuckets(listBucketsResult, _player,
+ listBucketsByBucketKeys.BucketKeys.data(), listBucketsByBucketKeys.BucketKeys.size(),
+ listBucketsByBucketKeys.Sorts.data(), listBucketsByBucketKeys.Sorts.size());
- uint64 deposit = sAuctionMgr->GetAuctionDeposit(auctionHouseEntry, packet.RunTime, item, finalCount);
- if (!_player->HasEnoughMoney(deposit))
+ listBucketsResult.BrowseMode = AuctionHouseBrowseMode::SpecificKeys;
+ listBucketsResult.DesiredDelay = uint32(throttle.DelayUntilNext.count());
+ SendPacket(listBucketsResult.Write());
+}
+
+void WorldSession::HandleAuctionListItemsByBucketKey(WorldPackets::AuctionHouse::AuctionListItemsByBucketKey& listItemsByBucketKey)
+{
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
+
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(listItemsByBucketKey.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ if (!creature)
{
- SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_NOT_ENOUGH_MONEY);
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionListItemsByBucketKey - %s not found or you can't interact with him.", listItemsByBucketKey.Auctioneer.ToString().c_str());
return;
}
- AuctionEntry* AH = new AuctionEntry();
+ // remove fake death
+ if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
+ GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
- AH->auctioneer = UI64LIT(23442); ///@TODO - HARDCODED DB GUID, BAD BAD BAD
- else
- AH->auctioneer = packet.Auctioneer.GetCounter();
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- // Required stack size of auction matches to current item stack size, just move item to auctionhouse
- if (packet.Items.size() == 1 && item->GetCount() == packet.Items[0].UseCount)
- {
- if (HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE))
- {
- sLog->outCommand(GetAccountId(), "GM %s (Account: %u) create auction: %s (Entry: %u Count: %u)",
- GetPlayerName().c_str(), GetAccountId(), item->GetTemplate()->GetDefaultLocaleName(), item->GetEntry(), item->GetCount());
- }
+ WorldPackets::AuctionHouse::AuctionListItemsResult listItemsResult;
+ listItemsResult.DesiredDelay = uint32(throttle.DelayUntilNext.count());
+ listItemsResult.BucketKey = listItemsByBucketKey.BucketKey;
+ ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(listItemsByBucketKey.BucketKey.ItemID);
+ listItemsResult.ListType = itemTemplate && itemTemplate->GetMaxStackSize() > 1 ? AuctionHouseListType::Commodities : AuctionHouseListType::Items;
- AH->Id = sObjectMgr->GenerateAuctionID();
- AH->itemGUIDLow = item->GetGUID().GetCounter();
- AH->itemEntry = item->GetEntry();
- AH->itemCount = item->GetCount();
- AH->owner = _player->GetGUID().GetCounter();
- AH->startbid = packet.MinBid;
- AH->bidder = UI64LIT(0);
- AH->bid = 0;
- AH->buyout = packet.BuyoutPrice;
- AH->expire_time = time(NULL) + auctionTime;
- AH->deposit = deposit;
- AH->etime = packet.RunTime;
- AH->auctionHouseEntry = auctionHouseEntry;
-
- TC_LOG_INFO("network", "CMSG_AUCTION_SELL_ITEM: %s %s is selling item %s %s to auctioneer " UI64FMTD " with count %u with initial bid " UI64FMTD " with buyout " UI64FMTD " and with time %u (in sec) in auctionhouse %u",
- _player->GetGUID().ToString().c_str(), _player->GetName().c_str(), item->GetGUID().ToString().c_str(), item->GetTemplate()->GetDefaultLocaleName(), AH->auctioneer, item->GetCount(), packet.MinBid, packet.BuyoutPrice, auctionTime, AH->GetHouseId());
-
- // Add to pending auctions, or fail with insufficient funds error
- if (!sAuctionMgr->PendingAuctionAdd(_player, AH))
- {
- SendAuctionCommandResult(AH, AUCTION_SELL_ITEM, ERR_AUCTION_NOT_ENOUGH_MONEY);
- return;
- }
+ auctionHouse->BuildListAuctionItems(listItemsResult, _player, listItemsByBucketKey.BucketKey, listItemsByBucketKey.Offset,
+ listItemsByBucketKey.Sorts.data(), listItemsByBucketKey.Sorts.size());
- sAuctionMgr->AddAItem(item);
- auctionHouse->AddAuction(AH);
- _player->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true);
+ SendPacket(listItemsResult.Write());
+}
- CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
- item->DeleteFromInventoryDB(trans);
- item->SaveToDB(trans);
+void WorldSession::HandleAuctionListItemsByItemID(WorldPackets::AuctionHouse::AuctionListItemsByItemID& listItemsByItemID)
+{
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
- AH->SaveToDB(trans);
- _player->SaveInventoryAndGoldToDB(trans);
- CharacterDatabase.CommitTransaction(trans);
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(listItemsByItemID.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ if (!creature)
+ {
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionListItemsByItemID - %s not found or you can't interact with him.", listItemsByItemID.Auctioneer.ToString().c_str());
+ return;
+ }
- SendAuctionCommandResult(AH, AUCTION_SELL_ITEM, ERR_AUCTION_OK);
+ // remove fake death
+ if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
+ GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- GetPlayer()->UpdateCriteria(CRITERIA_TYPE_CREATE_AUCTION, 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, _player);
- if (!newItem)
- {
- TC_LOG_ERROR("network", "CMSG_AUCTION_SELL_ITEM: Could not create clone of item %u", item->GetEntry());
- SendAuctionCommandResult(NULL, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR);
- delete AH;
- return;
- }
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- if (HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE))
- {
- sLog->outCommand(GetAccountId(), "GM %s (Account: %u) create auction: %s (Entry: %u Count: %u)",
- GetPlayerName().c_str(), GetAccountId(), newItem->GetTemplate()->GetDefaultLocaleName(), newItem->GetEntry(), newItem->GetCount());
- }
+ WorldPackets::AuctionHouse::AuctionListItemsResult listItemsResult;
+ listItemsResult.DesiredDelay = uint32(throttle.DelayUntilNext.count());
+ listItemsResult.BucketKey.ItemID = listItemsByItemID.ItemID;
+ ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(listItemsByItemID.ItemID);
+ listItemsResult.ListType = itemTemplate && itemTemplate->GetMaxStackSize() > 1 ? AuctionHouseListType::Commodities : AuctionHouseListType::Items;
- AH->Id = sObjectMgr->GenerateAuctionID();
- AH->itemGUIDLow = newItem->GetGUID().GetCounter();
- AH->itemEntry = newItem->GetEntry();
- AH->itemCount = newItem->GetCount();
- AH->owner = _player->GetGUID().GetCounter();
- AH->startbid = packet.MinBid;
- AH->bidder = UI64LIT(0);
- AH->bid = 0;
- AH->buyout = packet.BuyoutPrice;
- AH->expire_time = time(NULL) + auctionTime;
- AH->deposit = deposit;
- AH->etime = packet.RunTime;
- AH->auctionHouseEntry = auctionHouseEntry;
-
- TC_LOG_INFO("network", "CMSG_AUCTION_SELL_ITEM: %s %s is selling %s %s to auctioneer " UI64FMTD " with count %u with initial bid " UI64FMTD " with buyout " UI64FMTD " and with time %u (in sec) in auctionhouse %u",
- _player->GetGUID().ToString().c_str(), _player->GetName().c_str(), newItem->GetGUID().ToString().c_str(), newItem->GetTemplate()->GetDefaultLocaleName(), AH->auctioneer, newItem->GetCount(), packet.MinBid, packet.BuyoutPrice, auctionTime, AH->GetHouseId());
-
- // Add to pending auctions, or fail with insufficient funds error
- if (!sAuctionMgr->PendingAuctionAdd(_player, AH))
- {
- SendAuctionCommandResult(AH, AUCTION_SELL_ITEM, ERR_AUCTION_NOT_ENOUGH_MONEY);
- return;
- }
+ auctionHouse->BuildListAuctionItems(listItemsResult, _player, listItemsByItemID.ItemID, listItemsByItemID.Offset,
+ listItemsByItemID.Sorts.data(), listItemsByItemID.Sorts.size());
- sAuctionMgr->AddAItem(newItem);
- auctionHouse->AddAuction(AH);
+ SendPacket(listItemsResult.Write());
+}
- for (std::size_t i = 0; i < packet.Items.size(); ++i)
- {
- Item* item2 = items[i];
+//this void sends player info about his auctions
+void WorldSession::HandleAuctionListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItems& listOwnerItems)
+{
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
- // Item stack count equals required count, ready to delete item - cloned item will be used for auction
- if (item2->GetCount() == packet.Items[i].UseCount)
- {
- _player->MoveItemFromInventory(item2->GetBagSlot(), item2->GetSlot(), true);
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(listOwnerItems.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ if (!creature)
+ {
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionListOwnerItems - %s not found or you can't interact with him.", listOwnerItems.Auctioneer.ToString().c_str());
+ return;
+ }
- CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
- item2->DeleteFromInventoryDB(trans);
- item2->DeleteFromDB(trans);
- CharacterDatabase.CommitTransaction(trans);
- delete item2;
- }
- 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(ITEM_CHANGED, _player);
- _player->ItemRemovedQuestCheck(item2->GetEntry(), packet.Items[i].UseCount);
- item2->SendUpdateToPlayer(_player);
-
- CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
- item2->SaveToDB(trans);
- CharacterDatabase.CommitTransaction(trans);
- }
- }
+ // remove fake death
+ if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
+ GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
- newItem->SaveToDB(trans);
- AH->SaveToDB(trans);
- _player->SaveInventoryAndGoldToDB(trans);
- CharacterDatabase.CommitTransaction(trans);
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- SendAuctionCommandResult(AH, AUCTION_SELL_ITEM, ERR_AUCTION_OK);
+ WorldPackets::AuctionHouse::AuctionListOwnerItemsResult result;
- GetPlayer()->UpdateCriteria(CRITERIA_TYPE_CREATE_AUCTION, 1);
- }
+ auctionHouse->BuildListOwnerItems(result, _player, listOwnerItems.Offset, listOwnerItems.Sorts.data(), listOwnerItems.Sorts.size());
+ result.DesiredDelay = uint32(throttle.DelayUntilNext.count());
+ SendPacket(result.Write());
}
// this function is called when client bids or buys out auction
-void WorldSession::HandleAuctionPlaceBid(WorldPackets::AuctionHouse::AuctionPlaceBid& packet)
+void WorldSession::HandleAuctionPlaceBid(WorldPackets::AuctionHouse::AuctionPlaceBid& placeBid)
{
- if (!packet.AuctionID || !packet.BidAmount)
- return; // check for cheaters
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::PlaceBid);
+ if (throttle.Throttled)
+ return;
- Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(placeBid.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
if (!creature)
{
- TC_LOG_DEBUG("network", "WORLD: HandleAuctionPlaceBid - %s not found or you can't interact with him.", packet.Auctioneer.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionPlaceBid - %s not found or you can't interact with him.", placeBid.Auctioneer.ToString().c_str());
+ return;
+ }
+
+ // auction house does not deal with copper
+ if (placeBid.BidAmount % SILVER)
+ {
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::BidIncrement, throttle.DelayUntilNext);
return;
}
@@ -394,121 +347,119 @@ void WorldSession::HandleAuctionPlaceBid(WorldPackets::AuctionHouse::AuctionPlac
AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- AuctionEntry* auction = auctionHouse->GetAuction(packet.AuctionID);
- Player* player = GetPlayer();
-
- if (!auction || auction->owner == player->GetGUID().GetCounter())
+ AuctionPosting* auction = auctionHouse->GetAuction(placeBid.AuctionID);
+ if (!auction || auction->IsCommodity())
{
- //you cannot bid your own auction:
- SendAuctionCommandResult(NULL, AUCTION_PLACE_BID, ERR_AUCTION_BID_OWN);
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::ItemNotFound, throttle.DelayUntilNext);
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 = ObjectAccessor::FindPlayer(ownerGuid);
- if (!auction_owner && sCharacterCache->GetCharacterAccountIdByGuid(ownerGuid) == player->GetSession()->GetAccountId())
+ Player* player = GetPlayer();
+
+ // check auction owner - cannot buy own auctions
+ if (auction->Owner == player->GetGUID() || auction->OwnerAccount == GetAccountGUID())
{
- //you cannot bid your another character auction:
- SendAuctionCommandResult(NULL, AUCTION_PLACE_BID, ERR_AUCTION_BID_OWN);
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::BidOwn, throttle.DelayUntilNext);
return;
}
- // cheating
- if (packet.BidAmount <= auction->bid || packet.BidAmount < auction->startbid)
- return;
+ bool canBid = auction->MinBid != 0;
+ bool canBuyout = auction->BuyoutOrUnitPrice != 0;
- // price too low for next bid if not buyout
- if ((packet.BidAmount < auction->buyout || auction->buyout == 0) &&
- packet.BidAmount < auction->bid + auction->GetAuctionOutBid())
+ // buyout attempt with wrong amount
+ if (!canBid && placeBid.BidAmount != auction->BuyoutOrUnitPrice)
{
- // client already test it but just in case ...
- SendAuctionCommandResult(auction, AUCTION_PLACE_BID, ERR_AUCTION_HIGHER_BID);
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::BidIncrement, throttle.DelayUntilNext);
return;
}
- if (!player->HasEnoughMoney(packet.BidAmount))
+ uint64 minBid = auction->BidAmount ? auction->BidAmount + auction->CalculateMinIncrement() : auction->MinBid;
+ if (canBid && placeBid.BidAmount < minBid)
{
- // client already test it but just in case ...
- SendAuctionCommandResult(auction, AUCTION_PLACE_BID, ERR_AUCTION_NOT_ENOUGH_MONEY);
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::HigherBid, throttle.DelayUntilNext);
return;
}
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
- if (packet.BidAmount < auction->buyout || auction->buyout == 0)
+ uint64 priceToPay = placeBid.BidAmount;
+ if (!auction->Bidder.IsEmpty())
{
- if (auction->bidder)
- {
- if (auction->bidder == player->GetGUID().GetCounter())
- player->ModifyMoney(-int64(packet.BidAmount - auction->bid));
- else
- {
- // mail to last bidder and return money
- sAuctionMgr->SendAuctionOutbiddedMail(auction, packet.BidAmount, GetPlayer(), trans);
- player->ModifyMoney(-int64(packet.BidAmount));
- }
- }
+ // return money to previous bidder
+ if (auction->Bidder != player->GetGUID())
+ auctionHouse->SendAuctionOutbid(auction, player->GetGUID(), placeBid.BidAmount, trans);
else
- player->ModifyMoney(-int64(packet.BidAmount));
+ priceToPay = placeBid.BidAmount - auction->BidAmount;
+ }
- auction->bidder = player->GetGUID().GetCounter();
- auction->bid = packet.BidAmount;
- GetPlayer()->UpdateCriteria(CRITERIA_TYPE_HIGHEST_AUCTION_BID, packet.BidAmount);
+ // check money
+ if (!player->HasEnoughMoney(priceToPay))
+ {
+ SendAuctionCommandResult(placeBid.AuctionID, AuctionCommand::PlaceBid, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
- CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_BID);
- stmt->setUInt64(0, auction->bidder);
- stmt->setUInt64(1, auction->bid);
- stmt->setUInt32(2, auction->Id);
- trans->Append(stmt);
+ player->ModifyMoney(-int64(priceToPay));
+ auction->Bidder = player->GetGUID();
+ auction->BidAmount = placeBid.BidAmount;
- SendAuctionCommandResult(auction, AUCTION_PLACE_BID, ERR_AUCTION_OK);
+ if (canBuyout && placeBid.BidAmount == auction->BuyoutOrUnitPrice)
+ {
+ // buyout
+ auctionHouse->SendAuctionWon(auction, player, trans);
+ auctionHouse->SendAuctionSold(auction, nullptr, trans);
- // Not sure if we must send this now.
- Player* owner = ObjectAccessor::FindConnectedPlayer(ObjectGuid::Create<HighGuid::Player>(auction->owner));
- Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow);
- if (owner && item)
- owner->GetSession()->SendAuctionOwnerBidNotification(auction, item);
+ auctionHouse->RemoveAuction(trans, auction);
}
else
{
- //buyout:
- if (player->GetGUID().GetCounter() == auction->bidder)
- player->ModifyMoney(-int64(auction->buyout - auction->bid));
- else
+ // place bid
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_AUCTION_BID);
+ stmt->setUInt64(0, auction->Bidder.GetCounter());
+ stmt->setUInt64(1, auction->BidAmount);
+ stmt->setUInt32(2, auction->Id);
+ trans->Append(stmt);
+
+ if (auction->BidderHistory.insert(player->GetGUID()).second)
{
- player->ModifyMoney(-int64(auction->buyout));
- if (auction->bidder) //buyout for bidded auction ..
- sAuctionMgr->SendAuctionOutbiddedMail(auction, auction->buyout, GetPlayer(), trans);
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION_BIDDER);
+ stmt->setUInt32(0, auction->Id);
+ stmt->setUInt64(1, player->GetGUID().GetCounter());
+ trans->Append(stmt);
}
- auction->bidder = player->GetGUID().GetCounter();
- auction->bid = auction->buyout;
- GetPlayer()->UpdateCriteria(CRITERIA_TYPE_HIGHEST_AUCTION_BID, auction->buyout);
-
- SendAuctionCommandResult(auction, AUCTION_PLACE_BID, ERR_AUCTION_OK);
-
- //- Mails must be under transaction control too to prevent data loss
- sAuctionMgr->SendAuctionSalePendingMail(auction, trans);
- sAuctionMgr->SendAuctionSuccessfulMail(auction, trans);
- sAuctionMgr->SendAuctionWonMail(auction, trans);
-
- auction->DeleteFromDB(trans);
- sAuctionMgr->RemoveAItem(auction->itemGUIDLow);
- auctionHouse->RemoveAuction(auction);
+ // Not sure if we must send this now.
+ if (Player* owner = ObjectAccessor::FindConnectedPlayer(auction->Owner))
+ owner->GetSession()->SendAuctionOwnerBidNotification(auction);
}
player->SaveInventoryAndGoldToDB(trans);
- CharacterDatabase.CommitTransaction(trans);
+ AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete(
+ [this, auctionId = placeBid.AuctionID, bidAmount = placeBid.BidAmount, auctionPlayerGuid = _player->GetGUID(), throttle](bool success)
+ {
+ if (GetPlayer() && GetPlayer()->GetGUID() == auctionPlayerGuid)
+ {
+ if (success)
+ {
+ GetPlayer()->UpdateCriteria(CRITERIA_TYPE_HIGHEST_AUCTION_BID, bidAmount);
+ SendAuctionCommandResult(auctionId, AuctionCommand::PlaceBid, AuctionResult::Ok, throttle.DelayUntilNext);
+ }
+ else
+ SendAuctionCommandResult(auctionId, AuctionCommand::PlaceBid, AuctionResult::DatabaseError, throttle.DelayUntilNext);
+ }
+ });
}
-//this void is called when auction_owner cancels his auction
-void WorldSession::HandleAuctionRemoveItem(WorldPackets::AuctionHouse::AuctionRemoveItem& packet)
+void WorldSession::HandleAuctionRemoveItem(WorldPackets::AuctionHouse::AuctionRemoveItem& removeItem)
{
- Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::Cancel);
+ if (throttle.Throttled)
+ return;
+
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(removeItem.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
if (!creature)
{
- TC_LOG_DEBUG("network", "WORLD: HandleAuctionRemoveItem - %s not found or you can't interact with him.", packet.Auctioneer.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionRemoveItem - %s not found or you can't interact with him.", removeItem.Auctioneer.ToString().c_str());
return;
}
@@ -518,64 +469,59 @@ void WorldSession::HandleAuctionRemoveItem(WorldPackets::AuctionHouse::AuctionRe
AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- AuctionEntry* auction = auctionHouse->GetAuction(packet.AuctionID);
+ AuctionPosting* auction = auctionHouse->GetAuction(removeItem.AuctionID);
Player* player = GetPlayer();
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
- if (auction && auction->owner == player->GetGUID().GetCounter())
+ if (auction && auction->Owner == player->GetGUID())
{
- Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow);
- if (item)
+ if (auction->Bidder.IsEmpty()) // If we have a bidder, we have to send him the money he paid
{
- if (auction->bidder) // If we have a bidder, we have to send him the money he paid
+ uint64 cancelCost = CalculatePct(auction->BidAmount, 5u);
+ if (!player->HasEnoughMoney(cancelCost)) //player doesn't have enough money
{
- uint64 auctionCut = auction->GetAuctionCut();
- if (!player->HasEnoughMoney(auctionCut)) //player doesn't have enough money, maybe message needed
- return;
- sAuctionMgr->SendAuctionCancelledToBidderMail(auction, trans);
- player->ModifyMoney(-int64(auctionCut));
+ SendAuctionCommandResult(0, AuctionCommand::Cancel, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext);
+ return;
}
-
- // item will deleted or added to received mail list
- MailDraft(auction->BuildAuctionMailSubject(AUCTION_CANCELED), AuctionEntry::BuildAuctionMailBody(0, 0, auction->buyout, auction->deposit, 0))
- .AddItem(item)
- .SendMailTo(trans, player, auction, MAIL_CHECK_MASK_COPIED);
- }
- else
- {
- TC_LOG_ERROR("network", "Auction id: %u got non existing item (item guid : " UI64FMTD ")!", auction->Id, auction->itemGUIDLow);
- SendAuctionCommandResult(NULL, AUCTION_CANCEL, ERR_AUCTION_DATABASE_ERROR);
- return;
+ auctionHouse->SendAuctionCancelledToBidder(auction, trans);
+ player->ModifyMoney(-int64(cancelCost));
}
+
+ auctionHouse->SendAuctionRemoved(auction, player, trans);
}
else
{
- SendAuctionCommandResult(NULL, AUCTION_CANCEL, ERR_AUCTION_DATABASE_ERROR);
+ SendAuctionCommandResult(0, AuctionCommand::Cancel, AuctionResult::DatabaseError, throttle.DelayUntilNext);
//this code isn't possible ... maybe there should be assert
- TC_LOG_ERROR("entities.player.cheat", "CHEATER: %s tried to cancel auction (id: %u) of another player or auction is NULL", player->GetGUID().ToString().c_str(), packet.AuctionID);
+ TC_LOG_ERROR("entities.player.cheat", "CHEATER: %s tried to cancel auction (id: %u) of another player or auction is NULL", player->GetGUID().ToString().c_str(), removeItem.AuctionID);
return;
}
- //inform player, that auction is removed
- SendAuctionCommandResult(auction, AUCTION_CANCEL, ERR_AUCTION_OK);
+ // client bug - instead of removing auction in the UI, it only substracts 1 from visible count
+ uint32 auctionIdForClient = auction->IsCommodity() ? 0 : auction->Id;
// Now remove the auction
-
player->SaveInventoryAndGoldToDB(trans);
- auction->DeleteFromDB(trans);
- CharacterDatabase.CommitTransaction(trans);
-
- sAuctionMgr->RemoveAItem(auction->itemGUIDLow);
- auctionHouse->RemoveAuction(auction);
+ auctionHouse->RemoveAuction(trans, auction);
+ AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete(
+ [this, auctionIdForClient, auctionPlayerGuid = _player->GetGUID(), throttle](bool success)
+ {
+ if (GetPlayer() && GetPlayer()->GetGUID() == auctionPlayerGuid)
+ {
+ 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);
+ }
+ });
}
-//called when player lists his bids
-void WorldSession::HandleAuctionListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItems& packet)
+void WorldSession::HandleAuctionReplicateItems(WorldPackets::AuctionHouse::AuctionReplicateItems& replicateItems)
{
- Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(replicateItems.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
if (!creature)
{
- TC_LOG_DEBUG("network", "WORLD: HandleAuctionListBidderItems - %s not found or you can't interact with him.", packet.Auctioneer.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: HandleReplicateItems - %s not found or you can't interact with him.", replicateItems.Auctioneer.ToString().c_str());
return;
}
@@ -585,102 +531,415 @@ void WorldSession::HandleAuctionListBidderItems(WorldPackets::AuctionHouse::Auct
AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- WorldPackets::AuctionHouse::AuctionListBidderItemsResult result;
+ WorldPackets::AuctionHouse::AuctionReplicateResponse response;
- Player* player = GetPlayer();
- auctionHouse->BuildListBidderItems(result, player);
- result.DesiredDelay = 300;
- SendPacket(result.Write());
+ auctionHouse->BuildReplicate(response, GetPlayer(),
+ replicateItems.ChangeNumberGlobal, replicateItems.ChangeNumberCursor, replicateItems.ChangeNumberTombstone, replicateItems.Count);
+
+ response.DesiredDelay = sWorld->getIntConfig(CONFIG_AUCTION_SEARCH_DELAY) * 5;
+ response.Result = 0;
+ SendPacket(response.Write());
}
-//this void sends player info about his auctions
-void WorldSession::HandleAuctionListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItems& packet)
+void WorldSession::HandleAuctionSellCommodity(WorldPackets::AuctionHouse::AuctionSellCommodity& sellCommodity)
{
- Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::SellItem);
+ if (throttle.Throttled)
+ return;
+
+ if (!sellCommodity.UnitPrice || sellCommodity.UnitPrice > MAX_MONEY_AMOUNT)
+ {
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Player %s (%s) attempted to sell item with invalid price.", _player->GetName().c_str(), _player->GetGUID().ToString().c_str());
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ // auction house does not deal with copper
+ if (sellCommodity.UnitPrice % SILVER)
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(sellCommodity.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
if (!creature)
{
- TC_LOG_DEBUG("network", "WORLD: HandleAuctionListOwnerItems - %s not found or you can't interact with him.", packet.Auctioneer.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) not found or you can't interact with him.", sellCommodity.Auctioneer.ToString().c_str());
return;
}
- // remove fake death
+ uint32 houseId = 0;
+ AuctionHouseEntry const* auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(creature->getFaction(), &houseId);
+ if (!auctionHouseEntry)
+ {
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) has wrong faction.", sellCommodity.Auctioneer.ToString().c_str());
+ return;
+ }
+
+ switch (sellCommodity.RunTime)
+ {
+ case 1 * MIN_AUCTION_TIME / MINUTE:
+ case 2 * MIN_AUCTION_TIME / MINUTE:
+ case 4 * MIN_AUCTION_TIME / MINUTE:
+ break;
+ default:
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::AuctionHouseBusy, throttle.DelayUntilNext);
+ return;
+ }
+
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
+ // find all items for sale
+ uint64 totalCount = 0;
+ std::unordered_map<ObjectGuid, std::pair<Item*, uint64>> items2;
+
+ for (WorldPackets::AuctionHouse::AuctionItemForSale const& itemForSale : 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.begin()->second.first))
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (sAuctionMgr->GetAItem(item->GetGUID()) || !item->CanBeTraded() || item->IsNotEmptyBag() ||
+ item->GetTemplate()->GetFlags() & ITEM_FLAG_CONJURED || *item->m_itemData->Expiration ||
+ item->GetCount() < itemForSale.UseCount)
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ std::pair<Item*, uint64>& soldItem = items2[item->GetGUID()];
+ soldItem.first = item;
+ soldItem.second += itemForSale.UseCount;
+ if (item->GetCount() < soldItem.second)
+ {
+ // check that we have enough of this item to sell
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ totalCount += itemForSale.UseCount;
+ }
+
+ if (!totalCount)
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ Seconds auctionTime = Seconds(int64(std::chrono::duration_cast<Seconds>(Minutes(sellCommodity.RunTime)).count() * double(sWorld->getRate(RATE_AUCTION_TIME))));
AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- WorldPackets::AuctionHouse::AuctionListOwnerItemsResult result;
+ uint64 deposit = AuctionHouseMgr::GetCommodityAuctionDeposit(items2.begin()->second.first->GetTemplate(), Minutes(sellCommodity.RunTime), totalCount);
+ if (!_player->HasEnoughMoney(deposit))
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
- auctionHouse->BuildListOwnerItems(result, _player);
- result.DesiredDelay = 300;
- SendPacket(result.Write());
+ uint32 auctionId = sObjectMgr->GenerateAuctionID();
+ AuctionPosting auction;
+ 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
+ std::unordered_map<Item* /*original*/, std::unique_ptr<Item> /*clone*/> clones;
+ for (std::pair<ObjectGuid const, std::pair<Item*, uint64>>& it : items2)
+ {
+ Item* itemForSale;
+ if (it.second.first->GetCount() != it.second.second)
+ {
+ itemForSale = it.second.first->CloneItem(it.second.second, _player);
+ if (!itemForSale)
+ {
+ TC_LOG_ERROR("network", "CMSG_AUCTION_SELL_COMMODITY: Could not create clone of item %u", it.second.first->GetEntry());
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ clones.emplace(it.second.first, itemForSale);
+ }
+ }
+
+ if (!sAuctionMgr->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().c_str(), _player->GetName().c_str(), items2.begin()->second.first->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()).c_str(),
+ ([&items2]()
+ {
+ std::stringstream ss;
+ auto itr = items2.begin();
+ ss << (itr++)->first.ToString();
+ for (; itr != items2.end(); ++itr)
+ ss << ',' << itr->first.ToString();
+ return ss.str();
+ }()).c_str(),
+ creature->GetGUID().ToString().c_str(), totalCount, sellCommodity.UnitPrice, uint32(auctionTime.count()), auctionHouse->GetAuctionHouseId());
+
+ if (HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE))
+ {
+ Item* logItem = items2.begin()->second.first;
+ sLog->outCommand(GetAccountId(), "GM %s (Account: %u) create auction: %s (Entry: %u Count: " UI64FMTD ")",
+ GetPlayerName().c_str(), GetAccountId(), logItem->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()), logItem->GetEntry(), totalCount);
+ }
+
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+
+ for (std::pair<ObjectGuid const, std::pair<Item*, uint64>> const& it : items2)
+ {
+ Item* itemForSale = it.second.first;
+ auto cloneItr = clones.find(it.second.first);
+ if (cloneItr != clones.end())
+ {
+ Item* original = itemForSale;
+ original->SetCount(original->GetCount() - uint32(it.second.second));
+ original->SetState(ITEM_CHANGED, _player);
+ _player->ItemRemovedQuestCheck(original->GetEntry(), uint32(it.second.second));
+ original->SaveToDB(trans);
+
+ itemForSale = cloneItr->second.release();
+ }
+ else
+ {
+ _player->MoveItemFromInventory(itemForSale->GetBagSlot(), itemForSale->GetSlot(), true);
+ itemForSale->DeleteFromInventoryDB(trans);
+ }
+
+ itemForSale->SaveToDB(trans);
+ auction.Items.push_back(itemForSale);
+ }
+
+ auctionHouse->AddAuction(trans, std::move(auction));
+ _player->SaveInventoryAndGoldToDB(trans);
+
+ AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete([this, auctionId, auctionPlayerGuid = _player->GetGUID(), throttle](bool success)
+ {
+ if (GetPlayer() && GetPlayer()->GetGUID() == auctionPlayerGuid)
+ {
+ if (success)
+ {
+ GetPlayer()->UpdateCriteria(CRITERIA_TYPE_CREATE_AUCTION, 1);
+ SendAuctionCommandResult(auctionId, AuctionCommand::SellItem, AuctionResult::Ok, throttle.DelayUntilNext);
+ }
+ else
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext);
+ }
+ });
}
-//this void is called when player clicks on search button
-void WorldSession::HandleAuctionListItems(WorldPackets::AuctionHouse::AuctionBrowseQuery& browseQuery)
+void WorldSession::HandleAuctionSellItem(WorldPackets::AuctionHouse::AuctionSellItem& sellItem)
{
- Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(browseQuery.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::SellItem);
+ if (throttle.Throttled)
+ return;
+
+ if (sellItem.Items.size() != 1 || sellItem.Items[0].UseCount != 1)
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (!sellItem.MinBid && !sellItem.BuyoutPrice)
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
+
+ if (sellItem.MinBid > MAX_MONEY_AMOUNT || sellItem.BuyoutPrice > MAX_MONEY_AMOUNT)
+ {
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Player %s (%s) attempted to sell item with higher price than max gold amount.", _player->GetName().c_str(), _player->GetGUID().ToString().c_str());
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::Inventory, throttle.DelayUntilNext, EQUIP_ERR_TOO_MUCH_GOLD);
+ return;
+ }
+
+ // auction house does not deal with copper
+ if (sellItem.MinBid % SILVER || sellItem.BuyoutPrice % SILVER)
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext);
+ return;
+ }
+
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(sellItem.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
if (!creature)
{
- TC_LOG_DEBUG("network", "WORLD: HandleAuctionListItems - %s not found or you can't interact with him.", browseQuery.Auctioneer.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) not found or you can't interact with him.", sellItem.Auctioneer.ToString().c_str());
return;
}
- // remove fake death
+ uint32 houseId = 0;
+ AuctionHouseEntry const* auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(creature->getFaction(), &houseId);
+ if (!auctionHouseEntry)
+ {
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionSellItem - Unit (%s) has wrong faction.", sellItem.Auctioneer.ToString().c_str());
+ return;
+ }
+
+ switch (sellItem.RunTime)
+ {
+ case 1 * MIN_AUCTION_TIME / MINUTE:
+ case 2 * MIN_AUCTION_TIME / MINUTE:
+ case 4 * MIN_AUCTION_TIME / MINUTE:
+ break;
+ default:
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::AuctionHouseBusy, throttle.DelayUntilNext);
+ return;
+ }
+
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
+ Item* item = _player->GetItemByGuid(sellItem.Items[0].Guid);
+ if (!item)
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
- TC_LOG_DEBUG("auctionHouse", "Auctionhouse search (%s), searchedname: %s, levelmin: %u, levelmax: %u, filters: %u",
- browseQuery.Auctioneer.ToString().c_str(), browseQuery.Name.c_str(), browseQuery.MinLevel, browseQuery.MaxLevel , AsUnderlyingType(browseQuery.Filters));
+ if (item->GetTemplate()->GetMaxStackSize() > 1)
+ {
+ // commodity, must use different packet
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::ItemNotFound, throttle.DelayUntilNext);
+ return;
+ }
- // converting string that we try to find to lower case
- std::wstring wsearchedname;
- if (!Utf8toWStr(browseQuery.Name, wsearchedname))
+ if (sAuctionMgr->GetAItem(item->GetGUID()) || !item->CanBeTraded() || item->IsNotEmptyBag() ||
+ item->GetTemplate()->GetFlags() & ITEM_FLAG_CONJURED || *item->m_itemData->Expiration ||
+ item->GetCount() != 1)
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext);
return;
+ }
- wstrToLower(wsearchedname);
+ Seconds auctionTime = Seconds(int64(std::chrono::duration_cast<Seconds>(Minutes(sellItem.RunTime)).count() * double(sWorld->getRate(RATE_AUCTION_TIME))));
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- Optional<AuctionSearchClassFilters> classFilters;
+ uint64 deposit = AuctionHouseMgr::GetItemAuctionDeposit(_player, item, Minutes(sellItem.RunTime));
+ if (!_player->HasEnoughMoney(deposit))
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
- WorldPackets::AuctionHouse::AuctionListItemsResult result;
- if (!browseQuery.ItemClassFilters.empty())
+ uint32 auctionId = sObjectMgr->GenerateAuctionID();
+
+ AuctionPosting auction;
+ 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(rbac::RBAC_PERM_LOG_GM_TRADE))
{
- classFilters = boost::in_place();
+ sLog->outCommand(GetAccountId(), "GM %s (Account: %u) create auction: %s (Entry: %u Count: %u)",
+ GetPlayerName().c_str(), GetAccountId(), item->GetTemplate()->GetDefaultLocaleName(), item->GetEntry(), item->GetCount());
+ }
- for (auto const& classFilter : browseQuery.ItemClassFilters)
+ auction.Items.push_back(item);
+
+ TC_LOG_INFO("network", "CMSG_AuctionAction::SellItem: %s %s is selling item %s %s to auctioneer %s with count %u with initial bid " UI64FMTD " with buyout " UI64FMTD " and with time %u (in sec) in auctionhouse %u",
+ _player->GetGUID().ToString().c_str(), _player->GetName().c_str(), item->GetGUID().ToString().c_str(), item->GetTemplate()->GetDefaultLocaleName(),
+ creature->GetGUID().ToString().c_str(), item->GetCount(), sellItem.MinBid, sellItem.BuyoutPrice, uint32(auctionTime.count()), auctionHouse->GetAuctionHouseId());
+
+ // Add to pending auctions, or fail with insufficient funds error
+ if (!sAuctionMgr->PendingAuctionAdd(_player, auctionHouse->GetAuctionHouseId(), auctionId, auction.Deposit))
+ {
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::NotEnoughMoney, throttle.DelayUntilNext);
+ return;
+ }
+
+ _player->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true);
+
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ item->DeleteFromInventoryDB(trans);
+ item->SaveToDB(trans);
+
+ auctionHouse->AddAuction(trans, std::move(auction));
+ _player->SaveInventoryAndGoldToDB(trans);
+ AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete([this, auctionId, auctionPlayerGuid = _player->GetGUID(), throttle](bool success)
+ {
+ if (GetPlayer() && GetPlayer()->GetGUID() == auctionPlayerGuid)
{
- if (!classFilter.SubClassFilters.empty())
+ if (success)
{
- for (auto const& subClassFilter : classFilter.SubClassFilters)
- {
- if (classFilter.ItemClass < MAX_ITEM_CLASS)
- {
- classFilters->Classes[classFilter.ItemClass].SubclassMask |= 1 << subClassFilter.ItemSubclass;
- if (subClassFilter.ItemSubclass < MAX_ITEM_SUBCLASS_TOTAL)
- classFilters->Classes[classFilter.ItemClass].InvTypes[subClassFilter.ItemSubclass] = subClassFilter.InvTypeMask;
- }
- }
+ GetPlayer()->UpdateCriteria(CRITERIA_TYPE_CREATE_AUCTION, 1);
+ SendAuctionCommandResult(auctionId, AuctionCommand::SellItem, AuctionResult::Ok, throttle.DelayUntilNext);
}
else
- classFilters->Classes[classFilter.ItemClass].SubclassMask = AuctionSearchClassFilters::FILTER_SKIP_SUBCLASS;
+ SendAuctionCommandResult(0, AuctionCommand::SellItem, AuctionResult::DatabaseError, throttle.DelayUntilNext);
}
- }
+ });
+}
+
+void WorldSession::HandleAuctionSetFavoriteItem(WorldPackets::AuctionHouse::AuctionSetFavoriteItem& setFavoriteItem)
+{
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player);
+ if (throttle.Throttled)
+ return;
- auctionHouse->BuildListAuctionItems(result, _player, wsearchedname, browseQuery.Offset, browseQuery.MinLevel, browseQuery.MaxLevel,
- browseQuery.Filters, classFilters);
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
- result.DesiredDelay = sWorld->getIntConfig(CONFIG_AUCTION_SEARCH_DELAY);
- SendPacket(result.Write());
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_FAVORITE_AUCTION);
+ stmt->setUInt64(0, _player->GetGUID().GetCounter());
+ stmt->setUInt32(1, setFavoriteItem.Item.Order);
+ trans->Append(stmt);
+
+ if (!setFavoriteItem.IsNotFavorite)
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_FAVORITE_AUCTION);
+ stmt->setUInt64(0, _player->GetGUID().GetCounter());
+ stmt->setUInt32(1, setFavoriteItem.Item.Order);
+ stmt->setUInt32(2, setFavoriteItem.Item.ItemID);
+ stmt->setUInt32(3, setFavoriteItem.Item.ItemLevel);
+ stmt->setUInt32(4, setFavoriteItem.Item.BattlePetSpeciesID);
+ stmt->setUInt32(5, setFavoriteItem.Item.SuffixItemNameDescriptionID);
+ trans->Append(stmt);
+ }
+
+ CharacterDatabase.CommitTransaction(trans);
}
-void WorldSession::HandleReplicateItems(WorldPackets::AuctionHouse::AuctionReplicateItems& packet)
+void WorldSession::HandleAuctionStartCommoditiesPurchase(WorldPackets::AuctionHouse::AuctionStartCommoditiesPurchase& startCommoditiesPurchase)
{
- Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
+ AuctionThrottleResult throttle = sAuctionMgr->CheckThrottle(_player, AuctionCommand::PlaceBid);
+ if (throttle.Throttled)
+ return;
+
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(startCommoditiesPurchase.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER, UNIT_NPC_FLAG_2_NONE);
if (!creature)
{
- TC_LOG_DEBUG("network", "WORLD: HandleReplicateItems - %s not found or you can't interact with him.", packet.Auctioneer.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: HandleAuctionStartCommoditiesPurchase - %s not found or you can't interact with him.",
+ startCommoditiesPurchase.Auctioneer.ToString().c_str());
return;
}
@@ -690,11 +949,64 @@ void WorldSession::HandleReplicateItems(WorldPackets::AuctionHouse::AuctionRepli
AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction());
- WorldPackets::AuctionHouse::AuctionReplicateResponse response;
+ WorldPackets::AuctionHouse::AuctionCommodityQuote auctionCommodityQuote;
- auctionHouse->BuildReplicate(response, GetPlayer(), packet.ChangeNumberGlobal, packet.ChangeNumberCursor, packet.ChangeNumberTombstone, packet.Count);
+ if (CommodityQuote const* quote = auctionHouse->CreateCommodityQuote(_player, startCommoditiesPurchase.ItemID, startCommoditiesPurchase.Quantity))
+ {
+ auctionCommodityQuote.TotalPrice = quote->TotalPrice;
+ auctionCommodityQuote.Quantity = quote->Quantity;
+ auctionCommodityQuote.QuoteDuration = std::chrono::duration_cast<Milliseconds>(quote->ValidTo - GameTime::GetGameTimeSteadyPoint()).count();
+ }
- response.DesiredDelay = sWorld->getIntConfig(CONFIG_AUCTION_SEARCH_DELAY) * 5;
- response.Result = 0;
- SendPacket(response.Write());
+ auctionCommodityQuote.DesiredDelay = uint32(throttle.DelayUntilNext.count());
+
+ SendPacket(auctionCommodityQuote.Write());
+}
+
+//this void causes that auction window is opened
+void WorldSession::SendAuctionHello(ObjectGuid guid, Creature* unit)
+{
+ if (GetPlayer()->getLevel() < sWorld->getIntConfig(CONFIG_AUCTION_LEVEL_REQ))
+ {
+ SendNotification(GetTrinityString(LANG_AUCTION_REQ), sWorld->getIntConfig(CONFIG_AUCTION_LEVEL_REQ));
+ return;
+ }
+
+ AuctionHouseEntry const* ahEntry = AuctionHouseMgr::GetAuctionHouseEntry(unit->getFaction(), nullptr);
+ if (!ahEntry)
+ return;
+
+ WorldPackets::AuctionHouse::AuctionHelloResponse auctionHelloResponse;
+ auctionHelloResponse.Guid = guid;
+ auctionHelloResponse.OpenForBusiness = true; // 3.3.3: 1 - AH enabled, 0 - AH disabled
+ SendPacket(auctionHelloResponse.Write());
+}
+
+void WorldSession::SendAuctionCommandResult(uint32 auctionId, AuctionCommand command, AuctionResult errorCode, Milliseconds delayForNextAction, InventoryResult bagError /*= 0*/)
+{
+ WorldPackets::AuctionHouse::AuctionCommandResult auctionCommandResult;
+ auctionCommandResult.AuctionID = auctionId;
+ auctionCommandResult.Command = AsUnderlyingType(command);
+ auctionCommandResult.ErrorCode = AsUnderlyingType(errorCode);
+ auctionCommandResult.BagResult = AsUnderlyingType(bagError);
+ auctionCommandResult.DesiredDelay = uint32(delayForNextAction.count());
+ SendPacket(auctionCommandResult.Write());
+}
+
+void WorldSession::SendAuctionClosedNotification(AuctionPosting const* auction, float mailDelay, bool sold)
+{
+ WorldPackets::AuctionHouse::AuctionClosedNotification packet;
+ packet.Info.Initialize(auction);
+ packet.ProceedsMailDelay = mailDelay;
+ packet.Sold = sold;
+ SendPacket(packet.Write());
+}
+
+void WorldSession::SendAuctionOwnerBidNotification(AuctionPosting const* auction)
+{
+ WorldPackets::AuctionHouse::AuctionOwnerBidNotification packet;
+ packet.Info.Initialize(auction);
+ packet.Bidder = auction->Bidder;
+ packet.MinIncrement = auction->CalculateMinIncrement();
+ SendPacket(packet.Write());
}
diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp
index 8a33b26cf8b..355a3078b30 100644
--- a/src/server/game/Handlers/CharacterHandler.cpp
+++ b/src/server/game/Handlers/CharacterHandler.cpp
@@ -20,6 +20,7 @@
#include "ArenaTeam.h"
#include "ArenaTeamMgr.h"
#include "ArtifactPackets.h"
+#include "AuctionHousePackets.h"
#include "AuthenticationPackets.h"
#include "Battleground.h"
#include "BattlegroundPackets.h"
@@ -1085,6 +1086,33 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder)
// Place character in world (and load zone) before some object loading
pCurrChar->LoadCorpse(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION));
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_FAVORITE_AUCTIONS);
+ stmt->setUInt64(0, pCurrChar->GetGUID().GetCounter());
+ GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(stmt)).WithPreparedCallback([this](PreparedQueryResult favoriteAuctionResult)
+ {
+ WorldPackets::AuctionHouse::AuctionFavoriteItems favoriteItems;
+ if (favoriteAuctionResult)
+ {
+ favoriteItems.Items.reserve(favoriteAuctionResult->GetRowCount());
+
+ do
+ {
+ Field* fields = favoriteAuctionResult->Fetch();
+
+ favoriteItems.Items.emplace_back();
+ WorldPackets::AuctionHouse::AuctionFavoriteInfo& item = favoriteItems.Items.back();
+ item.Order = fields[0].GetUInt32();
+ item.ItemID = fields[1].GetUInt32();
+ item.ItemLevel = fields[2].GetUInt32();
+ item.BattlePetSpeciesID = fields[3].GetUInt32();
+ item.SuffixItemNameDescriptionID = fields[4].GetUInt32();
+
+ } while (favoriteAuctionResult->NextRow());
+
+ }
+ SendPacket(favoriteItems.Write());
+ });
+
// setting Ghost+speed if dead
if (pCurrChar->m_deathState != ALIVE)
{
diff --git a/src/server/game/Mails/Mail.cpp b/src/server/game/Mails/Mail.cpp
index e54a623c8a4..00fb553d539 100644
--- a/src/server/game/Mails/Mail.cpp
+++ b/src/server/game/Mails/Mail.cpp
@@ -62,8 +62,8 @@ MailSender::MailSender(CalendarEvent* sender)
{
}
-MailSender::MailSender(AuctionEntry* sender)
- : m_messageType(MAIL_AUCTION), m_senderId(uint64(sender->GetHouseId())), m_stationery(MAIL_STATIONERY_AUCTION) { }
+MailSender::MailSender(AuctionHouseObject const* sender)
+ : m_messageType(MAIL_AUCTION), m_senderId(uint64(sender->GetAuctionHouseId())), m_stationery(MAIL_STATIONERY_AUCTION) { }
MailSender::MailSender(BlackMarketEntry* sender)
: m_messageType(MAIL_BLACKMARKET), m_senderId(sender->GetTemplate()->SellerNPC), m_stationery(MAIL_STATIONERY_AUCTION) { }
@@ -89,6 +89,11 @@ MailReceiver::MailReceiver(Player* receiver, ObjectGuid::LowType receiver_lowgui
ASSERT(!receiver || receiver->GetGUID().GetCounter() == receiver_lowguid);
}
+MailReceiver::MailReceiver(Player* receiver, ObjectGuid receiverGuid) : m_receiver(receiver), m_receiver_lowguid(receiverGuid.GetCounter())
+{
+ ASSERT(!receiver || receiver->GetGUID() == receiverGuid);
+}
+
MailDraft& MailDraft::AddItem(Item* item)
{
m_items[item->GetGUID().GetCounter()] = item;
@@ -182,7 +187,7 @@ void MailDraft::SendReturnToSender(uint32 sender_acc, ObjectGuid::LowType sender
void MailDraft::SendMailTo(CharacterDatabaseTransaction& trans, MailReceiver const& receiver, MailSender const& sender, MailCheckMask checked, uint32 deliver_delay)
{
Player* pReceiver = receiver.GetPlayer(); // can be NULL
- Player* pSender = ObjectAccessor::FindPlayer(ObjectGuid::Create<HighGuid::Player>(sender.GetSenderId()));
+ Player* pSender = sender.GetMailMessageType() == MAIL_NORMAL ? ObjectAccessor::FindPlayer(ObjectGuid::Create<HighGuid::Player>(sender.GetSenderId())) : nullptr;
if (pReceiver)
prepareItems(pReceiver, trans); // generate mail template items
diff --git a/src/server/game/Mails/Mail.h b/src/server/game/Mails/Mail.h
index b5adf47448a..91fac20c6f3 100644
--- a/src/server/game/Mails/Mail.h
+++ b/src/server/game/Mails/Mail.h
@@ -23,8 +23,8 @@
#include "Transaction.h"
#include <map>
-struct AuctionEntry;
struct CalendarEvent;
+class AuctionHouseObject;
class BlackMarketEntry;
class Item;
class Object;
@@ -91,7 +91,7 @@ class TC_GAME_API MailSender
}
MailSender(Object* sender, MailStationery stationery = MAIL_STATIONERY_DEFAULT);
MailSender(CalendarEvent* sender);
- MailSender(AuctionEntry* sender);
+ MailSender(AuctionHouseObject const* sender);
MailSender(BlackMarketEntry* sender);
MailSender(Player* sender);
MailSender(uint32 senderEntry);
@@ -111,6 +111,7 @@ class TC_GAME_API MailReceiver
explicit MailReceiver(ObjectGuid::LowType receiver_lowguid) : m_receiver(NULL), m_receiver_lowguid(receiver_lowguid) { }
MailReceiver(Player* receiver);
MailReceiver(Player* receiver, ObjectGuid::LowType receiver_lowguid);
+ MailReceiver(Player* receiver, ObjectGuid receiverGuid);
public: // Accessors
Player* GetPlayer() const { return m_receiver; }
ObjectGuid::LowType GetPlayerGUIDLow() const { return m_receiver_lowguid; }
diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp
index b0f609d0724..e9596c4fdd3 100644
--- a/src/server/game/Scripting/ScriptMgr.cpp
+++ b/src/server/game/Scripting/ScriptMgr.cpp
@@ -1988,36 +1988,36 @@ void ScriptMgr::OnWeatherUpdate(Weather* weather, uint32 diff)
tmpscript->OnUpdate(weather, diff);
}
-void ScriptMgr::OnAuctionAdd(AuctionHouseObject* ah, AuctionEntry* entry)
+void ScriptMgr::OnAuctionAdd(AuctionHouseObject* ah, AuctionPosting* auction)
{
ASSERT(ah);
- ASSERT(entry);
+ ASSERT(auction);
- FOREACH_SCRIPT(AuctionHouseScript)->OnAuctionAdd(ah, entry);
+ FOREACH_SCRIPT(AuctionHouseScript)->OnAuctionAdd(ah, auction);
}
-void ScriptMgr::OnAuctionRemove(AuctionHouseObject* ah, AuctionEntry* entry)
+void ScriptMgr::OnAuctionRemove(AuctionHouseObject* ah, AuctionPosting* auction)
{
ASSERT(ah);
- ASSERT(entry);
+ ASSERT(auction);
- FOREACH_SCRIPT(AuctionHouseScript)->OnAuctionRemove(ah, entry);
+ FOREACH_SCRIPT(AuctionHouseScript)->OnAuctionRemove(ah, auction);
}
-void ScriptMgr::OnAuctionSuccessful(AuctionHouseObject* ah, AuctionEntry* entry)
+void ScriptMgr::OnAuctionSuccessful(AuctionHouseObject* ah, AuctionPosting* auction)
{
ASSERT(ah);
- ASSERT(entry);
+ ASSERT(auction);
- FOREACH_SCRIPT(AuctionHouseScript)->OnAuctionSuccessful(ah, entry);
+ FOREACH_SCRIPT(AuctionHouseScript)->OnAuctionSuccessful(ah, auction);
}
-void ScriptMgr::OnAuctionExpire(AuctionHouseObject* ah, AuctionEntry* entry)
+void ScriptMgr::OnAuctionExpire(AuctionHouseObject* ah, AuctionPosting* auction)
{
ASSERT(ah);
- ASSERT(entry);
+ ASSERT(auction);
- FOREACH_SCRIPT(AuctionHouseScript)->OnAuctionExpire(ah, entry);
+ FOREACH_SCRIPT(AuctionHouseScript)->OnAuctionExpire(ah, auction);
}
bool ScriptMgr::OnConditionCheck(Condition const* condition, ConditionSourceInfo& sourceInfo)
diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h
index ecad5c10de4..53465b1dca2 100644
--- a/src/server/game/Scripting/ScriptMgr.h
+++ b/src/server/game/Scripting/ScriptMgr.h
@@ -64,7 +64,7 @@ class WorldObject;
class WorldSession;
struct AreaTriggerEntry;
-struct AuctionEntry;
+struct AuctionPosting;
struct ConditionSourceInfo;
struct Condition;
struct CreatureTemplate;
@@ -569,16 +569,16 @@ class TC_GAME_API AuctionHouseScript : public ScriptObject
public:
// Called when an auction is added to an auction house.
- virtual void OnAuctionAdd(AuctionHouseObject* /*ah*/, AuctionEntry* /*entry*/) { }
+ virtual void OnAuctionAdd(AuctionHouseObject* /*ah*/, AuctionPosting* /*auction*/) { }
// Called when an auction is removed from an auction house.
- virtual void OnAuctionRemove(AuctionHouseObject* /*ah*/, AuctionEntry* /*entry*/) { }
+ virtual void OnAuctionRemove(AuctionHouseObject* /*ah*/, AuctionPosting* /*auction*/) { }
// Called when an auction was succesfully completed.
- virtual void OnAuctionSuccessful(AuctionHouseObject* /*ah*/, AuctionEntry* /*entry*/) { }
+ virtual void OnAuctionSuccessful(AuctionHouseObject* /*ah*/, AuctionPosting* /*auction*/) { }
// Called when an auction expires.
- virtual void OnAuctionExpire(AuctionHouseObject* /*ah*/, AuctionEntry* /*entry*/) { }
+ virtual void OnAuctionExpire(AuctionHouseObject* /*ah*/, AuctionPosting* /*auction*/) { }
};
class TC_GAME_API ConditionScript : public ScriptObject
@@ -1086,10 +1086,10 @@ class TC_GAME_API ScriptMgr
public: /* AuctionHouseScript */
- void OnAuctionAdd(AuctionHouseObject* ah, AuctionEntry* entry);
- void OnAuctionRemove(AuctionHouseObject* ah, AuctionEntry* entry);
- void OnAuctionSuccessful(AuctionHouseObject* ah, AuctionEntry* entry);
- void OnAuctionExpire(AuctionHouseObject* ah, AuctionEntry* entry);
+ void OnAuctionAdd(AuctionHouseObject* ah, AuctionPosting* auction);
+ void OnAuctionRemove(AuctionHouseObject* ah, AuctionPosting* auction);
+ void OnAuctionSuccessful(AuctionHouseObject* ah, AuctionPosting* auction);
+ void OnAuctionExpire(AuctionHouseObject* ah, AuctionPosting* auction);
public: /* ConditionScript */
diff --git a/src/server/game/Server/Packets/AuctionHousePackets.cpp b/src/server/game/Server/Packets/AuctionHousePackets.cpp
index 283978a3b36..41cd3912eb6 100644
--- a/src/server/game/Server/Packets/AuctionHousePackets.cpp
+++ b/src/server/game/Server/Packets/AuctionHousePackets.cpp
@@ -18,11 +18,26 @@
#include "AuctionHousePackets.h"
#include "AuctionHouseMgr.h"
#include "ObjectGuid.h"
+#include "Util.h"
namespace WorldPackets
{
namespace AuctionHouse
{
+AuctionBucketKey& AuctionBucketKey::operator=(AuctionsBucketKey const& key)
+{
+ ItemID = key.ItemId;
+ ItemLevel = key.ItemLevel;
+
+ if (key.BattlePetSpeciesId)
+ BattlePetSpeciesID = key.BattlePetSpeciesId;
+
+ if (key.SuffixItemNameDescriptionId)
+ SuffixItemNameDescriptionID = key.SuffixItemNameDescriptionId;
+
+ return *this;
+}
+
ByteBuffer& operator>>(ByteBuffer& data, AuctionBucketKey& itemKey)
{
data.ResetBitPos();
@@ -103,7 +118,7 @@ ByteBuffer& operator>>(ByteBuffer& data, AuctionFavoriteInfo& favoriteInfo)
data >> favoriteInfo.ItemID;
data >> favoriteInfo.ItemLevel;
data >> favoriteInfo.BattlePetSpeciesID;
- data >> favoriteInfo.ItemSuffix;
+ data >> favoriteInfo.SuffixItemNameDescriptionID;
return data;
}
@@ -114,16 +129,16 @@ ByteBuffer& operator<<(ByteBuffer& data, AuctionFavoriteInfo const& favoriteInfo
data << uint32(favoriteInfo.ItemID);
data << uint32(favoriteInfo.ItemLevel);
data << uint32(favoriteInfo.BattlePetSpeciesID);
- data << uint32(favoriteInfo.ItemSuffix);
+ data << uint32(favoriteInfo.SuffixItemNameDescriptionID);
return data;
}
-void AuctionOwnerNotification::Initialize(::AuctionEntry const* auction, ::Item const* item)
+void AuctionOwnerNotification::Initialize(::AuctionPosting const* auction)
{
AuctionID = auction->Id;
- Item.Initialize(item);
- BidAmount = auction->bid;
+ Item.Initialize(auction->Items[0]);
+ BidAmount = auction->BidAmount;
}
ByteBuffer& operator<<(ByteBuffer& data, AuctionOwnerNotification const& ownerNotification)
@@ -234,11 +249,11 @@ ByteBuffer& operator<<(ByteBuffer& data, AuctionItem const& auctionItem)
return data;
}
-void AuctionBidderNotification::Initialize(::AuctionEntry const* auction, ::Item const* item)
+void AuctionBidderNotification::Initialize(::AuctionPosting const* auction, ::Item const* item)
{
AuctionID = auction->Id;
Item.Initialize(item);
- Bidder = ObjectGuid::Create<HighGuid::Player>(auction->bidder);
+ Bidder = auction->Bidder;
}
ByteBuffer& operator<<(ByteBuffer& data, AuctionBidderNotification const& bidderNotification)
@@ -327,32 +342,31 @@ void AuctionListBidderItems::Read()
_worldPacket >> auctionID;
}
-void AuctionListItemsByBucketKey::Read()
+void AuctionListBucketsByBucketKeys::Read()
{
_worldPacket >> Auctioneer;
- _worldPacket >> Offset;
- _worldPacket >> Unknown830;
if (_worldPacket.ReadBit())
TaintedBy.emplace();
+ BucketKeys.resize(_worldPacket.ReadBits(7));
Sorts.resize(_worldPacket.ReadBits(2));
for (AuctionSortDef& sortDef : Sorts)
_worldPacket >> sortDef;
- _worldPacket >> BucketKey;
-
if (TaintedBy)
_worldPacket >> *TaintedBy;
+
+ for (AuctionBucketKey& bucketKey : BucketKeys)
+ _worldPacket >> bucketKey;
}
-void AuctionListItemsByItemID::Read()
+void AuctionListItemsByBucketKey::Read()
{
_worldPacket >> Auctioneer;
- _worldPacket >> ItemID;
- _worldPacket >> SuffixItemNameDescriptionID;
_worldPacket >> Offset;
+ _worldPacket >> Unknown830;
if (_worldPacket.ReadBit())
TaintedBy.emplace();
@@ -362,18 +376,22 @@ void AuctionListItemsByItemID::Read()
for (AuctionSortDef& sortDef : Sorts)
_worldPacket >> sortDef;
+ _worldPacket >> BucketKey;
+
if (TaintedBy)
_worldPacket >> *TaintedBy;
}
-void AuctionListItemsByItemKeys::Read()
+void AuctionListItemsByItemID::Read()
{
_worldPacket >> Auctioneer;
+ _worldPacket >> ItemID;
+ _worldPacket >> SuffixItemNameDescriptionID;
+ _worldPacket >> Offset;
if (_worldPacket.ReadBit())
TaintedBy.emplace();
- BucketKeys.resize(_worldPacket.ReadBits(7));
Sorts.resize(_worldPacket.ReadBits(2));
for (AuctionSortDef& sortDef : Sorts)
@@ -381,9 +399,6 @@ void AuctionListItemsByItemKeys::Read()
if (TaintedBy)
_worldPacket >> *TaintedBy;
-
- for (AuctionBucketKey& bucketKey : BucketKeys)
- _worldPacket >> bucketKey;
}
void AuctionListOwnerItems::Read()
@@ -505,17 +520,6 @@ WorldPacket const* AuctionClosedNotification::Write()
return &_worldPacket;
}
-void AuctionCommandResult::InitializeAuction(::AuctionEntry const* auction)
-{
- if (auction)
- {
- 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);
- }
-}
-
WorldPacket const* AuctionCommandResult::Write()
{
_worldPacket << int32(AuctionID);
@@ -530,7 +534,7 @@ WorldPacket const* AuctionCommandResult::Write()
return &_worldPacket;
}
-WorldPacket const* AuctionCommodityPriceUpdate::Write()
+WorldPacket const* AuctionCommodityQuote::Write()
{
_worldPacket.WriteBit(TotalPrice.is_initialized());
_worldPacket.WriteBit(Quantity.is_initialized());
@@ -550,6 +554,18 @@ WorldPacket const* AuctionCommodityPriceUpdate::Write()
return &_worldPacket;
}
+WorldPacket const* AuctionFavoriteItems::Write()
+{
+ _worldPacket << uint32(DesiredDelay);
+ _worldPacket.WriteBits(Items.size(), 7);
+ _worldPacket.FlushBits();
+
+ for (AuctionFavoriteInfo const& favoriteInfo : Items)
+ _worldPacket << favoriteInfo;
+
+ return &_worldPacket;
+}
+
WorldPacket const* AuctionHelloResponse::Write()
{
_worldPacket << Guid;
@@ -572,13 +588,13 @@ WorldPacket const* AuctionListBidderItemsResult::Write()
return &_worldPacket;
}
-WorldPacket const* AuctionListBucketItemsResult::Write()
+WorldPacket const* AuctionListBucketsResult::Write()
{
_worldPacket << uint32(Buckets.size());
_worldPacket << uint32(DesiredDelay);
_worldPacket << int32(Unknown830_0);
_worldPacket << int32(Unknown830_1);
- _worldPacket.WriteBits(BrowseMode, 1);
+ _worldPacket.WriteBits(AsUnderlyingType(BrowseMode), 1);
_worldPacket.WriteBit(HasMoreResults);
_worldPacket.FlushBits();
@@ -588,25 +604,13 @@ WorldPacket const* AuctionListBucketItemsResult::Write()
return &_worldPacket;
}
-WorldPacket const* AuctionListFavoriteItemsResult::Write()
-{
- _worldPacket << uint32(DesiredDelay);
- _worldPacket.WriteBits(Items.size(), 7);
- _worldPacket.FlushBits();
-
- for (AuctionFavoriteInfo const& favoriteInfo : Items)
- _worldPacket << favoriteInfo;
-
- return &_worldPacket;
-}
-
WorldPacket const* AuctionListItemsResult::Write()
{
_worldPacket << uint32(Items.size());
_worldPacket << uint32(Unknown830);
_worldPacket << uint32(TotalCount);
_worldPacket << uint32(DesiredDelay);
- _worldPacket.WriteBits(ListType, 2);
+ _worldPacket.WriteBits(AsUnderlyingType(ListType), 2);
_worldPacket.WriteBit(HasMoreResults);
_worldPacket.FlushBits();
diff --git a/src/server/game/Server/Packets/AuctionHousePackets.h b/src/server/game/Server/Packets/AuctionHousePackets.h
index 809655ab234..c117319f79e 100644
--- a/src/server/game/Server/Packets/AuctionHousePackets.h
+++ b/src/server/game/Server/Packets/AuctionHousePackets.h
@@ -24,9 +24,12 @@
#include "ItemPacketsCommon.h"
#include "ObjectGuid.h"
-struct AuctionEntry;
-enum class AuctionHouseSortOrder : uint8;
+struct AuctionsBucketKey;
+struct AuctionPosting;
+enum class AuctionHouseBrowseMode : uint8;
enum class AuctionHouseFilterMask : uint32;
+enum class AuctionHouseListType : uint8;
+enum class AuctionHouseSortOrder : uint8;
namespace WorldPackets
{
@@ -34,6 +37,12 @@ namespace WorldPackets
{
struct AuctionBucketKey
{
+ AuctionBucketKey() : ItemID(0), ItemLevel(0) { }
+ AuctionBucketKey(AuctionsBucketKey const& key) { *this = key; }
+
+ AuctionBucketKey& operator=(AuctionBucketKey const& key) = default;
+ AuctionBucketKey& operator=(AuctionsBucketKey const& key);
+
uint32 ItemID = 0;
uint16 ItemLevel = 0;
Optional<uint16> BattlePetSpeciesID;
@@ -70,12 +79,12 @@ namespace WorldPackets
uint32 ItemID = 0;
uint32 ItemLevel = 0;
uint32 BattlePetSpeciesID = 0;
- uint32 ItemSuffix = 0;
+ uint32 SuffixItemNameDescriptionID = 0;
};
struct AuctionOwnerNotification
{
- void Initialize(::AuctionEntry const* auction, ::Item const* item);
+ void Initialize(::AuctionPosting const* auction);
int32 AuctionID = 0;
uint64 BidAmount = 0;
@@ -123,7 +132,7 @@ namespace WorldPackets
struct AuctionBidderNotification
{
- void Initialize(::AuctionEntry const* auction, ::Item const* item);
+ void Initialize(::AuctionPosting const* auction, ::Item const* item);
int32 AuctionID = 0;
ObjectGuid Bidder;
@@ -198,46 +207,46 @@ namespace WorldPackets
Optional<Addon::AddOnInfo> TaintedBy;
};
- class AuctionListItemsByBucketKey final : public ClientPacket
+ class AuctionListBucketsByBucketKeys final : public ClientPacket
{
public:
- AuctionListItemsByBucketKey(WorldPacket&& packet) : ClientPacket(CMSG_AUCTION_LIST_ITEMS_BY_BUCKET_KEY, std::move(packet)) { }
+ AuctionListBucketsByBucketKeys(WorldPacket&& packet) : ClientPacket(CMSG_AUCTION_LIST_BUCKETS_BY_BUCKET_KEYS, std::move(packet)) { }
void Read() override;
ObjectGuid Auctioneer;
- uint32 Offset = 0;
- int8 Unknown830 = 0;
Optional<Addon::AddOnInfo> TaintedBy;
+ Array<AuctionBucketKey, 100> BucketKeys;
Array<AuctionSortDef, 2> Sorts;
- AuctionBucketKey BucketKey;
};
- class AuctionListItemsByItemID final : public ClientPacket
+ class AuctionListItemsByBucketKey final : public ClientPacket
{
public:
- AuctionListItemsByItemID(WorldPacket&& packet) : ClientPacket(CMSG_AUCTION_LIST_ITEMS_BY_ITEM_ID, std::move(packet)) { }
+ AuctionListItemsByBucketKey(WorldPacket&& packet) : ClientPacket(CMSG_AUCTION_LIST_ITEMS_BY_BUCKET_KEY, std::move(packet)) { }
void Read() override;
ObjectGuid Auctioneer;
- int32 ItemID = 0;
- int32 SuffixItemNameDescriptionID = 0;
uint32 Offset = 0;
+ int8 Unknown830 = 0;
Optional<Addon::AddOnInfo> TaintedBy;
Array<AuctionSortDef, 2> Sorts;
+ AuctionBucketKey BucketKey;
};
- class AuctionListItemsByItemKeys final : public ClientPacket
+ class AuctionListItemsByItemID final : public ClientPacket
{
public:
- AuctionListItemsByItemKeys(WorldPacket&& packet) : ClientPacket(CMSG_AUCTION_LIST_ITEMS_BY_ITEM_KEYS, std::move(packet)) { }
+ AuctionListItemsByItemID(WorldPacket&& packet) : ClientPacket(CMSG_AUCTION_LIST_ITEMS_BY_ITEM_ID, std::move(packet)) { }
void Read() override;
ObjectGuid Auctioneer;
+ int32 ItemID = 0;
+ int32 SuffixItemNameDescriptionID = 0;
+ uint32 Offset = 0;
Optional<Addon::AddOnInfo> TaintedBy;
- Array<AuctionBucketKey, 100> BucketKeys;
Array<AuctionSortDef, 2> Sorts;
};
@@ -320,7 +329,7 @@ namespace WorldPackets
uint64 MinBid = 0;
uint32 RunTime = 0;
Optional<Addon::AddOnInfo> TaintedBy;
- Array<AuctionItemForSale, 64> Items;
+ Array<AuctionItemForSale, 1> Items;
};
class AuctionSetFavoriteItem final : public ClientPacket
@@ -364,15 +373,6 @@ namespace WorldPackets
public:
AuctionCommandResult() : ServerPacket(SMSG_AUCTION_COMMAND_RESULT, 4 + 4 + 4 + 8 + 4 + 8 + 8 + 8) { }
- /**
- * @fn void WorldPackets::AuctionHousePackets::AuctionCommandResult::InitializeAuction(AuctionEntry* auction);
- *
- * @brief Initialize the following fields: AuctionId, Bid, AuctionOutBid, Bidder
- *
- * @param auction The relevant auction object
- */
- void InitializeAuction(::AuctionEntry const* auction);
-
WorldPacket const* Write() override;
int32 AuctionID = 0; ///< the id of the auction that triggered this notification
@@ -385,10 +385,10 @@ namespace WorldPackets
uint32 DesiredDelay = 0;
};
- class AuctionCommodityPriceUpdate final : public ServerPacket
+ class AuctionCommodityQuote final : public ServerPacket
{
public:
- AuctionCommodityPriceUpdate() : ServerPacket(SMSG_AUCTION_COMMODITY_PRICE_UPDATE, 1 + 8 + 4 + 4 + 4 + 4) { }
+ AuctionCommodityQuote() : ServerPacket(SMSG_AUCTION_COMMODITY_QUOTE, 1 + 8 + 4 + 4 + 4 + 4) { }
WorldPacket const* Write() override;
@@ -422,10 +422,10 @@ namespace WorldPackets
bool HasMoreResults = false;
};
- class AuctionListBucketItemsResult final : public ServerPacket
+ class AuctionListBucketsResult final : public ServerPacket
{
public:
- AuctionListBucketItemsResult() : ServerPacket(SMSG_AUCTION_LIST_BUCKET_ITEMS_RESULT) { }
+ AuctionListBucketsResult() : ServerPacket(SMSG_AUCTION_LIST_BUCKETS_RESULT) { }
WorldPacket const* Write() override;
@@ -433,14 +433,14 @@ namespace WorldPackets
uint32 DesiredDelay = 0;
int32 Unknown830_0 = 0;
int32 Unknown830_1 = 0;
- int32 BrowseMode = 0;
+ AuctionHouseBrowseMode BrowseMode = AuctionHouseBrowseMode(0);
bool HasMoreResults = false;
};
- class AuctionListFavoriteItemsResult final : public ServerPacket
+ class AuctionFavoriteItems final : public ServerPacket
{
public:
- AuctionListFavoriteItemsResult() : ServerPacket(SMSG_AUCTION_LIST_FAVORITE_ITEMS_RESULT, 4 + 4 + 20 * 100) { }
+ AuctionFavoriteItems() : ServerPacket(SMSG_AUCTION_FAVORITE_ITEMS, 4 + 4 + 20 * 100) { }
WorldPacket const* Write() override;
@@ -459,7 +459,7 @@ namespace WorldPackets
uint32 Unknown830 = 0;
uint32 TotalCount = 0;
uint32 DesiredDelay = 0;
- uint32 ListType = 0;
+ AuctionHouseListType ListType = AuctionHouseListType(0);
bool HasMoreResults = false;
AuctionBucketKey BucketKey;
};
diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp
index 3e9ae794b3a..306144a1ac8 100644
--- a/src/server/game/Server/Protocol/Opcodes.cpp
+++ b/src/server/game/Server/Protocol/Opcodes.cpp
@@ -163,22 +163,22 @@ void OpcodeTable::Initialize()
DEFINE_HANDLER(CMSG_ARTIFACT_SET_APPEARANCE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArtifactSetAppearance);
DEFINE_HANDLER(CMSG_ATTACK_STOP, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleAttackStopOpcode);
DEFINE_HANDLER(CMSG_ATTACK_SWING, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleAttackSwingOpcode);
- DEFINE_HANDLER(CMSG_AUCTION_BROWSE_QUERY, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_CANCEL_COMMODITIES_PURCHASE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_CONFIRM_COMMODITIES_PURCHASE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
+ DEFINE_HANDLER(CMSG_AUCTION_BROWSE_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionBrowseQuery);
+ DEFINE_HANDLER(CMSG_AUCTION_CANCEL_COMMODITIES_PURCHASE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionCancelCommoditiesPurchase);
+ DEFINE_HANDLER(CMSG_AUCTION_CONFIRM_COMMODITIES_PURCHASE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionConfirmCommoditiesPurchase);
DEFINE_HANDLER(CMSG_AUCTION_HELLO_REQUEST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionHelloOpcode);
- DEFINE_HANDLER(CMSG_AUCTION_LIST_BIDDER_ITEMS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_LIST_ITEMS_BY_BUCKET_KEY, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_LIST_ITEMS_BY_ITEM_ID, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_LIST_ITEMS_BY_ITEM_KEYS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_LIST_OWNER_ITEMS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_PLACE_BID, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_REMOVE_ITEM, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_REPLICATE_ITEMS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_SELL_COMMODITY, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_SELL_ITEM, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_SET_FAVORITE_ITEM, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_AUCTION_START_COMMODITIES_PURCHASE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
+ DEFINE_HANDLER(CMSG_AUCTION_LIST_BIDDER_ITEMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionListBidderItems);
+ DEFINE_HANDLER(CMSG_AUCTION_LIST_BUCKETS_BY_BUCKET_KEYS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionListBucketsByBucketKeys);
+ DEFINE_HANDLER(CMSG_AUCTION_LIST_ITEMS_BY_BUCKET_KEY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionListItemsByBucketKey);
+ DEFINE_HANDLER(CMSG_AUCTION_LIST_ITEMS_BY_ITEM_ID, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionListItemsByItemID);
+ DEFINE_HANDLER(CMSG_AUCTION_LIST_OWNER_ITEMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionListOwnerItems);
+ DEFINE_HANDLER(CMSG_AUCTION_PLACE_BID, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionPlaceBid);
+ DEFINE_HANDLER(CMSG_AUCTION_REMOVE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionRemoveItem);
+ DEFINE_HANDLER(CMSG_AUCTION_REPLICATE_ITEMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionReplicateItems);
+ DEFINE_HANDLER(CMSG_AUCTION_SELL_COMMODITY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionSellCommodity);
+ DEFINE_HANDLER(CMSG_AUCTION_SELL_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionSellItem);
+ DEFINE_HANDLER(CMSG_AUCTION_SET_FAVORITE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionSetFavoriteItem);
+ DEFINE_HANDLER(CMSG_AUCTION_START_COMMODITIES_PURCHASE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionStartCommoditiesPurchase);
DEFINE_HANDLER(CMSG_AUTH_CONTINUED_SESSION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_EarlyProccess);
DEFINE_HANDLER(CMSG_AUTH_SESSION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_EarlyProccess);
DEFINE_HANDLER(CMSG_AUTOBANK_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAutoBankItemOpcode);
@@ -939,16 +939,16 @@ void OpcodeTable::Initialize()
DEFINE_SERVER_OPCODE_HANDLER(SMSG_ATTACK_SWING_LANDED_LOG, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_CLOSED_NOTIFICATION, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_COMMAND_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_COMMODITY_PRICE_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_COMMODITY_QUOTE, STATUS_NEVER, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_FAVORITE_ITEMS, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_HELLO_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_LIST_BUCKET_ITEMS_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_LIST_FAVORITE_ITEMS_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_LIST_ITEMS_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_LIST_BUCKETS_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_LIST_ITEMS_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_OUTBID_NOTIFICATION, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_OWNER_BID_NOTIFICATION, STATUS_NEVER, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_REPLICATE_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_REPLICATE_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_AUCTION_WON_NOTIFICATION, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_AURA_POINTS_DEPLETED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_AURA_UPDATE, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
diff --git a/src/server/game/Server/Protocol/Opcodes.h b/src/server/game/Server/Protocol/Opcodes.h
index 7c3722d9a1c..2c90b66c0db 100644
--- a/src/server/game/Server/Protocol/Opcodes.h
+++ b/src/server/game/Server/Protocol/Opcodes.h
@@ -72,7 +72,7 @@ enum OpcodeClient : uint16
CMSG_AUCTION_LIST_BIDDER_ITEMS = 0x34D4,
CMSG_AUCTION_LIST_ITEMS_BY_BUCKET_KEY = 0x34D1,
CMSG_AUCTION_LIST_ITEMS_BY_ITEM_ID = 0x34D2,
- CMSG_AUCTION_LIST_ITEMS_BY_ITEM_KEYS = 0x34D5,
+ CMSG_AUCTION_LIST_BUCKETS_BY_BUCKET_KEYS = 0x34D5,
CMSG_AUCTION_LIST_OWNER_ITEMS = 0x34D3,
CMSG_AUCTION_PLACE_BID = 0x34CF,
CMSG_AUCTION_REMOVE_ITEM = 0x34CD,
@@ -839,11 +839,11 @@ enum OpcodeServer : uint16
SMSG_ATTACK_SWING_LANDED_LOG = 0x273A,
SMSG_AUCTION_CLOSED_NOTIFICATION = 0x2731,
SMSG_AUCTION_COMMAND_RESULT = 0x272E,
- SMSG_AUCTION_COMMODITY_PRICE_UPDATE = 0x28C3,
+ SMSG_AUCTION_COMMODITY_QUOTE = 0x28C3,
+ SMSG_AUCTION_FAVORITE_ITEMS = 0x28CC,
SMSG_AUCTION_HELLO_RESPONSE = 0x272C,
SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT = 0x28C2,
- SMSG_AUCTION_LIST_BUCKET_ITEMS_RESULT = 0x28BF,
- SMSG_AUCTION_LIST_FAVORITE_ITEMS_RESULT = 0x28CC,
+ SMSG_AUCTION_LIST_BUCKETS_RESULT = 0x28BF,
SMSG_AUCTION_LIST_ITEMS_RESULT = 0x28C0,
SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT = 0x28C1,
SMSG_AUCTION_OUTBID_NOTIFICATION = 0x2730,
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index 1f8b8100b02..9466c8682cf 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -25,6 +25,7 @@
#include "Common.h"
#include "AsyncCallbackProcessor.h"
#include "DatabaseEnvFwd.h"
+#include "Duration.h"
#include "LockedQueue.h"
#include "ObjectGuid.h"
#include "Packet.h"
@@ -46,12 +47,15 @@ class Unit;
class Warden;
class WorldSession;
class WorldSocket;
-struct AuctionEntry;
+struct AuctionPosting;
struct BlackMarketTemplate;
struct DeclinedName;
struct ItemTemplate;
struct MovementInfo;
struct Position;
+enum class AuctionCommand : int8;
+enum class AuctionResult : int8;
+enum InventoryResult : uint8;
namespace lfg
{
@@ -92,14 +96,22 @@ namespace WorldPackets
namespace AuctionHouse
{
+ class AuctionBrowseQuery;
+ class AuctionCancelCommoditiesPurchase;
+ class AuctionConfirmCommoditiesPurchase;
class AuctionHelloRequest;
class AuctionListBidderItems;
- class AuctionBrowseQuery;
+ class AuctionListBucketsByBucketKeys;
+ class AuctionListItemsByBucketKey;
+ class AuctionListItemsByItemID;
class AuctionListOwnerItems;
class AuctionPlaceBid;
class AuctionRemoveItem;
class AuctionReplicateItems;
+ class AuctionSellCommodity;
class AuctionSellItem;
+ class AuctionSetFavoriteItem;
+ class AuctionStartCommoditiesPurchase;
}
namespace Auth
@@ -1032,20 +1044,18 @@ class TC_GAME_API WorldSession
void SendAuctionHello(ObjectGuid guid, Creature* unit);
/**
- * @fn void WorldSession::SendAuctionCommandResult(AuctionEntry* auction, uint32 Action, uint32 ErrorCode, uint32 bidError = 0);
+ * @fn void WorldSession::SendAuctionCommandResult(uint32 auctionId, uint32 action, uint32 errorCode, uint32 bagError = 0);
*
* @brief Notifies the client of the result of his last auction operation. It is called when the player bids, creates, or deletes an auction
*
- * @param auction The relevant auction object
- * @param Action The action that was performed.
- * @param ErrorCode The resulting error code.
- * @param bidError (Optional) the bid error.
+ * @param auctionId The relevant auction id
+ * @param command The action that was performed.
+ * @param errorCode The resulting error code.
+ * @param bagError (Optional) InventoryResult.
*/
- void SendAuctionCommandResult(AuctionEntry* auction, uint32 Action, uint32 ErrorCode, uint32 bidError = 0);
- void SendAuctionOutBidNotification(AuctionEntry const* auction, Item const* item);
- void SendAuctionClosedNotification(AuctionEntry const* auction, float mailDelay, bool sold, Item const* item);
- void SendAuctionWonNotification(AuctionEntry const* auction, Item const* item);
- void SendAuctionOwnerBidNotification(AuctionEntry const* auction, Item const* item);
+ void SendAuctionCommandResult(uint32 auctionId, AuctionCommand command, AuctionResult errorCode, Milliseconds delayForNextAction, InventoryResult bagError = InventoryResult(0));
+ void SendAuctionClosedNotification(AuctionPosting const* auction, float mailDelay, bool sold);
+ void SendAuctionOwnerBidNotification(AuctionPosting const* auction);
// Black Market
void SendBlackMarketOpenResult(ObjectGuid guid, Creature* auctioneer);
@@ -1377,14 +1387,22 @@ class TC_GAME_API WorldSession
void HandleSetTradeItemOpcode(WorldPackets::Trade::SetTradeItem& setTradeItem);
void HandleUnacceptTradeOpcode(WorldPackets::Trade::UnacceptTrade& unacceptTrade);
- void HandleAuctionHelloOpcode(WorldPackets::AuctionHouse::AuctionHelloRequest& packet);
- void HandleAuctionListItems(WorldPackets::AuctionHouse::AuctionBrowseQuery& browseQuery);
- void HandleAuctionListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItems& packet);
- void HandleAuctionSellItem(WorldPackets::AuctionHouse::AuctionSellItem& packet);
- void HandleAuctionRemoveItem(WorldPackets::AuctionHouse::AuctionRemoveItem& packet);
- void HandleAuctionListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItems& packet);
- void HandleAuctionPlaceBid(WorldPackets::AuctionHouse::AuctionPlaceBid& packet);
- void HandleReplicateItems(WorldPackets::AuctionHouse::AuctionReplicateItems& packet);
+ void HandleAuctionBrowseQuery(WorldPackets::AuctionHouse::AuctionBrowseQuery& browseQuery);
+ void HandleAuctionCancelCommoditiesPurchase(WorldPackets::AuctionHouse::AuctionCancelCommoditiesPurchase& cancelCommoditiesPurchase);
+ void HandleAuctionConfirmCommoditiesPurchase(WorldPackets::AuctionHouse::AuctionConfirmCommoditiesPurchase& confirmCommoditiesPurchase);
+ void HandleAuctionHelloOpcode(WorldPackets::AuctionHouse::AuctionHelloRequest& hello);
+ void HandleAuctionListBidderItems(WorldPackets::AuctionHouse::AuctionListBidderItems& listBidderItems);
+ void HandleAuctionListBucketsByBucketKeys(WorldPackets::AuctionHouse::AuctionListBucketsByBucketKeys& listBucketsByBucketKeys);
+ void HandleAuctionListItemsByBucketKey(WorldPackets::AuctionHouse::AuctionListItemsByBucketKey& listItemsByBucketKey);
+ void HandleAuctionListItemsByItemID(WorldPackets::AuctionHouse::AuctionListItemsByItemID& listItemsByItemID);
+ void HandleAuctionListOwnerItems(WorldPackets::AuctionHouse::AuctionListOwnerItems& listOwnerItems);
+ void HandleAuctionPlaceBid(WorldPackets::AuctionHouse::AuctionPlaceBid& placeBid);
+ void HandleAuctionRemoveItem(WorldPackets::AuctionHouse::AuctionRemoveItem& removeItem);
+ void HandleAuctionReplicateItems(WorldPackets::AuctionHouse::AuctionReplicateItems& replicateItems);
+ void HandleAuctionSellCommodity(WorldPackets::AuctionHouse::AuctionSellCommodity& sellCommodity);
+ void HandleAuctionSellItem(WorldPackets::AuctionHouse::AuctionSellItem& sellItem);
+ void HandleAuctionSetFavoriteItem(WorldPackets::AuctionHouse::AuctionSetFavoriteItem& setFavoriteItem);
+ void HandleAuctionStartCommoditiesPurchase(WorldPackets::AuctionHouse::AuctionStartCommoditiesPurchase& startCommoditiesPurchase);
// Bank
void HandleAutoBankItemOpcode(WorldPackets::Bank::AutoBankItem& packet);
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index 028f1bb7800..950a59d80fc 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -1922,10 +1922,11 @@ void World::SetInitialWorldSettings()
TC_LOG_INFO("server.loading", "Loading Completed Achievements...");
sAchievementMgr->LoadCompletedAchievements();
- ///- Load dynamic data tables from the database
- TC_LOG_INFO("server.loading", "Loading Item Auctions...");
- sAuctionMgr->LoadAuctionItems();
+ // Load before guilds and arena teams
+ TC_LOG_INFO("server.loading", "Loading character cache store...");
+ sCharacterCache->LoadCharacterCacheStorage();
+ ///- Load dynamic data tables from the database
TC_LOG_INFO("server.loading", "Loading Auctions...");
sAuctionMgr->LoadAuctions();
@@ -1938,10 +1939,6 @@ void World::SetInitialWorldSettings()
sBlackMarketMgr->LoadAuctions();
}
- // Load before guilds and arena teams
- TC_LOG_INFO("server.loading", "Loading character cache store...");
- sCharacterCache->LoadCharacterCacheStorage();
-
TC_LOG_INFO("server.loading", "Loading Guild rewards...");
sGuildMgr->LoadGuildRewards();
diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp
index 67e1fc64671..5e197e17972 100644
--- a/src/server/scripts/Commands/cs_reload.cpp
+++ b/src/server/scripts/Commands/cs_reload.cpp
@@ -1086,7 +1086,6 @@ public:
{
///- Reload dynamic data tables from the database
TC_LOG_INFO("misc", "Re-Loading Auctions...");
- sAuctionMgr->LoadAuctionItems();
sAuctionMgr->LoadAuctions();
handler->SendGlobalGMSysMessage("Auctions reloaded.");
return true;