From db8552567d1c371a0581ea353202102bff243465 Mon Sep 17 00:00:00 2001 From: Shauren Date: Sat, 21 Mar 2026 13:41:42 +0100 Subject: [PATCH] Core/Transmog: Update transmog system to 12.0 --- sql/base/auth_database.sql | 27 +- sql/base/characters_database.sql | 88 +- .../auth/master/2026_03_21_00_auth.sql | 10 + .../master/2026_03_21_00_characters.sql | 47 ++ .../master/2026_03_21_00_hotfixes.sql | 190 +++++ .../Implementation/CharacterDatabase.cpp | 26 +- .../Implementation/CharacterDatabase.h | 13 + .../Implementation/HotfixDatabase.cpp | 39 + .../Database/Implementation/HotfixDatabase.h | 22 + .../Database/Implementation/LoginDatabase.cpp | 2 + .../Database/Implementation/LoginDatabase.h | 2 + .../game/Achievements/CriteriaHandler.cpp | 14 +- src/server/game/Chat/HyperlinkTags.cpp | 3 +- src/server/game/DataStores/DB2LoadInfo.h | 98 +++ src/server/game/DataStores/DB2Stores.cpp | 82 +- src/server/game/DataStores/DB2Stores.h | 12 +- src/server/game/DataStores/DB2Structure.h | 84 ++ src/server/game/DataStores/DBCEnums.h | 173 ++++ src/server/game/Entities/Item/Item.cpp | 60 +- .../game/Entities/Item/ItemTemplate.cpp | 46 ++ src/server/game/Entities/Item/ItemTemplate.h | 1 + .../Entities/Object/Updates/UpdateFields.cpp | 24 +- .../Entities/Object/Updates/UpdateFields.h | 4 +- .../game/Entities/Player/CollectionMgr.cpp | 106 ++- .../game/Entities/Player/CollectionMgr.h | 11 + src/server/game/Entities/Player/Player.cpp | 768 ++++++++++++++++-- src/server/game/Entities/Player/Player.h | 38 +- src/server/game/Globals/ObjectMgr.cpp | 5 +- src/server/game/Handlers/CharacterHandler.cpp | 18 +- .../Handlers/TransmogrificationHandler.cpp | 280 ++++++- .../Packets/TransmogrificationPackets.cpp | 155 ++++ .../Packets/TransmogrificationPackets.h | 128 +++ src/server/game/Server/Protocol/Opcodes.cpp | 16 +- src/server/game/Server/WorldSession.cpp | 12 +- src/server/game/Server/WorldSession.h | 8 + .../game/Spells/Auras/SpellAuraDefines.h | 2 +- .../game/Spells/Auras/SpellAuraEffects.cpp | 2 +- src/server/game/Spells/Spell.cpp | 11 +- src/server/game/Spells/Spell.h | 9 + src/server/game/Spells/SpellEffects.cpp | 29 +- src/server/game/Transmog/TransmogMgr.cpp | 416 ++++++++++ src/server/game/Transmog/TransmogMgr.h | 88 ++ src/server/game/World/World.cpp | 4 + 43 files changed, 2883 insertions(+), 290 deletions(-) create mode 100644 sql/updates/auth/master/2026_03_21_00_auth.sql create mode 100644 sql/updates/characters/master/2026_03_21_00_characters.sql create mode 100644 sql/updates/hotfixes/master/2026_03_21_00_hotfixes.sql create mode 100644 src/server/game/Transmog/TransmogMgr.cpp create mode 100644 src/server/game/Transmog/TransmogMgr.h diff --git a/sql/base/auth_database.sql b/sql/base/auth_database.sql index 87b0e15cd8..8798c3598f 100644 --- a/sql/base/auth_database.sql +++ b/sql/base/auth_database.sql @@ -459,6 +459,30 @@ LOCK TABLES `battlenet_account_transmog_illusions` WRITE; /*!40000 ALTER TABLE `battlenet_account_transmog_illusions` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Table structure for table `battlenet_account_transmog_outfits` +-- + +DROP TABLE IF EXISTS `battlenet_account_transmog_outfits`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `battlenet_account_transmog_outfits` ( + `battlenetAccountId` int unsigned NOT NULL, + `transmogOutfitId` int NOT NULL DEFAULT '0', + PRIMARY KEY (`battlenetAccountId`,`blobIndex`), + CONSTRAINT `fk_battlenet_account_transmog_outfits` FOREIGN KEY (`battlenetAccountId`) REFERENCES `battlenet_accounts` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `battlenet_account_transmog_outfits` +-- + +LOCK TABLES `battlenet_account_transmog_outfits` WRITE; +/*!40000 ALTER TABLE `battlenet_account_transmog_outfits` DISABLE KEYS */; +/*!40000 ALTER TABLE `battlenet_account_transmog_outfits` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `battlenet_account_warband_scenes` -- @@ -4147,7 +4171,8 @@ INSERT INTO `updates` VALUES ('2026_03_10_00_auth.sql','4BB4EBC719F80F7BEA667E6DA49815B358956FEA','RELEASED','2026-03-10 20:42:13',0), ('2026_03_14_00_auth.sql','4CE4AF3D9E1BF173832B25F4D1A730A93ED016FA','RELEASED','2026-03-14 12:03:14',0), ('2026_03_18_00_auth.sql','7A755DADF9AAC9016FD01B26ACEC714FAB5E3269','RELEASED','2026-03-18 12:36:48',0), -('2026_03_20_00_auth.sql','9083386953291DA3D0634E90D40863F7625FF37E','RELEASED','2026-03-20 01:11:00',0); +('2026_03_20_00_auth.sql','9083386953291DA3D0634E90D40863F7625FF37E','RELEASED','2026-03-20 01:11:00',0), +('2026_03_21_00_auth.sql','6D47EFD7FBCD159F5BC5B5A6020E61824280E92A','RELEASED','2026-03-21 00:08:26',0); /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index 6691f0aca1..9c127807f8 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -1891,6 +1891,89 @@ LOCK TABLES `character_trait_entry` WRITE; /*!40000 ALTER TABLE `character_trait_entry` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Table structure for table `character_transmog_outfit` +-- + +DROP TABLE IF EXISTS `character_transmog_outfit`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `character_transmog_outfit` ( + `guid` bigint NOT NULL, + `transmogOutfitId` int NOT NULL, + `name` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL, + `icon` int NOT NULL, + `situationsEnabled` tinyint(1) NOT NULL, + PRIMARY KEY (`guid`,`transmogOutfitId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `character_transmog_outfit` +-- + +LOCK TABLES `character_transmog_outfit` WRITE; +/*!40000 ALTER TABLE `character_transmog_outfit` DISABLE KEYS */; +/*!40000 ALTER TABLE `character_transmog_outfit` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `character_transmog_outfit_situation` +-- + +DROP TABLE IF EXISTS `character_transmog_outfit_situation`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `character_transmog_outfit_situation` ( + `guid` bigint NOT NULL, + `transmogOutfitId` int NOT NULL, + `situationID` int NOT NULL, + `specID` int NOT NULL, + `loadoutID` int NOT NULL, + `equipmentSetID` int NOT NULL, + PRIMARY KEY (`guid`,`transmogOutfitId`,`situationID`,`specID`,`loadoutID`,`equipmentSetID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `character_transmog_outfit_situation` +-- + +LOCK TABLES `character_transmog_outfit_situation` WRITE; +/*!40000 ALTER TABLE `character_transmog_outfit_situation` DISABLE KEYS */; +/*!40000 ALTER TABLE `character_transmog_outfit_situation` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `character_transmog_outfit_slot` +-- + +DROP TABLE IF EXISTS `character_transmog_outfit_slot`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `character_transmog_outfit_slot` ( + `guid` bigint NOT NULL, + `transmogOutfitId` int NOT NULL, + `slot` tinyint NOT NULL, + `slotOption` tinyint NOT NULL, + `itemModifiedAppearanceID` int NOT NULL, + `appearanceDisplayType` tinyint NOT NULL, + `spellItemEnchantmentID` int NOT NULL, + `illusionDisplayType` tinyint NOT NULL, + `flags` int NOT NULL, + PRIMARY KEY (`guid`,`transmogOutfitId`,`slot`,`slotOption`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `character_transmog_outfit_slot` +-- + +LOCK TABLES `character_transmog_outfit_slot` WRITE; +/*!40000 ALTER TABLE `character_transmog_outfit_slot` DISABLE KEYS */; +/*!40000 ALTER TABLE `character_transmog_outfit_slot` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `character_transmog_outfits` -- @@ -2043,6 +2126,8 @@ CREATE TABLE `characters` ( `personalTabardBorderStyle` int NOT NULL DEFAULT '-1', `personalTabardBorderColor` int NOT NULL DEFAULT '-1', `personalTabardBackgroundColor` int NOT NULL DEFAULT '-1', + `transmogOutfitEquippedId` int NOT NULL DEFAULT '2', + `transmogOutfitLocked` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`guid`), UNIQUE KEY `idx_name` (`name`), KEY `idx_account` (`account`), @@ -3832,7 +3917,8 @@ INSERT INTO `updates` VALUES ('2025_11_25_00_characters.sql','A0C04B2404B1832421402F78436DDC4AA18EBAD8','ARCHIVED','2025-11-25 22:28:32',0), ('2026_01_14_00_characters.sql','FF5D889A41BBD9F9827489DEC08BCA9DB457933E','ARCHIVED','2026-01-14 23:40:33',0), ('2026_01_28_00_characters.sql','807B6622970E81089806C3B45C6C7A32EF531BCA','ARCHIVED','2026-01-25 21:53:51',0), -('2026_02_06_00_characters.sql','90735F4481A137E79B8371F291008CF6051657AC','ARCHIVED','2026-02-06 12:45:48',0); +('2026_02_06_00_characters.sql','90735F4481A137E79B8371F291008CF6051657AC','ARCHIVED','2026-02-06 12:45:48',0), +('2026_03_21_00_characters.sql','87963F4E341B195D4B4C4514A3119092DF127431','RELEASED','2026-03-21 00:42:13',0); /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/auth/master/2026_03_21_00_auth.sql b/sql/updates/auth/master/2026_03_21_00_auth.sql new file mode 100644 index 0000000000..3d9874bdc3 --- /dev/null +++ b/sql/updates/auth/master/2026_03_21_00_auth.sql @@ -0,0 +1,10 @@ +-- +-- Table structure for table `battlenet_account_transmog_outfits` +-- +DROP TABLE IF EXISTS `battlenet_account_transmog_outfits`; +CREATE TABLE `battlenet_account_transmog_outfits` ( + `battlenetAccountId` int unsigned NOT NULL, + `transmogOutfitId` int NOT NULL DEFAULT '0', + PRIMARY KEY (`battlenetAccountId`,`transmogOutfitId`), + CONSTRAINT `fk_battlenet_account_transmog_outfits` FOREIGN KEY (`battlenetAccountId`) REFERENCES `battlenet_accounts` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/sql/updates/characters/master/2026_03_21_00_characters.sql b/sql/updates/characters/master/2026_03_21_00_characters.sql new file mode 100644 index 0000000000..865bea9218 --- /dev/null +++ b/sql/updates/characters/master/2026_03_21_00_characters.sql @@ -0,0 +1,47 @@ +ALTER TABLE `characters` + ADD `transmogOutfitEquippedId` int NOT NULL DEFAULT 2 AFTER `personalTabardBackgroundColor`, + ADD `transmogOutfitLocked` tinyint(1) NOT NULL DEFAULT 0 AFTER `transmogOutfitEquippedId`; + +-- +-- Table structure for table `character_transmog_outfit` +-- +DROP TABLE IF EXISTS `character_transmog_outfit`; +CREATE TABLE `character_transmog_outfit` ( + `guid` bigint NOT NULL, + `transmogOutfitId` int NOT NULL, + `name` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL, + `icon` int NOT NULL, + `situationsEnabled` tinyint(1) NOT NULL, + PRIMARY KEY (`guid`,`transmogOutfitId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `character_transmog_outfit_situation` +-- +DROP TABLE IF EXISTS `character_transmog_outfit_situation`; +CREATE TABLE `character_transmog_outfit_situation` ( + `guid` bigint NOT NULL, + `transmogOutfitId` int NOT NULL, + `situationID` int NOT NULL, + `specID` int NOT NULL, + `loadoutID` int NOT NULL, + `equipmentSetID` int NOT NULL, + PRIMARY KEY (`guid`,`transmogOutfitId`,`situationID`,`specID`,`loadoutID`,`equipmentSetID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `character_transmog_outfit_slot` +-- +DROP TABLE IF EXISTS `character_transmog_outfit_slot`; +CREATE TABLE `character_transmog_outfit_slot` ( + `guid` bigint NOT NULL, + `transmogOutfitId` int NOT NULL, + `slot` tinyint NOT NULL, + `slotOption` tinyint NOT NULL, + `itemModifiedAppearanceID` int NOT NULL, + `appearanceDisplayType` tinyint NOT NULL, + `spellItemEnchantmentID` int NOT NULL, + `illusionDisplayType` tinyint NOT NULL, + `flags` int NOT NULL, + PRIMARY KEY (`guid`,`transmogOutfitId`,`slot`,`slotOption`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/sql/updates/hotfixes/master/2026_03_21_00_hotfixes.sql b/sql/updates/hotfixes/master/2026_03_21_00_hotfixes.sql new file mode 100644 index 0000000000..a66c041162 --- /dev/null +++ b/sql/updates/hotfixes/master/2026_03_21_00_hotfixes.sql @@ -0,0 +1,190 @@ +-- +-- Table structure for table `transmog_outfit_entry` +-- +DROP TABLE IF EXISTS `transmog_outfit_entry`; +CREATE TABLE `transmog_outfit_entry` ( + `Cost` bigint unsigned NOT NULL DEFAULT '0', + `Name` text, + `ID` int unsigned NOT NULL DEFAULT '0', + `OrderIndex` int NOT NULL DEFAULT '0', + `Source` tinyint unsigned NOT NULL DEFAULT '0', + `Flags` int NOT NULL DEFAULT '0', + `SetType` tinyint unsigned NOT NULL DEFAULT '0', + `OverrideCostModifier` float NOT NULL DEFAULT '0', + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `transmog_outfit_entry_locale` +-- +DROP TABLE IF EXISTS `transmog_outfit_entry_locale`; +CREATE TABLE `transmog_outfit_entry_locale` ( + `ID` int unsigned NOT NULL DEFAULT '0', + `locale` varchar(4) NOT NULL, + `Name_lang` text, + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci +PARTITION BY LIST COLUMNS(locale) +(PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB, + PARTITION esES VALUES IN ('esES') ENGINE = InnoDB, + PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB, + PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB, + PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB, + PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB, + PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB, + PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB, + PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB, + PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB); + +-- +-- Table structure for table `transmog_outfit_slot_info` +-- +DROP TABLE IF EXISTS `transmog_outfit_slot_info`; +CREATE TABLE `transmog_outfit_slot_info` ( + `InventorySlotName` text, + `ID` int unsigned NOT NULL DEFAULT '0', + `TransmogOutfitSlotEnum` tinyint NOT NULL DEFAULT '0', + `InventorySlotEnum` int NOT NULL DEFAULT '0', + `Flags` int NOT NULL DEFAULT '0', + `Unused1200` tinyint unsigned NOT NULL DEFAULT '0', + `TransmogCollectionType` tinyint unsigned NOT NULL DEFAULT '0', + `SecondarySlotID` int NOT NULL DEFAULT '0', + `InventorySlotID` int NOT NULL DEFAULT '0', + `UnassignedAtlasID` int NOT NULL DEFAULT '0', + `UnassignedDisplayAtlasID` int NOT NULL DEFAULT '0', + `ItemCostMultiplier` float NOT NULL DEFAULT '0', + `IllusionCostMultiplier` float NOT NULL DEFAULT '0', + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `transmog_outfit_slot_option` +-- +DROP TABLE IF EXISTS `transmog_outfit_slot_option`; +CREATE TABLE `transmog_outfit_slot_option` ( + `ID` int unsigned NOT NULL DEFAULT '0', + `Name` text, + `OptionEnum` tinyint unsigned NOT NULL DEFAULT '0', + `TransmogOutfitSlotInfoID` int unsigned NOT NULL DEFAULT '0', + `Flags` int NOT NULL DEFAULT '0', + `SecondaryOptionID` int NOT NULL DEFAULT '0', + `ItemCostMultiplier` float NOT NULL DEFAULT '0', + `IllusionCostMultiplier` float NOT NULL DEFAULT '0', + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `transmog_outfit_slot_option_locale` +-- +DROP TABLE IF EXISTS `transmog_outfit_slot_option_locale`; +CREATE TABLE `transmog_outfit_slot_option_locale` ( + `ID` int unsigned NOT NULL DEFAULT '0', + `locale` varchar(4) NOT NULL, + `Name_lang` text, + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci +PARTITION BY LIST COLUMNS(locale) +(PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB, + PARTITION esES VALUES IN ('esES') ENGINE = InnoDB, + PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB, + PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB, + PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB, + PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB, + PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB, + PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB, + PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB, + PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB); + +-- +-- Table structure for table `transmog_situation` +-- +DROP TABLE IF EXISTS `transmog_situation`; +CREATE TABLE `transmog_situation` ( + `Name` text, + `ID` int unsigned NOT NULL DEFAULT '0', + `SituationEnum` tinyint NOT NULL DEFAULT '0', + `Flags` int NOT NULL DEFAULT '0', + `TransmogSituationGroupID` int unsigned NOT NULL DEFAULT '0', + `OrderIndex` int NOT NULL DEFAULT '0', + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `transmog_situation_locale` +-- +DROP TABLE IF EXISTS `transmog_situation_locale`; +CREATE TABLE `transmog_situation_locale` ( + `ID` int unsigned NOT NULL DEFAULT '0', + `locale` varchar(4) NOT NULL, + `Name_lang` text, + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci +PARTITION BY LIST COLUMNS(locale) +(PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB, + PARTITION esES VALUES IN ('esES') ENGINE = InnoDB, + PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB, + PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB, + PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB, + PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB, + PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB, + PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB, + PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB, + PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB); + +-- +-- Table structure for table `transmog_situation_group` +-- +DROP TABLE IF EXISTS `transmog_situation_group`; +CREATE TABLE `transmog_situation_group` ( + `ID` int unsigned NOT NULL DEFAULT '0', + `TransmogSituationTriggerID` int unsigned NOT NULL DEFAULT '0', + `OrderIndex` int NOT NULL DEFAULT '0', + `Flags` int NOT NULL DEFAULT '0', + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `transmog_situation_trigger` +-- +DROP TABLE IF EXISTS `transmog_situation_trigger`; +CREATE TABLE `transmog_situation_trigger` ( + `Name` text, + `Description` text, + `ID` int unsigned NOT NULL DEFAULT '0', + `TriggerEnum` tinyint unsigned NOT NULL DEFAULT '0', + `Flags` int NOT NULL DEFAULT '0', + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `transmog_situation_trigger_locale` +-- +DROP TABLE IF EXISTS `transmog_situation_trigger_locale`; +CREATE TABLE `transmog_situation_trigger_locale` ( + `ID` int unsigned NOT NULL DEFAULT '0', + `locale` varchar(4) NOT NULL, + `Name_lang` text, + `Description_lang` text, + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci +PARTITION BY LIST COLUMNS(locale) +(PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB, + PARTITION esES VALUES IN ('esES') ENGINE = InnoDB, + PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB, + PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB, + PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB, + PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB, + PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB, + PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB, + PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB, + PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 04773aede6..def6ab00b4 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -99,7 +99,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() "totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, " "health, power1, power2, power3, power4, power5, power6, power7, power8, power9, power10, instance_id, activeTalentGroup, lootSpecId, exploredZones, knownTitles, actionBars, " "raidDifficulty, legacyRaidDifficulty, fishingSteps, honor, honorLevel, honorRestState, honorRestBonus, numRespecs, " - "personalTabardEmblemStyle, personalTabardEmblemColor, personalTabardBorderStyle, personalTabardBorderColor, personalTabardBackgroundColor " + "personalTabardEmblemStyle, personalTabardEmblemColor, personalTabardBorderStyle, personalTabardBorderColor, personalTabardBackgroundColor, transmogOutfitEquippedId, transmogOutfitLocked " "FROM characters c LEFT JOIN character_fishingsteps cfs ON c.guid = cfs.guid WHERE c.guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_CUSTOMIZATIONS, "SELECT chrCustomizationOptionID, chrCustomizationChoiceID FROM character_customizations WHERE guid = ? ORDER BY chrCustomizationOptionID", CONNECTION_ASYNC); @@ -150,6 +150,9 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_CHARACTER_TRANSMOG_OUTFITS, "SELECT setguid, setindex, name, iconname, ignore_mask, appearance0, appearance1, appearance2, appearance3, appearance4, " "appearance5, appearance6, appearance7, appearance8, appearance9, appearance10, appearance11, appearance12, appearance13, appearance14, appearance15, appearance16, " "appearance17, appearance18, mainHandEnchant, offHandEnchant FROM character_transmog_outfits WHERE guid = ? ORDER BY setindex", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_TRANSMOG_OUTFIT, "SELECT transmogOutfitId, name, icon, situationsEnabled FROM character_transmog_outfit WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_TRANSMOG_OUTFIT_SITUATION, "SELECT transmogOutfitId, situationID, specID, loadoutID, equipmentSetID FROM character_transmog_outfit_situation WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_TRANSMOG_OUTFIT_SLOT, "SELECT transmogOutfitId, slot, slotOption, itemModifiedAppearanceID, appearanceDisplayType, spellItemEnchantmentID, illusionDisplayType, flags FROM character_transmog_outfit_slot WHERE guid = ? ORDER BY transmogOutfitId, slot, slotOption", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_BGDATA, "SELECT instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell, queueId FROM character_battleground_data WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_GLYPHS, "SELECT talentGroup, glyphId FROM character_glyphs WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_TALENTS, "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?", CONNECTION_ASYNC); @@ -360,6 +363,12 @@ void CharacterDatabaseConnection::DoPrepareStatements() "appearance16, appearance17, appearance18, mainHandEnchant, offHandEnchant) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_TRANSMOG_OUTFIT, "DELETE FROM character_transmog_outfits WHERE setguid=?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_TRANSMOG_OUTFIT_2, "INSERT INTO character_transmog_outfit (guid, transmogOutfitId, name, icon, situationsEnabled) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_TRANSMOG_OUTFIT_2, "DELETE FROM character_transmog_outfit WHERE guid = ? AND transmogOutfitId = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_TRANSMOG_OUTFIT_SITUATION, "INSERT INTO character_transmog_outfit_situation (guid, transmogOutfitId, situationID, specID, loadoutID, equipmentSetID) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_TRANSMOG_OUTFIT_SITUATION, "DELETE FROM character_transmog_outfit_situation WHERE guid = ? AND transmogOutfitId = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_TRANSMOG_OUTFIT_SLOT, "INSERT INTO character_transmog_outfit_slot (guid, transmogOutfitId, slot, slotOption, itemModifiedAppearanceID, appearanceDisplayType, spellItemEnchantmentID, illusionDisplayType, flags) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_TRANSMOG_OUTFIT_SLOT, "DELETE FROM character_transmog_outfit_slot WHERE guid = ? AND transmogOutfitId = ?", CONNECTION_ASYNC); // Auras PrepareStatement(CHAR_INS_AURA, "INSERT INTO character_aura (guid, casterGuid, itemGuid, spell, effectMask, recalculateMask, difficulty, stackCount, maxDuration, remainTime, remainCharges, castItemId, castItemLevel) " @@ -481,17 +490,19 @@ void CharacterDatabaseConnection::DoPrepareStatements() "taximask, createTime, createMode, cinematic, " "totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, primarySpecialization, " "extra_flags, summonedPetNumber, at_login, " - "death_expire_time, taxi_path, totalKills, " - "todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, health, power1, power2, power3, " - "power4, power5, power6, power7, power8, power9, power10, latency, activeTalentGroup, lootSpecId, exploredZones, equipmentCache, knownTitles, actionBars, lastLoginBuild) VALUES " - "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", CONNECTION_ASYNC); + "death_expire_time, taxi_path, totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, health, " + "power1, power2, power3, power4, power5, power6, power7, power8, power9, power10, " + "latency, activeTalentGroup, lootSpecId, exploredZones, equipmentCache, knownTitles, actionBars, lastLoginBuild, " + "personalTabardEmblemStyle, personalTabardEmblemColor, personalTabardBorderStyle, personalTabardBorderColor, personalTabardBackgroundColor, transmogOutfitEquippedId, transmogOutfitLocked) VALUES " + "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHARACTER, "UPDATE characters SET name=?,race=?,class=?,gender=?,level=?,xp=?,money=?,inventorySlots=?,inventoryBagFlags=?,bagSlotFlags1=?,bagSlotFlags2=?,bagSlotFlags3=?,bagSlotFlags4=?,bagSlotFlags5=?," "bankSlots=?,bankTabs=?,bankBagFlags=?,restState=?,playerFlags=?,playerFlagsEx=?," "map=?,instance_id=?,dungeonDifficulty=?,raidDifficulty=?,legacyRaidDifficulty=?,position_x=?,position_y=?,position_z=?,orientation=?,trans_x=?,trans_y=?,trans_z=?,trans_o=?,transguid=?,taximask=?,cinematic=?,totaltime=?,leveltime=?,rest_bonus=?," "logout_time=?,is_logout_resting=?,resettalents_cost=?,resettalents_time=?,numRespecs=?,primarySpecialization=?,extra_flags=?,summonedPetNumber=?,at_login=?,zone=?,death_expire_time=?,taxi_path=?," "totalKills=?,todayKills=?,yesterdayKills=?,chosenTitle=?," "watchedFaction=?,drunk=?,health=?,power1=?,power2=?,power3=?,power4=?,power5=?,power6=?,power7=?,power8=?,power9=?,power10=?,latency=?,activeTalentGroup=?,lootSpecId=?,exploredZones=?," - "equipmentCache=?,knownTitles=?,actionBars=?,online=?,honor=?,honorLevel=?,honorRestState=?,honorRestBonus=?,lastLoginBuild=? WHERE guid=?", CONNECTION_ASYNC); + "equipmentCache=?,knownTitles=?,actionBars=?,online=?,honor=?,honorLevel=?,honorRestState=?,honorRestBonus=?,lastLoginBuild=?," + "personalTabardEmblemStyle=?,personalTabardEmblemColor=?,personalTabardBorderStyle=?,personalTabardBorderColor=?,personalTabardBackgroundColor=?,transmogOutfitEquippedId=?,transmogOutfitLocked=? WHERE guid=?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG, "UPDATE characters SET at_login = at_login | ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_REM_AT_LOGIN_FLAG, "UPDATE characters set at_login = at_login & ~ ? WHERE guid = ?", CONNECTION_ASYNC); @@ -626,6 +637,9 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_CHAR_ACHIEVEMENTS, "DELETE FROM character_achievement WHERE guid = ? AND achievement NOT IN (456,457,458,459,460,461,462,463,464,465,466,467,1400,1402,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1463,3117,3259,4078,4576,4998,4999,5000,5001,5002,5003,5004,5005,5006,5007,5008,5381,5382,5383,5384,5385,5386,5387,5388,5389,5390,5391,5392,5393,5394,5395,5396,6433,6523,6524,6743,6744,6745,6746,6747,6748,6749,6750,6751,6752,6829,6859,6860,6861,6862,6863,6864,6865,6866,6867,6868,6869,6870,6871,6872,6873)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_EQUIPMENTSETS, "DELETE FROM character_equipmentsets WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_TRANSMOG_OUTFITS, "DELETE FROM character_transmog_outfits WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_TRANSMOG_OUTFIT_BY_CHAR, "DELETE FROM character_transmog_outfit WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_TRANSMOG_OUTFIT_SITUATION_BY_CHAR, "DELETE FROM character_transmog_outfit_slot WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_TRANSMOG_OUTFIT_SLOT_BY_CHAR, "DELETE FROM character_transmog_outfit_situation WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_GUILD_EVENTLOG_BY_PLAYER, "DELETE FROM guild_eventlog WHERE PlayerGuid1 = ? OR PlayerGuid2 = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER, "DELETE FROM guild_bank_eventlog WHERE PlayerGuid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_GLYPHS, "DELETE FROM character_glyphs WHERE guid = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 33205a47d3..4838f2c85f 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -107,6 +107,9 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_CHARACTER_CRITERIAPROGRESS, CHAR_SEL_CHARACTER_EQUIPMENTSETS, CHAR_SEL_CHARACTER_TRANSMOG_OUTFITS, + CHAR_SEL_CHARACTER_TRANSMOG_OUTFIT, + CHAR_SEL_CHARACTER_TRANSMOG_OUTFIT_SITUATION, + CHAR_SEL_CHARACTER_TRANSMOG_OUTFIT_SLOT, CHAR_SEL_CHARACTER_BGDATA, CHAR_SEL_CHARACTER_GLYPHS, CHAR_SEL_CHARACTER_TALENTS, @@ -270,6 +273,13 @@ enum CharacterDatabaseStatements : uint32 CHAR_INS_TRANSMOG_OUTFIT, CHAR_DEL_TRANSMOG_OUTFIT, + CHAR_INS_TRANSMOG_OUTFIT_2, + CHAR_DEL_TRANSMOG_OUTFIT_2, + CHAR_INS_TRANSMOG_OUTFIT_SITUATION, + CHAR_DEL_TRANSMOG_OUTFIT_SITUATION, + CHAR_INS_TRANSMOG_OUTFIT_SLOT, + CHAR_DEL_TRANSMOG_OUTFIT_SLOT, + CHAR_INS_AURA, CHAR_INS_AURA_EFFECT, @@ -496,6 +506,9 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_CHAR_ACHIEVEMENTS, CHAR_DEL_CHAR_EQUIPMENTSETS, CHAR_DEL_CHAR_TRANSMOG_OUTFITS, + CHAR_DEL_CHAR_TRANSMOG_OUTFIT_BY_CHAR, + CHAR_DEL_CHAR_TRANSMOG_OUTFIT_SITUATION_BY_CHAR, + CHAR_DEL_CHAR_TRANSMOG_OUTFIT_SLOT_BY_CHAR, CHAR_DEL_GUILD_EVENTLOG_BY_PLAYER, CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER, CHAR_DEL_CHAR_GLYPHS, diff --git a/src/server/database/Database/Implementation/HotfixDatabase.cpp b/src/server/database/Database/Implementation/HotfixDatabase.cpp index c6b7c5e9fc..eafaf95c7b 100644 --- a/src/server/database/Database/Implementation/HotfixDatabase.cpp +++ b/src/server/database/Database/Implementation/HotfixDatabase.cpp @@ -1997,6 +1997,26 @@ void HotfixDatabaseConnection::DoPrepareStatements() " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRANSMOG_ILLUSION, "SELECT MAX(ID) + 1 FROM transmog_illusion", CONNECTION_SYNCH); + // TransmogOutfitEntry.db2 + PrepareStatement(HOTFIX_SEL_TRANSMOG_OUTFIT_ENTRY, "SELECT Cost, Name, ID, OrderIndex, Source, Flags, SetType, OverrideCostModifier" + " FROM transmog_outfit_entry WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRANSMOG_OUTFIT_ENTRY, "SELECT MAX(ID) + 1 FROM transmog_outfit_entry", CONNECTION_SYNCH); + PREPARE_LOCALE_STMT(HOTFIX_SEL_TRANSMOG_OUTFIT_ENTRY, "SELECT ID, Name_lang FROM transmog_outfit_entry_locale WHERE (`VerifiedBuild` > 0) = ?" + " AND locale = ?", CONNECTION_SYNCH); + + // TransmogOutfitSlotInfo.db2 + PrepareStatement(HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_INFO, "SELECT InventorySlotName, ID, TransmogOutfitSlotEnum, InventorySlotEnum, Flags, " + "Unused1200, TransmogCollectionType, SecondarySlotID, InventorySlotID, UnassignedAtlasID, UnassignedDisplayAtlasID, ItemCostMultiplier, " + "IllusionCostMultiplier FROM transmog_outfit_slot_info WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_INFO, "SELECT MAX(ID) + 1 FROM transmog_outfit_slot_info", CONNECTION_SYNCH); + + // TransmogOutfitSlotOption.db2 + PrepareStatement(HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_OPTION, "SELECT ID, Name, OptionEnum, TransmogOutfitSlotInfoID, Flags, SecondaryOptionID, " + "ItemCostMultiplier, IllusionCostMultiplier FROM transmog_outfit_slot_option WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_OPTION, "SELECT MAX(ID) + 1 FROM transmog_outfit_slot_option", CONNECTION_SYNCH); + PREPARE_LOCALE_STMT(HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_OPTION, "SELECT ID, Name_lang FROM transmog_outfit_slot_option_locale" + " WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH); + // TransmogSet.db2 PrepareStatement(HOTFIX_SEL_TRANSMOG_SET, "SELECT Name, ID, ClassMask, TrackingQuestID, Flags, TransmogSetGroupID, ItemNameDescriptionID, " "ParentTransmogSetID, Unknown810, ExpansionID, PatchID, UiOrder, PlayerConditionID FROM transmog_set WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); @@ -2014,6 +2034,25 @@ void HotfixDatabaseConnection::DoPrepareStatements() " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRANSMOG_SET_ITEM, "SELECT MAX(ID) + 1 FROM transmog_set_item", CONNECTION_SYNCH); + // TransmogSituation.db2 + PrepareStatement(HOTFIX_SEL_TRANSMOG_SITUATION, "SELECT Name, ID, SituationEnum, Flags, TransmogSituationGroupID, OrderIndex" + " FROM transmog_situation WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRANSMOG_SITUATION, "SELECT MAX(ID) + 1 FROM transmog_situation", CONNECTION_SYNCH); + PREPARE_LOCALE_STMT(HOTFIX_SEL_TRANSMOG_SITUATION, "SELECT ID, Name_lang FROM transmog_situation_locale WHERE (`VerifiedBuild` > 0) = ?" + " AND locale = ?", CONNECTION_SYNCH); + + // TransmogSituationGroup.db2 + PrepareStatement(HOTFIX_SEL_TRANSMOG_SITUATION_GROUP, "SELECT ID, TransmogSituationTriggerID, OrderIndex, Flags FROM transmog_situation_group" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRANSMOG_SITUATION_GROUP, "SELECT MAX(ID) + 1 FROM transmog_situation_group", CONNECTION_SYNCH); + + // TransmogSituationTrigger.db2 + PrepareStatement(HOTFIX_SEL_TRANSMOG_SITUATION_TRIGGER, "SELECT Name, Description, ID, TriggerEnum, Flags FROM transmog_situation_trigger" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRANSMOG_SITUATION_TRIGGER, "SELECT MAX(ID) + 1 FROM transmog_situation_trigger", CONNECTION_SYNCH); + PREPARE_LOCALE_STMT(HOTFIX_SEL_TRANSMOG_SITUATION_TRIGGER, "SELECT ID, Name_lang, Description_lang FROM transmog_situation_trigger_locale" + " WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH); + // TransportAnimation.db2 PrepareStatement(HOTFIX_SEL_TRANSPORT_ANIMATION, "SELECT ID, PosX, PosY, PosZ, SequenceID, TimeIndex, TransportID FROM transport_animation" " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/HotfixDatabase.h b/src/server/database/Database/Implementation/HotfixDatabase.h index 078c45c64d..d3a50924b5 100644 --- a/src/server/database/Database/Implementation/HotfixDatabase.h +++ b/src/server/database/Database/Implementation/HotfixDatabase.h @@ -1155,6 +1155,17 @@ enum HotfixDatabaseStatements : uint32 HOTFIX_SEL_TRANSMOG_ILLUSION, HOTFIX_SEL_TRANSMOG_ILLUSION_MAX_ID, + HOTFIX_SEL_TRANSMOG_OUTFIT_ENTRY, + HOTFIX_SEL_TRANSMOG_OUTFIT_ENTRY_MAX_ID, + HOTFIX_SEL_TRANSMOG_OUTFIT_ENTRY_LOCALE, + + HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_INFO, + HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_INFO_MAX_ID, + + HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_OPTION, + HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_OPTION_MAX_ID, + HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_OPTION_LOCALE, + HOTFIX_SEL_TRANSMOG_SET, HOTFIX_SEL_TRANSMOG_SET_MAX_ID, HOTFIX_SEL_TRANSMOG_SET_LOCALE, @@ -1166,6 +1177,17 @@ enum HotfixDatabaseStatements : uint32 HOTFIX_SEL_TRANSMOG_SET_ITEM, HOTFIX_SEL_TRANSMOG_SET_ITEM_MAX_ID, + HOTFIX_SEL_TRANSMOG_SITUATION, + HOTFIX_SEL_TRANSMOG_SITUATION_MAX_ID, + HOTFIX_SEL_TRANSMOG_SITUATION_LOCALE, + + HOTFIX_SEL_TRANSMOG_SITUATION_GROUP, + HOTFIX_SEL_TRANSMOG_SITUATION_GROUP_MAX_ID, + + HOTFIX_SEL_TRANSMOG_SITUATION_TRIGGER, + HOTFIX_SEL_TRANSMOG_SITUATION_TRIGGER_MAX_ID, + HOTFIX_SEL_TRANSMOG_SITUATION_TRIGGER_LOCALE, + HOTFIX_SEL_TRANSPORT_ANIMATION, HOTFIX_SEL_TRANSPORT_ANIMATION_MAX_ID, diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index 820e828003..c11ba3085e 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -191,6 +191,8 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_SEL_BNET_TRANSMOG_ILLUSIONS, "SELECT blobIndex, illusionMask FROM battlenet_account_transmog_illusions WHERE battlenetAccountId = ? ORDER BY blobIndex DESC", CONNECTION_ASYNC); PrepareStatement(LOGIN_INS_BNET_TRANSMOG_ILLUSIONS, "INSERT INTO battlenet_account_transmog_illusions (battlenetAccountId, blobIndex, illusionMask) VALUES (?, ?, ?) " "ON DUPLICATE KEY UPDATE illusionMask = illusionMask | VALUES(illusionMask)", CONNECTION_ASYNC); + PrepareStatement(LOGIN_SEL_BNET_TRANSMOG_OUTFITS, "SELECT transmogOutfitId FROM battlenet_account_transmog_outfits WHERE battlenetAccountId = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_INS_BNET_TRANSMOG_OUTFITS, "INSERT IGNORE INTO battlenet_account_transmog_outfits (battlenetAccountId, transmogOutfitId) VALUES (?, ?)", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_BNET_WARBAND_SCENES, "SELECT warbandSceneId, isFavorite, hasFanfare FROM battlenet_account_warband_scenes WHERE battlenetAccountId = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_INS_BNET_WARBAND_SCENE, "INSERT INTO battlenet_account_warband_scenes (battlenetAccountId, warbandSceneId, isFavorite, hasFanfare) VALUES (?, ?, ?, ?) " "ON DUPLICATE KEY UPDATE isFavorite = VALUES(isFavorite)", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index 811931fd75..53d1d7a769 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.h +++ b/src/server/database/Database/Implementation/LoginDatabase.h @@ -176,6 +176,8 @@ enum LoginDatabaseStatements : uint32 LOGIN_DEL_BNET_ITEM_FAVORITE_APPEARANCE, LOGIN_SEL_BNET_TRANSMOG_ILLUSIONS, LOGIN_INS_BNET_TRANSMOG_ILLUSIONS, + LOGIN_SEL_BNET_TRANSMOG_OUTFITS, + LOGIN_INS_BNET_TRANSMOG_OUTFITS, LOGIN_SEL_BNET_WARBAND_SCENES, LOGIN_INS_BNET_WARBAND_SCENE, LOGIN_UPD_BNET_WARBAND_SCENE, diff --git a/src/server/game/Achievements/CriteriaHandler.cpp b/src/server/game/Achievements/CriteriaHandler.cpp index 0a49db74fd..54b0f0e5d5 100644 --- a/src/server/game/Achievements/CriteriaHandler.cpp +++ b/src/server/game/Achievements/CriteriaHandler.cpp @@ -3227,10 +3227,9 @@ bool CriteriaHandler::ModifierSatisfied(ModifierTreeEntry const* modifier, uint6 { itemSubclass = itemTemplate->GetSubClass(); - if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = sDB2Manager.GetItemModifiedAppearance(visibleItem.ItemID, visibleItem.ItemAppearanceModID)) - if (ItemModifiedAppearanceExtraEntry const* itemModifiedAppearaceExtra = sItemModifiedAppearanceExtraStore.LookupEntry(itemModifiedAppearance->ID)) - if (itemModifiedAppearaceExtra->DisplayWeaponSubclassID > 0) - itemSubclass = itemModifiedAppearaceExtra->DisplayWeaponSubclassID; + if (ItemModifiedAppearanceExtraEntry const* itemModifiedAppearaceExtra = sItemModifiedAppearanceExtraStore.LookupEntry(visibleItem.ItemModifiedAppearanceID)) + if (itemModifiedAppearaceExtra->DisplayWeaponSubclassID > 0) + itemSubclass = itemModifiedAppearaceExtra->DisplayWeaponSubclassID; } } if (itemSubclass != reqValue) @@ -3247,10 +3246,9 @@ bool CriteriaHandler::ModifierSatisfied(ModifierTreeEntry const* modifier, uint6 { itemSubclass = itemTemplate->GetSubClass(); - if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = sDB2Manager.GetItemModifiedAppearance(visibleItem.ItemID, visibleItem.ItemAppearanceModID)) - if (ItemModifiedAppearanceExtraEntry const* itemModifiedAppearaceExtra = sItemModifiedAppearanceExtraStore.LookupEntry(itemModifiedAppearance->ID)) - if (itemModifiedAppearaceExtra->DisplayWeaponSubclassID > 0) - itemSubclass = itemModifiedAppearaceExtra->DisplayWeaponSubclassID; + if (ItemModifiedAppearanceExtraEntry const* itemModifiedAppearaceExtra = sItemModifiedAppearanceExtraStore.LookupEntry(visibleItem.ItemModifiedAppearanceID)) + if (itemModifiedAppearaceExtra->DisplayWeaponSubclassID > 0) + itemSubclass = itemModifiedAppearaceExtra->DisplayWeaponSubclassID; } } if (itemSubclass != reqValue) diff --git a/src/server/game/Chat/HyperlinkTags.cpp b/src/server/game/Chat/HyperlinkTags.cpp index 6d7c41fa85..c4fa8c9296 100644 --- a/src/server/game/Chat/HyperlinkTags.cpp +++ b/src/server/game/Chat/HyperlinkTags.cpp @@ -22,6 +22,7 @@ #include "ObjectMgr.h" #include "SpellInfo.h" #include "SpellMgr.h" +#include "TransmogMgr.h" static constexpr char HYPERLINK_DATA_DELIMITER = ':'; @@ -527,7 +528,7 @@ bool Trinity::Hyperlinks::LinkTags::transmogillusion::StoreTo(SpellItemEnchantme if (!t.TryConsumeTo(spellItemEnchantmentId)) return false; return !!(val = sSpellItemEnchantmentStore.LookupEntry(spellItemEnchantmentId)) - && sDB2Manager.GetTransmogIllusionForEnchantment(spellItemEnchantmentId) && t.IsEmpty(); + && TransmogMgr::GetTransmogIllusionForSpellItemEnchantment(spellItemEnchantmentId) && t.IsEmpty(); } bool Trinity::Hyperlinks::LinkTags::transmogset::StoreTo(TransmogSetEntry const*& val, std::string_view text) diff --git a/src/server/game/DataStores/DB2LoadInfo.h b/src/server/game/DataStores/DB2LoadInfo.h index 6d78bfb4ca..b41ffab613 100644 --- a/src/server/game/DataStores/DB2LoadInfo.h +++ b/src/server/game/DataStores/DB2LoadInfo.h @@ -6579,6 +6579,62 @@ struct TransmogIllusionLoadInfo static constexpr DB2LoadInfo Instance{ Fields, 5, &TransmogIllusionMeta::Instance, HOTFIX_SEL_TRANSMOG_ILLUSION }; }; +struct TransmogOutfitEntryLoadInfo +{ + static constexpr DB2FieldMeta Fields[8] = + { + { .IsSigned = false, .Type = FT_LONG, .Name = "Cost" }, + { .IsSigned = false, .Type = FT_STRING, .Name = "Name" }, + { .IsSigned = false, .Type = FT_INT, .Name = "ID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "OrderIndex" }, + { .IsSigned = false, .Type = FT_BYTE, .Name = "Source" }, + { .IsSigned = true, .Type = FT_INT, .Name = "Flags" }, + { .IsSigned = false, .Type = FT_BYTE, .Name = "SetType" }, + { .IsSigned = false, .Type = FT_FLOAT, .Name = "OverrideCostModifier" }, + }; + + static constexpr DB2LoadInfo Instance{ Fields, 8, &TransmogOutfitEntryMeta::Instance, HOTFIX_SEL_TRANSMOG_OUTFIT_ENTRY }; +}; + +struct TransmogOutfitSlotInfoLoadInfo +{ + static constexpr DB2FieldMeta Fields[13] = + { + { .IsSigned = false, .Type = FT_STRING_NOT_LOCALIZED, .Name = "InventorySlotName" }, + { .IsSigned = false, .Type = FT_INT, .Name = "ID" }, + { .IsSigned = true, .Type = FT_BYTE, .Name = "TransmogOutfitSlotEnum" }, + { .IsSigned = true, .Type = FT_INT, .Name = "InventorySlotEnum" }, + { .IsSigned = true, .Type = FT_INT, .Name = "Flags" }, + { .IsSigned = false, .Type = FT_BYTE, .Name = "Unused1200" }, + { .IsSigned = false, .Type = FT_BYTE, .Name = "TransmogCollectionType" }, + { .IsSigned = true, .Type = FT_INT, .Name = "SecondarySlotID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "InventorySlotID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "UnassignedAtlasID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "UnassignedDisplayAtlasID" }, + { .IsSigned = false, .Type = FT_FLOAT, .Name = "ItemCostMultiplier" }, + { .IsSigned = false, .Type = FT_FLOAT, .Name = "IllusionCostMultiplier" }, + }; + + static constexpr DB2LoadInfo Instance{ Fields, 13, &TransmogOutfitSlotInfoMeta::Instance, HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_INFO }; +}; + +struct TransmogOutfitSlotOptionLoadInfo +{ + static constexpr DB2FieldMeta Fields[8] = + { + { .IsSigned = false, .Type = FT_INT, .Name = "ID" }, + { .IsSigned = false, .Type = FT_STRING, .Name = "Name" }, + { .IsSigned = false, .Type = FT_BYTE, .Name = "OptionEnum" }, + { .IsSigned = false, .Type = FT_INT, .Name = "TransmogOutfitSlotInfoID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "Flags" }, + { .IsSigned = true, .Type = FT_INT, .Name = "SecondaryOptionID" }, + { .IsSigned = false, .Type = FT_FLOAT, .Name = "ItemCostMultiplier" }, + { .IsSigned = false, .Type = FT_FLOAT, .Name = "IllusionCostMultiplier" }, + }; + + static constexpr DB2LoadInfo Instance{ Fields, 8, &TransmogOutfitSlotOptionMeta::Instance, HOTFIX_SEL_TRANSMOG_OUTFIT_SLOT_OPTION }; +}; + struct TransmogSetLoadInfo { static constexpr DB2FieldMeta Fields[13] = @@ -6625,6 +6681,48 @@ struct TransmogSetItemLoadInfo static constexpr DB2LoadInfo Instance{ Fields, 4, &TransmogSetItemMeta::Instance, HOTFIX_SEL_TRANSMOG_SET_ITEM }; }; +struct TransmogSituationLoadInfo +{ + static constexpr DB2FieldMeta Fields[6] = + { + { .IsSigned = false, .Type = FT_STRING, .Name = "Name" }, + { .IsSigned = false, .Type = FT_INT, .Name = "ID" }, + { .IsSigned = true, .Type = FT_BYTE, .Name = "SituationEnum" }, + { .IsSigned = true, .Type = FT_INT, .Name = "Flags" }, + { .IsSigned = false, .Type = FT_INT, .Name = "TransmogSituationGroupID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "OrderIndex" }, + }; + + static constexpr DB2LoadInfo Instance{ Fields, 6, &TransmogSituationMeta::Instance, HOTFIX_SEL_TRANSMOG_SITUATION }; +}; + +struct TransmogSituationGroupLoadInfo +{ + static constexpr DB2FieldMeta Fields[4] = + { + { .IsSigned = false, .Type = FT_INT, .Name = "ID" }, + { .IsSigned = false, .Type = FT_INT, .Name = "TransmogSituationTriggerID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "OrderIndex" }, + { .IsSigned = true, .Type = FT_INT, .Name = "Flags" }, + }; + + static constexpr DB2LoadInfo Instance{ Fields, 4, &TransmogSituationGroupMeta::Instance, HOTFIX_SEL_TRANSMOG_SITUATION_GROUP }; +}; + +struct TransmogSituationTriggerLoadInfo +{ + static constexpr DB2FieldMeta Fields[5] = + { + { .IsSigned = false, .Type = FT_STRING, .Name = "Name" }, + { .IsSigned = false, .Type = FT_STRING, .Name = "Description" }, + { .IsSigned = false, .Type = FT_INT, .Name = "ID" }, + { .IsSigned = false, .Type = FT_BYTE, .Name = "TriggerEnum" }, + { .IsSigned = true, .Type = FT_INT, .Name = "Flags" }, + }; + + static constexpr DB2LoadInfo Instance{ Fields, 5, &TransmogSituationTriggerMeta::Instance, HOTFIX_SEL_TRANSMOG_SITUATION_TRIGGER }; +}; + struct TransportAnimationLoadInfo { static constexpr DB2FieldMeta Fields[7] = diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp index 15145e7827..c91cff7bef 100644 --- a/src/server/game/DataStores/DB2Stores.cpp +++ b/src/server/game/DataStores/DB2Stores.cpp @@ -382,6 +382,12 @@ DB2Storage sTraitTreeXTraitCostStore("Trait DB2Storage sTraitTreeXTraitCurrencyStore("TraitTreeXTraitCurrency.db2", &TraitTreeXTraitCurrencyLoadInfo::Instance); DB2Storage sTransmogHolidayStore("TransmogHoliday.db2", &TransmogHolidayLoadInfo::Instance); DB2Storage sTransmogIllusionStore("TransmogIllusion.db2", &TransmogIllusionLoadInfo::Instance); +DB2Storage sTransmogOutfitEntryStore("TransmogOutfitEntry.db2", &TransmogOutfitEntryLoadInfo::Instance); +DB2Storage sTransmogOutfitSlotInfoStore("TransmogOutfitSlotInfo.db2", &TransmogOutfitSlotInfoLoadInfo::Instance); +DB2Storage sTransmogOutfitSlotOptionInfoStore("TransmogOutfitSlotOption.db2", &TransmogOutfitSlotOptionLoadInfo::Instance); +DB2Storage sTransmogSituationStore("TransmogSituation.db2", &TransmogSituationLoadInfo::Instance); +DB2Storage sTransmogSituationGroupStore("TransmogSituationGroup.db2", &TransmogSituationGroupLoadInfo::Instance); +DB2Storage sTransmogSituationTriggerStore("TransmogSituationTrigger.db2", &TransmogSituationTriggerLoadInfo::Instance); DB2Storage sTransmogSetStore("TransmogSet.db2", &TransmogSetLoadInfo::Instance); DB2Storage sTransmogSetGroupStore("TransmogSetGroup.db2", &TransmogSetGroupLoadInfo::Instance); DB2Storage sTransmogSetItemStore("TransmogSetItem.db2", &TransmogSetItemLoadInfo::Instance); @@ -426,7 +432,6 @@ typedef std::unordered_map ItemChildEquipmentContainer; typedef std::array ItemClassByOldEnumContainer; typedef std::unordered_map> ItemLimitCategoryConditionContainer; -typedef std::unordered_map ItemModifiedAppearanceByItemContainer; typedef std::unordered_map> ItemSetSpellContainer; typedef std::unordered_map> ItemSpecOverridesContainer; typedef std::unordered_map> MapDifficultyContainer; @@ -505,7 +510,6 @@ namespace ItemClassByOldEnumContainer _itemClassByOldEnum; std::unordered_set _itemsWithCurrencyCost; ItemLimitCategoryConditionContainer _itemCategoryConditions; - ItemModifiedAppearanceByItemContainer _itemModifiedAppearancesByItem; ItemSetSpellContainer _itemSetSpells; ItemSpecOverridesContainer _itemSpecOverrides; std::vector _journalTiersByIndex; @@ -536,9 +540,6 @@ namespace TalentsByPosition _talentsByPosition; std::unordered_map, TaxiPathEntry const*> _taxiPaths; ToyItemIdsContainer _toys; - std::unordered_map _transmogIllusionsByEnchantmentId; - std::unordered_map> _transmogSetsByItemModifiedAppearance; - std::unordered_map> _transmogSetItemsByTransmogSet; std::unordered_map _uiMapBounds; std::unordered_multimap _uiMapAssignmentByMap[MAX_UI_MAP_SYSTEM]; std::unordered_multimap _uiMapAssignmentByArea[MAX_UI_MAP_SYSTEM]; @@ -1013,6 +1014,12 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul LOAD_DB2(sTraitTreeXTraitCurrencyStore); LOAD_DB2(sTransmogHolidayStore); LOAD_DB2(sTransmogIllusionStore); + LOAD_DB2(sTransmogOutfitEntryStore); + LOAD_DB2(sTransmogOutfitSlotInfoStore); + LOAD_DB2(sTransmogOutfitSlotOptionInfoStore); + LOAD_DB2(sTransmogSituationStore); + LOAD_DB2(sTransmogSituationGroupStore); + LOAD_DB2(sTransmogSituationTriggerStore); LOAD_DB2(sTransmogSetStore); LOAD_DB2(sTransmogSetGroupStore); LOAD_DB2(sTransmogSetItemStore); @@ -1359,12 +1366,6 @@ void DB2Manager::IndexLoadedStores() for (ItemLimitCategoryConditionEntry const* condition : sItemLimitCategoryConditionStore) _itemCategoryConditions[condition->ParentItemLimitCategoryID].push_back(condition); - for (ItemModifiedAppearanceEntry const* appearanceMod : sItemModifiedAppearanceStore) - { - ASSERT(appearanceMod->ItemID <= 0xFFFFFF); - _itemModifiedAppearancesByItem[appearanceMod->ItemID | (appearanceMod->ItemAppearanceModifierID << 24)] = appearanceMod; - } - for (ItemSetSpellEntry const* itemSetSpell : sItemSetSpellStore) _itemSetSpells[itemSetSpell->ItemSetID].push_back(itemSetSpell); @@ -1576,19 +1577,6 @@ void DB2Manager::IndexLoadedStores() for (ToyEntry const* toy : sToyStore) _toys.insert(toy->ItemID); - for (TransmogIllusionEntry const* transmogIllusion : sTransmogIllusionStore) - _transmogIllusionsByEnchantmentId[transmogIllusion->SpellItemEnchantmentID] = transmogIllusion; - - for (TransmogSetItemEntry const* transmogSetItem : sTransmogSetItemStore) - { - TransmogSetEntry const* set = sTransmogSetStore.LookupEntry(transmogSetItem->TransmogSetID); - if (!set) - continue; - - _transmogSetsByItemModifiedAppearance[transmogSetItem->ItemModifiedAppearanceID].push_back(set); - _transmogSetItemsByTransmogSet[transmogSetItem->TransmogSetID].push_back(transmogSetItem); - } - std::unordered_multimap uiMapAssignmentByUiMap; for (UiMapAssignmentEntry const* uiMapAssignment : sUiMapAssignmentStore) { @@ -2649,37 +2637,6 @@ std::vector const* DB2Manager::GetItemLi return Trinity::Containers::MapGetValuePtr(_itemCategoryConditions, categoryId); } -uint32 DB2Manager::GetItemDisplayId(uint32 itemId, uint32 appearanceModId) const -{ - if (ItemModifiedAppearanceEntry const* modifiedAppearance = GetItemModifiedAppearance(itemId, appearanceModId)) - if (ItemAppearanceEntry const* itemAppearance = sItemAppearanceStore.LookupEntry(modifiedAppearance->ItemAppearanceID)) - return itemAppearance->ItemDisplayInfoID; - - return 0; -} - -ItemModifiedAppearanceEntry const* DB2Manager::GetItemModifiedAppearance(uint32 itemId, uint32 appearanceModId) const -{ - auto itr = _itemModifiedAppearancesByItem.find(itemId | (appearanceModId << 24)); - if (itr != _itemModifiedAppearancesByItem.end()) - return itr->second; - - // Fall back to unmodified appearance - if (appearanceModId) - { - itr = _itemModifiedAppearancesByItem.find(itemId); - if (itr != _itemModifiedAppearancesByItem.end()) - return itr->second; - } - - return nullptr; -} - -ItemModifiedAppearanceEntry const* DB2Manager::GetDefaultItemModifiedAppearance(uint32 itemId) const -{ - return Trinity::Containers::MapGetValuePtr(_itemModifiedAppearancesByItem, itemId); -} - std::vector const* DB2Manager::GetItemSetSpells(uint32 itemSetId) const { return Trinity::Containers::MapGetValuePtr(_itemSetSpells, itemSetId); @@ -3116,21 +3073,6 @@ bool DB2Manager::IsToyItem(uint32 toy) const return _toys.count(toy) > 0; } -TransmogIllusionEntry const* DB2Manager::GetTransmogIllusionForEnchantment(uint32 spellItemEnchantmentId) const -{ - return Trinity::Containers::MapGetValuePtr(_transmogIllusionsByEnchantmentId, spellItemEnchantmentId); -} - -std::vector const* DB2Manager::GetTransmogSetsForItemModifiedAppearance(uint32 itemModifiedAppearanceId) const -{ - return Trinity::Containers::MapGetValuePtr(_transmogSetsByItemModifiedAppearance, itemModifiedAppearanceId); -} - -std::vector const* DB2Manager::GetTransmogSetItems(uint32 transmogSetId) const -{ - return Trinity::Containers::MapGetValuePtr(_transmogSetItemsByTransmogSet, transmogSetId); -} - struct UiMapAssignmentStatus { UiMapAssignmentEntry const* UiMapAssignment = nullptr; diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h index 0c43a982b5..da37df4d11 100644 --- a/src/server/game/DataStores/DB2Stores.h +++ b/src/server/game/DataStores/DB2Stores.h @@ -306,6 +306,12 @@ TC_GAME_API extern DB2Storage sTraitTreeXT TC_GAME_API extern DB2Storage sTraitTreeXTraitCurrencyStore; TC_GAME_API extern DB2Storage sTransmogHolidayStore; TC_GAME_API extern DB2Storage sTransmogIllusionStore; +TC_GAME_API extern DB2Storage sTransmogOutfitEntryStore; +TC_GAME_API extern DB2Storage sTransmogOutfitSlotInfoStore; +TC_GAME_API extern DB2Storage sTransmogOutfitSlotOptionInfoStore; +TC_GAME_API extern DB2Storage sTransmogSituationStore; +TC_GAME_API extern DB2Storage sTransmogSituationGroupStore; +TC_GAME_API extern DB2Storage sTransmogSituationTriggerStore; TC_GAME_API extern DB2Storage sTransmogSetStore; TC_GAME_API extern DB2Storage sTransmogSetGroupStore; TC_GAME_API extern DB2Storage sTransmogSetItemStore; @@ -499,9 +505,6 @@ public: ItemClassEntry const* GetItemClassByOldEnum(uint32 itemClass) const; bool HasItemCurrencyCost(uint32 itemId) const; std::vector const* GetItemLimitCategoryConditions(uint32 categoryId) const; - uint32 GetItemDisplayId(uint32 itemId, uint32 appearanceModId) const; - ItemModifiedAppearanceEntry const* GetItemModifiedAppearance(uint32 itemId, uint32 appearanceModId) const; - ItemModifiedAppearanceEntry const* GetDefaultItemModifiedAppearance(uint32 itemId) const; std::vector const* GetItemSetSpells(uint32 itemSetId) const; std::vector const* GetItemSpecOverrides(uint32 itemId) const; JournalTierEntry const* GetJournalTier(uint32 index) const; @@ -548,9 +551,6 @@ public: TaxiPathEntry const* GetTaxiPath(uint32 from, uint32 to) const; static bool IsTotemCategoryCompatibleWith(uint32 itemTotemCategoryId, uint32 requiredTotemCategoryId, bool requireAllTotems = true); bool IsToyItem(uint32 toy) const; - TransmogIllusionEntry const* GetTransmogIllusionForEnchantment(uint32 spellItemEnchantmentId) const; - std::vector const* GetTransmogSetsForItemModifiedAppearance(uint32 itemModifiedAppearanceId) const; - std::vector const* GetTransmogSetItems(uint32 transmogSetId) const; static bool GetUiMapPosition(float x, float y, float z, int32 mapId, int32 areaId, int32 wmoDoodadPlacementId, int32 wmoGroupId, UiMapSystem system, bool local, uint32* uiMapId = nullptr, DBCPosition2D* newPos = nullptr); bool Zone2MapCoordinates(uint32 areaId, float& x, float& y) const; diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h index 6bc353477c..c0371d272b 100644 --- a/src/server/game/DataStores/DB2Structure.h +++ b/src/server/game/DataStores/DB2Structure.h @@ -4696,6 +4696,57 @@ struct TransmogIllusionEntry EnumFlag GetFlags() const { return static_cast(Flags); } }; +struct TransmogOutfitEntryEntry +{ + uint64 Cost; + LocalizedString Name; + uint32 ID; + int32 OrderIndex; + uint8 Source; + int32 Flags; + uint8 SetType; + float OverrideCostModifier; + + TransmogOutfitEntrySource GetSource() const { return static_cast(Source); } + bool HasFlag(TransmogOutfitEntryFlags flag) const { return EnumFlag(static_cast(Flags)).HasFlag(flag); } + TransmogOutfitSetType GetSetType() const { return static_cast(SetType); } +}; + +struct TransmogOutfitSlotInfoEntry +{ + char const* InventorySlotName; + uint32 ID; + int8 TransmogOutfitSlotEnum; + int32 InventorySlotEnum; + int32 Flags; + uint8 Unused1200; + uint8 TransmogCollectionType; + int32 SecondarySlotID; + int32 InventorySlotID; + int32 UnassignedAtlasID; + int32 UnassignedDisplayAtlasID; + float ItemCostMultiplier; + float IllusionCostMultiplier; + + TransmogOutfitSlot GetSlot() const { return static_cast(TransmogOutfitSlotEnum); } + bool HasFlag(TransmogOutfitSlotFlags flag) const { return EnumFlag(static_cast(Flags)).HasFlag(flag); } +}; + +struct TransmogOutfitSlotOptionEntry +{ + uint32 ID; + LocalizedString Name; + uint8 OptionEnum; + uint32 TransmogOutfitSlotInfoID; + int32 Flags; + int32 SecondaryOptionID; + float ItemCostMultiplier; + float IllusionCostMultiplier; + + TransmogOutfitSlotOption GetOption() const { return static_cast(OptionEnum); } + bool HasFlag(TransmogOutfitSlotOptionFlags flag) const { return EnumFlag(static_cast(Flags)).HasFlag(flag); } +}; + struct TransmogSetEntry { LocalizedString Name; @@ -4727,6 +4778,39 @@ struct TransmogSetItemEntry int32 Flags; }; +struct TransmogSituationEntry +{ + LocalizedString Name; + uint32 ID; + int8 SituationEnum; + int32 Flags; + uint32 TransmogSituationGroupID; + int32 OrderIndex; + + TransmogSituation GetSituation() const { return static_cast(SituationEnum); } + bool HasFlag(TransmogSituationFlags flag) const { return EnumFlag(static_cast(Flags)).HasFlag(flag); } +}; + +struct TransmogSituationGroupEntry +{ + uint32 ID; + uint32 TransmogSituationTriggerID; + int32 OrderIndex; + int32 Flags; +}; + +struct TransmogSituationTriggerEntry +{ + LocalizedString Name; + LocalizedString Description; + uint32 ID; + uint8 TriggerEnum; + int32 Flags; + + TransmogSituationTrigger GetTrigger() const { return static_cast(TriggerEnum); } + bool HasFlag(TransmogSituationTriggerFlags flag) const { return EnumFlag(static_cast(Flags)).HasFlag(flag); } +}; + struct TransportAnimationEntry { uint32 ID; diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h index cc7bd88004..0d0ea64cb3 100644 --- a/src/server/game/DataStores/DBCEnums.h +++ b/src/server/game/DataStores/DBCEnums.h @@ -2520,6 +2520,179 @@ enum class TransmogIllusionFlags : int32 DEFINE_ENUM_FLAG(TransmogIllusionFlags); +enum class TransmogOutfitDisplayType : uint8 +{ + Unassigned = 0, + Assigned = 1, + Equipped = 2, + Hidden = 3, + Disabled = 4, + + Max +}; + +enum class TransmogOutfitEquipAction : uint8 +{ + Equip = 0, + EquipAndLock = 1, + Remove = 2, + RemoveAndLock = 3, + Unlock = 4, + Lock = 5, +}; + +enum class TransmogOutfitEntryFlags : int32 +{ + AutomaticallyAwardedOnLogin = 0x01, + UseOverrideName = 0x02, + OnlyAvailableDuringEvent = 0x04, + SortedToTopOfList = 0x08, + UseOverrideCostModifier = 0x10, + IsDefaultEquipped = 0x20 +}; + +DEFINE_ENUM_FLAG(TransmogOutfitEntryFlags); + +enum class TransmogOutfitEntrySource : uint8 +{ + StampedSource = 0, + AutomaticallyAwarded = 1, + PlayerPurchased = 2, + + Max +}; + +enum class TransmogOutfitSetType : uint8 +{ + Equipped = 0, + Outfit = 1, + CustomSet = 2 +}; + +enum class TransmogOutfitSlot : int8 +{ + Head = 0, + ShoulderRight = 1, + ShoulderLeft = 2, + Back = 3, + Chest = 4, + Tabard = 5, + Body = 6, + Wrist = 7, + Hand = 8, + Waist = 9, + Legs = 10, + Feet = 11, + WeaponMainHand = 12, + WeaponOffHand = 13, + WeaponRanged = 14, + + Max +}; + +enum class TransmogOutfitSlotFlags : int32 +{ + CannotBeHidden = 0x01, + CanHaveIllusions = 0x02, + IsSecondarySlot = 0x04 +}; + +DEFINE_ENUM_FLAG(TransmogOutfitSlotFlags); + +enum class TransmogOutfitSlotOption : uint8 +{ + None = 0, + OneHandedWeapon = 1, + TwoHandedWeapon = 2, + RangedWeapon = 3, + OffHand = 4, + Shield = 5, + DeprecatedReuseMe = 6, + FuryTwoHandedWeapon = 7, + ArtifactSpecOne = 8, + ArtifactSpecTwo = 9, + ArtifactSpecThree = 10, + ArtifactSpecFour = 11, + + Max +}; + +enum class TransmogOutfitSlotOptionFlags : int32 +{ + IllusionNotAllowed = 0x01, + DynamicOptionName = 0x02, + DisablesOffhandSlot = 0x04 +}; + +DEFINE_ENUM_FLAG(TransmogOutfitSlotOptionFlags); + +enum class TransmogSituation : int8 +{ + AllSpecs = 0, + Spec = 1, + AllLocations = 2, + LocationRested = 3, + LocationHouse = 4, + LocationCharacterSelect = 5, + LocationWorld = 6, + LocationDelves = 7, + LocationDungeons = 8, + LocationRaids = 9, + LocationArenas = 10, + LocationBattlegrounds = 11, + AllMovement = 12, + MovementUnmounted = 13, + MovementSwimming = 14, + MovementGroundMount = 15, + MovementFlyingMount = 16, + AllEquipmentSets = 17, + EquipmentSets = 18, + AllRacialForms = 19, + FormNative = 20, + FormNonNative = 21, + + Max +}; + +enum class TransmogSituationFlags : int32 +{ + IsPlayerFacing = 0x01, + SpecUseTalentLoadout = 0x02, + AllSituation = 0x04, + DefaultsToOn = 0x08, + DynamicallyNamed = 0x10, + NoneSituation = 0x20, + DisabledSituation = 0x40 +}; + +DEFINE_ENUM_FLAG(TransmogSituationFlags); + +enum class TransmogSituationTrigger : uint8 +{ + None = 0, + Manual = 1, + TransmogUpdate = 2, + Location = 3, + Movement = 4, + Specialization = 5, + EquipmentSet = 6, + Forms = 7, + EventOutfit = 8, + + Max +}; + +enum class TransmogSituationTriggerFlags : int32 +{ + CanLockOutfit = 0x01, + CanChangeLockedOutfit = 0x02, + IsPlayerFacing = 0x04, + SituationsAreExclusive = 0x08, + DisabledTrigger = 0x10 +}; + +DEFINE_ENUM_FLAG(TransmogSituationTriggerFlags); + // SummonProperties.dbc, col 1 enum SummonPropGroup { diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index 4566e09e50..89c4e33cd9 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -46,6 +46,7 @@ #include "SpellMgr.h" #include "StringConvert.h" #include "TradeData.h" +#include "TransmogMgr.h" #include "UpdateData.h" #include "World.h" #include "WorldSession.h" @@ -2010,51 +2011,6 @@ bool Item::IsValidTransmogrificationTarget() const return true; } -enum class ItemTransmogrificationWeaponCategory : uint8 -{ - // Two-handed - MELEE_2H, - RANGED, - - // One-handed - AXE_MACE_SWORD_1H, - DAGGER, - - INVALID -}; - -static ItemTransmogrificationWeaponCategory GetTransmogrificationWeaponCategory(ItemTemplate const* proto) -{ - if (proto->GetClass() == ITEM_CLASS_WEAPON) - { - switch (proto->GetSubClass()) - { - case ITEM_SUBCLASS_WEAPON_AXE2: - case ITEM_SUBCLASS_WEAPON_MACE2: - case ITEM_SUBCLASS_WEAPON_SWORD2: - case ITEM_SUBCLASS_WEAPON_STAFF: - case ITEM_SUBCLASS_WEAPON_POLEARM: - return ItemTransmogrificationWeaponCategory::MELEE_2H; - case ITEM_SUBCLASS_WEAPON_BOW: - case ITEM_SUBCLASS_WEAPON_GUN: - case ITEM_SUBCLASS_WEAPON_CROSSBOW: - return ItemTransmogrificationWeaponCategory::RANGED; - case ITEM_SUBCLASS_WEAPON_AXE: - case ITEM_SUBCLASS_WEAPON_MACE: - case ITEM_SUBCLASS_WEAPON_SWORD: - case ITEM_SUBCLASS_WEAPON_WARGLAIVES: - case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: - return ItemTransmogrificationWeaponCategory::AXE_MACE_SWORD_1H; - case ITEM_SUBCLASS_WEAPON_DAGGER: - return ItemTransmogrificationWeaponCategory::DAGGER; - default: - break; - } - } - - return ItemTransmogrificationWeaponCategory::INVALID; -} - int32 const ItemTransmogrificationSlots[MAX_INVTYPE] = { -1, // INVTYPE_NON_EQUIP @@ -2124,7 +2080,7 @@ bool Item::CanTransmogrifyItemWithItem(Item const* item, ItemModifiedAppearanceE switch (source->GetClass()) { case ITEM_CLASS_WEAPON: - if (GetTransmogrificationWeaponCategory(source) != GetTransmogrificationWeaponCategory(target)) + if (source->GetWeaponTransmogOutfitSlotOption() != target->GetWeaponTransmogOutfitSlotOption()) return false; break; case ITEM_CLASS_ARMOR: @@ -2495,16 +2451,20 @@ uint32 Item::GetDisplayId(Player const* owner) const if (!itemModifiedAppearanceId) itemModifiedAppearanceId = GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_ALL_SPECS); - if (ItemModifiedAppearanceEntry const* transmog = sItemModifiedAppearanceStore.LookupEntry(itemModifiedAppearanceId)) - if (ItemAppearanceEntry const* itemAppearance = sItemAppearanceStore.LookupEntry(transmog->ItemAppearanceID)) + ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(itemModifiedAppearanceId); + if (!itemModifiedAppearance) + itemModifiedAppearance = GetItemModifiedAppearance(); + + if (itemModifiedAppearance) + if (ItemAppearanceEntry const* itemAppearance = sItemAppearanceStore.LookupEntry(itemModifiedAppearance->ItemAppearanceID)) return itemAppearance->ItemDisplayInfoID; - return sDB2Manager.GetItemDisplayId(GetEntry(), GetAppearanceModId()); + return 0; } ItemModifiedAppearanceEntry const* Item::GetItemModifiedAppearance() const { - return sDB2Manager.GetItemModifiedAppearance(GetEntry(), _bonusData.AppearanceModID); + return TransmogMgr::GetItemModifiedAppearance(GetEntry(), _bonusData.AppearanceModID); } uint32 Item::GetModifier(ItemModifier modifier) const diff --git a/src/server/game/Entities/Item/ItemTemplate.cpp b/src/server/game/Entities/Item/ItemTemplate.cpp index b247dc88f8..31580e0b68 100644 --- a/src/server/game/Entities/Item/ItemTemplate.cpp +++ b/src/server/game/Entities/Item/ItemTemplate.cpp @@ -297,3 +297,49 @@ std::size_t ItemTemplate::CalculateItemSpecBit(ChrSpecializationEntry const* spe { return (spec->ClassID - 1) * MAX_SPECIALIZATIONS + spec->OrderIndex; } + +TransmogOutfitSlotOption ItemTemplate::GetWeaponTransmogOutfitSlotOption() const +{ + switch (GetClass()) + { + case ITEM_CLASS_WEAPON: + switch (GetSubClass()) + { + case ITEM_SUBCLASS_WEAPON_AXE2: + case ITEM_SUBCLASS_WEAPON_MACE2: + case ITEM_SUBCLASS_WEAPON_SWORD2: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_POLEARM: + return TransmogOutfitSlotOption::TwoHandedWeapon; + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_GUN: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + return TransmogOutfitSlotOption::RangedWeapon; + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_WARGLAIVES: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + case ITEM_SUBCLASS_WEAPON_DAGGER: + return TransmogOutfitSlotOption::OneHandedWeapon; + default: + break; + } + break; + case ITEM_CLASS_ARMOR: + switch (GetInventoryType()) + { + case INVTYPE_SHIELD: + return TransmogOutfitSlotOption::Shield; + case INVTYPE_HOLDABLE: + return TransmogOutfitSlotOption::OffHand; + default: + break; + } + break; + default: + break; + } + + return TransmogOutfitSlotOption::None; +} diff --git a/src/server/game/Entities/Item/ItemTemplate.h b/src/server/game/Entities/Item/ItemTemplate.h index e86710e0b3..3a8757407e 100644 --- a/src/server/game/Entities/Item/ItemTemplate.h +++ b/src/server/game/Entities/Item/ItemTemplate.h @@ -967,6 +967,7 @@ struct TC_GAME_API ItemTemplate void GetDamage(uint32 itemLevel, float& minDamage, float& maxDamage) const; bool IsUsableByLootSpecialization(Player const* player, bool alwaysAllowBoundToAccount) const; static std::size_t CalculateItemSpecBit(ChrSpecializationEntry const* spec); + TransmogOutfitSlotOption GetWeaponTransmogOutfitSlotOption() const; }; #endif diff --git a/src/server/game/Entities/Object/Updates/UpdateFields.cpp b/src/server/game/Entities/Object/Updates/UpdateFields.cpp index fc6f13743a..0b8f82c65b 100644 --- a/src/server/game/Entities/Object/Updates/UpdateFields.cpp +++ b/src/server/game/Entities/Object/Updates/UpdateFields.cpp @@ -882,7 +882,7 @@ void VisibleItem::WriteCreate(ByteBuffer& data, Player const* receiver, Unit con data << uint16(ItemAppearanceModID); data << uint16(ItemVisual); data << uint32(ItemModifiedAppearanceID); - data << uint8(Field_18); + data << uint8(TransmogSlotOption); data.WriteBit(HasTransmog); data.WriteBit(HasIllusion); data.FlushBits(); @@ -936,7 +936,7 @@ void VisibleItem::WriteUpdate(bool ignoreChangesMask, ByteBuffer& data, Player c } if (changesMask[9]) { - data << uint8(Field_18); + data << uint8(TransmogSlotOption); } } data.FlushBits(); @@ -952,7 +952,7 @@ void VisibleItem::ClearChangesMask() Base::ClearChangesMask(ItemAppearanceModID); Base::ClearChangesMask(ItemVisual); Base::ClearChangesMask(ItemModifiedAppearanceID); - Base::ClearChangesMask(Field_18); + Base::ClearChangesMask(TransmogSlotOption); _changesMask.ResetAll(); } @@ -5444,7 +5444,7 @@ void ActivePlayerData::WriteCreate(EnumFlag fieldVisibilityFlag data << uint32(HouseThemes.size()); data << uint32(HouseRoomComponentTextures.size()); data << uint32(HouseTypes.size()); - data << uint32(Field_1980.size()); + data << uint32(UnlockedTransmogOutfits.size()); data << uint32(CharacterRestrictions.size()); data << uint32(SpellPctModByLabel.size()); data << uint32(SpellFlatModByLabel.size()); @@ -5563,9 +5563,9 @@ void ActivePlayerData::WriteCreate(EnumFlag fieldVisibilityFlag { data << uint32(HouseTypes[i]); } - for (uint32 i = 0; i < Field_1980.size(); ++i) + for (uint32 i = 0; i < UnlockedTransmogOutfits.size(); ++i) { - data << int32(Field_1980[i]); + data << int32(UnlockedTransmogOutfits[i]); } for (uint32 i = 0; i < SpellPctModByLabel.size(); ++i) { @@ -5979,9 +5979,9 @@ void ActivePlayerData::WriteUpdate(Mask const& changesMask, ByteBuffer& data, Pl if (changesMask[29]) { if (!ignoreNestedChangesMask) - Field_1980.WriteUpdateMask(data); + UnlockedTransmogOutfits.WriteUpdateMask(data); else - WriteCompleteDynamicFieldUpdateMask(Field_1980.size(), data); + WriteCompleteDynamicFieldUpdateMask(UnlockedTransmogOutfits.size(), data); } if (changesMask[30]) { @@ -6321,11 +6321,11 @@ void ActivePlayerData::WriteUpdate(Mask const& changesMask, ByteBuffer& data, Pl } if (changesMask[29]) { - for (uint32 i = 0; i < Field_1980.size(); ++i) + for (uint32 i = 0; i < UnlockedTransmogOutfits.size(); ++i) { - if (Field_1980.HasChanged(i) || ignoreNestedChangesMask) + if (UnlockedTransmogOutfits.HasChanged(i) || ignoreNestedChangesMask) { - data << int32(Field_1980[i]); + data << int32(UnlockedTransmogOutfits[i]); } } } @@ -7158,7 +7158,7 @@ void ActivePlayerData::ClearChangesMask() Base::ClearChangesMask(HouseThemes); Base::ClearChangesMask(HouseRoomComponentTextures); Base::ClearChangesMask(HouseTypes); - Base::ClearChangesMask(Field_1980); + Base::ClearChangesMask(UnlockedTransmogOutfits); Base::ClearChangesMask(SpellPctModByLabel); Base::ClearChangesMask(SpellFlatModByLabel); Base::ClearChangesMask(SpellPctModPVPByLabel); diff --git a/src/server/game/Entities/Object/Updates/UpdateFields.h b/src/server/game/Entities/Object/Updates/UpdateFields.h index 602f2740a5..2f51ad0ade 100644 --- a/src/server/game/Entities/Object/Updates/UpdateFields.h +++ b/src/server/game/Entities/Object/Updates/UpdateFields.h @@ -262,7 +262,7 @@ struct VisibleItem : public IsUpdateFieldStructureTag, public HasChangesMask<10> UpdateField ItemAppearanceModID; UpdateField ItemVisual; UpdateField ItemModifiedAppearanceID; - UpdateField Field_18; + UpdateField TransmogSlotOption; using OwnerObject = Unit; void WriteCreate(ByteBuffer& data, Player const* receiver, Unit const* owner) const; @@ -1311,7 +1311,7 @@ struct ActivePlayerData : public IsUpdateFieldStructureTag, public HasChangesMas DynamicUpdateField HouseThemes; DynamicUpdateField HouseRoomComponentTextures; DynamicUpdateField HouseTypes; - DynamicUpdateField Field_1980; + DynamicUpdateField UnlockedTransmogOutfits; DynamicUpdateField SpellPctModByLabel; DynamicUpdateField SpellFlatModByLabel; DynamicUpdateField SpellPctModPVPByLabel; diff --git a/src/server/game/Entities/Player/CollectionMgr.cpp b/src/server/game/Entities/Player/CollectionMgr.cpp index 299cc3885d..d12f5673b4 100644 --- a/src/server/game/Entities/Player/CollectionMgr.cpp +++ b/src/server/game/Entities/Player/CollectionMgr.cpp @@ -26,6 +26,7 @@ #include "ObjectMgr.h" #include "Player.h" #include "Timer.h" +#include "TransmogMgr.h" #include "TransmogrificationPackets.h" #include "WorldSession.h" #include @@ -95,12 +96,36 @@ namespace } } -CollectionMgr::CollectionMgr(WorldSession* owner) : _owner(owner), _appearances(std::make_unique>()), _transmogIllusions(std::make_unique>()) +CollectionMgr::CollectionMgr(WorldSession* owner) : _owner(owner), + _appearances(std::make_unique>()), + _transmogIllusions(std::make_unique>()) { } CollectionMgr::~CollectionMgr() = default; +void CollectionMgr::LoadCharacterData() +{ + LoadToys(); + LoadHeirlooms(); + LoadMounts(); + LoadItemAppearances(); + LoadTransmogIllusions(); + LoadTransmogOutfits(); + LoadWarbandScenes(); +} + +void CollectionMgr::SaveToDB(LoginDatabaseTransaction trans) +{ + SaveAccountToys(trans); + SaveAccountHeirlooms(trans); + SaveAccountMounts(trans); + SaveAccountItemAppearances(trans); + SaveAccountTransmogIllusions(trans); + SaveAccountTransmogOutfits(trans); + SaveAccountWarbandScenes(trans); +} + void CollectionMgr::LoadToys() { for (auto const& [itemId, flags] : _toys) @@ -529,7 +554,7 @@ void CollectionMgr::LoadAccountItemAppearances(PreparedQueryResult knownAppearan for (uint32 hiddenItem : hiddenAppearanceItems) { - ItemModifiedAppearanceEntry const* hiddenAppearance = sDB2Manager.GetItemModifiedAppearance(hiddenItem, 0); + ItemModifiedAppearanceEntry const* hiddenAppearance = TransmogMgr::GetDefaultItemModifiedAppearance(hiddenItem); ASSERT(hiddenAppearance); if (_appearances->size() <= hiddenAppearance->ID) _appearances->resize(hiddenAppearance->ID + 1); @@ -603,7 +628,7 @@ void CollectionMgr::AddItemAppearance(Item* item) void CollectionMgr::AddItemAppearance(uint32 itemId, uint32 appearanceModId /*= 0*/) { - ItemModifiedAppearanceEntry const* itemModifiedAppearance = sDB2Manager.GetItemModifiedAppearance(itemId, appearanceModId); + ItemModifiedAppearanceEntry const* itemModifiedAppearance = TransmogMgr::GetItemModifiedAppearance(itemId, appearanceModId); if (!CanAddAppearance(itemModifiedAppearance)) return; @@ -612,11 +637,7 @@ void CollectionMgr::AddItemAppearance(uint32 itemId, uint32 appearanceModId /*= void CollectionMgr::AddTransmogSet(uint32 transmogSetId) { - std::vector const* items = sDB2Manager.GetTransmogSetItems(transmogSetId); - if (!items) - return; - - for (TransmogSetItemEntry const* item : *items) + for (TransmogSetItemEntry const* item : TransmogMgr::GetTransmogSetItems(transmogSetId)) { ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(item->ItemModifiedAppearanceID); if (!itemModifiedAppearance) @@ -628,13 +649,13 @@ void CollectionMgr::AddTransmogSet(uint32 transmogSetId) bool CollectionMgr::IsSetCompleted(uint32 transmogSetId) const { - std::vector const* transmogSetItems = sDB2Manager.GetTransmogSetItems(transmogSetId); - if (!transmogSetItems) + std::span transmogSetItems = TransmogMgr::GetTransmogSetItems(transmogSetId); + if (transmogSetItems.empty()) return false; std::array knownPieces; knownPieces.fill(-1); - for (TransmogSetItemEntry const* transmogSetItem : *transmogSetItems) + for (TransmogSetItemEntry const* transmogSetItem : transmogSetItems) { ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(transmogSetItem->ItemModifiedAppearanceID); if (!itemModifiedAppearance) @@ -756,18 +777,14 @@ void CollectionMgr::AddItemAppearance(ItemModifiedAppearanceEntry const* itemMod owner->UpdateCriteria(CriteriaType::LearnAnyTransmogInSlot, transmogSlot, itemModifiedAppearance->ID); } - if (std::vector const* sets = sDB2Manager.GetTransmogSetsForItemModifiedAppearance(itemModifiedAppearance->ID)) + for (TransmogSetEntry const* set : TransmogMgr::GetTransmogSetsForItemModifiedAppearance(itemModifiedAppearance->ID)) { - for (TransmogSetEntry const* set : *sets) + if (IsSetCompleted(set->ID)) { - if (IsSetCompleted(set->ID)) - { + if (Quest const* quest = sObjectMgr->GetQuestTemplate(set->TrackingQuestID)) + owner->RewardQuest(quest, LootItemType::Item, 0, owner, false); - if (Quest const* quest = sObjectMgr->GetQuestTemplate(set->TrackingQuestID)) - owner->RewardQuest(quest, LootItemType::Item, 0, owner, false); - - owner->UpdateCriteria(CriteriaType::CollectTransmogSetFromGroup, set->TransmogSetGroupID); - } + owner->UpdateCriteria(CriteriaType::CollectTransmogSetFromGroup, set->TransmogSetGroupID); } } } @@ -966,6 +983,55 @@ bool CollectionMgr::HasTransmogIllusion(uint32 transmogIllusionId) const return transmogIllusionId < _transmogIllusions->size() && _transmogIllusions->test(transmogIllusionId); } +void CollectionMgr::LoadTransmogOutfits() +{ + _owner->GetPlayer()->AddUnlockedTransmogOutfits(_transmogOutfits); +} + +void CollectionMgr::LoadAccountTransmogOutfits(PreparedQueryResult unlockedTransmogOutfits) +{ + if (unlockedTransmogOutfits) + { + do + { + Field* fields = unlockedTransmogOutfits->Fetch(); + + int32 transmogOutfitId = fields[0].GetInt32(); + + if (!sTransmogOutfitEntryStore.HasRecord(transmogOutfitId)) + continue; + + _transmogOutfits.insert(transmogOutfitId); + + } while (unlockedTransmogOutfits->NextRow()); + } + + for (TransmogOutfitEntryEntry const* transmogOutfitEntry : TransmogMgr::GetAutomaticallyUnlockedOutfits()) + _transmogOutfits.insert(transmogOutfitEntry->ID); +} + +void CollectionMgr::SaveAccountTransmogOutfits(LoginDatabaseTransaction trans) +{ + for (int32 transmogOutfitId : _transmogOutfits) + { + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BNET_TRANSMOG_OUTFITS); + stmt->setUInt32(0, _owner->GetBattlenetAccountId()); + stmt->setInt32(1, transmogOutfitId); + trans->Append(stmt); + } +} + +void CollectionMgr::AddTransmogOutfit(int32 transmogOutfitId) +{ + if (_transmogOutfits.insert(transmogOutfitId).second) + _owner->GetPlayer()->AddUnlockedTransmogOutfit(transmogOutfitId); +} + +bool CollectionMgr::HasTransmogOutfit(int32 transmogOutfitId) const +{ + return _transmogOutfits.contains(transmogOutfitId); +} + void CollectionMgr::LoadWarbandScenes() { Player* owner = _owner->GetPlayer(); diff --git a/src/server/game/Entities/Player/CollectionMgr.h b/src/server/game/Entities/Player/CollectionMgr.h index 9b17eb0c8b..2eb0e81de8 100644 --- a/src/server/game/Entities/Player/CollectionMgr.h +++ b/src/server/game/Entities/Player/CollectionMgr.h @@ -21,6 +21,7 @@ #include "Define.h" #include "DatabaseEnvFwd.h" #include "EnumFlag.h" +#include "FlatSet.h" #include "ObjectGuid.h" #include #include @@ -117,6 +118,9 @@ public: static void LoadMountDefinitions(); static void LoadWarbandSceneDefinitions(); + void LoadCharacterData(); + void SaveToDB(LoginDatabaseTransaction trans); + // Account-wide toys void LoadToys(); void LoadAccountToys(PreparedQueryResult result); @@ -179,6 +183,12 @@ public: void AddTransmogIllusion(uint32 transmogIllusionId); bool HasTransmogIllusion(uint32 transmogIllusionId) const; + void LoadTransmogOutfits(); + void LoadAccountTransmogOutfits(PreparedQueryResult unlockedTransmogOutfits); + void SaveAccountTransmogOutfits(LoginDatabaseTransaction trans); + void AddTransmogOutfit(int32 transmogOutfitId); + bool HasTransmogOutfit(int32 transmogOutfitId) const; + // Warband Scenes void LoadWarbandScenes(); void LoadAccountWarbandScenes(PreparedQueryResult knownWarbandScenes); @@ -204,6 +214,7 @@ private: std::unordered_map> _temporaryAppearances; std::unordered_map _favoriteAppearances; std::unique_ptr> _transmogIllusions; + Trinity::Containers::FlatSet _transmogOutfits; WarbandSceneCollectionContainer _warbandScenes; }; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 3f5a86662d..70534d6b5f 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -131,6 +131,8 @@ #include "TradeData.h" #include "TraitMgr.h" #include "TraitPacketsCommon.h" +#include "TransmogMgr.h" +#include "TransmogrificationPackets.h" #include "Transport.h" #include "UpdateData.h" #include "Util.h" @@ -559,6 +561,8 @@ bool Player::Create(ObjectGuid::LowType guidlow, WorldPackets::Character::Charac GetThreatManager().Initialize(); + EquipTransmogOutfit(0, TransmogSituationTrigger::Manual, false); + return true; } @@ -4146,6 +4150,18 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe stmt->setUInt64(0, guid); trans->Append(stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRANSMOG_OUTFIT_SITUATION_BY_CHAR); + stmt->setUInt64(0, guid); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRANSMOG_OUTFIT_SLOT_BY_CHAR); + stmt->setUInt64(0, guid); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRANSMOG_OUTFIT_BY_CHAR); + stmt->setUInt64(0, guid); + trans->Append(stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_EVENTLOG_BY_PLAYER); stmt->setUInt64(0, guid); stmt->setUInt64(1, guid); @@ -4521,19 +4537,10 @@ Corpse* Player::CreateCorpse() corpse->SetFactionTemplate(sChrRacesStore.AssertEntry(GetRace())->FactionID); for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; i++) - { - if (m_items[i]) - { - uint32 itemDisplayId = m_items[i]->GetDisplayId(this); - uint32 itemInventoryType; - if (ItemEntry const* itemEntry = sItemStore.LookupEntry(m_items[i]->GetVisibleEntry(this))) - itemInventoryType = itemEntry->InventoryType; - else - itemInventoryType = m_items[i]->GetTemplate()->GetInventoryType(); - - corpse->SetItem(i, itemDisplayId | (itemInventoryType << 24)); - } - } + if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(m_playerData->VisibleItems[i].ItemModifiedAppearanceID)) + if (ItemAppearanceEntry const* itemAppearance = sItemAppearanceStore.LookupEntry(itemModifiedAppearance->ItemAppearanceID)) + if (ItemEntry const* item = sItemStore.LookupEntry(itemModifiedAppearance->ItemID)) + corpse->SetItem(i, itemAppearance->ItemDisplayInfoID | int32(item->InventoryType) << 24); // register for player, but not show GetMap()->AddCorpse(corpse); @@ -6176,6 +6183,7 @@ bool Player::IsActionButtonDataValid(uint8 button, uint64 action, uint8 type) co case ACTION_BUTTON_MACRO: case ACTION_BUTTON_EQSET: case ACTION_BUTTON_DROPDOWN: + case ACTION_BUTTON_OUTFIT: break; default: TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Unknown action type {}", type); @@ -11825,25 +11833,123 @@ void Player::QuickEquipItem(uint16 pos, Item* pItem) } } -void Player::SetVisibleItemSlot(uint8 slot, Item* pItem) +void Player::SetVisibleItemSlot(uint8 slot, Item const* item) { - auto itemField = m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::VisibleItems, slot); - if (pItem) + auto setVisibleItemSlot = [this](uint32 slot, int32 itemId, int32 secondaryItemModifiedAppearanceId, int32 conditionalItemAppearanceId, + uint16 itemAppearanceModId, uint16 itemVisual, uint32 itemModifiedAppearanceId, TransmogOutfitSlotOption transmogSlotOption, + bool hasTransmog, bool hasIllusion) { - SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemID), pItem->GetVisibleEntry(this)); - SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::SecondaryItemModifiedAppearanceID), pItem->GetVisibleSecondaryModifiedAppearanceId(this)); - SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemAppearanceModID), pItem->GetVisibleAppearanceModId(this)); - SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemVisual), pItem->GetVisibleItemVisual(this)); - SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemModifiedAppearanceID), pItem->GetVisibleModifiedAppearanceId(this)); + auto itemField = m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::VisibleItems, slot); + SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemID), itemId); + SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::SecondaryItemModifiedAppearanceID), secondaryItemModifiedAppearanceId); + SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ConditionalItemAppearanceID), conditionalItemAppearanceId); + SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemAppearanceModID), itemAppearanceModId); + SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemVisual), itemVisual); + SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemModifiedAppearanceID), itemModifiedAppearanceId); + SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::TransmogSlotOption), AsUnderlyingType(transmogSlotOption)); + SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::HasTransmog), hasTransmog); + SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::HasIllusion), hasIllusion); + + auto transmogMetadata = m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TransmogMetadata); + if (slot == EQUIPMENT_SLOT_MAINHAND) + SetUpdateFieldValue(transmogMetadata.ModifyValue(&UF::TransmogOutfitMetadata::StampedOptionMainHand), AsUnderlyingType(transmogSlotOption)); + else if (slot == EQUIPMENT_SLOT_OFFHAND) + SetUpdateFieldValue(transmogMetadata.ModifyValue(&UF::TransmogOutfitMetadata::StampedOptionOffHand), AsUnderlyingType(transmogSlotOption)); + }; + + if (item) + { + TransmogOutfitSlotOption transmogSlotOption = item->GetTemplate()->GetWeaponTransmogOutfitSlotOption(); + if (transmogSlotOption == TransmogOutfitSlotOption::TwoHandedWeapon && CanTitanGrip()) + transmogSlotOption = TransmogOutfitSlotOption::FuryTwoHandedWeapon; + + int32 itemId = item->GetVisibleEntry(this); + int32 secondaryItemModifiedAppearanceId = item->GetVisibleSecondaryModifiedAppearanceId(this); + int32 conditionalItemAppearanceId = 0; + uint16 itemAppearanceModId = item->GetVisibleAppearanceModId(this); + uint16 itemVisual = item->GetVisibleItemVisual(this); + uint32 itemModifiedAppearanceId = item->GetVisibleModifiedAppearanceId(this); + bool hasTransmog = false; + bool hasIllusion = false; + + if (!m_activePlayerData->ViewedOutfit->Slots.empty()) + { + TransmogMgr::TransmogOutfitSlotAndOptionInfo const* slotInfo = TransmogMgr::GetSlotAndOption(EquipmentSlots(slot), transmogSlotOption); + if (transmogSlotOption != TransmogOutfitSlotOption::None) + { + // check if artifact override is active + TransmogOutfitSlotOption artifactOption = static_cast(AsUnderlyingType(TransmogOutfitSlotOption::ArtifactSpecOne) + GetPrimarySpecializationEntry()->OrderIndex); + TransmogMgr::TransmogOutfitSlotAndOptionInfo const* artifactSlotInfo = TransmogMgr::GetSlotAndOption(EquipmentSlots(slot), artifactOption); + if (artifactSlotInfo && static_cast(*m_activePlayerData->ViewedOutfit->Slots[artifactSlotInfo->SlotIndex].AppearanceDisplayType) == TransmogOutfitDisplayType::Assigned) + { + transmogSlotOption = artifactOption; + slotInfo = artifactSlotInfo; + } + } + + if (slotInfo) + { + auto isTransmogDisplayed = [](TransmogOutfitDisplayType displayType) + { + return displayType == TransmogOutfitDisplayType::Assigned || displayType == TransmogOutfitDisplayType::Hidden; + }; + + UF::TransmogOutfitSlotData const& transmogOutfitItem = m_activePlayerData->ViewedOutfit->Slots[slotInfo->SlotIndex]; + if (isTransmogDisplayed(static_cast(*transmogOutfitItem.AppearanceDisplayType))) + { + if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(transmogOutfitItem.ItemModifiedAppearanceID)) + { + itemId = itemModifiedAppearance->ItemID; + itemAppearanceModId = itemModifiedAppearance->ItemAppearanceModifierID; + itemModifiedAppearanceId = itemModifiedAppearance->ID; + } + + hasTransmog = true; + } + + if (TransmogOutfitSlotInfoEntry const* secondarySlot = sTransmogOutfitSlotInfoStore.LookupEntry(slotInfo->Slot->SecondarySlotID)) + { + if (TransmogMgr::TransmogOutfitSlotAndOptionInfo const* secondarySlotInfo = TransmogMgr::GetSlotAndOption(secondarySlot->GetSlot(), transmogSlotOption)) + { + UF::TransmogOutfitSlotData const& secondaryTransmogOutfitItem = m_activePlayerData->ViewedOutfit->Slots[secondarySlotInfo->SlotIndex]; + if (isTransmogDisplayed(static_cast(*secondaryTransmogOutfitItem.AppearanceDisplayType)) + || static_cast(*secondaryTransmogOutfitItem.AppearanceDisplayType) == TransmogOutfitDisplayType::Equipped) + { + secondaryItemModifiedAppearanceId = secondaryTransmogOutfitItem.ItemModifiedAppearanceID; + hasTransmog = true; + } + } + } + + if (TransmogOutfitSlotOptionEntry const* secondarySlotOption = sTransmogOutfitSlotOptionInfoStore.LookupEntry(slotInfo->SlotOption ? slotInfo->SlotOption->SecondaryOptionID : 0)) + { + if (TransmogMgr::TransmogOutfitSlotAndOptionInfo const* secondarySlotInfo = TransmogMgr::GetSlotAndOption(slotInfo->Slot->GetSlot(), secondarySlotOption->GetOption())) + { + UF::TransmogOutfitSlotData const& secondaryTransmogOutfitItem = m_activePlayerData->ViewedOutfit->Slots[secondarySlotInfo->SlotIndex]; + if (isTransmogDisplayed(static_cast(*secondaryTransmogOutfitItem.AppearanceDisplayType)) + || static_cast(*secondaryTransmogOutfitItem.AppearanceDisplayType) == TransmogOutfitDisplayType::Equipped) + { + secondaryItemModifiedAppearanceId = secondaryTransmogOutfitItem.ItemModifiedAppearanceID; + hasTransmog = true; + } + } + } + + if (isTransmogDisplayed(static_cast(*transmogOutfitItem.IllusionDisplayType))) + { + if (SpellItemEnchantmentEntry const* spellItemEnchantment = sSpellItemEnchantmentStore.LookupEntry(transmogOutfitItem.SpellItemEnchantmentID)) + itemVisual = spellItemEnchantment->ItemVisual; + + hasIllusion = true; + } + } + } + + setVisibleItemSlot(slot, itemId, secondaryItemModifiedAppearanceId, conditionalItemAppearanceId, itemAppearanceModId, + itemVisual, itemModifiedAppearanceId, transmogSlotOption, hasTransmog, hasIllusion); } else - { - SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemID), 0); - SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::SecondaryItemModifiedAppearanceID), 0); - SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemAppearanceModID), 0); - SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemVisual), 0); - SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemModifiedAppearanceID), 0); - } + setVisibleItemSlot(slot, 0, 0, 0, 0, 0, 0, TransmogOutfitSlotOption::None, false, false); } void Player::VisualizeItem(uint8 slot, Item* pItem) @@ -17579,7 +17685,7 @@ void Player::_LoadEquipmentSets(PreparedQueryResult result) } while (result->NextRow()); } -void Player::_LoadTransmogOutfits(PreparedQueryResult result) +void Player::_LoadTransmogCustomSets(PreparedQueryResult result) { // 0 1 2 3 4 5 6 7 8 9 //SELECT setguid, setindex, name, iconname, ignore_mask, appearance0, appearance1, appearance2, appearance3, appearance4, @@ -17616,6 +17722,153 @@ void Player::_LoadTransmogOutfits(PreparedQueryResult result) } while (result->NextRow()); } +void Player::_LoadTransmogOutfits(PreparedQueryResult setsResult, PreparedQueryResult situationsResult, PreparedQueryResult slotsResult, + int32 equippedTransmogOutfitId, bool locked) +{ + struct SetData + { + std::vector situations; + std::vector slots; + }; + + std::unordered_map sets; + + // SELECT transmogOutfitId, situationID, specID, loadoutID, equipmentSetID FROM character_transmog_outfit_situation WHERE guid = ? + if (situationsResult) + { + do + { + DEFINE_FIELD_ACCESSOR_CACHE_ANONYMOUS(PreparedResultSet, (transmogOutfitId)(situationID)(specID)(loadoutID)(equipmentSetID)) fields { *situationsResult }; + + uint32 transmogOutfitId = fields.transmogOutfitId().GetUInt32(); + + WorldPackets::Transmogrification::TransmogOutfitSituationInfo& situation = sets[transmogOutfitId].situations.emplace_back(); + situation.SituationID = fields.situationID().GetUInt32(); + situation.SpecID = fields.specID().GetUInt32(); + situation.LoadoutID = fields.loadoutID().GetUInt32(); + situation.EquipmentSetID = fields.equipmentSetID().GetUInt32(); + + } while (situationsResult->NextRow()); + } + + // SELECT transmogOutfitId, slot, slotOption, itemModifiedAppearanceID, appearanceDisplayType, spellItemEnchantmentID, illusionDisplayType, flags FROM character_transmog_outfit_slot guid = ? + if (slotsResult) + { + do + { + DEFINE_FIELD_ACCESSOR_CACHE_ANONYMOUS(PreparedResultSet, (transmogOutfitId)(slot)(slotOption)(itemModifiedAppearanceID)(appearanceDisplayType) + (spellItemEnchantmentID)(illusionDisplayType)(flags)) fields { *slotsResult }; + + uint32 transmogOutfitId = fields.transmogOutfitId().GetUInt32(); + + WorldPackets::Transmogrification::TransmogOutfitSlotData& slot = sets[transmogOutfitId].slots.emplace_back(); + slot.Slot = static_cast(fields.slot().GetInt8()); + slot.SlotOption = static_cast(fields.slotOption().GetUInt8()); + slot.ItemModifiedAppearanceID = fields.itemModifiedAppearanceID().GetUInt32(); + slot.AppearanceDisplayType = static_cast(fields.appearanceDisplayType().GetUInt8()); + slot.SpellItemEnchantmentID = fields.spellItemEnchantmentID().GetUInt32(); + slot.IllusionDisplayType = static_cast(fields.illusionDisplayType().GetUInt8()); + slot.Flags = fields.flags().GetUInt32(); + + } while (slotsResult->NextRow()); + } + + auto activePlayerData = m_values.ModifyValue(&Player::m_activePlayerData); + + // SELECT transmogOutfitId, name, icon, situationsEnabled FROM character_transmog_outfit WHERE guid = ? + if (setsResult) + { + do + { + DEFINE_FIELD_ACCESSOR_CACHE_ANONYMOUS(PreparedResultSet, (transmogOutfitId)(name)(icon)(situationsEnabled)) fields { *setsResult }; + + uint32 transmogOutfitId = fields.transmogOutfitId().GetUInt32(); + TransmogOutfitEntryEntry const* transmogOutfitEntry = sTransmogOutfitEntryStore.LookupEntry(transmogOutfitId); + if (!transmogOutfitEntry || transmogOutfitEntry->GetSetType() == TransmogOutfitSetType::CustomSet) + continue; + + if (transmogOutfitEntry->HasFlag(TransmogOutfitEntryFlags::OnlyAvailableDuringEvent) && !IsHolidayActive(HOLIDAY_TRIAL_OF_STYLE)) + continue; + + WorldPackets::Transmogrification::TransmogOutfitDataInfo outfitInfo; + outfitInfo.SetType = transmogOutfitEntry->GetSetType(); + outfitInfo.Name = fields.name().GetString(); + outfitInfo.Icon = fields.icon().GetUInt32(); + outfitInfo.SituationsEnabled = fields.situationsEnabled().GetBool(); + + auto transmogOutfit = activePlayerData.ModifyValue(&UF::ActivePlayerData::TransmogOutfits, transmogOutfitId); + InitializeNewTransmogOutfit(transmogOutfit, transmogOutfitId, outfitInfo); + + auto setData = sets.extract(transmogOutfitId); + if (!setData) + continue; + + auto situations = transmogOutfit.ModifyValue(&UF::TransmogOutfitData::Situations); + ClearDynamicUpdateFieldValues(situations); + for (WorldPackets::Transmogrification::TransmogOutfitSituationInfo const& situationInfo : setData.mapped().situations) + { + auto situation = AddDynamicUpdateFieldValue(situations); + situation.ModifyValue(&UF::TransmogOutfitSituationInfo::SituationID).SetValue(situationInfo.SituationID); + situation.ModifyValue(&UF::TransmogOutfitSituationInfo::SpecID).SetValue(situationInfo.SpecID); + situation.ModifyValue(&UF::TransmogOutfitSituationInfo::LoadoutID).SetValue(situationInfo.LoadoutID); + situation.ModifyValue(&UF::TransmogOutfitSituationInfo::EquipmentSetID).SetValue(situationInfo.EquipmentSetID); + } + + for (WorldPackets::Transmogrification::TransmogOutfitSlotData const& slotData : setData.mapped().slots) + { + TransmogMgr::TransmogOutfitSlotAndOptionInfo const* slotInfo = TransmogMgr::GetSlotAndOption(slotData.Slot, slotData.SlotOption); + if (!slotInfo) + continue; + + auto slot = transmogOutfit.ModifyValue(&UF::TransmogOutfitData::Slots, slotInfo->SlotIndex); + SetUpdateFieldValue(slot.ModifyValue(&UF::TransmogOutfitSlotData::Slot), AsUnderlyingType(slotData.Slot)); + SetUpdateFieldValue(slot.ModifyValue(&UF::TransmogOutfitSlotData::SlotOption), AsUnderlyingType(slotData.SlotOption)); + SetUpdateFieldValue(slot.ModifyValue(&UF::TransmogOutfitSlotData::ItemModifiedAppearanceID), slotData.ItemModifiedAppearanceID); + SetUpdateFieldValue(slot.ModifyValue(&UF::TransmogOutfitSlotData::AppearanceDisplayType), AsUnderlyingType(slotData.AppearanceDisplayType)); + SetUpdateFieldValue(slot.ModifyValue(&UF::TransmogOutfitSlotData::SpellItemEnchantmentID), slotData.SpellItemEnchantmentID); + SetUpdateFieldValue(slot.ModifyValue(&UF::TransmogOutfitSlotData::IllusionDisplayType), AsUnderlyingType(slotData.IllusionDisplayType)); + SetUpdateFieldValue(slot.ModifyValue(&UF::TransmogOutfitSlotData::Flags), slotData.Flags); + } + } while (setsResult->NextRow()); + } + + for (int32 transmogOutfitId : m_activePlayerData->UnlockedTransmogOutfits) + { + TransmogOutfitEntryEntry const* transmogOutfitEntry = sTransmogOutfitEntryStore.AssertEntry(transmogOutfitId); + + if (transmogOutfitEntry->HasFlag(TransmogOutfitEntryFlags::OnlyAvailableDuringEvent) && !IsHolidayActive(HOLIDAY_TRIAL_OF_STYLE)) + continue; + + WorldPackets::Transmogrification::TransmogOutfitDataInfo outfitData; + outfitData.SetType = transmogOutfitEntry->GetSetType(); + outfitData.SituationsEnabled = false; + outfitData.Icon = TransmogMgr::DefaultOutfitIcon; + outfitData.Name = TransmogMgr::DefaultOutfitName[GetSession()->GetSessionDbcLocale()]; + if (transmogOutfitEntry->Name.Str[GetSession()->GetSessionDbcLocale()][0]) + outfitData.Name = transmogOutfitEntry->Name.Str[GetSession()->GetSessionDbcLocale()]; + + switch (transmogOutfitEntry->GetSetType()) + { + case TransmogOutfitSetType::Equipped: + { + outfitData.SituationsEnabled = true; + auto equippedOutfit = activePlayerData.ModifyValue(&UF::ActivePlayerData::ViewedOutfit); + InitializeNewTransmogOutfit(equippedOutfit, transmogOutfitId, outfitData); + break; + } + case TransmogOutfitSetType::Outfit: + if (!m_activePlayerData->TransmogOutfits.Get(transmogOutfitId)) + CreateTransmogOutfit(transmogOutfitId, outfitData); + break; + case TransmogOutfitSetType::CustomSet: + default: + break; + } + } + + EquipTransmogOutfit(equippedTransmogOutfitId, TransmogSituationTrigger::Manual, locked); +} + void Player::_LoadBGData(PreparedQueryResult result) { if (!result) @@ -17717,7 +17970,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol // "totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, " // "health, power1, power2, power3, power4, power5, power6, power7, power8, power9, power10, instance_id, activeTalentGroup, lootSpecId, exploredZones, knownTitles, actionBars, " // "raidDifficulty, legacyRaidDifficulty, fishingSteps, honor, honorLevel, honorRestState, honorRestBonus, numRespecs, " - // "personalTabardEmblemStyle, personalTabardEmblemColor, personalTabardBorderStyle, personalTabardBorderColor, personalTabardBackgroundColor " + // "personalTabardEmblemStyle, personalTabardEmblemColor, personalTabardBorderStyle, personalTabardBorderColor, personalTabardBackgroundColor, transmogOutfitEquippedId, transmogOutfitLocked " // "FROM characters c LEFT JOIN character_fishingsteps cfs ON c.guid = cfs.guid WHERE c.guid = ?", CONNECTION_ASYNC); ObjectGuid::LowType guid; @@ -17795,6 +18048,8 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol int32 personalTabardBorderStyle; int32 personalTabardBorderColor; int32 personalTabardBackgroundColor; + int32 transmogOutfitEquippedId; + bool transmogOutfitLocked; explicit PlayerLoadData(Field const* fields) { @@ -17876,6 +18131,8 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol personalTabardBorderStyle = fields[i++].GetInt32(); personalTabardBorderColor = fields[i++].GetInt32(); personalTabardBackgroundColor = fields[i++].GetInt32(); + transmogOutfitEquippedId = fields[i++].GetInt32(); + transmogOutfitLocked = fields[i++].GetBool(); } } fields(result->Fetch()); @@ -18379,12 +18636,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol _LoadTalents(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TALENTS)); _LoadPvpTalents(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_PVP_TALENTS)); _LoadSpells(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELLS), holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELL_FAVORITES)); - GetSession()->GetCollectionMgr()->LoadToys(); - GetSession()->GetCollectionMgr()->LoadHeirlooms(); - GetSession()->GetCollectionMgr()->LoadMounts(); - GetSession()->GetCollectionMgr()->LoadItemAppearances(); - GetSession()->GetCollectionMgr()->LoadTransmogIllusions(); - GetSession()->GetCollectionMgr()->LoadWarbandScenes(); + GetSession()->GetCollectionMgr()->LoadCharacterData(); LearnSpecializationSpells(); @@ -18560,6 +18812,9 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol } } + if (extraflags & PLAYER_EXTRA_FLAG_FREE_TRANSMOG_CLAIMED) + SetPlayerLocalFlag(PLAYER_LOCAL_FLAG_FREE_TRANSMOG_CLAIMED); + InitPvP(); // RaF stuff. @@ -18569,7 +18824,11 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol _LoadDeclinedNames(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_DECLINED_NAMES)); _LoadEquipmentSets(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_EQUIPMENT_SETS)); - _LoadTransmogOutfits(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFITS)); + _LoadTransmogCustomSets(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFITS)); + _LoadTransmogOutfits(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFIT), + holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFIT_SITUATION), + holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFIT_SLOT), + fields.transmogOutfitEquippedId, fields.transmogOutfitLocked); _LoadCUFProfiles(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_CUF_PROFILES)); @@ -20314,27 +20573,7 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba } stmt->setString(index++, ss.str()); - ss.str(""); - // cache equipment... - for (uint32 i = 0; i < REAGENT_BAG_SLOT_END; ++i) - { - if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) - { - ss << uint32(item->GetTemplate()->GetInventoryType()) << ' ' << item->GetDisplayId(this) << ' '; - if (SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(item->GetVisibleEnchantmentId(this))) - ss << enchant->ItemVisual; - else - ss << '0'; - - ss << ' ' - << uint32(sItemStore.AssertEntry(item->GetVisibleEntry(this))->SubclassID) << ' ' - << uint32(item->GetVisibleSecondaryModifiedAppearanceId(this)) << ' '; - } - else - ss << "0 0 0 0 0 "; - } - - stmt->setString(index++, ss.str()); + stmt->setString(index++, GetCharacterSelectOutfit()); ss.str(""); for (uint32 i = 0; i < m_activePlayerData->KnownTitles.size(); ++i) @@ -20349,6 +20588,14 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba stmt->setUInt32(index++, ClientBuild::GetMinorMajorBugfixVersionForBuild(currentRealm->Build)); else stmt->setUInt32(index++, 0); + + stmt->setInt32(index++, m_playerData->PersonalTabard->EmblemStyle); + stmt->setInt32(index++, m_playerData->PersonalTabard->EmblemColor); + stmt->setInt32(index++, m_playerData->PersonalTabard->BorderStyle); + stmt->setInt32(index++, m_playerData->PersonalTabard->BorderColor); + stmt->setInt32(index++, m_playerData->PersonalTabard->BackgroundColor); + stmt->setInt32(index++, m_activePlayerData->TransmogMetadata->TransmogOutfitID); + stmt->setBool(index++, m_activePlayerData->TransmogMetadata->Locked); } else { @@ -20473,27 +20720,7 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba } stmt->setString(index++, ss.str()); - ss.str(""); - // cache equipment... - for (uint32 i = 0; i < REAGENT_BAG_SLOT_END; ++i) - { - if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) - { - ss << uint32(item->GetTemplate()->GetInventoryType()) << ' ' << item->GetDisplayId(this) << ' '; - if (SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(item->GetVisibleEnchantmentId(this))) - ss << enchant->ItemVisual; - else - ss << '0'; - - ss << ' ' - << uint32(sItemStore.AssertEntry(item->GetVisibleEntry(this))->SubclassID) << ' ' - << uint32(item->GetVisibleSecondaryModifiedAppearanceId(this)) << ' '; - } - else - ss << "0 0 0 0 0 "; - } - - stmt->setString(index++, ss.str()); + stmt->setString(index++, GetCharacterSelectOutfit()); ss.str(""); for (uint32 i = 0; i < m_activePlayerData->KnownTitles.size(); ++i) @@ -20515,6 +20742,14 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba else stmt->setUInt32(index++, 0); + stmt->setInt32(index++, m_playerData->PersonalTabard->EmblemStyle); + stmt->setInt32(index++, m_playerData->PersonalTabard->EmblemColor); + stmt->setInt32(index++, m_playerData->PersonalTabard->BorderStyle); + stmt->setInt32(index++, m_playerData->PersonalTabard->BorderColor); + stmt->setInt32(index++, m_playerData->PersonalTabard->BackgroundColor); + stmt->setInt32(index++, m_activePlayerData->TransmogMetadata->TransmogOutfitID); + stmt->setBool(index++, m_activePlayerData->TransmogMetadata->Locked); + // Index stmt->setUInt64(index, GetGUID().GetCounter()); } @@ -20554,6 +20789,7 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba m_reputationMgr->SaveToDB(trans); m_questObjectiveCriteriaMgr->SaveToDB(trans); _SaveEquipmentSets(trans); + _SaveTransmogOutfits(trans); GetSession()->SaveTutorialsData(trans); // changed only while character in game _SaveInstanceTimeRestrictions(trans); _SaveCurrency(trans); @@ -20569,13 +20805,8 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba _SaveStats(trans); // TODO: Move this out - GetSession()->GetCollectionMgr()->SaveAccountToys(loginTransaction); + GetSession()->GetCollectionMgr()->SaveToDB(loginTransaction); GetSession()->GetBattlePetMgr()->SaveToDB(loginTransaction); - GetSession()->GetCollectionMgr()->SaveAccountHeirlooms(loginTransaction); - GetSession()->GetCollectionMgr()->SaveAccountMounts(loginTransaction); - GetSession()->GetCollectionMgr()->SaveAccountItemAppearances(loginTransaction); - GetSession()->GetCollectionMgr()->SaveAccountTransmogIllusions(loginTransaction); - GetSession()->GetCollectionMgr()->SaveAccountWarbandScenes(loginTransaction); GetSession()->SavePlayerDataAccount(loginTransaction); Battlenet::RealmHandle currentRealmId = sRealmList->GetCurrentRealmId(); @@ -28047,6 +28278,71 @@ void Player::_SaveEquipmentSets(CharacterDatabaseTransaction trans) } } +void Player::_SaveTransmogOutfits(CharacterDatabaseTransaction trans) +{ + CharacterDatabasePreparedStatement* stmt = nullptr; + ObjectGuid::LowType guid = GetGUID().GetCounter(); + + for (uint32 transmogOutfitId : m_changedTransmogOutfits) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_TRANSMOG_OUTFIT_SLOT); + stmt->setUInt64(0, guid); + stmt->setUInt32(1, transmogOutfitId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_TRANSMOG_OUTFIT_SITUATION); + stmt->setUInt64(0, guid); + stmt->setUInt32(1, transmogOutfitId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_TRANSMOG_OUTFIT_2); + stmt->setUInt64(0, guid); + stmt->setUInt32(1, transmogOutfitId); + trans->Append(stmt); + + UF::TransmogOutfitData const* transmogOutfit = m_activePlayerData->TransmogOutfits.Get(transmogOutfitId); + if (!transmogOutfit) + continue; + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_TRANSMOG_OUTFIT_2); + stmt->setUInt64(0, guid); + stmt->setUInt32(1, transmogOutfitId); + stmt->setString(2, *transmogOutfit->OutfitInfo->Name); + stmt->setUInt32(3, *transmogOutfit->OutfitInfo->Icon); + stmt->setBool(4, *transmogOutfit->OutfitInfo->SituationsEnabled); + trans->Append(stmt); + + for (UF::TransmogOutfitSituationInfo const& situation : transmogOutfit->Situations) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_TRANSMOG_OUTFIT_SITUATION); + stmt->setUInt64(0, guid); + stmt->setUInt32(1, transmogOutfitId); + stmt->setUInt32(2, *situation.SituationID); + stmt->setUInt32(3, *situation.SpecID); + stmt->setUInt32(4, *situation.LoadoutID); + stmt->setUInt32(5, *situation.EquipmentSetID); + trans->Append(stmt); + } + + for (UF::TransmogOutfitSlotData const& slot : transmogOutfit->Slots) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_TRANSMOG_OUTFIT_SLOT); + stmt->setUInt64(0, guid); + stmt->setUInt32(1, transmogOutfitId); + stmt->setInt8(2, *slot.Slot); + stmt->setUInt8(3, *slot.SlotOption); + stmt->setUInt32(4, *slot.ItemModifiedAppearanceID); + stmt->setUInt8(5, *slot.AppearanceDisplayType); + stmt->setUInt32(6, *slot.SpellItemEnchantmentID); + stmt->setUInt8(7, *slot.IllusionDisplayType); + stmt->setUInt32(8, *slot.Flags); + trans->Append(stmt); + } + } + + m_changedTransmogOutfits.clear(); +} + void Player::_SaveBGData(CharacterDatabaseTransaction trans) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_BGDATA); @@ -30891,6 +31187,308 @@ void Player::UpdateWarModeAuras() } } +void Player::AddUnlockedTransmogOutfits(std::span transmogOutfitIds) +{ + auto unlockedTransmogOutfits = m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::UnlockedTransmogOutfits); + for (int32 transmogOutfitId : transmogOutfitIds) + AddDynamicUpdateFieldValue(unlockedTransmogOutfits) = transmogOutfitId; +} + +void Player::CreateTransmogOutfit(uint32 id, WorldPackets::Transmogrification::TransmogOutfitDataInfo const& outfitData) +{ + auto outfit = m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TransmogOutfits, id); + InitializeNewTransmogOutfit(outfit, id, outfitData); + m_changedTransmogOutfits.insert(id); +} + +void Player::InitializeNewTransmogOutfit(UF::MutableFieldReference outfit, + uint32 id, WorldPackets::Transmogrification::TransmogOutfitDataInfo const& outfitData) +{ + SetUpdateFieldValue(outfit.ModifyValue(&UF::TransmogOutfitData::Id), id); + + auto outfitInfo = outfit.ModifyValue(&UF::TransmogOutfitData::OutfitInfo); + SetUpdateFieldValue(outfitInfo.ModifyValue(&UF::TransmogOutfitDataInfo::SetType), AsUnderlyingType(outfitData.SetType)); + SetUpdateFieldValue(outfitInfo.ModifyValue(&UF::TransmogOutfitDataInfo::SituationsEnabled), outfitData.SituationsEnabled); + SetUpdateFieldValue(outfitInfo.ModifyValue(&UF::TransmogOutfitDataInfo::Icon), outfitData.Icon); + SetUpdateFieldValue(outfitInfo.ModifyValue(&UF::TransmogOutfitDataInfo::Name), std::string(outfitData.Name)); + + auto situations = outfit.ModifyValue(&UF::TransmogOutfitData::Situations); + for (TransmogSituationEntry const* defaultSituation : TransmogMgr::GetDefaultSituations()) + { + auto situation = AddDynamicUpdateFieldValue(situations); + situation.ModifyValue(&UF::TransmogOutfitSituationInfo::SituationID).SetValue(defaultSituation->ID); + } + + auto slots = outfit.ModifyValue(&UF::TransmogOutfitData::Slots); + for (TransmogMgr::TransmogOutfitSlotAndOptionInfo const& slotInfo : TransmogMgr::GetAllSlots()) + { + auto slot = AddDynamicUpdateFieldValue(slots); + slot.ModifyValue(&UF::TransmogOutfitSlotData::Slot).SetValue(AsUnderlyingType(slotInfo.Slot->GetSlot())); + + if (slotInfo.SlotOption) + { + slot.ModifyValue(&UF::TransmogOutfitSlotData::SlotOption).SetValue(AsUnderlyingType(slotInfo.SlotOption->GetOption())); + + switch (slotInfo.SlotOption->GetOption()) + { + case TransmogOutfitSlotOption::ArtifactSpecOne: + case TransmogOutfitSlotOption::ArtifactSpecTwo: + case TransmogOutfitSlotOption::ArtifactSpecThree: + case TransmogOutfitSlotOption::ArtifactSpecFour: + // artifacts are disabled by default + slot.ModifyValue(&UF::TransmogOutfitSlotData::AppearanceDisplayType).SetValue(AsUnderlyingType(TransmogOutfitDisplayType::Disabled)); + slot.ModifyValue(&UF::TransmogOutfitSlotData::IllusionDisplayType).SetValue(AsUnderlyingType(TransmogOutfitDisplayType::Disabled)); + break; + default: + break; + } + } + } +} + +bool Player::UpdateTransmogOutfit(uint32 id, WorldPackets::Transmogrification::TransmogOutfitDataInfo const& outfitData) +{ + if (!m_activePlayerData->TransmogOutfits.Get(id)) + return false; + + auto outfitInfo = m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TransmogOutfits, id) + .ModifyValue(&UF::TransmogOutfitData::OutfitInfo); + SetUpdateFieldValue(outfitInfo.ModifyValue(&UF::TransmogOutfitDataInfo::SetType), AsUnderlyingType(outfitData.SetType)); + SetUpdateFieldValue(outfitInfo.ModifyValue(&UF::TransmogOutfitDataInfo::SituationsEnabled), outfitData.SituationsEnabled); + SetUpdateFieldValue(outfitInfo.ModifyValue(&UF::TransmogOutfitDataInfo::Icon), outfitData.Icon); + SetUpdateFieldValue(outfitInfo.ModifyValue(&UF::TransmogOutfitDataInfo::Name), std::string(outfitData.Name)); + + m_changedTransmogOutfits.insert(id); + return true; +} + +void Player::UpdateTransmogOutfitSituations(uint32 id, bool situationsEnabled, std::span situations) +{ + auto outfit = m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TransmogOutfits, id); + SetUpdateFieldValue(outfit.ModifyValue(&UF::TransmogOutfitData::OutfitInfo).ModifyValue(&UF::TransmogOutfitDataInfo::SituationsEnabled), situationsEnabled); + + auto outfitSituations = outfit.ModifyValue(&UF::TransmogOutfitData::Situations); + + ClearDynamicUpdateFieldValues(outfitSituations); + + for (WorldPackets::Transmogrification::TransmogOutfitSituationInfo const& situation : situations) + { + auto outfitSituation = AddDynamicUpdateFieldValue(outfitSituations); + outfitSituation.ModifyValue(&UF::TransmogOutfitSituationInfo::SituationID).SetValue(situation.SituationID); + outfitSituation.ModifyValue(&UF::TransmogOutfitSituationInfo::SpecID).SetValue(situation.SpecID); + outfitSituation.ModifyValue(&UF::TransmogOutfitSituationInfo::LoadoutID).SetValue(situation.LoadoutID); + outfitSituation.ModifyValue(&UF::TransmogOutfitSituationInfo::EquipmentSetID).SetValue(situation.EquipmentSetID); + } + + m_changedTransmogOutfits.insert(id); +} + +void Player::UpdateTransmogOutfitSlots(uint32 id, std::span slots) +{ + UF::TransmogOutfitData const* transmogOutfit = m_activePlayerData->TransmogOutfits.Get(id); + if (!transmogOutfit) + return; + + auto outfit = m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TransmogOutfits, id); + + uint32 outfitSlotIndex = 0; + for (WorldPackets::Transmogrification::TransmogOutfitSlotData const& slot : slots) + { + while (std::pair(*transmogOutfit->Slots[outfitSlotIndex].Slot, *transmogOutfit->Slots[outfitSlotIndex].SlotOption) < std::pair(AsUnderlyingType(slot.Slot), AsUnderlyingType(slot.SlotOption))) + if (++outfitSlotIndex >= transmogOutfit->Slots.size()) + return; + + auto viewedOutfitSlot = outfit.ModifyValue(&UF::TransmogOutfitData::Slots, outfitSlotIndex); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::Slot), AsUnderlyingType(slot.Slot)); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::SlotOption), AsUnderlyingType(slot.SlotOption)); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::ItemModifiedAppearanceID), slot.ItemModifiedAppearanceID); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::AppearanceDisplayType), AsUnderlyingType(slot.AppearanceDisplayType)); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::SpellItemEnchantmentID), slot.SpellItemEnchantmentID); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::IllusionDisplayType), AsUnderlyingType(slot.IllusionDisplayType)); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::Flags), slot.Flags); + } + + m_changedTransmogOutfits.insert(id); +} + +void Player::EquipTransmogOutfit(uint32 id, TransmogSituationTrigger trigger, Optional locked) +{ + auto activePlayerData = m_values.ModifyValue(&Player::m_activePlayerData); + + auto transmogMetadata = activePlayerData.ModifyValue(&UF::ActivePlayerData::TransmogMetadata); + SetUpdateFieldValue(transmogMetadata.ModifyValue(&UF::TransmogOutfitMetadata::TransmogOutfitID), id); + SetUpdateFieldValue(transmogMetadata.ModifyValue(&UF::TransmogOutfitMetadata::SituationTrigger), AsUnderlyingType(trigger)); + if (locked.has_value()) + SetUpdateFieldValue(transmogMetadata.ModifyValue(&UF::TransmogOutfitMetadata::Locked), *locked); + + auto viewedOutfit = activePlayerData.ModifyValue(&UF::ActivePlayerData::ViewedOutfit); + + if (UF::TransmogOutfitData const* transmogOutfit = m_activePlayerData->TransmogOutfits.Get(id)) + { + for (UF::TransmogOutfitSlotData const& slot : transmogOutfit->Slots) + { + uint32 slotIndex = TransmogMgr::GetSlotAndOption(static_cast(*slot.Slot), static_cast(*slot.SlotOption))->SlotIndex; + auto viewedOutfitSlot = viewedOutfit.ModifyValue(&UF::TransmogOutfitData::Slots, slotIndex); + if (static_cast(*slot.AppearanceDisplayType) != TransmogOutfitDisplayType::Unassigned) + { + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::ItemModifiedAppearanceID), slot.ItemModifiedAppearanceID); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::AppearanceDisplayType), slot.AppearanceDisplayType); + } + if (static_cast(*slot.IllusionDisplayType) != TransmogOutfitDisplayType::Unassigned) + { + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::SpellItemEnchantmentID), slot.SpellItemEnchantmentID); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::IllusionDisplayType), slot.IllusionDisplayType); + } + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::Flags), slot.Flags); + } + } + else + { + for (TransmogMgr::TransmogOutfitSlotAndOptionInfo const& slotInfo : TransmogMgr::GetAllSlots()) + { + auto viewedOutfitSlot = viewedOutfit.ModifyValue(&UF::TransmogOutfitData::Slots, slotInfo.SlotIndex); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::Slot), AsUnderlyingType(slotInfo.Slot->GetSlot())); + + TransmogOutfitSlotOption slotOption = slotInfo.SlotOption ? slotInfo.SlotOption->GetOption() : TransmogOutfitSlotOption::None; + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::SlotOption), AsUnderlyingType(slotOption)); + + if (Item const* item = GetItemByPos(INVENTORY_SLOT_BAG_0, slotInfo.Slot->InventorySlotEnum)) + { + if (item->GetTemplate()->GetWeaponTransmogOutfitSlotOption() == slotOption) + { + if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = item->GetItemModifiedAppearance()) + { + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::ItemModifiedAppearanceID), itemModifiedAppearance->ID); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::AppearanceDisplayType), AsUnderlyingType(TransmogOutfitDisplayType::Equipped)); + } + } + } + + switch (slotOption) + { + case TransmogOutfitSlotOption::ArtifactSpecOne: + case TransmogOutfitSlotOption::ArtifactSpecTwo: + case TransmogOutfitSlotOption::ArtifactSpecThree: + case TransmogOutfitSlotOption::ArtifactSpecFour: + // artifacts are disabled by default + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::AppearanceDisplayType), AsUnderlyingType(TransmogOutfitDisplayType::Disabled)); + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::IllusionDisplayType), AsUnderlyingType(TransmogOutfitDisplayType::Disabled)); + break; + case TransmogOutfitSlotOption::None: + break; + default: + SetUpdateFieldValue(viewedOutfitSlot.ModifyValue(&UF::TransmogOutfitSlotData::IllusionDisplayType), AsUnderlyingType(TransmogOutfitDisplayType::Equipped)); + break; + } + } + } + + for (uint8 equipSlot = EQUIPMENT_SLOT_START; equipSlot < EQUIPMENT_SLOT_END; ++equipSlot) + SetVisibleItemSlot(equipSlot, GetItemByPos(INVENTORY_SLOT_BAG_0, equipSlot)); +} + +std::string Player::GetCharacterSelectOutfit() const +{ + std::vector outfits; + for (auto const& [_, transmogOutfit] : m_activePlayerData->TransmogOutfits) + { + if (!transmogOutfit.value.OutfitInfo->SituationsEnabled) + continue; + + bool isCharacterSelect = transmogOutfit.value.Situations.FindIndexIf([](UF::TransmogOutfitSituationInfo const& situation) + { + return sTransmogSituationStore.AssertEntry(situation.SituationID)->GetSituation() == TransmogSituation::LocationCharacterSelect; + }) >= 0; + + if (!isCharacterSelect) + continue; + + outfits.push_back(&transmogOutfit.value); + } + + UF::TransmogOutfitData const* outfit = &*m_activePlayerData->ViewedOutfit; + if (!outfits.empty()) + outfit = Trinity::Containers::SelectRandomContainerElement(outfits); + + auto isTransmogDisplayed = [](TransmogOutfitDisplayType displayType) + { + return displayType == TransmogOutfitDisplayType::Assigned || displayType == TransmogOutfitDisplayType::Hidden; + }; + + std::string result; + for (EquipmentSlots i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; i = EquipmentSlots(i + 1)) + { + TransmogOutfitSlotOption transmogSlotOption = TransmogOutfitSlotOption::None; + switch (i) + { + case EQUIPMENT_SLOT_MAINHAND: + transmogSlotOption = static_cast(m_activePlayerData->TransmogMetadata->StampedOptionMainHand); + break; + case EQUIPMENT_SLOT_OFFHAND: + transmogSlotOption = static_cast(m_activePlayerData->TransmogMetadata->StampedOptionOffHand); + break; + default: + break; + } + + if (TransmogMgr::TransmogOutfitSlotAndOptionInfo const* slotInfo = TransmogMgr::GetSlotAndOption(i, transmogSlotOption)) + { + UF::TransmogOutfitSlotData const& transmogOutfitSlot = outfit->Slots[slotInfo->SlotIndex]; + + uint32 itemModifiedAppearanceId = transmogOutfitSlot.ItemModifiedAppearanceID; + if (!isTransmogDisplayed(static_cast(*transmogOutfitSlot.AppearanceDisplayType))) + itemModifiedAppearanceId = m_activePlayerData->ViewedOutfit->Slots[slotInfo->SlotIndex].ItemModifiedAppearanceID; + + uint32 spellItemEnchantmentId = transmogOutfitSlot.SpellItemEnchantmentID; + if (!isTransmogDisplayed(static_cast(*transmogOutfitSlot.IllusionDisplayType))) + spellItemEnchantmentId = m_activePlayerData->ViewedOutfit->Slots[slotInfo->SlotIndex].SpellItemEnchantmentID; + + InventoryType inventoryType = INVTYPE_NON_EQUIP; + int32 displayId = 0; + uint16 itemVisual = 0; + uint8 subClass = 0; + uint32 secondaryItemModifiedAppearanceId = 0; + if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(itemModifiedAppearanceId)) + { + if (ItemEntry const* item = sItemStore.LookupEntry(itemModifiedAppearance->ItemID)) + { + subClass = item->SubclassID; + inventoryType = static_cast(item->InventoryType); + } + + if (ItemAppearanceEntry const* itemAppearance = sItemAppearanceStore.LookupEntry(itemModifiedAppearance->ItemAppearanceID)) + displayId = itemAppearance->ItemDisplayInfoID; + } + + if (SpellItemEnchantmentEntry const* spellItemEnchantment = sSpellItemEnchantmentStore.LookupEntry(spellItemEnchantmentId)) + itemVisual = spellItemEnchantment->ItemVisual; + + if (TransmogOutfitSlotInfoEntry const* secondarySlot = sTransmogOutfitSlotInfoStore.LookupEntry(slotInfo->Slot->SecondarySlotID)) + { + if (TransmogMgr::TransmogOutfitSlotAndOptionInfo const* secondarySlotInfo = TransmogMgr::GetSlotAndOption(secondarySlot->GetSlot(), transmogSlotOption)) + { + UF::TransmogOutfitSlotData const& secondaryTransmogOutfitSlot = outfit->Slots[secondarySlotInfo->SlotIndex]; + + secondaryItemModifiedAppearanceId = secondaryTransmogOutfitSlot.ItemModifiedAppearanceID; + if (!isTransmogDisplayed(static_cast(*secondaryTransmogOutfitSlot.AppearanceDisplayType))) + secondaryItemModifiedAppearanceId = m_activePlayerData->ViewedOutfit->Slots[secondarySlotInfo->SlotIndex].ItemModifiedAppearanceID; + } + } + + Trinity::StringFormatTo(std::back_inserter(result), "{} {} {} {} {} ", inventoryType, displayId, itemVisual, subClass, secondaryItemModifiedAppearanceId); + } + else + result += "0 0 0 0 0 "sv; + } + + for (uint32 i = EQUIPMENT_SLOT_END; i < REAGENT_BAG_SLOT_END; ++i) + result += "0 0 0 0 0 "sv; + + return result; +} + void Player::OnPhaseChange() { Unit::OnPhaseChange(); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 16bd13ff59..5381cfccd6 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -135,6 +135,13 @@ namespace WorldPackets struct TraitConfig; struct TraitEntry; } + + namespace Transmogrification + { + struct TransmogOutfitDataInfo; + struct TransmogOutfitSituationInfo; + struct TransmogOutfitSlotData; + } } TC_GAME_API uint32 GetBagSize(Bag const* bag); @@ -334,7 +341,8 @@ enum ActionButtonType ACTION_BUTTON_CMACRO = ACTION_BUTTON_C | ACTION_BUTTON_MACRO, ACTION_BUTTON_COMPANION = 0x50, ACTION_BUTTON_MOUNT = 0x60, - ACTION_BUTTON_ITEM = 0x80 + ACTION_BUTTON_ITEM = 0x80, + ACTION_BUTTON_OUTFIT = 0x90 }; enum class HonorGainSource : uint8 @@ -548,6 +556,7 @@ enum PlayerLocalFlags PLAYER_LOCAL_FLAG_CHARACTER_BANK_DISABLED = 0x00080000, PLAYER_LOCAL_FLAG_CHARACTER_BANK_CONVERSION_FAILED = 0x00100000, PLAYER_LOCAL_FLAG_ACCOUNT_BANK_DISABLED = 0x00200000, + PLAYER_LOCAL_FLAG_FREE_TRANSMOG_CLAIMED = 0x00400000, }; DEFINE_ENUM_FLAG(PlayerLocalFlags); @@ -586,6 +595,8 @@ enum PlayerExtraFlags PLAYER_EXTRA_HAS_RACE_CHANGED = 0x0200, PLAYER_EXTRA_GRANTED_LEVELS_FROM_RAF = 0x0400, PLAYER_EXTRA_LEVEL_BOOSTED = 0x0800, + + PLAYER_EXTRA_FLAG_FREE_TRANSMOG_CLAIMED = 0x1000, }; // 2^n values @@ -937,6 +948,9 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_ACHIEVEMENTS, PLAYER_LOGIN_QUERY_LOAD_CRITERIA_PROGRESS, PLAYER_LOGIN_QUERY_LOAD_EQUIPMENT_SETS, + PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFIT, + PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFIT_SITUATION, + PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFIT_SLOT, PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFITS, PLAYER_LOGIN_QUERY_LOAD_BG_DATA, PLAYER_LOGIN_QUERY_LOAD_GLYPHS, @@ -1264,6 +1278,8 @@ class TC_GAME_API Player final : public Unit, public GridObject void SetBeenGrantedLevelsFromRaF() { m_ExtraFlags |= PLAYER_EXTRA_GRANTED_LEVELS_FROM_RAF; } bool HasLevelBoosted() const { return (m_ExtraFlags & PLAYER_EXTRA_LEVEL_BOOSTED) != 0; } void SetHasLevelBoosted() { m_ExtraFlags |= PLAYER_EXTRA_LEVEL_BOOSTED; } + bool HasClaimedFreeTransmog() const { return (m_ExtraFlags & PLAYER_EXTRA_FLAG_FREE_TRANSMOG_CLAIMED) != 0; } + void SetHasClaimedFreeTransmog() { m_ExtraFlags |= PLAYER_EXTRA_FLAG_FREE_TRANSMOG_CLAIMED; } uint32 GetXP() const { return m_activePlayerData->XP; } uint32 GetXPForNextLevel() const { return m_activePlayerData->NextLevelXP; } @@ -1550,7 +1566,7 @@ class TC_GAME_API Player final : public Unit, public GridObject void ApplyEquipCooldown(Item* pItem); void QuickEquipItem(uint16 pos, Item* pItem); void VisualizeItem(uint8 slot, Item* pItem); - void SetVisibleItemSlot(uint8 slot, Item* pItem); + void SetVisibleItemSlot(uint8 slot, Item const* item); Item* BankItem(ItemPosCountVec const& dest, Item* pItem, bool update); void RemoveItem(uint8 bag, uint8 slot, bool update); void MoveItemFromInventory(uint8 bag, uint8 slot, bool update); @@ -2987,6 +3003,18 @@ class TC_GAME_API Player final : public Unit, public GridObject bool CanEnableWarModeInArea() const; void UpdateWarModeAuras(); + void AddUnlockedTransmogOutfits(std::span transmogOutfitIds); + void AddUnlockedTransmogOutfit(int32 transmogOutfitIds) { AddUnlockedTransmogOutfits(std::span(&transmogOutfitIds, 1)); } + + void CreateTransmogOutfit(uint32 id, WorldPackets::Transmogrification::TransmogOutfitDataInfo const& outfitData); + void InitializeNewTransmogOutfit(UF::MutableFieldReference outfit, + uint32 id, WorldPackets::Transmogrification::TransmogOutfitDataInfo const& outfitData); + bool UpdateTransmogOutfit(uint32 id, WorldPackets::Transmogrification::TransmogOutfitDataInfo const& outfitData); + void UpdateTransmogOutfitSituations(uint32 id, bool situationsEnabled, std::span situations); + void UpdateTransmogOutfitSlots(uint32 id, std::span slots); + void EquipTransmogOutfit(uint32 id, TransmogSituationTrigger trigger, Optional locked); + std::string GetCharacterSelectOutfit() const; + std::string GetDebugInfo() const override; UF::UpdateField m_playerData; @@ -3075,7 +3103,9 @@ class TC_GAME_API Player final : public Unit, public GridObject void _LoadDeclinedNames(PreparedQueryResult result); void _LoadArenaTeamInfo(PreparedQueryResult result); void _LoadEquipmentSets(PreparedQueryResult result); - void _LoadTransmogOutfits(PreparedQueryResult result); + void _LoadTransmogCustomSets(PreparedQueryResult result); + void _LoadTransmogOutfits(PreparedQueryResult setsResult, PreparedQueryResult situationsResult, PreparedQueryResult slotsResult, + int32 equippedTransmogOutfitId, bool locked); void _LoadBGData(PreparedQueryResult result); void _LoadGlyphs(PreparedQueryResult result); void _LoadTalents(PreparedQueryResult result); @@ -3106,6 +3136,7 @@ class TC_GAME_API Player final : public Unit, public GridObject void _SaveSpells(CharacterDatabaseTransaction trans); void _SaveStoredAuraTeleportLocations(CharacterDatabaseTransaction trans); void _SaveEquipmentSets(CharacterDatabaseTransaction trans); + void _SaveTransmogOutfits(CharacterDatabaseTransaction trans); void _SaveBGData(CharacterDatabaseTransaction trans); void _SaveGlyphs(CharacterDatabaseTransaction trans) const; void _SaveTalents(CharacterDatabaseTransaction trans); @@ -3254,6 +3285,7 @@ class TC_GAME_API Player final : public Unit, public GridObject std::unique_ptr m_runes; EquipmentSetContainer _equipmentSets; + std::set m_changedTransmogOutfits; bool CanNeverSee(WorldObject const* obj, bool ignorePhaseShift = false) const override; bool CanAlwaysSee(WorldObject const* obj) const override; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 3126b09860..dc23ab27c2 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -70,6 +70,7 @@ #include "TerrainMgr.h" #include "ThreadPool.h" #include "Timer.h" +#include "TransmogMgr.h" #include "TransportMgr.h" #include "VMapFactory.h" #include "VMapManager.h" @@ -1509,12 +1510,12 @@ void ObjectMgr::LoadEquipmentTemplates() } // AppearanceModId 0 is always valid - if (equipmentInfo.Items[i].AppearanceModId && !sDB2Manager.GetItemModifiedAppearance(equipmentInfo.Items[i].ItemId, equipmentInfo.Items[i].AppearanceModId)) + if (equipmentInfo.Items[i].AppearanceModId && !TransmogMgr::GetItemModifiedAppearance(equipmentInfo.Items[i].ItemId, equipmentInfo.Items[i].AppearanceModId)) { TC_LOG_ERROR("sql.sql", "Unknown item appearance for (ID={}, AppearanceModID={}) pair in creature_equip_template.ItemID{} creature_equip_template.AppearanceModID{} " "for CreatureID = {} and ID={}, forced to default.", equipmentInfo.Items[i].ItemId, equipmentInfo.Items[i].AppearanceModId, i + 1, i + 1, entry, id); - if (ItemModifiedAppearanceEntry const* defaultAppearance = sDB2Manager.GetDefaultItemModifiedAppearance(equipmentInfo.Items[i].ItemId)) + if (ItemModifiedAppearanceEntry const* defaultAppearance = TransmogMgr::GetDefaultItemModifiedAppearance(equipmentInfo.Items[i].ItemId)) equipmentInfo.Items[i].AppearanceModId = defaultAppearance->ItemAppearanceModifierID; else equipmentInfo.Items[i].AppearanceModId = 0; diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 842b6cb845..d9f29ada31 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -61,6 +61,7 @@ #include "SocialMgr.h" #include "StringConvert.h" #include "SystemPackets.h" +#include "TransmogMgr.h" #include "Util.h" #include "World.h" #include @@ -257,6 +258,18 @@ bool LoginQueryHolder::Initialize() stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFITS, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_TRANSMOG_OUTFIT); + stmt->setUInt64(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFIT, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_TRANSMOG_OUTFIT_SITUATION); + stmt->setUInt64(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFIT_SITUATION, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_TRANSMOG_OUTFIT_SLOT); + stmt->setUInt64(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFIT_SLOT, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_CUF_PROFILES); stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CUF_PROFILES, stmt); @@ -2047,10 +2060,7 @@ void WorldSession::HandleEquipmentSetSave(WorldPackets::EquipmentSet::SaveEquipm auto validateIllusion = [this](uint32 enchantId) -> bool { SpellItemEnchantmentEntry const* illusion = sSpellItemEnchantmentStore.LookupEntry(enchantId); - if (!illusion) - return false; - - if (!illusion->ItemVisual || !illusion->GetFlags().HasFlag(SpellItemEnchantmentFlags::AllowTransmog)) + if (!illusion || !TransmogMgr::GetTransmogIllusionForSpellItemEnchantment(enchantId)) return false; if (!ConditionMgr::IsPlayerMeetingCondition(_player, illusion->TransmogUseConditionID)) diff --git a/src/server/game/Handlers/TransmogrificationHandler.cpp b/src/server/game/Handlers/TransmogrificationHandler.cpp index 5e89574464..36f1622840 100644 --- a/src/server/game/Handlers/TransmogrificationHandler.cpp +++ b/src/server/game/Handlers/TransmogrificationHandler.cpp @@ -23,6 +23,7 @@ #include "NPCPackets.h" #include "ObjectMgr.h" #include "Player.h" +#include "TransmogMgr.h" #include "TransmogrificationPackets.h" void WorldSession::HandleTransmogrifyItems(WorldPackets::Transmogrification::TransmogrifyItems& transmogrifyItems) @@ -128,7 +129,7 @@ void WorldSession::HandleTransmogrifyItems(WorldPackets::Transmogrification::Tra return; } - TransmogIllusionEntry const* illusion = sDB2Manager.GetTransmogIllusionForEnchantment(transmogItem.SpellItemEnchantmentID); + TransmogIllusionEntry const* illusion = TransmogMgr::GetTransmogIllusionForSpellItemEnchantment(transmogItem.SpellItemEnchantmentID); if (!illusion) { TC_LOG_DEBUG("network", "WORLD: HandleTransmogrifyItems - {}, Name: {} tried to transmogrify illusion using invalid enchant ({}).", player->GetGUID().ToString(), player->GetName(), transmogItem.SpellItemEnchantmentID); @@ -326,6 +327,283 @@ void WorldSession::HandleTransmogrifyItems(WorldPackets::Transmogrification::Tra } } +void WorldSession::HandleTransmogOutfitNew(WorldPackets::Transmogrification::TransmogOutfitNew const& transmogOutfitNew) +{ + if (!_player->GetNPCIfCanInteractWith(transmogOutfitNew.Npc, UNIT_NPC_FLAG_TRANSMOGRIFIER, UNIT_NPC_FLAG_2_NONE)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitNew - {} not found or player can't interact with it.", + GetPlayerInfo(), transmogOutfitNew.Npc); + return; + } + + _player->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Interacting); + + if (transmogOutfitNew.Source != TransmogOutfitEntrySource::PlayerPurchased) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitNew - source {} not allowed.", + GetPlayerInfo(), transmogOutfitNew.Source); + return; + } + + if (transmogOutfitNew.Info.SetType != TransmogOutfitSetType::Outfit) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitNew - set type {} not allowed.", + GetPlayerInfo(), transmogOutfitNew.Info.SetType); + return; + } + + TransmogOutfitEntryEntry const* transmogOutfitEntry = TransmogMgr::GetNextOutfitToUnlock(transmogOutfitNew.Source, _player); + if (!transmogOutfitEntry) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitNew - no next unlockable outfit entry found for source {}.", + GetPlayerInfo(), transmogOutfitNew.Source); + return; + } + + if (!_player->HasEnoughMoney(transmogOutfitEntry->Cost)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitNew - not enough money.", + GetPlayerInfo()); + return; + } + + GetCollectionMgr()->AddTransmogOutfit(transmogOutfitEntry->ID); + _player->CreateTransmogOutfit(transmogOutfitEntry->ID, transmogOutfitNew.Info); + _player->ModifyMoney(-int64(transmogOutfitEntry->Cost)); + + WorldPackets::Transmogrification::TransmogOutfitNewEntryAdded transmogOutfitNewEntryAdded; + transmogOutfitNewEntryAdded.TransmogOutfitID = transmogOutfitEntry->ID; + SendPacket(transmogOutfitNewEntryAdded.Write()); +} + +void WorldSession::HandleTransmogOutfitUpdateInfo(WorldPackets::Transmogrification::TransmogOutfitUpdateInfo const& transmogOutfitUpdateInfo) +{ + if (!_player->GetNPCIfCanInteractWith(transmogOutfitUpdateInfo.Npc, UNIT_NPC_FLAG_TRANSMOGRIFIER, UNIT_NPC_FLAG_2_NONE)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitNew - {} not found or player can't interact with it.", + GetPlayerInfo(), transmogOutfitUpdateInfo.Npc); + return; + } + + _player->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Interacting); + + if (!_player->UpdateTransmogOutfit(transmogOutfitUpdateInfo.OutfitID, transmogOutfitUpdateInfo.Info)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitUpdateInfo - player does not have outfit {}.", + GetPlayerInfo(), transmogOutfitUpdateInfo.OutfitID); + return; + } + + // SMSG_UPDATE_OBJECT must be received by client before transmog packet for UI to properly update + Player::ValuesUpdateForPlayerWithMaskSender sendUpdateObject(_player); + sendUpdateObject.ActivePlayerMask.MarkChanged(&UF::ActivePlayerData::TransmogOutfits); + sendUpdateObject(_player); + + WorldPackets::Transmogrification::TransmogOutfitInfoUpdated transmogOutfitInfoUpdated; + transmogOutfitInfoUpdated.TransmogOutfitID = transmogOutfitUpdateInfo.OutfitID; + transmogOutfitInfoUpdated.OutfitInfo = &transmogOutfitUpdateInfo.Info; + SendPacket(transmogOutfitInfoUpdated.Write()); +} + +void WorldSession::HandleTransmogOutfitUpdateSituations(WorldPackets::Transmogrification::TransmogOutfitUpdateSituations const& transmogOutfitUpdateSituations) +{ + if (!_player->GetNPCIfCanInteractWith(transmogOutfitUpdateSituations.Npc, UNIT_NPC_FLAG_TRANSMOGRIFIER, UNIT_NPC_FLAG_2_NONE)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitNew - {} not found or player can't interact with it.", + GetPlayerInfo(), transmogOutfitUpdateSituations.Npc); + return; + } + + _player->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Interacting); + + if (!_player->m_activePlayerData->TransmogOutfits.Get(transmogOutfitUpdateSituations.OutfitID)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitUpdateSituations - player does not have outfit {}.", + GetPlayerInfo(), transmogOutfitUpdateSituations.OutfitID); + return; + } + + if (!TransmogMgr::ValidateSituations(transmogOutfitUpdateSituations.Situations)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitUpdateSituations - player sent invalid situations.", + GetPlayerInfo()); + return; + } + + _player->UpdateTransmogOutfitSituations(transmogOutfitUpdateSituations.OutfitID, transmogOutfitUpdateSituations.SituationsEnabled, + transmogOutfitUpdateSituations.Situations); + + // SMSG_UPDATE_OBJECT must be received by client before transmog packet for UI to properly update + Player::ValuesUpdateForPlayerWithMaskSender sendUpdateObject(_player); + sendUpdateObject.ActivePlayerMask.MarkChanged(&UF::ActivePlayerData::TransmogOutfits); + sendUpdateObject(_player); + + WorldPackets::Transmogrification::TransmogOutfitSituationsUpdated transmogOutfitSituationsUpdated; + transmogOutfitSituationsUpdated.TransmogOutfitID = transmogOutfitUpdateSituations.OutfitID; + transmogOutfitSituationsUpdated.SituationsEnabled = transmogOutfitUpdateSituations.SituationsEnabled; + transmogOutfitSituationsUpdated.Situations = transmogOutfitUpdateSituations.Situations; + SendPacket(transmogOutfitSituationsUpdated.Write()); +} + +void WorldSession::HandleTransmogOutfitUpdateSlots(WorldPackets::Transmogrification::TransmogOutfitUpdateSlots const& transmogOutfitUpdateSlots) +{ + if (!_player->GetNPCIfCanInteractWith(transmogOutfitUpdateSlots.Npc, UNIT_NPC_FLAG_TRANSMOGRIFIER, UNIT_NPC_FLAG_2_NONE)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitNew - {} not found or player can't interact with it.", + GetPlayerInfo(), transmogOutfitUpdateSlots.Npc); + return; + } + + _player->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Interacting); + + UF::TransmogOutfitData const* transmogOutfit = _player->m_activePlayerData->TransmogOutfits.Get(transmogOutfitUpdateSlots.OutfitID); + if (!transmogOutfit) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitUpdateSlots - player does not have outfit {}.", + GetPlayerInfo(), transmogOutfitUpdateSlots.OutfitID); + return; + } + + if (!TransmogMgr::ValidateSlots(transmogOutfitUpdateSlots.Slots)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitUpdateSlots - player sent invalid slots.", + GetPlayerInfo()); + return; + } + + std::vector bindAppearances; + + for (WorldPackets::Transmogrification::TransmogOutfitSlotData const& slot : transmogOutfitUpdateSlots.Slots) + { + if (slot.ItemModifiedAppearanceID) + { + auto [hasAppearance, isTemporary] = GetCollectionMgr()->HasItemAppearance(slot.ItemModifiedAppearanceID); + if (!hasAppearance) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitUpdateSlots - player does not have appearance {} in collection.", + GetPlayerInfo(), slot.ItemModifiedAppearanceID); + return; + } + + if (isTemporary) + bindAppearances.push_back(slot.ItemModifiedAppearanceID); + } + + if (slot.SpellItemEnchantmentID && !GetCollectionMgr()->HasTransmogIllusion(TransmogMgr::GetTransmogIllusionForSpellItemEnchantment(slot.SpellItemEnchantmentID)->ID)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitUpdateSlots - player does not have enchant {} in illusion collection.", + GetPlayerInfo(), slot.SpellItemEnchantmentID); + return; + } + } + + if (transmogOutfitUpdateSlots.UseAvailableDiscount && _player->HasPlayerLocalFlag(PLAYER_LOCAL_FLAG_FREE_TRANSMOG_CLAIMED)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitUpdateSlots - player has already claimed free transmog before.", + GetPlayerInfo()); + return; + } + + // calculate cost + float baseCost = 0; + if (uint32 curveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::TransmogCost)) + baseCost = sDB2Manager.GetCurveValueAt(curveId, std::max(_player->GetLevel(), _player->m_activePlayerData->MaxLevel)); + + float costMultiplier = 1.0f; + TransmogOutfitEntryEntry const* transmogOutfitEntry = sTransmogOutfitEntryStore.AssertEntry(transmogOutfitUpdateSlots.OutfitID); + if (transmogOutfitEntry->HasFlag(TransmogOutfitEntryFlags::UseOverrideCostModifier)) + costMultiplier *= transmogOutfitEntry->OverrideCostModifier; + + if (_player->HasAuraType(SPELL_AURA_MOD_TRANSMOG_OUTFIT_UPDATE_COST)) + costMultiplier *= _player->m_activePlayerData->TransmogMetadata->CostMod; + + if (sChrRacesStore.AssertEntry(_player->GetRace())->GetFlags().HasFlag(ChrRacesFlag::VoidVendorDiscount)) + costMultiplier *= 0.5f; + + uint64 cost = 0; + + if (!transmogOutfitUpdateSlots.UseAvailableDiscount) + { + auto oldSlotItr = transmogOutfit->Slots.begin(); + auto oldSlotEnd = transmogOutfit->Slots.end(); + for (WorldPackets::Transmogrification::TransmogOutfitSlotData const& slot : transmogOutfitUpdateSlots.Slots) + { + oldSlotItr = std::ranges::find(oldSlotItr, oldSlotEnd, + std::pair(AsUnderlyingType(slot.Slot), AsUnderlyingType(slot.SlotOption)), + [](UF::TransmogOutfitSlotData const& slotData) { return std::pair(*slotData.Slot, *slotData.SlotOption); }); + + auto [slotEntry, slotOptionEntry, _] = *TransmogMgr::GetSlotAndOption(slot.Slot, slot.SlotOption); + + if (slot.AppearanceDisplayType == TransmogOutfitDisplayType::Assigned && oldSlotItr->ItemModifiedAppearanceID != slot.ItemModifiedAppearanceID) + { + if (slotEntry) + cost = static_cast(std::floor(baseCost * slotEntry->ItemCostMultiplier)) + cost; + + if (slotOptionEntry) + cost = static_cast(std::floor(baseCost * slotOptionEntry->ItemCostMultiplier)) + cost; + } + + if (slot.IllusionDisplayType == TransmogOutfitDisplayType::Assigned && oldSlotItr->SpellItemEnchantmentID != slot.SpellItemEnchantmentID) + { + if (slotEntry) + cost = static_cast(std::floor(baseCost * slotEntry->IllusionCostMultiplier)) + cost; + + if (slotOptionEntry) + cost = static_cast(std::floor(baseCost * slotOptionEntry->IllusionCostMultiplier)) + cost; + } + + ++oldSlotItr; + } + + cost = static_cast(std::clamp(costMultiplier, 0.0f, 1.0f) * cost); + + if (cost != transmogOutfitUpdateSlots.Cost) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitUpdateSlots - player sent invalid cost {}.", + GetPlayerInfo(), transmogOutfitUpdateSlots.Cost); + return; + } + + if (!_player->HasEnoughMoney(cost)) + { + TC_LOG_ERROR("entities.player.cheat", "{} WorldSession::HandleTransmogOutfitUpdateSlots - not enough money.", + GetPlayerInfo()); + return; + } + } + else + { + _player->SetPlayerLocalFlag(PLAYER_LOCAL_FLAG_FREE_TRANSMOG_CLAIMED); + _player->SetHasClaimedFreeTransmog(); + } + + _player->ModifyMoney(-int64(cost)); + + _player->UpdateTransmogOutfitSlots(transmogOutfitUpdateSlots.OutfitID, transmogOutfitUpdateSlots.Slots); + + if (transmogOutfitUpdateSlots.OutfitID == _player->m_activePlayerData->TransmogMetadata->TransmogOutfitID) + _player->EquipTransmogOutfit(transmogOutfitUpdateSlots.OutfitID, TransmogSituationTrigger::TransmogUpdate, {}); + + WorldPackets::Transmogrification::TransmogOutfitSlotsUpdated transmogOutfitSlotsUpdated; + transmogOutfitSlotsUpdated.TransmogOutfitID = transmogOutfitUpdateSlots.OutfitID; + transmogOutfitSlotsUpdated.Slots = transmogOutfitUpdateSlots.Slots; + SendPacket(transmogOutfitSlotsUpdated.Write()); + + for (uint32 itemModifedAppearanceId : bindAppearances) + { + std::unordered_set itemsProvidingAppearance = GetCollectionMgr()->GetItemsProvidingTemporaryAppearance(itemModifedAppearanceId); + for (ObjectGuid const& itemGuid : itemsProvidingAppearance) + { + if (Item* item = _player->GetItemByGuid(itemGuid)) + { + item->SetNotRefundable(_player); + item->ClearSoulboundTradeable(_player); + GetCollectionMgr()->AddItemAppearance(item); + } + } + } +} + void WorldSession::SendOpenTransmogrifier(ObjectGuid const& guid) { WorldPackets::NPC::NPCInteractionOpenResult npcInteraction; diff --git a/src/server/game/Server/Packets/TransmogrificationPackets.cpp b/src/server/game/Server/Packets/TransmogrificationPackets.cpp index 68b5c6632a..8d507ac68d 100644 --- a/src/server/game/Server/Packets/TransmogrificationPackets.cpp +++ b/src/server/game/Server/Packets/TransmogrificationPackets.cpp @@ -37,9 +37,164 @@ void TransmogrifyItems::Read() for (TransmogrifyItem& item : Items) _worldPacket >> item; + _worldPacket.ResetBitPos(); _worldPacket >> Bits<1>(CurrentSpecOnly); } +ByteBuffer& operator>>(ByteBuffer& data, TransmogOutfitDataInfo& transmogOutfitDataInfo) +{ + data.ResetBitPos(); + data >> As(transmogOutfitDataInfo.SetType); + data >> transmogOutfitDataInfo.Icon; + data >> SizedString::BitsSize<8>(transmogOutfitDataInfo.Name); + data >> Bits<1>(transmogOutfitDataInfo.SituationsEnabled); + + data >> SizedString::Data(transmogOutfitDataInfo.Name); + + return data; +} + +ByteBuffer& operator<<(ByteBuffer& data, TransmogOutfitDataInfo const& transmogOutfitDataInfo) +{ + data << As(transmogOutfitDataInfo.SetType); + data << uint32(transmogOutfitDataInfo.Icon); + data << SizedString::BitsSize<8>(transmogOutfitDataInfo.Name); + data << Bits<1>(transmogOutfitDataInfo.SituationsEnabled); + data.FlushBits(); + + data << SizedString::Data(transmogOutfitDataInfo.Name); + + return data; +} + +void TransmogOutfitNew::Read() +{ + _worldPacket >> Npc; + _worldPacket >> As(Source); + _worldPacket >> Info; +} + +WorldPacket const* TransmogOutfitNewEntryAdded::Write() +{ + _worldPacket << uint32(TransmogOutfitID); + + return &_worldPacket; +} + +void TransmogOutfitUpdateInfo::Read() +{ + _worldPacket >> OutfitID; + _worldPacket >> Npc; + _worldPacket >> Info; +} + +WorldPacket const* TransmogOutfitInfoUpdated::Write() +{ + _worldPacket << uint32(TransmogOutfitID); + _worldPacket << *OutfitInfo; + + return &_worldPacket; +} + +ByteBuffer& operator>>(ByteBuffer& data, TransmogOutfitSituationInfo& transmogOutfitSituationInfo) +{ + data >> transmogOutfitSituationInfo.SituationID; + data >> transmogOutfitSituationInfo.SpecID; + data >> transmogOutfitSituationInfo.LoadoutID; + data >> transmogOutfitSituationInfo.EquipmentSetID; + + return data; +} + +ByteBuffer& operator<<(ByteBuffer& data, TransmogOutfitSituationInfo const& transmogOutfitSituationInfo) +{ + data << uint32(transmogOutfitSituationInfo.SituationID); + data << uint32(transmogOutfitSituationInfo.SpecID); + data << uint32(transmogOutfitSituationInfo.LoadoutID); + data << uint32(transmogOutfitSituationInfo.EquipmentSetID); + + return data; +} + +void TransmogOutfitUpdateSituations::Read() +{ + _worldPacket >> OutfitID; + _worldPacket >> Npc; + _worldPacket >> Size(Situations); + for (TransmogOutfitSituationInfo& situation : Situations) + _worldPacket >> situation; + + _worldPacket.ResetBitPos(); + _worldPacket >> Bits<1>(SituationsEnabled); +} + +WorldPacket const* TransmogOutfitSituationsUpdated::Write() +{ + _worldPacket << uint32(TransmogOutfitID); + _worldPacket << Size(Situations); + + for (TransmogOutfitSituationInfo const& situation : Situations) + _worldPacket << situation; + + _worldPacket << Bits<1>(SituationsEnabled); + _worldPacket.FlushBits(); + + return &_worldPacket; +} + +ByteBuffer& operator>>(ByteBuffer& data, TransmogOutfitSlotData& transmogOutfitSlotData) +{ + data >> As(transmogOutfitSlotData.Slot); + data >> As(transmogOutfitSlotData.SlotOption); + data >> transmogOutfitSlotData.ItemModifiedAppearanceID; + data >> As(transmogOutfitSlotData.AppearanceDisplayType); + data >> transmogOutfitSlotData.SpellItemEnchantmentID; + data >> As(transmogOutfitSlotData.IllusionDisplayType); + data >> transmogOutfitSlotData.Flags; + + return data; +} + +ByteBuffer& operator<<(ByteBuffer& data, TransmogOutfitSlotData const& transmogOutfitSlotData) +{ + data << As(transmogOutfitSlotData.Slot); + data << As(transmogOutfitSlotData.SlotOption); + data << uint32(transmogOutfitSlotData.ItemModifiedAppearanceID); + data << As(transmogOutfitSlotData.AppearanceDisplayType); + data << uint32(transmogOutfitSlotData.SpellItemEnchantmentID); + data << As(transmogOutfitSlotData.IllusionDisplayType); + data << uint32(transmogOutfitSlotData.Flags); + + return data; +} + +void TransmogOutfitUpdateSlots::Read() +{ + _worldPacket >> OutfitID; + _worldPacket >> Size(Slots); + _worldPacket >> Npc; + _worldPacket >> Cost; + + for (TransmogOutfitSlotData& slot : Slots) + _worldPacket >> slot; + + _worldPacket.ResetBitPos(); + _worldPacket >> Bits<1>(UseAvailableDiscount); + + std::ranges::sort(Slots, std::ranges::less(), [](TransmogOutfitSlotData const& slot) { return std::pair(slot.Slot, slot.SlotOption); }); +} + +WorldPacket const* TransmogOutfitSlotsUpdated::Write() +{ + _worldPacket << uint32(TransmogOutfitID); + _worldPacket << Size(Slots); + + for (TransmogOutfitSlotData const& slot : Slots) + _worldPacket << slot; + + return &_worldPacket; +} + WorldPacket const* AccountTransmogUpdate::Write() { _worldPacket << Bits<1>(IsFullUpdate); diff --git a/src/server/game/Server/Packets/TransmogrificationPackets.h b/src/server/game/Server/Packets/TransmogrificationPackets.h index b8a496f594..12f9a859fa 100644 --- a/src/server/game/Server/Packets/TransmogrificationPackets.h +++ b/src/server/game/Server/Packets/TransmogrificationPackets.h @@ -22,6 +22,12 @@ #include "ObjectGuid.h" #include "PacketUtilities.h" +enum class TransmogOutfitDisplayType : uint8; +enum class TransmogOutfitEntrySource : uint8; +enum class TransmogOutfitSetType : uint8; +enum class TransmogOutfitSlot : int8; +enum class TransmogOutfitSlotOption : uint8; + namespace WorldPackets { namespace Transmogrification @@ -51,6 +57,128 @@ namespace WorldPackets bool CurrentSpecOnly = false; }; + struct TransmogOutfitDataInfo + { + TransmogOutfitSetType SetType = { }; + bool SituationsEnabled = false; + uint32 Icon = 0; + std::string_view Name; + }; + + class TransmogOutfitNew final : public ClientPacket + { + public: + explicit TransmogOutfitNew(WorldPacket&& packet) : ClientPacket(CMSG_TRANSMOG_OUTFIT_NEW, std::move(packet)) { } + + void Read() override; + + ObjectGuid Npc; + TransmogOutfitDataInfo Info; + TransmogOutfitEntrySource Source = { }; + }; + + class TransmogOutfitNewEntryAdded final : public ServerPacket + { + public: + explicit TransmogOutfitNewEntryAdded() : ServerPacket(SMSG_TRANSMOG_OUTFIT_NEW_ENTRY_ADDED, 4) { } + + WorldPacket const* Write() override; + + uint32 TransmogOutfitID = 0; + }; + + class TransmogOutfitUpdateInfo final : public ClientPacket + { + public: + explicit TransmogOutfitUpdateInfo(WorldPacket&& packet) : ClientPacket(CMSG_TRANSMOG_OUTFIT_UPDATE_INFO, std::move(packet)) { } + + void Read() override; + + uint32 OutfitID = 0; + ObjectGuid Npc; + TransmogOutfitDataInfo Info; + }; + + class TransmogOutfitInfoUpdated final : public ServerPacket + { + public: + explicit TransmogOutfitInfoUpdated() : ServerPacket(SMSG_TRANSMOG_OUTFIT_INFO_UPDATED, 4 + 1 + 4 + 1 + 1 + 128) { } + + WorldPacket const* Write() override; + + uint32 TransmogOutfitID = 0; + TransmogOutfitDataInfo const* OutfitInfo = nullptr; + }; + + struct TransmogOutfitSituationInfo + { + uint32 SituationID = 0; + uint32 SpecID = 0; + uint32 LoadoutID = 0; + uint32 EquipmentSetID = 0; + }; + + class TransmogOutfitUpdateSituations final : public ClientPacket + { + public: + explicit TransmogOutfitUpdateSituations(WorldPacket&& packet) : ClientPacket(CMSG_TRANSMOG_OUTFIT_UPDATE_SITUATIONS, std::move(packet)) { } + + void Read() override; + + uint32 OutfitID = 0; + ObjectGuid Npc; + bool SituationsEnabled = false; + Array Situations; + }; + + class TransmogOutfitSituationsUpdated final : public ServerPacket + { + public: + explicit TransmogOutfitSituationsUpdated() : ServerPacket(SMSG_TRANSMOG_OUTFIT_SITUATIONS_UPDATED, 4 + 4 + 10 * (4 + 4 + 4 + 4) + 1) { } + + WorldPacket const* Write() override; + + uint32 TransmogOutfitID = 0; + bool SituationsEnabled = false; + std::span Situations; + }; + + struct TransmogOutfitSlotData + { + TransmogOutfitSlot Slot = { }; + TransmogOutfitSlotOption SlotOption = { }; + TransmogOutfitDisplayType AppearanceDisplayType = { }; + TransmogOutfitDisplayType IllusionDisplayType = { }; + uint32 ItemModifiedAppearanceID = 0; + uint32 SpellItemEnchantmentID = 0; + uint32 Flags = 0; + }; + + class TransmogOutfitUpdateSlots final : public ClientPacket + { + public: + explicit TransmogOutfitUpdateSlots(WorldPacket&& packet) : ClientPacket(CMSG_TRANSMOG_OUTFIT_UPDATE_SLOTS, std::move(packet)) { } + + void Read() override; + + uint32 OutfitID = 0; + Array Slots; + ObjectGuid Npc; + uint64 Cost = 0; + bool UseAvailableDiscount = false; + }; + + class TransmogOutfitSlotsUpdated final : public ServerPacket + { + public: + explicit TransmogOutfitSlotsUpdated() : ServerPacket(SMSG_TRANSMOG_OUTFIT_SLOTS_UPDATED, 4 + 4 + 30 * (1 + 1 + 4 + 1 + 4 + 1 + 4)) { } + + WorldPacket const* Write() override; + + uint32 TransmogOutfitID = 0; + std::span Slots; + }; + class AccountTransmogUpdate final : public ServerPacket { public: diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index bb90a71f0a..52ebf06b6d 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -1105,10 +1105,10 @@ void OpcodeTable::InitializeClientOpcodes() DEFINE_HANDLER(CMSG_TRAITS_TALENT_TEST_UNLEARN_SPELLS, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_TRANSFER_CURRENCY_FROM_ACCOUNT_CHARACTER, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_TRANSMOGRIFY_ITEMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTransmogrifyItems); - DEFINE_HANDLER(CMSG_TRANSMOG_OUTFIT_NEW, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_TRANSMOG_OUTFIT_UPDATE_INFO, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_TRANSMOG_OUTFIT_UPDATE_SITUATIONS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_TRANSMOG_OUTFIT_UPDATE_SLOTS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_TRANSMOG_OUTFIT_NEW, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTransmogOutfitNew); + DEFINE_HANDLER(CMSG_TRANSMOG_OUTFIT_UPDATE_INFO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTransmogOutfitUpdateInfo); + DEFINE_HANDLER(CMSG_TRANSMOG_OUTFIT_UPDATE_SITUATIONS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTransmogOutfitUpdateSituations); + DEFINE_HANDLER(CMSG_TRANSMOG_OUTFIT_UPDATE_SLOTS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTransmogOutfitUpdateSlots); DEFINE_HANDLER(CMSG_TURN_IN_PETITION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTurnInPetition); DEFINE_HANDLER(CMSG_TUTORIAL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTutorialFlag); DEFINE_HANDLER(CMSG_UI_MAP_QUEST_LINES_REQUEST, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleUiMapQuestLinesRequest); @@ -2454,10 +2454,10 @@ void OpcodeTable::InitializeServerOpcodes() DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRAIT_CONFIG_COMMIT_FAILED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSFER_ABORTED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSFER_PENDING, STATUS_NEVER, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSMOG_OUTFIT_INFO_UPDATED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSMOG_OUTFIT_NEW_ENTRY_ADDED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSMOG_OUTFIT_SITUATIONS_UPDATED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSMOG_OUTFIT_SLOTS_UPDATED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSMOG_OUTFIT_INFO_UPDATED, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSMOG_OUTFIT_NEW_ENTRY_ADDED, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSMOG_OUTFIT_SITUATIONS_UPDATED, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSMOG_OUTFIT_SLOTS_UPDATED, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_TREASURE_PICKER_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_TREASURE_PUNCH_LIST_ITEMS_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRIGGER_CINEMATIC, STATUS_NEVER, CONNECTION_TYPE_REALM); diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 0dfed8f095..2ab2959bc6 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -1136,11 +1136,7 @@ bool WorldSession::IsAddonRegistered(std::string_view prefix) const if (!_filterAddonMessages) // if we have hit the softcap (64) nothing should be filtered return true; - if (_registeredAddonPrefixes.empty()) - return false; - - std::vector::const_iterator itr = std::find(_registeredAddonPrefixes.begin(), _registeredAddonPrefixes.end(), prefix); - return itr != _registeredAddonPrefixes.end(); + return advstd::ranges::contains(_registeredAddonPrefixes, prefix); } void WorldSession::HandleUnregisterAllAddonPrefixesOpcode(WorldPackets::Chat::ChatUnregisterAllAddonPrefixes& /*packet*/) // empty packet @@ -1259,6 +1255,7 @@ public: ITEM_APPEARANCES, ITEM_FAVORITE_APPEARANCES, TRANSMOG_ILLUSIONS, + TRANSMOG_OUTFITS, WARBAND_SCENES, PLAYER_DATA_ELEMENTS_ACCOUNT, PLAYER_DATA_FLAGS_ACCOUNT, @@ -1309,6 +1306,10 @@ public: stmt->setUInt32(0, battlenetAccountId); ok = SetPreparedQuery(TRANSMOG_ILLUSIONS, stmt) && ok; + stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_TRANSMOG_OUTFITS); + stmt->setUInt32(0, battlenetAccountId); + ok = SetPreparedQuery(TRANSMOG_OUTFITS, stmt) && ok; + stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_WARBAND_SCENES); stmt->setUInt32(0, battlenetAccountId); ok = SetPreparedQuery(WARBAND_SCENES, stmt) && ok; @@ -1373,6 +1374,7 @@ void WorldSession::InitializeSessionCallback(LoginDatabaseQueryHolder const& hol _collectionMgr->LoadAccountMounts(holder.GetPreparedResult(AccountInfoQueryHolder::MOUNTS)); _collectionMgr->LoadAccountItemAppearances(holder.GetPreparedResult(AccountInfoQueryHolder::ITEM_APPEARANCES), holder.GetPreparedResult(AccountInfoQueryHolder::ITEM_FAVORITE_APPEARANCES)); _collectionMgr->LoadAccountTransmogIllusions(holder.GetPreparedResult(AccountInfoQueryHolder::TRANSMOG_ILLUSIONS)); + _collectionMgr->LoadAccountTransmogOutfits(holder.GetPreparedResult(AccountInfoQueryHolder::TRANSMOG_OUTFITS)); _collectionMgr->LoadAccountWarbandScenes(holder.GetPreparedResult(AccountInfoQueryHolder::WARBAND_SCENES)); LoadPlayerDataAccount(holder.GetPreparedResult(AccountInfoQueryHolder::PLAYER_DATA_ELEMENTS_ACCOUNT), holder.GetPreparedResult(AccountInfoQueryHolder::PLAYER_DATA_FLAGS_ACCOUNT)); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 4781d7df8f..a2228a4aad 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -819,6 +819,10 @@ namespace WorldPackets namespace Transmogrification { class TransmogrifyItems; + class TransmogOutfitNew; + class TransmogOutfitUpdateInfo; + class TransmogOutfitUpdateSituations; + class TransmogOutfitUpdateSlots; } namespace Vehicle @@ -1790,6 +1794,10 @@ class TC_GAME_API WorldSession // Transmogrification void HandleTransmogrifyItems(WorldPackets::Transmogrification::TransmogrifyItems& transmogrifyItems); + void HandleTransmogOutfitNew(WorldPackets::Transmogrification::TransmogOutfitNew const& transmogOutfitNew); + void HandleTransmogOutfitUpdateInfo(WorldPackets::Transmogrification::TransmogOutfitUpdateInfo const& transmogOutfitUpdateInfo); + void HandleTransmogOutfitUpdateSituations(WorldPackets::Transmogrification::TransmogOutfitUpdateSituations const& transmogOutfitUpdateSituations); + void HandleTransmogOutfitUpdateSlots(WorldPackets::Transmogrification::TransmogOutfitUpdateSlots const& transmogOutfitUpdateSlots); // Miscellaneous void HandleSpellClick(WorldPackets::Spells::SpellClick& spellClick); diff --git a/src/server/game/Spells/Auras/SpellAuraDefines.h b/src/server/game/Spells/Auras/SpellAuraDefines.h index 6ab522d838..a75abe985d 100644 --- a/src/server/game/Spells/Auras/SpellAuraDefines.h +++ b/src/server/game/Spells/Auras/SpellAuraDefines.h @@ -735,7 +735,7 @@ enum AuraType : uint32 SPELL_AURA_ADD_FLAT_PVP_MODIFIER_BY_SPELL_LABEL = 648, SPELL_AURA_ADD_PCT_PVP_MODIFIER_BY_SPELL_LABEL = 649, SPELL_AURA_650 = 650, - SPELL_AURA_651 = 651, + SPELL_AURA_ENABLE_EVENT_TRANSMOG_OUTFIT = 651, SPELL_AURA_652 = 652, SPELL_AURA_653 = 653, SPELL_AURA_654 = 654, diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 12091e5e2a..c65e4cedf8 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -720,7 +720,7 @@ NonDefaultConstructible AuraEffectHandler[TOTAL_AURAS]= &AuraEffect::HandleNULL, //648 SPELL_AURA_ADD_FLAT_PVP_MODIFIER_BY_SPELL_LABEL &AuraEffect::HandleNULL, //649 SPELL_AURA_ADD_PCT_PVP_MODIFIER_BY_SPELL_LABEL &AuraEffect::HandleNULL, //650 - &AuraEffect::HandleNULL, //651 + &AuraEffect::HandleNULL, //651 SPELL_AURA_ENABLE_EVENT_TRANSMOG_OUTFIT &AuraEffect::HandleNULL, //652 &AuraEffect::HandleNULL, //653 &AuraEffect::HandleNULL, //654 diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index d15090a061..56ff494f2b 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -61,6 +61,7 @@ #include "TemporarySummon.h" #include "TradeData.h" #include "TraitPackets.h" +#include "TransmogMgr.h" #include "UniqueTrackablePtr.h" #include "Util.h" #include "VMapFactory.h" @@ -4973,7 +4974,10 @@ int32 Spell::GetSpellCastDataAmmo() switch (itemEntry->SubclassID) { case ITEM_SUBCLASS_WEAPON_THROWN: - ammoDisplayID = sDB2Manager.GetItemDisplayId(item_id, unitCaster->GetVirtualItemAppearanceMod(i)); + if (ItemModifiedAppearanceEntry const* modifiedAppearance = TransmogMgr::GetItemModifiedAppearance(item_id, unitCaster->GetVirtualItemAppearanceMod(i))) + if (ItemAppearanceEntry const* itemAppearance = sItemAppearanceStore.LookupEntry(modifiedAppearance->ItemAppearanceID)) + ammoDisplayID = itemAppearance->ItemDisplayInfoID; + ammoInventoryType = itemEntry->InventoryType; break; case ITEM_SUBCLASS_WEAPON_BOW: @@ -4986,7 +4990,10 @@ int32 Spell::GetSpellCastDataAmmo() ammoInventoryType = INVTYPE_AMMO; break; default: - nonRangedAmmoDisplayID = sDB2Manager.GetItemDisplayId(item_id, unitCaster->GetVirtualItemAppearanceMod(i)); + if (ItemModifiedAppearanceEntry const* modifiedAppearance = TransmogMgr::GetItemModifiedAppearance(item_id, unitCaster->GetVirtualItemAppearanceMod(i))) + if (ItemAppearanceEntry const* itemAppearance = sItemAppearanceStore.LookupEntry(modifiedAppearance->ItemAppearanceID)) + nonRangedAmmoDisplayID = itemAppearance->ItemDisplayInfoID; + nonRangedAmmoInventoryType = itemEntry->InventoryType; break; } diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index c161995b33..cb8bcc4f1b 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -464,6 +464,7 @@ class TC_GAME_API Spell void EffectSetPlayerDataElementCharacter(); void EffectSetPlayerDataFlagAccount(); void EffectSetPlayerDataFlagCharacter(); + void EffectEquipTransmogOutfit(); typedef std::unordered_set UsedSpellMods; @@ -632,6 +633,14 @@ class TC_GAME_API Spell // SPELL_EFFECT_UPGRADE_HEIRLOOM uint32 ItemId; + // SPELL_EFFECT_EQUIP_TRANSMOG_OUTFIT + struct + { + uint32 EquipAction; + uint32 TransmogOutfitId; + uint32 SituationTrigger; + } EquipTransmogOutfit; + struct { uint32 Data[3]; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 70bf5f8bcc..5ca21302a6 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -436,7 +436,7 @@ NonDefaultConstructible SpellEffectHandlers[TOTAL_SPELL_EF &Spell::EffectNULL, //344 SPELL_EFFECT_344 &Spell::EffectNULL, //345 SPELL_EFFECT_ASSIST_ACTION &Spell::EffectNULL, //346 SPELL_EFFECT_346 - &Spell::EffectNULL, //347 SPELL_EFFECT_EQUIP_TRANSMOG_OUTFIT + &Spell::EffectEquipTransmogOutfit, //347 SPELL_EFFECT_EQUIP_TRANSMOG_OUTFIT &Spell::EffectNULL, //348 SPELL_EFFECT_GIVE_HOUSE_LEVEL &Spell::EffectNULL, //349 SPELL_EFFECT_LEARN_HOUSE_ROOM &Spell::EffectNULL, //350 SPELL_EFFECT_LEARN_HOUSE_EXTERIOR_COMPONENT @@ -6338,3 +6338,30 @@ void Spell::EffectSetPlayerDataFlagCharacter() target->SetDataFlagCharacter(effectInfo->MiscValue, damage != 0); } + +void Spell::EffectEquipTransmogOutfit() +{ + if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) + return; + + Player* target = Object::ToPlayer(unitTarget); + if (!target) + return; + + Optional locked; + switch (static_cast(m_misc.EquipTransmogOutfit.EquipAction)) + { + case TransmogOutfitEquipAction::EquipAndLock: + case TransmogOutfitEquipAction::RemoveAndLock: + case TransmogOutfitEquipAction::Lock: + locked = true; + break; + case TransmogOutfitEquipAction::Unlock: + locked = false; + break; + default: + break; + } + + target->EquipTransmogOutfit(m_misc.EquipTransmogOutfit.TransmogOutfitId, static_cast(m_misc.EquipTransmogOutfit.SituationTrigger), locked); +} diff --git a/src/server/game/Transmog/TransmogMgr.cpp b/src/server/game/Transmog/TransmogMgr.cpp new file mode 100644 index 0000000000..22dd3edebe --- /dev/null +++ b/src/server/game/Transmog/TransmogMgr.cpp @@ -0,0 +1,416 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "TransmogMgr.h" +#include "DB2Stores.h" +#include "FlatSet.h" +#include "MapUtils.h" +#include "ObjectMgr.h" +#include "Player.h" +#include "TransmogrificationPackets.h" +#include "Util.h" + +namespace +{ +struct TransmogOutfitSlotOptionInfo +{ + TransmogOutfitSlotOptionEntry const* Data = nullptr; + int32 SlotIndex = -1; +}; + +struct TransmogOutfitSlotInfo +{ + TransmogOutfitSlotInfoEntry const* Data = nullptr; + std::variant> SlotIndexOrOptions; + + int32 GetSlotIndex(TransmogOutfitSlotOption slotOption) const + { + switch (SlotIndexOrOptions.index()) + { + case 0: + return std::get<0>(SlotIndexOrOptions); + case 1: + return std::get<1>(SlotIndexOrOptions)[AsUnderlyingType(slotOption)].SlotIndex; + default: + return -1; + } + } +}; + +std::unordered_map, ItemModifiedAppearanceEntry const*> ItemModifiedAppearancesByItem; +std::unordered_map TransmogIllusionBySpellItemEnchantment; +std::unordered_map> TransmogSetsByItemModifiedAppearance; +std::vector TransmogSetItemsByTransmogSet; +std::array, AsUnderlyingType(TransmogOutfitEntrySource::Max)> TransmogOutfitsBySource; +std::vector TransmogOutfitsAutomaticallyCreated; +std::vector AllSlots; +std::array SlotInfoByOutfitSlot; +std::array SlotInfoByInvSlot; +std::vector DefaultSituations; + +constexpr bool IsArtifactTransmogOutfitSlotOption(TransmogOutfitSlotOption option) +{ + return option == TransmogOutfitSlotOption::ArtifactSpecOne + || option == TransmogOutfitSlotOption::ArtifactSpecTwo + || option == TransmogOutfitSlotOption::ArtifactSpecThree + || option == TransmogOutfitSlotOption::ArtifactSpecFour; +} + +bool IsValidTransmogOutfitSlotForItem(ItemTemplate const* item, TransmogOutfitSlot slot, TransmogOutfitSlotOption option) +{ + if (IsArtifactTransmogOutfitSlotOption(option)) + if (ArtifactEntry const* artifact = sArtifactStore.LookupEntry(item->GetArtifactID())) + if (ChrSpecializationEntry const* specialization = sChrSpecializationStore.LookupEntry(artifact->ChrSpecializationID)) + if ((int8(option) - int8(TransmogOutfitSlotOption::ArtifactSpecOne)) != specialization->OrderIndex) + return false; + + switch (item->GetInventoryType()) + { + case INVTYPE_HEAD: + return slot == TransmogOutfitSlot::Head; + case INVTYPE_SHOULDERS: + return slot == TransmogOutfitSlot::ShoulderLeft || slot == TransmogOutfitSlot::ShoulderRight; + case INVTYPE_BODY: + return slot == TransmogOutfitSlot::Body; + case INVTYPE_CHEST: + case INVTYPE_ROBE: + return slot == TransmogOutfitSlot::Chest; + case INVTYPE_WAIST: + return slot == TransmogOutfitSlot::Waist; + case INVTYPE_LEGS: + return slot == TransmogOutfitSlot::Legs; + case INVTYPE_FEET: + return slot == TransmogOutfitSlot::Feet; + case INVTYPE_WRISTS: + return slot == TransmogOutfitSlot::Wrist; + case INVTYPE_HANDS: + return slot == TransmogOutfitSlot::Hand; + case INVTYPE_WEAPON: + case INVTYPE_WEAPONMAINHAND: + case INVTYPE_WEAPONOFFHAND: + return slot == TransmogOutfitSlot::WeaponMainHand || slot == TransmogOutfitSlot::WeaponOffHand || IsArtifactTransmogOutfitSlotOption(option); + case INVTYPE_SHIELD: + case INVTYPE_HOLDABLE: + return slot == TransmogOutfitSlot::WeaponOffHand || IsArtifactTransmogOutfitSlotOption(option); + case INVTYPE_RANGED: + return (slot == TransmogOutfitSlot::WeaponMainHand && option == TransmogOutfitSlotOption::RangedWeapon) || IsArtifactTransmogOutfitSlotOption(option); + case INVTYPE_CLOAK: + return slot == TransmogOutfitSlot::Back; + case INVTYPE_2HWEAPON: + return slot == TransmogOutfitSlot::WeaponMainHand || (slot == TransmogOutfitSlot::WeaponOffHand && option == TransmogOutfitSlotOption::FuryTwoHandedWeapon) || IsArtifactTransmogOutfitSlotOption(option); + case INVTYPE_TABARD: + return slot == TransmogOutfitSlot::Tabard; + case INVTYPE_RANGEDRIGHT: + return slot == (item->GetSubClass() == ITEM_SUBCLASS_WEAPON_WAND ? TransmogOutfitSlot::WeaponMainHand : TransmogOutfitSlot::WeaponRanged) || IsArtifactTransmogOutfitSlotOption(option); + default: + break; + } + return false; +} +} + +void TransmogMgr::Load() +{ + for (ItemModifiedAppearanceEntry const* appearanceMod : sItemModifiedAppearanceStore) + ItemModifiedAppearancesByItem[{ appearanceMod->ItemID, appearanceMod->ItemAppearanceModifierID }] = appearanceMod; + + for (TransmogIllusionEntry const* transmogIllusion : sTransmogIllusionStore) + TransmogIllusionBySpellItemEnchantment[transmogIllusion->SpellItemEnchantmentID] = transmogIllusion; + + for (TransmogSetItemEntry const* transmogSetItem : sTransmogSetItemStore) + { + TransmogSetEntry const* set = sTransmogSetStore.LookupEntry(transmogSetItem->TransmogSetID); + if (!set) + continue; + + TransmogSetsByItemModifiedAppearance[transmogSetItem->ItemModifiedAppearanceID].insert(set); + TransmogSetItemsByTransmogSet.push_back(transmogSetItem); + } + + std::ranges::sort(TransmogSetItemsByTransmogSet, {}, &TransmogSetItemEntry::TransmogSetID); + + for (TransmogOutfitEntryEntry const* transmogOutfitEntry : sTransmogOutfitEntryStore) + { + if (transmogOutfitEntry->HasFlag(TransmogOutfitEntryFlags::AutomaticallyAwardedOnLogin)) + TransmogOutfitsAutomaticallyCreated.push_back(transmogOutfitEntry); + + if (transmogOutfitEntry->GetSetType() == TransmogOutfitSetType::Outfit) + TransmogOutfitsBySource[AsUnderlyingType(transmogOutfitEntry->GetSource())].push_back(transmogOutfitEntry); + } + + for (std::vector& transmogOutfitEntries : TransmogOutfitsBySource) + std::ranges::sort(transmogOutfitEntries, {}, &TransmogOutfitEntryEntry::OrderIndex); + + for (TransmogOutfitSlotInfoEntry const* transmogOutfitSlot : sTransmogOutfitSlotInfoStore) + { + ASSERT(transmogOutfitSlot->GetSlot() < TransmogOutfitSlot::Max); + + TransmogOutfitSlotInfo* slot = &SlotInfoByOutfitSlot[AsUnderlyingType(transmogOutfitSlot->GetSlot())]; + slot->Data = transmogOutfitSlot; + + if (!transmogOutfitSlot->HasFlag(TransmogOutfitSlotFlags::IsSecondarySlot)) + { + ASSERT(transmogOutfitSlot->InventorySlotEnum < EQUIPMENT_SLOT_END); + SlotInfoByInvSlot[transmogOutfitSlot->InventorySlotEnum] = slot; + } + } + + for (TransmogOutfitSlotOptionEntry const* transmogOutfitSlotOption : sTransmogOutfitSlotOptionInfoStore) + { + ASSERT(transmogOutfitSlotOption->GetOption() < TransmogOutfitSlotOption::Max); + + TransmogOutfitSlotInfoEntry const* transmogOutfitSlot = sTransmogOutfitSlotInfoStore.AssertEntry(transmogOutfitSlotOption->TransmogOutfitSlotInfoID); + + TransmogOutfitSlotInfo& slotInfo = SlotInfoByOutfitSlot[AsUnderlyingType(transmogOutfitSlot->GetSlot())]; + if (!std::holds_alternative>(slotInfo.SlotIndexOrOptions)) + slotInfo.SlotIndexOrOptions.emplace>(new TransmogOutfitSlotOptionInfo[AsUnderlyingType(TransmogOutfitSlotOption::Max)]); + + std::get>(slotInfo.SlotIndexOrOptions)[AsUnderlyingType(transmogOutfitSlotOption->GetOption())].Data = transmogOutfitSlotOption; + } + + for (TransmogOutfitSlotInfo& slotInfo : SlotInfoByOutfitSlot) + { + if (!slotInfo.Data) + continue; + + TransmogOutfitSlotAndOptionInfo& slot = AllSlots.emplace_back(); + slot.Slot = slotInfo.Data; + slot.SlotIndex = AllSlots.size() - 1; + + if (std::holds_alternative>(slotInfo.SlotIndexOrOptions)) + { + // if slot has options, keep adding transmog slots for every option + auto options = std::span( + &std::get>(slotInfo.SlotIndexOrOptions)[0], + AsUnderlyingType(TransmogOutfitSlotOption::Max)); + + auto optionItr = std::ranges::find_if(options, + [](TransmogOutfitSlotOptionEntry const* option) { return option != nullptr; }, + &TransmogOutfitSlotOptionInfo::Data); + + slot.SlotOption = optionItr->Data; + optionItr->SlotIndex = AllSlots.size() - 1; + + while (++optionItr != options.end()) + { + if (!optionItr->Data) + continue; + + TransmogOutfitSlotAndOptionInfo& newSlot = AllSlots.emplace_back(); + newSlot.Slot = slotInfo.Data; + newSlot.SlotOption = optionItr->Data; + newSlot.SlotIndex = AllSlots.size() - 1; + optionItr->SlotIndex = AllSlots.size() - 1; + } + } + if (std::holds_alternative(slotInfo.SlotIndexOrOptions)) + std::get(slotInfo.SlotIndexOrOptions) = AllSlots.size() - 1; + } + + for (TransmogSituationEntry const* transmogSituation : sTransmogSituationStore) + if (transmogSituation->HasFlag(TransmogSituationFlags::DefaultsToOn)) + DefaultSituations.push_back(transmogSituation); +} + +ItemModifiedAppearanceEntry const* TransmogMgr::GetItemModifiedAppearance(uint32 itemId, uint32 appearanceModId) +{ + auto itr = ItemModifiedAppearancesByItem.find({ itemId, appearanceModId }); + if (itr != ItemModifiedAppearancesByItem.end()) + return itr->second; + + // Fall back to unmodified appearance + if (appearanceModId) + return GetDefaultItemModifiedAppearance(itemId); + + return nullptr; +} + +ItemModifiedAppearanceEntry const* TransmogMgr::GetDefaultItemModifiedAppearance(uint32 itemId) +{ + return Trinity::Containers::MapGetValuePtr(ItemModifiedAppearancesByItem, { itemId, 0 }); +} + +TransmogIllusionEntry const* TransmogMgr::GetTransmogIllusionForSpellItemEnchantment(uint32 spellItemEnchantmentId) +{ + return Trinity::Containers::MapGetValuePtr(TransmogIllusionBySpellItemEnchantment, spellItemEnchantmentId); +} + +std::span TransmogMgr::GetTransmogSetsForItemModifiedAppearance(uint32 itemModifiedAppearanceId) +{ + std::span result; + auto itr = TransmogSetsByItemModifiedAppearance.find(itemModifiedAppearanceId); + if (itr != TransmogSetsByItemModifiedAppearance.end()) + result = itr->second; + + return result; +} + +std::span TransmogMgr::GetTransmogSetItems(uint32 transmogSetId) +{ + return std::ranges::equal_range(TransmogSetItemsByTransmogSet, transmogSetId, {}, &TransmogSetItemEntry::TransmogSetID); +} + +std::span TransmogMgr::GetAutomaticallyUnlockedOutfits() +{ + return TransmogOutfitsAutomaticallyCreated; +} + +std::span TransmogMgr::GetAllSlots() +{ + return AllSlots; +} + +TransmogMgr::TransmogOutfitSlotAndOptionInfo const* TransmogMgr::GetSlotAndOption(TransmogOutfitSlot slot, TransmogOutfitSlotOption slotOption) +{ + int32 slotIndex = SlotInfoByOutfitSlot[AsUnderlyingType(slot)].GetSlotIndex(slotOption); + if (slotIndex >= 0) + return &AllSlots[slotIndex]; + + return nullptr; +} + +TransmogMgr::TransmogOutfitSlotAndOptionInfo const* TransmogMgr::GetSlotAndOption(EquipmentSlots inventorySlot, TransmogOutfitSlotOption slotOption) +{ + if (TransmogOutfitSlotInfo const* slotInfo = SlotInfoByInvSlot[inventorySlot]) + if (int32 slotIndex = slotInfo->GetSlotIndex(slotOption); slotIndex >= 0) + return &AllSlots[slotIndex]; + + return nullptr; +} + +std::span TransmogMgr::GetDefaultSituations() +{ + return DefaultSituations; +} + +TransmogOutfitEntryEntry const* TransmogMgr::GetNextOutfitToUnlock(TransmogOutfitEntrySource source, Player const* player) +{ + if (source >= TransmogOutfitEntrySource::Max) + return nullptr; + + TransmogOutfitEntryEntry const* lastOwnedOutfit = nullptr; + for (auto const& [id, transmogOutfit] : player->m_activePlayerData->TransmogOutfits) + { + TransmogOutfitEntryEntry const* transmogOutfitEntry = sTransmogOutfitEntryStore.LookupEntry(transmogOutfit.value.Id); + if (!transmogOutfitEntry || transmogOutfitEntry->GetSource() != source) + continue; + + if (!lastOwnedOutfit || transmogOutfitEntry->OrderIndex > lastOwnedOutfit->OrderIndex) + lastOwnedOutfit = transmogOutfitEntry; + } + + if (!lastOwnedOutfit) + return TransmogOutfitsBySource[AsUnderlyingType(source)].front(); + + auto itr = std::ranges::find(TransmogOutfitsBySource[AsUnderlyingType(source)], lastOwnedOutfit) + 1; + if (itr != TransmogOutfitsBySource[AsUnderlyingType(source)].end()) + return *itr; + + return nullptr; +} + +bool TransmogMgr::ValidateSituations(std::span situations) +{ + struct SituationTriggerStatus + { + uint8 AllSituationCount = 0; + uint8 NoneSituationCount = 0; + uint8 RegularSituationCount = 0; + }; + + std::array statusByTrigger; + + for (WorldPackets::Transmogrification::TransmogOutfitSituationInfo const& situation : situations) + { + TransmogSituationEntry const* transmogSituation = sTransmogSituationStore.LookupEntry(situation.SituationID); + if (!transmogSituation) + return false; + + TransmogSituationGroupEntry const* transmogSituationGroup = sTransmogSituationGroupStore.LookupEntry(transmogSituation->TransmogSituationGroupID); + if (!transmogSituationGroup) + return false; + + TransmogSituationTriggerEntry const* transmogSituationTrigger = sTransmogSituationTriggerStore.LookupEntry(transmogSituationGroup->TransmogSituationTriggerID); + if (!transmogSituationTrigger) + return false; + + SituationTriggerStatus& triggers = statusByTrigger[AsUnderlyingType(transmogSituationTrigger->GetTrigger())]; + uint8* count = nullptr; + if (transmogSituation->HasFlag(TransmogSituationFlags::AllSituation)) + count = &triggers.AllSituationCount; + else if (transmogSituation->HasFlag(TransmogSituationFlags::NoneSituation)) + count = &triggers.NoneSituationCount; + else + count = &triggers.RegularSituationCount; + + *count += 1; + if (transmogSituationTrigger->HasFlag(TransmogSituationTriggerFlags::SituationsAreExclusive) && *count > 1) + return false; + } + + for (SituationTriggerStatus const& triggers : statusByTrigger) + if ((triggers.AllSituationCount > 0) + (triggers.NoneSituationCount > 0) + (triggers.RegularSituationCount > 0) > 1) // only 1 group can be active + return false; + + return true; +} + +bool TransmogMgr::ValidateSlots(std::span slots) +{ + for (WorldPackets::Transmogrification::TransmogOutfitSlotData const& slot : slots) + { + if (slot.Slot >= TransmogOutfitSlot::Max) + return false; + + if (slot.SlotOption >= TransmogOutfitSlotOption::Max) + return false; + + if (slot.AppearanceDisplayType >= TransmogOutfitDisplayType::Max) + return false; + + if (slot.IllusionDisplayType >= TransmogOutfitDisplayType::Max) + return false; + + if (!GetSlotAndOption(slot.Slot, slot.SlotOption)) + return false; + + if (slot.ItemModifiedAppearanceID) + { + ItemModifiedAppearanceEntry const* itemModifiedAppearance = sItemModifiedAppearanceStore.LookupEntry(slot.ItemModifiedAppearanceID); + if (!itemModifiedAppearance) + return false; + + ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemModifiedAppearance->ItemID); + if (!itemTemplate) + return false; + + if (!IsValidTransmogOutfitSlotForItem(itemTemplate, slot.Slot, slot.SlotOption)) + return false; + + TransmogOutfitSlotOption appearanceSlotOption = itemTemplate->GetWeaponTransmogOutfitSlotOption(); + if (appearanceSlotOption != slot.SlotOption + && (slot.SlotOption != TransmogOutfitSlotOption::FuryTwoHandedWeapon + || appearanceSlotOption != TransmogOutfitSlotOption::TwoHandedWeapon)) + return false; + } + + if (slot.SpellItemEnchantmentID && !TransmogIllusionBySpellItemEnchantment.contains(slot.SpellItemEnchantmentID)) + return false; + } + + return true; +} diff --git a/src/server/game/Transmog/TransmogMgr.h b/src/server/game/Transmog/TransmogMgr.h new file mode 100644 index 0000000000..b4bd984a35 --- /dev/null +++ b/src/server/game/Transmog/TransmogMgr.h @@ -0,0 +1,88 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_TRANSMOG_MGR_H +#define TRINITYCORE_TRANSMOG_MGR_H + +#include "Common.h" +#include + +class Player; +struct ItemModifiedAppearanceEntry; +struct TransmogIllusionEntry; +struct TransmogOutfitEntryEntry; +struct TransmogOutfitSlotInfoEntry; +struct TransmogOutfitSlotOptionEntry; +struct TransmogSetEntry; +struct TransmogSetItemEntry; +struct TransmogSituationEntry; +enum EquipmentSlots : uint8; +enum class TransmogOutfitEntrySource : uint8; +enum class TransmogOutfitSlot : int8; +enum class TransmogOutfitSlotOption : uint8; + +namespace WorldPackets::Transmogrification +{ +struct TransmogOutfitSituationInfo; +struct TransmogOutfitSlotData; +} + +namespace TransmogMgr +{ +struct TransmogOutfitSlotAndOptionInfo +{ + TransmogOutfitSlotInfoEntry const* Slot = nullptr; + TransmogOutfitSlotOptionEntry const* SlotOption = nullptr; + uint32 SlotIndex = 0; +}; + +inline constexpr std::array DefaultOutfitName = +{ + "Outfit"sv, + "\354\235\230\354\203\201"sv, + "Tenue"sv, + "Outfit"sv, + "\345\244\226\350\247\202\346\226\271\346\241\210"sv, + "\346\234\215\350\243\235"sv, + "Atuendo"sv, + "Indumentaria"sv, + "\320\241\320\275\320\260\321\200\321\217\320\266\320\265\320\275\320\270\320\265"sv, + ""sv, + "Roupa"sv, + "Completo"sv +}; + +inline constexpr uint32 DefaultOutfitIcon = 134400; + +void Load(); + +ItemModifiedAppearanceEntry const* GetItemModifiedAppearance(uint32 itemId, uint32 appearanceModId); +ItemModifiedAppearanceEntry const* GetDefaultItemModifiedAppearance(uint32 itemId); +TransmogIllusionEntry const* GetTransmogIllusionForSpellItemEnchantment(uint32 spellItemEnchantmentId); +std::span GetTransmogSetsForItemModifiedAppearance(uint32 itemModifiedAppearanceId); +std::span GetTransmogSetItems(uint32 transmogSetId); +std::span GetAutomaticallyUnlockedOutfits(); +std::span GetAllSlots(); +TransmogOutfitSlotAndOptionInfo const* GetSlotAndOption(TransmogOutfitSlot slot, TransmogOutfitSlotOption slotOption); +TransmogOutfitSlotAndOptionInfo const* GetSlotAndOption(EquipmentSlots inventorySlot, TransmogOutfitSlotOption slotOption); +std::span GetDefaultSituations(); +TransmogOutfitEntryEntry const* GetNextOutfitToUnlock(TransmogOutfitEntrySource source, Player const* player); +bool ValidateSituations(std::span situations); +bool ValidateSlots(std::span slots); +} + +#endif // TRINITYCORE_TRANSMOG_MGR_H diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index b53db3d6d5..13883d3ee7 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -96,6 +96,7 @@ #include "TaxiPathGraph.h" #include "TerrainMgr.h" #include "TraitMgr.h" +#include "TransmogMgr.h" #include "TransportMgr.h" #include "Unit.h" #include "UpdateTime.h" @@ -2066,6 +2067,9 @@ bool World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading phase names..."); sObjectMgr->LoadPhaseNames(); + TC_LOG_INFO("server.loading", "Loading transmog data..."); + TransmogMgr::Load(); + uint32 startupDuration = GetMSTimeDiffToNow(startupBegin); TC_LOG_INFO("server.worldserver", "World initialized in {} minutes {} seconds", startupDuration / 60000, startupDuration % 60000 / 1000);