diff options
31 files changed, 3203 insertions, 1351 deletions
diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index 5e8d4795561..c0d8d36e917 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -159,6 +159,53 @@ LOCK TABLES `arena_team_member` WRITE; UNLOCK TABLES; -- +-- Table structure for table `auction_bidders` +-- + +DROP TABLE IF EXISTS `auction_bidders`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auction_bidders` ( + `auctionId` int unsigned NOT NULL, + `playerGuid` bigint unsigned NOT NULL, + PRIMARY KEY (`auctionId`,`playerGuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `auction_bidders` +-- + +LOCK TABLES `auction_bidders` WRITE; +/*!40000 ALTER TABLE `auction_bidders` DISABLE KEYS */; +/*!40000 ALTER TABLE `auction_bidders` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `auction_items` +-- + +DROP TABLE IF EXISTS `auction_items`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auction_items` ( + `auctionId` int unsigned NOT NULL, + `itemGuid` bigint unsigned NOT NULL, + PRIMARY KEY (`auctionId`,`itemGuid`), + UNIQUE KEY `idx_itemGuid` (`itemGuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `auction_items` +-- + +LOCK TABLES `auction_items` WRITE; +/*!40000 ALTER TABLE `auction_items` DISABLE KEYS */; +/*!40000 ALTER TABLE `auction_items` ENABLE KEYS */; +UNLOCK TABLES; + +-- -- Table structure for table `auctionhouse` -- @@ -167,17 +214,16 @@ DROP TABLE IF EXISTS `auctionhouse`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `auctionhouse` ( `id` int(10) unsigned NOT NULL DEFAULT '0', - `auctioneerguid` bigint(20) unsigned NOT NULL DEFAULT '0', - `itemguid` bigint(20) unsigned NOT NULL DEFAULT '0', - `itemowner` bigint(20) unsigned NOT NULL DEFAULT '0', - `buyoutprice` bigint(20) unsigned NOT NULL DEFAULT '0', - `time` int(10) unsigned NOT NULL DEFAULT '0', - `buyguid` bigint(20) unsigned NOT NULL DEFAULT '0', - `lastbid` bigint(20) unsigned NOT NULL DEFAULT '0', - `startbid` bigint(20) unsigned NOT NULL DEFAULT '0', + `auctionHouseId` int(10) unsigned NOT NULL DEFAULT '0', + `owner` bigint(20) unsigned NOT NULL DEFAULT '0', + `bidder` bigint(20) unsigned NOT NULL DEFAULT '0', + `minBid` bigint(20) unsigned NOT NULL DEFAULT '0', + `buyoutOrUnitPrice` bigint(20) unsigned NOT NULL DEFAULT '0', `deposit` bigint(20) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - UNIQUE KEY `item_guid` (`itemguid`) + `bidAmount` bigint(20) unsigned NOT NULL DEFAULT '0', + `startTime` int(10) unsigned NOT NULL DEFAULT '0', + `endTime` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; @@ -734,6 +780,33 @@ LOCK TABLES `character_equipmentsets` WRITE; UNLOCK TABLES; -- +-- Table structure for table `character_favorite_auctions` +-- + +DROP TABLE IF EXISTS `character_favorite_auctions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `character_favorite_auctions` ( + `guid` bigint(20) unsigned NOT NULL, + `order` int(10) unsigned NOT NULL DEFAULT '0', + `itemId` int(10) unsigned NOT NULL DEFAULT '0', + `itemLevel` int(10) unsigned NOT NULL DEFAULT '0', + `battlePetSpeciesId` int(10) unsigned NOT NULL DEFAULT '0', + `suffixItemNameDescriptionId` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`,`order`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `character_favorite_auctions` +-- + +LOCK TABLES `character_favorite_auctions` WRITE; +/*!40000 ALTER TABLE `character_favorite_auctions` DISABLE KEYS */; +/*!40000 ALTER TABLE `character_favorite_auctions` ENABLE KEYS */; +UNLOCK TABLES; + +-- -- Table structure for table `character_fishingsteps` -- @@ -3696,7 +3769,8 @@ INSERT INTO `updates` VALUES ('2019_11_22_00_characters.sql','95DFA71DBD75542C098CD86E9C0051C9690902F0','RELEASED','2019-11-20 15:10:12',0), ('2019_11_30_00_characters.sql','D0678E62B651AECA60C2DD6989BF80BD999AD12B','RELEASED','2019-11-29 22:42:01',0), ('2019_12_05_00_characters.sql','EA381C9634A5646A3168F15DF4E06A708A622762','RELEASED','2019-12-05 20:56:58',0), -('2020_02_17_00_characters.sql','E1519A81D35F19B48B3C75A83A270CB4BA0B84F2','RELEASED','2020-02-17 21:55:17',0); +('2020_02_17_00_characters.sql','E1519A81D35F19B48B3C75A83A270CB4BA0B84F2','RELEASED','2020-02-17 21:55:17',0), +('2020_04_20_00_characters.sql','45F8D1AF0A345BED93BE55FF82D3823452DFF0FC','RELEASED','2020-04-20 19:08:18',0); /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/characters/master/2020_04_20_00_characters.sql b/sql/updates/characters/master/2020_04_20_00_characters.sql new file mode 100644 index 00000000000..9ab1c90ab46 --- /dev/null +++ b/sql/updates/characters/master/2020_04_20_00_characters.sql @@ -0,0 +1,46 @@ +ALTER TABLE `auctionhouse` ADD `auctionHouseId` int(10) unsigned NOT NULL DEFAULT '0' AFTER `id`; +ALTER TABLE `auctionhouse` DROP `auctioneerguid`; + +-- temporarily mark all auctions as coming from neutral AH (not goblin one) and expired +UPDATE `auctionhouse` SET `auctionHouseId`=1, `time`=UNIX_TIMESTAMP(); + +DROP TABLE IF EXISTS `auction_bidders`; +CREATE TABLE `auction_bidders` ( + `auctionId` int unsigned NOT NULL, + `playerGuid` bigint unsigned NOT NULL, + PRIMARY KEY (`auctionId`,`playerGuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `auction_bidders` SELECT `id`, `buyguid` FROM `auctionhouse`; + +DROP TABLE IF EXISTS `auction_items`; +CREATE TABLE `auction_items` ( + `auctionId` int unsigned NOT NULL, + `itemGuid` bigint unsigned NOT NULL, + PRIMARY KEY (`auctionId`,`itemGuid`), + UNIQUE KEY `idx_itemGuid` (`itemGuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `character_favorite_auctions`; +CREATE TABLE `character_favorite_auctions` ( + `guid` bigint(20) unsigned NOT NULL, + `order` int(10) unsigned NOT NULL DEFAULT '0', + `itemId` int(10) unsigned NOT NULL DEFAULT '0', + `itemLevel` int(10) unsigned NOT NULL DEFAULT '0', + `battlePetSpeciesId` int(10) unsigned NOT NULL DEFAULT '0', + `suffixItemNameDescriptionId` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`,`order`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `auction_items` SELECT `id`, `itemguid` FROM `auctionhouse`; + +ALTER TABLE `auctionhouse` DROP INDEX `item_guid`; +ALTER TABLE `auctionhouse` DROP `itemguid`; +ALTER TABLE `auctionhouse` CHANGE `itemowner` `owner` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `auctionHouseId`; +ALTER TABLE `auctionhouse` CHANGE `buyguid` `bidder` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `owner`; +ALTER TABLE `auctionhouse` CHANGE `startbid` `minBid` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `bidder`; +ALTER TABLE `auctionhouse` CHANGE `buyoutprice` `buyoutOrUnitPrice` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `bid`; +ALTER TABLE `auctionhouse` MODIFY `deposit` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `buyoutOrUnitPrice`; +ALTER TABLE `auctionhouse` CHANGE `lastbid` `bidAmount` bigint(20) unsigned NOT NULL DEFAULT '0' AFTER `deposit`; +ALTER TABLE `auctionhouse` ADD `startTime` int(10) unsigned NOT NULL DEFAULT '0' AFTER `deposit`; +ALTER TABLE `auctionhouse` CHANGE `time` `endTime` int(10) unsigned NOT NULL DEFAULT '0' AFTER `startTime`; 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; |