· 6 years ago · Oct 02, 2019, 02:02 AM
1diff --git a/cmake/options.cmake b/cmake/options.cmake
2index efa60175353b..768c3879a095 100644
3--- a/cmake/options.cmake
4+++ b/cmake/options.cmake
5@@ -51,3 +51,4 @@ option(WITH_COREDEBUG "Include additional debug-code in core"
6 set(WITH_SOURCE_TREE "hierarchical" CACHE STRING "Build the source tree for IDE's.")
7 set_property(CACHE WITH_SOURCE_TREE PROPERTY STRINGS no flat hierarchical hierarchical-folders)
8 option(WITHOUT_GIT "Disable the GIT testing routines" 0)
9+option(DISABLE_DRESSNPCS_CORESOUNDS "Disable server side 'missing sounds' workaround" 0)
10diff --git a/cmake/showoptions.cmake b/cmake/showoptions.cmake
11index 069b90b544fe..eec0cdbcbe77 100644
12--- a/cmake/showoptions.cmake
13+++ b/cmake/showoptions.cmake
14@@ -125,4 +125,10 @@ if (BUILD_SHARED_LIBS)
15 WarnAboutSpacesInBuildPath()
16 endif()
17
18+if (DISABLE_DRESSNPCS_CORESOUNDS)
19+ message("")
20+ message("DressNPCs sound workaround disabled. Live without sounds or use a client side patch.")
21+ add_definitions(-DDISABLE_DRESSNPCS_CORESOUNDS)
22+endif()
23+
24 message("")
25diff --git a/sql/custom/hotfixes/2018_01_04_00_hotfixes_dressnpcs.sql b/sql/custom/hotfixes/2018_01_04_00_hotfixes_dressnpcs.sql
26new file mode 100644
27index 000000000000..d740677b496b
28--- /dev/null
29+++ b/sql/custom/hotfixes/2018_01_04_00_hotfixes_dressnpcs.sql
30@@ -0,0 +1,12 @@
31+DROP TABLE IF EXISTS `npc_sounds`;
32+CREATE TABLE `npc_sounds` (
33+ `ID` INT(10) UNSIGNED NOT NULL,
34+ `hello` INT(10) UNSIGNED NOT NULL DEFAULT '0',
35+ `goodbye` INT(10) UNSIGNED NOT NULL DEFAULT '0',
36+ `pissed` INT(10) UNSIGNED NOT NULL DEFAULT '0',
37+ `ack` INT(10) UNSIGNED NOT NULL DEFAULT '0',
38+ PRIMARY KEY (`ID`)
39+)
40+COLLATE='utf8_general_ci'
41+ENGINE=MyISAM
42+;
43diff --git a/sql/custom/world/2016_07_24_00_world_dressnpcs.sql b/sql/custom/world/2016_07_24_00_world_dressnpcs.sql
44new file mode 100644
45index 000000000000..387cf774ae92
46--- /dev/null
47+++ b/sql/custom/world/2016_07_24_00_world_dressnpcs.sql
48@@ -0,0 +1,31 @@
49+CREATE TABLE IF NOT EXISTS `creature_template_outfits` (
50+ `entry` INT(10) UNSIGNED NOT NULL,
51+ `race` TINYINT(3) UNSIGNED NOT NULL DEFAULT '1',
52+ `gender` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0 for male, 1 for female',
53+ `skin` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
54+ `face` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
55+ `hair` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
56+ `haircolor` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
57+ `facialhair` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
58+ `head` INT(10) NOT NULL DEFAULT '0',
59+ `shoulders` INT(10) NOT NULL DEFAULT '0',
60+ `body` INT(10) NOT NULL DEFAULT '0',
61+ `chest` INT(10) NOT NULL DEFAULT '0',
62+ `waist` INT(10) NOT NULL DEFAULT '0',
63+ `legs` INT(10) NOT NULL DEFAULT '0',
64+ `feet` INT(10) NOT NULL DEFAULT '0',
65+ `wrists` INT(10) NOT NULL DEFAULT '0',
66+ `hands` INT(10) NOT NULL DEFAULT '0',
67+ `back` INT(10) NOT NULL DEFAULT '0',
68+ `tabard` INT(10) NOT NULL DEFAULT '0',
69+ PRIMARY KEY (`entry`)
70+)
71+COMMENT='Use positive values for item entries and negative to use item displayid for head, shoulders etc.'
72+COLLATE='utf8_general_ci'
73+ENGINE=InnoDB;
74+
75+ALTER TABLE `creature_template`
76+ CHANGE COLUMN `modelid1` `modelid1` INT NOT NULL DEFAULT '0' AFTER `KillCredit2`,
77+ CHANGE COLUMN `modelid2` `modelid2` INT NOT NULL DEFAULT '0' AFTER `modelid1`,
78+ CHANGE COLUMN `modelid3` `modelid3` INT NOT NULL DEFAULT '0' AFTER `modelid2`,
79+ CHANGE COLUMN `modelid4` `modelid4` INT NOT NULL DEFAULT '0' AFTER `modelid3`;
80diff --git a/sql/custom/world/2016_07_24_01_world_dressnpcs.sql b/sql/custom/world/2016_07_24_01_world_dressnpcs.sql
81new file mode 100644
82index 000000000000..cd6e5b962338
83--- /dev/null
84+++ b/sql/custom/world/2016_07_24_01_world_dressnpcs.sql
85@@ -0,0 +1,2 @@
86+ALTER TABLE `creature_template_outfits`
87+ ADD COLUMN `class` TINYINT(3) UNSIGNED NOT NULL DEFAULT '1' AFTER `race`;
88diff --git a/sql/custom/world/2016_11_06_00_world_dressnpcs.sql b/sql/custom/world/2016_11_06_00_world_dressnpcs.sql
89new file mode 100644
90index 000000000000..255748a43933
91--- /dev/null
92+++ b/sql/custom/world/2016_11_06_00_world_dressnpcs.sql
93@@ -0,0 +1,18 @@
94+ALTER TABLE `creature_template_outfits`
95+ ADD COLUMN `feature1` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' AFTER `facialhair`,
96+ ADD COLUMN `feature2` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' AFTER `feature1`,
97+ ADD COLUMN `feature3` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' AFTER `feature2`;
98+
99+ALTER TABLE `creature_template_outfits`
100+ COMMENT='Use positive values for item entries and negative to use item displayid for head, shoulders etc.\r\nTo use special appearances either find the displayid for it or use ((AppearanceModID << 32) | item_entry). The appearance IDs are usually from 0 to 10.',
101+ CHANGE COLUMN `head` `head` BIGINT NOT NULL DEFAULT '0' AFTER `feature3`,
102+ CHANGE COLUMN `shoulders` `shoulders` BIGINT NOT NULL DEFAULT '0' AFTER `head`,
103+ CHANGE COLUMN `body` `body` BIGINT NOT NULL DEFAULT '0' AFTER `shoulders`,
104+ CHANGE COLUMN `chest` `chest` BIGINT NOT NULL DEFAULT '0' AFTER `body`,
105+ CHANGE COLUMN `waist` `waist` BIGINT NOT NULL DEFAULT '0' AFTER `chest`,
106+ CHANGE COLUMN `legs` `legs` BIGINT NOT NULL DEFAULT '0' AFTER `waist`,
107+ CHANGE COLUMN `feet` `feet` BIGINT NOT NULL DEFAULT '0' AFTER `legs`,
108+ CHANGE COLUMN `wrists` `wrists` BIGINT NOT NULL DEFAULT '0' AFTER `feet`,
109+ CHANGE COLUMN `hands` `hands` BIGINT NOT NULL DEFAULT '0' AFTER `wrists`,
110+ CHANGE COLUMN `back` `back` BIGINT NOT NULL DEFAULT '0' AFTER `hands`,
111+ CHANGE COLUMN `tabard` `tabard` BIGINT NOT NULL DEFAULT '0' AFTER `back`;
112diff --git a/sql/custom/world/2018_01_03_00_world_dressnpcs.sql b/sql/custom/world/2018_01_03_00_world_dressnpcs.sql
113new file mode 100644
114index 000000000000..ab2a7721bc02
115--- /dev/null
116+++ b/sql/custom/world/2018_01_03_00_world_dressnpcs.sql
117@@ -0,0 +1,2 @@
118+ALTER TABLE `creature_template_outfits`
119+ ADD COLUMN `guildid` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0' AFTER `tabard`;
120diff --git a/sql/custom/world/2018_01_03_01_world_dressnpcs.sql b/sql/custom/world/2018_01_03_01_world_dressnpcs.sql
121new file mode 100644
122index 000000000000..93a8a6bb54fe
123--- /dev/null
124+++ b/sql/custom/world/2018_01_03_01_world_dressnpcs.sql
125@@ -0,0 +1,12 @@
126+ALTER TABLE `creature_template_outfits`
127+ ADD COLUMN `head_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `head`,
128+ ADD COLUMN `shoulders_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `shoulders`,
129+ ADD COLUMN `body_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `body`,
130+ ADD COLUMN `chest_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `chest`,
131+ ADD COLUMN `waist_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `waist`,
132+ ADD COLUMN `legs_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `legs`,
133+ ADD COLUMN `feet_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `feet`,
134+ ADD COLUMN `wrists_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `wrists`,
135+ ADD COLUMN `hands_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `hands`,
136+ ADD COLUMN `back_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `back`,
137+ ADD COLUMN `tabard_appearance` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `tabard`;
138diff --git a/sql/custom/world/2018_01_03_02_world_dressnpcs.sql b/sql/custom/world/2018_01_03_02_world_dressnpcs.sql
139new file mode 100644
140index 000000000000..b8e0161194e1
141--- /dev/null
142+++ b/sql/custom/world/2018_01_03_02_world_dressnpcs.sql
143@@ -0,0 +1,11 @@
144+UPDATE `creature_template_outfits` SET `head_appearance` = (`head` >> 32), `head` = (`head` & 0xFFFFFFFF) WHERE `head` > 0;
145+UPDATE `creature_template_outfits` SET `shoulders_appearance` = (`shoulders` >> 32), `shoulders` = (`shoulders` & 0xFFFFFFFF) WHERE `shoulders` > 0;
146+UPDATE `creature_template_outfits` SET `body_appearance` = (`body` >> 32), `body` = (`body` & 0xFFFFFFFF) WHERE `body` > 0;
147+UPDATE `creature_template_outfits` SET `chest_appearance` = (`chest` >> 32), `chest` = (`chest` & 0xFFFFFFFF) WHERE `chest` > 0;
148+UPDATE `creature_template_outfits` SET `waist_appearance` = (`waist` >> 32), `waist` = (`waist` & 0xFFFFFFFF) WHERE `waist` > 0;
149+UPDATE `creature_template_outfits` SET `legs_appearance` = (`legs` >> 32), `legs` = (`legs` & 0xFFFFFFFF) WHERE `legs` > 0;
150+UPDATE `creature_template_outfits` SET `feet_appearance` = (`feet` >> 32), `feet` = (`feet` & 0xFFFFFFFF) WHERE `feet` > 0;
151+UPDATE `creature_template_outfits` SET `wrists_appearance` = (`wrists` >> 32), `wrists` = (`wrists` & 0xFFFFFFFF) WHERE `wrists` > 0;
152+UPDATE `creature_template_outfits` SET `hands_appearance` = (`hands` >> 32), `hands` = (`hands` & 0xFFFFFFFF) WHERE `hands` > 0;
153+UPDATE `creature_template_outfits` SET `back_appearance` = (`back` >> 32), `back` = (`back` & 0xFFFFFFFF) WHERE `back` > 0;
154+UPDATE `creature_template_outfits` SET `tabard_appearance` = (`tabard` >> 32), `tabard` = (`tabard` & 0xFFFFFFFF) WHERE `tabard` > 0;
155diff --git a/sql/custom/world/2018_01_03_03_world_dressnpcs.sql b/sql/custom/world/2018_01_03_03_world_dressnpcs.sql
156new file mode 100644
157index 000000000000..751297971319
158--- /dev/null
159+++ b/sql/custom/world/2018_01_03_03_world_dressnpcs.sql
160@@ -0,0 +1,2 @@
161+ALTER TABLE `creature_template_outfits`
162+ COMMENT='Use positive values for item entries and negative to use item displayid for head, shoulders etc.';
163diff --git a/sql/custom/world/2018_01_03_04_world_dressnpcs.sql b/sql/custom/world/2018_01_03_04_world_dressnpcs.sql
164new file mode 100644
165index 000000000000..141c920ebd83
166--- /dev/null
167+++ b/sql/custom/world/2018_01_03_04_world_dressnpcs.sql
168@@ -0,0 +1,2 @@
169+ALTER TABLE `creature_template_outfits`
170+ ADD COLUMN `description` TEXT NULL DEFAULT NULL AFTER `guildid`;
171diff --git a/sql/custom/world/2018_01_04_00_world_dressnpcs.sql b/sql/custom/world/2018_01_04_00_world_dressnpcs.sql
172new file mode 100644
173index 000000000000..ef9393b75ca1
174--- /dev/null
175+++ b/sql/custom/world/2018_01_04_00_world_dressnpcs.sql
176@@ -0,0 +1,2 @@
177+ALTER TABLE `creature_template_outfits`
178+ ADD COLUMN `npcsoundsid` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'entry from NPCSounds.dbc/db2' AFTER `entry`;
179diff --git a/sql/custom/world/2018_02_17_00_world_dressnpcs.sql b/sql/custom/world/2018_02_17_00_world_dressnpcs.sql
180new file mode 100644
181index 000000000000..fbacdf65380d
182--- /dev/null
183+++ b/sql/custom/world/2018_02_17_00_world_dressnpcs.sql
184@@ -0,0 +1,20 @@
185+ALTER TABLE `creature_template` CHANGE COLUMN `modelid1` `modelid1` BIGINT;
186+ALTER TABLE `creature_template` CHANGE COLUMN `modelid2` `modelid2` BIGINT;
187+ALTER TABLE `creature_template` CHANGE COLUMN `modelid3` `modelid3` BIGINT;
188+ALTER TABLE `creature_template` CHANGE COLUMN `modelid4` `modelid4` BIGINT;
189+UPDATE creature_template SET modelid1 = (-modelid1) + 3000000000 WHERE modelid1 < 0;
190+UPDATE creature_template SET modelid2 = (-modelid2) + 3000000000 WHERE modelid2 < 0;
191+UPDATE creature_template SET modelid3 = (-modelid3) + 3000000000 WHERE modelid3 < 0;
192+UPDATE creature_template SET modelid4 = (-modelid4) + 3000000000 WHERE modelid4 < 0;
193+ALTER TABLE `creature_template` CHANGE COLUMN `modelid1` `modelid1` INT(10) UNSIGNED NOT NULL DEFAULT '0';
194+ALTER TABLE `creature_template` CHANGE COLUMN `modelid2` `modelid2` INT(10) UNSIGNED NOT NULL DEFAULT '0';
195+ALTER TABLE `creature_template` CHANGE COLUMN `modelid3` `modelid3` INT(10) UNSIGNED NOT NULL DEFAULT '0';
196+ALTER TABLE `creature_template` CHANGE COLUMN `modelid4` `modelid4` INT(10) UNSIGNED NOT NULL DEFAULT '0';
197+
198+ALTER TABLE `creature_template_outfits` DROP PRIMARY KEY;
199+UPDATE creature_template_outfits SET entry = entry + 3000000000 WHERE entry <= 0x7FFFFFFF;
200+ALTER TABLE `creature_template_outfits` ADD PRIMARY KEY (`entry`);
201+
202+ALTER TABLE `creature` CHANGE COLUMN `modelid` `modelid` INT(10) UNSIGNED NOT NULL DEFAULT '0';
203+ALTER TABLE `game_event_model_equip` CHANGE COLUMN `modelid` `modelid` INT(10) UNSIGNED NOT NULL DEFAULT '0';
204+ALTER TABLE `creature_model_info` CHANGE COLUMN `DisplayID` `DisplayID` INT(10) UNSIGNED NOT NULL DEFAULT '0';
205diff --git a/sql/custom/world/2018_02_24_00_world_dressnpcs.sql b/sql/custom/world/2018_02_24_00_world_dressnpcs.sql
206new file mode 100644
207index 000000000000..70ab872fd333
208--- /dev/null
209+++ b/sql/custom/world/2018_02_24_00_world_dressnpcs.sql
210@@ -0,0 +1,8 @@
211+INSERT IGNORE INTO `creature_model_info` (`DisplayID`, `BoundingRadius`, `CombatReach`, `DisplayID_Other_Gender`, `VerifiedBuild`) SELECT 75078, `BoundingRadius`, `CombatReach`, 0, 0 FROM `creature_model_info` WHERE `DisplayID` = 55;
212+INSERT IGNORE INTO `creature_model_info` (`DisplayID`, `BoundingRadius`, `CombatReach`, `DisplayID_Other_Gender`, `VerifiedBuild`) SELECT 75079, `BoundingRadius`, `CombatReach`, 0, 0 FROM `creature_model_info` WHERE `DisplayID` = 56;
213+INSERT IGNORE INTO `creature_model_info` (`DisplayID`, `BoundingRadius`, `CombatReach`, `DisplayID_Other_Gender`, `VerifiedBuild`) SELECT 75080, `BoundingRadius`, `CombatReach`, 0, 0 FROM `creature_model_info` WHERE `DisplayID` = 55;
214+INSERT IGNORE INTO `creature_model_info` (`DisplayID`, `BoundingRadius`, `CombatReach`, `DisplayID_Other_Gender`, `VerifiedBuild`) SELECT 75081, `BoundingRadius`, `CombatReach`, 0, 0 FROM `creature_model_info` WHERE `DisplayID` = 59;
215+INSERT IGNORE INTO `creature_model_info` (`DisplayID`, `BoundingRadius`, `CombatReach`, `DisplayID_Other_Gender`, `VerifiedBuild`) SELECT 75082, `BoundingRadius`, `CombatReach`, 0, 0 FROM `creature_model_info` WHERE `DisplayID` = 15476;
216+INSERT IGNORE INTO `creature_model_info` (`DisplayID`, `BoundingRadius`, `CombatReach`, `DisplayID_Other_Gender`, `VerifiedBuild`) SELECT 75083, `BoundingRadius`, `CombatReach`, 0, 0 FROM `creature_model_info` WHERE `DisplayID` = 15475;
217+INSERT IGNORE INTO `creature_model_info` (`DisplayID`, `BoundingRadius`, `CombatReach`, `DisplayID_Other_Gender`, `VerifiedBuild`) SELECT 75084, `BoundingRadius`, `CombatReach`, 0, 0 FROM `creature_model_info` WHERE `DisplayID` = 16125;
218+INSERT IGNORE INTO `creature_model_info` (`DisplayID`, `BoundingRadius`, `CombatReach`, `DisplayID_Other_Gender`, `VerifiedBuild`) SELECT 75085, `BoundingRadius`, `CombatReach`, 0, 0 FROM `creature_model_info` WHERE `DisplayID` = 16126;
219diff --git a/src/server/database/Database/Implementation/HotfixDatabase.cpp b/src/server/database/Database/Implementation/HotfixDatabase.cpp
220index 8c1bb487363c..0c242e883731 100644
221--- a/src/server/database/Database/Implementation/HotfixDatabase.cpp
222+++ b/src/server/database/Database/Implementation/HotfixDatabase.cpp
223@@ -672,6 +672,9 @@ void HotfixDatabaseConnection::DoPrepareStatements()
224 // NamesReservedLocale.db2
225 PrepareStatement(HOTFIX_SEL_NAMES_RESERVED_LOCALE, "SELECT ID, Name, LocaleMask FROM names_reserved_locale ORDER BY ID DESC", CONNECTION_SYNCH);
226
227+ // NPCSounds.db2
228+ PrepareStatement(HOTFIX_SEL_NPC_SOUNDS, "SELECT ID, hello, goodbye, pissed, ack FROM npc_sounds ORDER BY ID DESC", CONNECTION_SYNCH);
229+
230 // OverrideSpellData.db2
231 PrepareStatement(HOTFIX_SEL_OVERRIDE_SPELL_DATA, "SELECT ID, Spells1, Spells2, Spells3, Spells4, Spells5, Spells6, Spells7, Spells8, Spells9, "
232 "Spells10, PlayerActionBarFileDataID, Flags FROM override_spell_data ORDER BY ID DESC", CONNECTION_SYNCH);
233diff --git a/src/server/database/Database/Implementation/HotfixDatabase.h b/src/server/database/Database/Implementation/HotfixDatabase.h
234index 77f3600ff398..f3aff7418ac3 100644
235--- a/src/server/database/Database/Implementation/HotfixDatabase.h
236+++ b/src/server/database/Database/Implementation/HotfixDatabase.h
237@@ -359,6 +359,8 @@ enum HotfixDatabaseStatements : uint32
238
239 HOTFIX_SEL_NAMES_RESERVED_LOCALE,
240
241+ HOTFIX_SEL_NPC_SOUNDS,
242+
243 HOTFIX_SEL_OVERRIDE_SPELL_DATA,
244
245 HOTFIX_SEL_PHASE,
246diff --git a/src/server/game/DataStores/DB2LoadInfo.h b/src/server/game/DataStores/DB2LoadInfo.h
247index a6b4c1c04261..25d83e15e224 100644
248--- a/src/server/game/DataStores/DB2LoadInfo.h
249+++ b/src/server/game/DataStores/DB2LoadInfo.h
250@@ -3308,6 +3308,23 @@ struct NamesReservedLocaleLoadInfo
251 }
252 };
253
254+struct NPCSoundsLoadInfo
255+{
256+ static DB2LoadInfo const* Instance()
257+ {
258+ static DB2FieldMeta const fields[] =
259+ {
260+ { false, FT_INT, "ID" },
261+ { false, FT_INT, "hello" },
262+ { false, FT_INT, "goodbye" },
263+ { false, FT_INT, "pissed" },
264+ { false, FT_INT, "ack" },
265+ };
266+ static DB2LoadInfo const loadInfo(&fields[0], std::extent<decltype(fields)>::value, NPCSoundsMeta::Instance(), HOTFIX_SEL_NPC_SOUNDS);
267+ return &loadInfo;
268+ }
269+};
270+
271 struct OverrideSpellDataLoadInfo
272 {
273 static DB2LoadInfo const* Instance()
274diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp
275index fe91d679d973..70e80359cf5c 100644
276--- a/src/server/game/DataStores/DB2Stores.cpp
277+++ b/src/server/game/DataStores/DB2Stores.cpp
278@@ -23,6 +23,7 @@
279 #include "IteratorPair.h"
280 #include "Log.h"
281 #include "ObjectDefines.h"
282+#include "ObjectMgr.h"
283 #include "Regex.h"
284 #include "Timer.h"
285 #include "Util.h"
286@@ -76,7 +77,10 @@ DB2Storage<ChrSpecializationEntry> sChrSpecializationStore("ChrSpec
287 DB2Storage<CinematicCameraEntry> sCinematicCameraStore("CinematicCamera.db2", CinematicCameraLoadInfo::Instance());
288 DB2Storage<CinematicSequencesEntry> sCinematicSequencesStore("CinematicSequences.db2", CinematicSequencesLoadInfo::Instance());
289 DB2Storage<ConversationLineEntry> sConversationLineStore("ConversationLine.db2", ConversationLineLoadInfo::Instance());
290-DB2Storage<CreatureDisplayInfoEntry> sCreatureDisplayInfoStore("CreatureDisplayInfo.db2", CreatureDisplayInfoLoadInfo::Instance());
291+DB2Storage<CreatureDisplayInfoEntry> sCreatureDisplayInfoStoreRaw("CreatureDisplayInfo.db2", CreatureDisplayInfoLoadInfo::Instance());
292+CreatureDisplayInfoStore sCreatureDisplayInfoStore;
293+bool CreatureDisplayInfoStore::HasRecord(uint32 id) const { return sCreatureDisplayInfoStoreRaw.HasRecord(sObjectMgr->GetRealDisplayId(id)); }
294+const CreatureDisplayInfoEntry * CreatureDisplayInfoStore::LookupEntry(uint32 id) const { return sCreatureDisplayInfoStoreRaw.LookupEntry(sObjectMgr->GetRealDisplayId(id)); }
295 DB2Storage<CreatureDisplayInfoExtraEntry> sCreatureDisplayInfoExtraStore("CreatureDisplayInfoExtra.db2", CreatureDisplayInfoExtraLoadInfo::Instance());
296 DB2Storage<CreatureFamilyEntry> sCreatureFamilyStore("CreatureFamily.db2", CreatureFamilyLoadInfo::Instance());
297 DB2Storage<CreatureModelDataEntry> sCreatureModelDataStore("CreatureModelData.db2", CreatureModelDataLoadInfo::Instance());
298@@ -178,6 +182,7 @@ DB2Storage<NameGenEntry> sNameGenStore("NameGen.db2", Nam
299 DB2Storage<NamesProfanityEntry> sNamesProfanityStore("NamesProfanity.db2", NamesProfanityLoadInfo::Instance());
300 DB2Storage<NamesReservedEntry> sNamesReservedStore("NamesReserved.db2", NamesReservedLoadInfo::Instance());
301 DB2Storage<NamesReservedLocaleEntry> sNamesReservedLocaleStore("NamesReservedLocale.db2", NamesReservedLocaleLoadInfo::Instance());
302+DB2Storage<NPCSoundsEntry> sNPCSoundsStore("NPCSounds.db2", NPCSoundsLoadInfo::Instance());
303 DB2Storage<OverrideSpellDataEntry> sOverrideSpellDataStore("OverrideSpellData.db2", OverrideSpellDataLoadInfo::Instance());
304 DB2Storage<PhaseEntry> sPhaseStore("Phase.db2", PhaseLoadInfo::Instance());
305 DB2Storage<PhaseXPhaseGroupEntry> sPhaseXPhaseGroupStore("PhaseXPhaseGroup.db2", PhaseXPhaseGroupLoadInfo::Instance());
306@@ -516,7 +521,7 @@ void DB2Manager::LoadStores(std::string const& dataPath, uint32 defaultLocale)
307 LOAD_DB2(sCinematicCameraStore);
308 LOAD_DB2(sCinematicSequencesStore);
309 LOAD_DB2(sConversationLineStore);
310- LOAD_DB2(sCreatureDisplayInfoStore);
311+ LOAD_DB2(sCreatureDisplayInfoStoreRaw);
312 LOAD_DB2(sCreatureDisplayInfoExtraStore);
313 LOAD_DB2(sCreatureFamilyStore);
314 LOAD_DB2(sCreatureModelDataStore);
315@@ -618,6 +623,7 @@ void DB2Manager::LoadStores(std::string const& dataPath, uint32 defaultLocale)
316 LOAD_DB2(sNamesProfanityStore);
317 LOAD_DB2(sNamesReservedStore);
318 LOAD_DB2(sNamesReservedLocaleStore);
319+ LOAD_DB2(sNPCSoundsStore);
320 LOAD_DB2(sOverrideSpellDataStore);
321 LOAD_DB2(sPhaseStore);
322 LOAD_DB2(sPhaseXPhaseGroupStore);
323diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h
324index d5f0af253a0c..8c7664fc8244 100644
325--- a/src/server/game/DataStores/DB2Stores.h
326+++ b/src/server/game/DataStores/DB2Stores.h
327@@ -66,7 +66,8 @@ TC_GAME_API extern DB2Storage<ChrSpecializationEntry> sChrSpeciali
328 TC_GAME_API extern DB2Storage<CinematicCameraEntry> sCinematicCameraStore;
329 TC_GAME_API extern DB2Storage<CinematicSequencesEntry> sCinematicSequencesStore;
330 TC_GAME_API extern DB2Storage<ConversationLineEntry> sConversationLineStore;
331-TC_GAME_API extern DB2Storage<CreatureDisplayInfoEntry> sCreatureDisplayInfoStore;
332+TC_GAME_API extern DB2Storage<CreatureDisplayInfoEntry> sCreatureDisplayInfoStoreRaw;
333+TC_GAME_API extern CreatureDisplayInfoStore sCreatureDisplayInfoStore;
334 TC_GAME_API extern DB2Storage<CreatureDisplayInfoExtraEntry> sCreatureDisplayInfoExtraStore;
335 TC_GAME_API extern DB2Storage<CreatureFamilyEntry> sCreatureFamilyStore;
336 TC_GAME_API extern DB2Storage<CreatureModelDataEntry> sCreatureModelDataStore;
337@@ -143,6 +144,7 @@ TC_GAME_API extern DB2Storage<ModifierTreeEntry> sModifierTre
338 TC_GAME_API extern DB2Storage<MountCapabilityEntry> sMountCapabilityStore;
339 TC_GAME_API extern DB2Storage<MountEntry> sMountStore;
340 TC_GAME_API extern DB2Storage<MovieEntry> sMovieStore;
341+TC_GAME_API extern DB2Storage<NPCSoundsEntry> sNPCSoundsStore;
342 TC_GAME_API extern DB2Storage<OverrideSpellDataEntry> sOverrideSpellDataStore;
343 TC_GAME_API extern DB2Storage<PhaseEntry> sPhaseStore;
344 TC_GAME_API extern DB2Storage<PlayerConditionEntry> sPlayerConditionStore;
345diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h
346index e5f46417a81d..5e9971caaeff 100644
347--- a/src/server/game/DataStores/DB2Structure.h
348+++ b/src/server/game/DataStores/DB2Structure.h
349@@ -1990,6 +1990,21 @@ struct NamesReservedLocaleEntry
350 uint8 LocaleMask;
351 };
352
353+struct NPCSoundsEntry
354+{
355+ uint32 ID;
356+ uint32 hello;
357+ uint32 goodbye;
358+ uint32 pissed;
359+ uint32 ack;
360+};
361+
362+struct CreatureDisplayInfoStore
363+{
364+ bool HasRecord(uint32 id) const;
365+ const CreatureDisplayInfoEntry * LookupEntry(uint32 id) const;
366+};
367+
368 #define MAX_OVERRIDE_SPELL 10
369
370 struct OverrideSpellDataEntry
371diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
372index e0f03a7717e8..425dc9c27a62 100644
373--- a/src/server/game/Entities/Creature/Creature.cpp
374+++ b/src/server/game/Entities/Creature/Creature.cpp
375@@ -24,6 +24,7 @@
376 #include "CreatureAI.h"
377 #include "CreatureAISelector.h"
378 #include "CreatureGroups.h"
379+#include "CreatureOutfit.h"
380 #include "DatabaseEnv.h"
381 #include "Formulas.h"
382 #include "GameEventMgr.h"
383@@ -245,6 +246,49 @@ void Creature::RemoveFromWorld()
384 }
385 }
386
387+void Creature::SetOutfit(std::shared_ptr<CreatureOutfit> const & outfit)
388+{
389+ // Set new outfit
390+ if (m_outfit)
391+ {
392+ // if had old outfit
393+ // then delay displayid setting to allow equipment
394+ // to change by using invisible model in between
395+ SetDisplayId(CreatureOutfit::invisible_model);
396+ m_outfit = outfit;
397+ }
398+ else
399+ {
400+ // else set new outfit directly since we change from non-outfit->outfit
401+ m_outfit = outfit;
402+ SetDisplayId(outfit->GetDisplayId());
403+ }
404+}
405+
406+void Creature::SendMirrorSound(Player* target, uint8 type)
407+{
408+ std::shared_ptr<CreatureOutfit> const & outfit = GetOutfit();
409+ if (!outfit)
410+ return;
411+ if (!outfit->npcsoundsid)
412+ return;
413+ if (auto const* npcsounds = sNPCSoundsStore.LookupEntry(outfit->npcsoundsid))
414+ {
415+ switch (type)
416+ {
417+ case 0:
418+ PlayDistanceSound(npcsounds->hello, target);
419+ break;
420+ case 1:
421+ PlayDistanceSound(npcsounds->goodbye, target);
422+ break;
423+ case 2:
424+ PlayDistanceSound(npcsounds->pissed, target);
425+ break;
426+ }
427+ }
428+}
429+
430 void Creature::DisappearAndDie()
431 {
432 DestroyForNearbyPlayers();
433@@ -497,6 +541,13 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/,
434
435 void Creature::Update(uint32 diff)
436 {
437+ if (m_outfit && _changesMask[UNIT_FIELD_DISPLAYID] != 1 && Unit::GetDisplayId() == CreatureOutfit::invisible_model)
438+ {
439+ // has outfit, displayid is invisible and displayid update already sent to clients
440+ // set outfit display
441+ SetDisplayId(m_outfit->GetDisplayId());
442+ }
443+
444 if (IsAIEnabled && m_TriggerJustRespawned)
445 {
446 m_TriggerJustRespawned = false;
447@@ -2853,7 +2904,41 @@ void Creature::SetObjectScale(float scale)
448 }
449 }
450
451+uint32 Creature::GetDisplayId() const
452+{
453+ if (m_outfit && m_outfit->GetId())
454+ return m_outfit->GetId();
455+ return Unit::GetDisplayId();
456+}
457+
458 void Creature::SetDisplayId(uint32 modelId)
459+{
460+ if (auto const & outfit = sObjectMgr->GetOutfit(modelId))
461+ {
462+ SetOutfit(outfit);
463+ return;
464+ }
465+ else
466+ {
467+ if (!m_outfit || modelId != m_outfit->GetDisplayId())
468+ {
469+ // no outfit or outfit's real modelid doesnt match modelid being set
470+ // remove outfit and continue setting the new model
471+ m_outfit.reset();
472+ SetMirrorImageFlag(false);
473+ }
474+ else
475+ {
476+ // outfit's real modelid being set
477+ // add flags and continue setting the model
478+ SetMirrorImageFlag(true);
479+ }
480+ }
481+
482+ SetDisplayIdRaw(modelId);
483+}
484+
485+void Creature::SetDisplayIdRaw(uint32 modelId)
486 {
487 Unit::SetDisplayId(modelId);
488
489diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
490index d87db9be2cad..b9f9753fb263 100644
491--- a/src/server/game/Entities/Creature/Creature.h
492+++ b/src/server/game/Entities/Creature/Creature.h
493@@ -29,6 +29,9 @@
494
495 #include <list>
496
497+class CreatureOutfit;
498+#include <memory>
499+
500 class CreatureAI;
501 class CreatureGroup;
502 class Group;
503@@ -70,6 +73,13 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
504
505 void SetObjectScale(float scale) override;
506 void SetDisplayId(uint32 modelId) override;
507+ uint32 GetDisplayId() const final;
508+ void SetDisplayIdRaw(uint32 modelId);
509+
510+ std::shared_ptr<CreatureOutfit> & GetOutfit() { return m_outfit; };
511+ void SetOutfit(std::shared_ptr<CreatureOutfit> const & outfit);
512+ void SetMirrorImageFlag(bool on) { if (on) SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_MIRROR_IMAGE); else RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_MIRROR_IMAGE); };
513+ void SendMirrorSound(Player* target, uint8 type);
514
515 void DisappearAndDie();
516
517@@ -414,6 +424,8 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
518 ObjectGuid m_suppressedTarget; // Stores the creature's "real" target while casting
519 float m_suppressedOrientation; // Stores the creature's "real" orientation while casting
520
521+ std::shared_ptr<CreatureOutfit> m_outfit;
522+
523 CreatureTextRepeatGroup m_textRepeat;
524 };
525
526diff --git a/src/server/game/Entities/Creature/CreatureOutfit.cpp b/src/server/game/Entities/Creature/CreatureOutfit.cpp
527new file mode 100644
528index 000000000000..37958371ef38
529--- /dev/null
530+++ b/src/server/game/Entities/Creature/CreatureOutfit.cpp
531@@ -0,0 +1,31 @@
532+#include "CreatureOutfit.h"
533+#include "DB2Structure.h" // ChrRacesEntry
534+#include "DB2Stores.h" // sChrRacesStore, sDB2Manager
535+
536+constexpr uint32 CreatureOutfit::max_custom_displays;
537+constexpr uint32 CreatureOutfit::invisible_model;
538+constexpr uint32 CreatureOutfit::max_real_modelid;
539+constexpr EquipmentSlots CreatureOutfit::item_slots[];
540+
541+CreatureOutfit::CreatureOutfit(uint8 race, Gender gender) : race(race), gender(gender)
542+{
543+ const ChrRacesEntry* rEntry = sChrRacesStore.LookupEntry(race);
544+ if (!rEntry)
545+ {
546+ rEntry = sChrRacesStore.LookupEntry(RACE_HUMAN);
547+ }
548+ switch (gender)
549+ {
550+ case GENDER_FEMALE: displayId = rEntry->FemaleDisplayId; break;
551+ default: displayId = rEntry->MaleDisplayId; break;
552+ }
553+}
554+
555+CreatureOutfit& CreatureOutfit::SetItemEntry(EquipmentSlots slot, uint32 item_entry, uint32 appearancemodid)
556+{
557+ if (uint32 display = sDB2Manager.GetItemDisplayId(item_entry, appearancemodid))
558+ outfitdisplays[slot] = display;
559+ else
560+ outfitdisplays[slot] = 0;
561+ return *this;
562+}
563diff --git a/src/server/game/Entities/Creature/CreatureOutfit.h b/src/server/game/Entities/Creature/CreatureOutfit.h
564new file mode 100644
565index 000000000000..d1d9027d34f4
566--- /dev/null
567+++ b/src/server/game/Entities/Creature/CreatureOutfit.h
568@@ -0,0 +1,71 @@
569+#ifndef CREATURE_OUTFIT_H
570+#define CREATURE_OUTFIT_H
571+
572+#include "Define.h"
573+#include "Player.h" // EquipmentSlots
574+#include "SharedDefines.h" // Gender
575+#include <memory>
576+
577+class Creature;
578+class WorldSession;
579+
580+class TC_GAME_API CreatureOutfit
581+{
582+public:
583+ friend class ObjectMgr;
584+
585+ // Remember to change DB query too!
586+ static constexpr uint32 max_custom_displays = 3;
587+ static constexpr uint32 invisible_model = 11686;
588+ static constexpr uint32 max_real_modelid = 0x7FFFFFFF;
589+ static constexpr EquipmentSlots item_slots[] =
590+ {
591+ EQUIPMENT_SLOT_HEAD,
592+ EQUIPMENT_SLOT_SHOULDERS,
593+ EQUIPMENT_SLOT_BODY,
594+ EQUIPMENT_SLOT_CHEST,
595+ EQUIPMENT_SLOT_WAIST,
596+ EQUIPMENT_SLOT_LEGS,
597+ EQUIPMENT_SLOT_FEET,
598+ EQUIPMENT_SLOT_WRISTS,
599+ EQUIPMENT_SLOT_HANDS,
600+ EQUIPMENT_SLOT_TABARD,
601+ EQUIPMENT_SLOT_BACK,
602+ };
603+
604+ static bool IsFake(uint32 modelid) { return modelid > max_real_modelid; };
605+
606+ CreatureOutfit(uint8 race, Gender gender);
607+
608+ uint8 Class = 1;
609+ uint8 face = 0;
610+ uint8 skin = 0;
611+ uint8 hair = 0;
612+ uint8 facialhair = 0;
613+ uint8 haircolor = 0;
614+ uint8 customdisplay[max_custom_displays] = { 0 };
615+ uint32 outfitdisplays[EQUIPMENT_SLOT_END] = { 0 };
616+ uint32 npcsoundsid = 0;
617+ uint64 guild = 0;
618+
619+ uint32 GetId() const { return id; }
620+ uint8 GetGender() const { return gender; }
621+ uint8 GetRace() const { return race; }
622+ uint32 GetDisplayId() const { return displayId; }
623+
624+ CreatureOutfit& SetItemEntry(EquipmentSlots slot, uint32 item_entry, uint32 appearancemodid = 0);
625+ CreatureOutfit& SetItemDisplay(EquipmentSlots slot, uint32 displayid)
626+ {
627+ outfitdisplays[slot] = displayid;
628+ return *this;
629+ }
630+
631+private:
632+ CreatureOutfit() {};
633+ uint32 id = 0;
634+ uint8 race;
635+ uint8 gender;
636+ uint32 displayId;
637+};
638+
639+#endif
640diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
641index 74e2ed60f779..0c6d8d743434 100644
642--- a/src/server/game/Entities/Unit/Unit.h
643+++ b/src/server/game/Entities/Unit/Unit.h
644@@ -1674,7 +1674,7 @@ class TC_GAME_API Unit : public WorldObject
645 }
646 void UpdateInterruptMask();
647
648- uint32 GetDisplayId() const { return GetUInt32Value(UNIT_FIELD_DISPLAYID); }
649+ virtual uint32 GetDisplayId() const { return GetUInt32Value(UNIT_FIELD_DISPLAYID); }
650 virtual void SetDisplayId(uint32 modelId);
651 uint32 GetNativeDisplayId() const { return GetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID); }
652 void RestoreDisplayId(bool ignorePositiveAurasPreventingMounting = false);
653diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
654index 2dd2753cde35..955c0af50402 100644
655--- a/src/server/game/Globals/ObjectMgr.cpp
656+++ b/src/server/game/Globals/ObjectMgr.cpp
657@@ -20,6 +20,8 @@
658 #include "ArenaTeamMgr.h"
659 #include "Chat.h"
660 #include "Containers.h"
661+#include "Creature.h"
662+#include "CreatureOutfit.h"
663 #include "DatabaseEnv.h"
664 #include "DB2Stores.h"
665 #include "DisableMgr.h"
666@@ -1395,6 +1397,7 @@ void ObjectMgr::LoadEquipmentTemplates()
667
668 CreatureModelInfo const* ObjectMgr::GetCreatureModelInfo(uint32 modelId) const
669 {
670+ modelId = GetRealDisplayId(modelId);
671 CreatureModelContainer::const_iterator itr = _creatureModelStore.find(modelId);
672 if (itr != _creatureModelStore.end())
673 return &(itr->second);
674@@ -1444,6 +1447,12 @@ void ObjectMgr::ChooseCreatureFlags(CreatureTemplate const* cInfo, uint64& npcFl
675
676 CreatureModelInfo const* ObjectMgr::GetCreatureModelRandomGender(uint32* displayID) const
677 {
678+ {
679+ uint32 displayid_temp = GetRealDisplayId(*displayID);
680+ if (displayid_temp != *displayID)
681+ return GetCreatureModelRandomGender(&displayid_temp);
682+ }
683+
684 CreatureModelInfo const* modelInfo = GetCreatureModelInfo(*displayID);
685 if (!modelInfo)
686 return nullptr;
687@@ -8398,6 +8407,142 @@ SkillRangeType GetSkillRangeType(SkillRaceClassInfoEntry const* rcEntry)
688 return SKILL_RANGE_LEVEL;
689 }
690
691+void ObjectMgr::LoadCreatureOutfits()
692+{
693+ uint32 oldMSTime = getMSTime();
694+
695+ _creatureOutfitStore.clear();
696+
697+ for (auto* e : sChrRacesStore)
698+ {
699+ const char* error = "Dress NPCs requires an entry in creature_model_info for modelid %u (%s %s)";
700+ ASSERT(GetCreatureModelInfo(e->MaleDisplayId), error, e->MaleDisplayId, e->Name->Str[DEFAULT_LOCALE], "Male");
701+ ASSERT(GetCreatureModelInfo(e->FemaleDisplayId), error, e->FemaleDisplayId, e->Name->Str[DEFAULT_LOCALE], "Female");
702+ }
703+
704+ QueryResult result = WorldDatabase.Query("SELECT entry, npcsoundsid, race, class, gender, skin, face, hair, haircolor, facialhair, feature1, feature2, feature3, "
705+ "head, head_appearance, shoulders, shoulders_appearance, body, body_appearance, chest, chest_appearance, waist, waist_appearance, "
706+ "legs, legs_appearance, feet, feet_appearance, wrists, wrists_appearance, hands, hands_appearance, tabard, tabard_appearance, back, back_appearance, "
707+ "guildid FROM creature_template_outfits");
708+
709+ if (!result)
710+ {
711+ TC_LOG_ERROR("server.loading", ">> Loaded 0 creature outfits. DB table `creature_template_outfits` is empty!");
712+ return;
713+ }
714+
715+ uint32 count = 0;
716+
717+ do
718+ {
719+ Field* fields = result->Fetch();
720+
721+ uint32 i = 0;
722+ uint32 entry = fields[i++].GetUInt32();
723+
724+ if (!CreatureOutfit::IsFake(entry))
725+ {
726+ TC_LOG_ERROR("server.loading", ">> Outfit entry %u in `creature_template_outfits` has too low entry (entry <= %u). Ignoring.", entry, CreatureOutfit::max_real_modelid);
727+ continue;
728+ }
729+
730+ std::shared_ptr<CreatureOutfit> co(new CreatureOutfit());
731+
732+ co->id = entry;
733+ co->npcsoundsid = fields[i++].GetUInt32();
734+ if (co->npcsoundsid && !sNPCSoundsStore.HasRecord(co->npcsoundsid))
735+ {
736+ TC_LOG_ERROR("server.loading", ">> Outfit entry %u in `creature_template_outfits` has incorrect npcsoundsid (%u). Using 0.", entry, co->npcsoundsid);
737+ co->npcsoundsid = 0;
738+ }
739+ co->race = fields[i++].GetUInt8();
740+ const ChrRacesEntry* rEntry = sChrRacesStore.LookupEntry(co->race);
741+ if (!rEntry)
742+ {
743+ TC_LOG_ERROR("server.loading", ">> Outfit entry %u in `creature_template_outfits` has incorrect race (%u).", entry, uint32(co->race));
744+ continue;
745+ }
746+
747+ co->Class = fields[i++].GetUInt8();
748+ const ChrClassesEntry* cEntry = sChrClassesStore.LookupEntry(co->Class);
749+ if (!cEntry)
750+ {
751+ TC_LOG_ERROR("server.loading", ">> Outfit entry %u in `creature_template_outfits` has incorrect class (%u).", entry, uint32(co->Class));
752+ continue;
753+ }
754+
755+ co->gender = fields[i++].GetUInt8();
756+ switch (co->gender)
757+ {
758+ case GENDER_FEMALE: co->displayId = rEntry->FemaleDisplayId; break;
759+ case GENDER_MALE: co->displayId = rEntry->MaleDisplayId; break;
760+ default:
761+ TC_LOG_ERROR("server.loading", ">> Outfit entry %u in `creature_template_outfits` has invalid gender %u", entry, uint32(co->gender));
762+ continue;
763+ }
764+
765+ co->skin = fields[i++].GetUInt8();
766+ co->face = fields[i++].GetUInt8();
767+ co->hair = fields[i++].GetUInt8();
768+ co->haircolor = fields[i++].GetUInt8();
769+ co->facialhair = fields[i++].GetUInt8();
770+ for (uint32 j = 0; j < CreatureOutfit::max_custom_displays; ++j)
771+ co->customdisplay[j] = fields[i++].GetUInt8();
772+ for (EquipmentSlots slot : CreatureOutfit::item_slots)
773+ {
774+ int64 displayInfo = fields[i++].GetInt64();
775+ uint32 appearancemodid = fields[i++].GetUInt32();
776+ if (displayInfo > 0) // entry
777+ {
778+ uint32 item_entry = static_cast<uint32>(displayInfo);
779+ if (uint32 display = sDB2Manager.GetItemDisplayId(item_entry, appearancemodid))
780+ co->outfitdisplays[slot] = display;
781+ else
782+ {
783+ TC_LOG_ERROR("server.loading", ">> Outfit entry %u in `creature_template_outfits` has invalid (item entry, appearance) combination: %u, %u. Ignoring.", entry, item_entry, appearancemodid);
784+ co->outfitdisplays[slot] = 0;
785+ }
786+ }
787+ else // display
788+ {
789+ if (appearancemodid)
790+ {
791+ TC_LOG_ERROR("server.loading", ">> Outfit entry %u in `creature_template_outfits` is using displayid (negative value), but also has appearance set (displayid, appearance): %s, %u. Ignoring appearance.", entry, std::to_string(displayInfo).c_str(), appearancemodid);
792+ }
793+ co->outfitdisplays[slot] = static_cast<uint32>(-displayInfo);
794+ }
795+ }
796+ co->guild = fields[i++].GetUInt64();
797+
798+ _creatureOutfitStore[co->id] = std::move(co);
799+
800+ ++count;
801+ }
802+ while (result->NextRow());
803+
804+ TC_LOG_INFO("server.loading", ">> Loaded %u creature outfits in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
805+}
806+
807+std::shared_ptr<CreatureOutfit> const & ObjectMgr::GetOutfit(uint32 modelid) const
808+{
809+ static std::shared_ptr<CreatureOutfit> empty;
810+ if (CreatureOutfit::IsFake(modelid))
811+ {
812+ auto const & outfits = GetCreatureOutfitMap();
813+ auto it = outfits.find(modelid);
814+ if (it != outfits.end())
815+ return it->second;
816+ }
817+ return empty;
818+}
819+
820+uint32 ObjectMgr::GetRealDisplayId(uint32 modelid) const
821+{
822+ if (std::shared_ptr<CreatureOutfit> outfit = GetOutfit(modelid))
823+ return outfit->displayId;
824+ return modelid;
825+}
826+
827 void ObjectMgr::LoadGameTele()
828 {
829 uint32 oldMSTime = getMSTime();
830diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h
831index d8a667210c16..cf0d0f070e88 100644
832--- a/src/server/game/Globals/ObjectMgr.h
833+++ b/src/server/game/Globals/ObjectMgr.h
834@@ -36,6 +36,9 @@
835 #include <map>
836 #include <unordered_map>
837
838+#include <memory>
839+class CreatureOutfit;
840+
841 class Item;
842 class Unit;
843 class Vehicle;
844@@ -949,6 +952,8 @@ class TC_GAME_API ObjectMgr
845
846 typedef std::map<uint32, uint32> CharacterConversionMap;
847
848+ typedef std::unordered_map<uint32, std::shared_ptr<CreatureOutfit>> CreatureOutfitContainer;
849+
850 GameObjectTemplate const* GetGameObjectTemplate(uint32 entry) const;
851 GameObjectTemplateContainer const* GetGameObjectTemplates() const { return &_gameObjectTemplateStore; }
852 int LoadReferenceVendor(int32 vendor, int32 item, std::set<uint32> *skip_vendors);
853@@ -1475,6 +1480,11 @@ class TC_GAME_API ObjectMgr
854 bool AddGameTele(GameTele& data);
855 bool DeleteGameTele(std::string const& name);
856
857+ const CreatureOutfitContainer& GetCreatureOutfitMap() const { return _creatureOutfitStore; }
858+ std::shared_ptr<CreatureOutfit> const & GetOutfit(uint32 modelid) const;
859+ uint32 GetRealDisplayId(uint32 modelid) const;
860+ void LoadCreatureOutfits();
861+
862 Trainer::Trainer const* GetTrainer(uint32 trainerId) const;
863 uint32 GetCreatureDefaultTrainer(uint32 creatureId) const;
864
865@@ -1662,6 +1672,8 @@ class TC_GAME_API ObjectMgr
866 PageTextContainer _pageTextStore;
867 InstanceTemplateContainer _instanceTemplateStore;
868
869+ CreatureOutfitContainer _creatureOutfitStore;
870+
871 public:
872 PhaseInfoStruct const* GetPhaseInfo(uint32 phaseId) const;
873 std::vector<PhaseAreaInfo> const* GetPhasesForArea(uint32 areaId) const;
874diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp
875index f5a4911ec6ae..8664deb2f257 100644
876--- a/src/server/game/Handlers/AuctionHouseHandler.cpp
877+++ b/src/server/game/Handlers/AuctionHouseHandler.cpp
878@@ -26,6 +26,7 @@
879 #include "Language.h"
880 #include "Log.h"
881 #include "Mail.h"
882+#include "Map.h"
883 #include "ObjectAccessor.h"
884 #include "ObjectMgr.h"
885 #include "Player.h"
886@@ -36,6 +37,11 @@
887 //void called when player click on auctioneer npc
888 void WorldSession::HandleAuctionHelloOpcode(WorldPackets::AuctionHouse::AuctionHelloRequest& packet)
889 {
890+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
891+ if (packet.Guid.IsAnyTypeCreature())
892+ if (Creature* creature = _player->GetMap()->GetCreature(packet.Guid))
893+ creature->SendMirrorSound(_player, 0);
894+#endif
895 Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(packet.Guid, UNIT_NPC_FLAG_AUCTIONEER);
896 if (!unit)
897 {
898diff --git a/src/server/game/Handlers/BankHandler.cpp b/src/server/game/Handlers/BankHandler.cpp
899index 8f8de925da21..78d85e3b507c 100644
900--- a/src/server/game/Handlers/BankHandler.cpp
901+++ b/src/server/game/Handlers/BankHandler.cpp
902@@ -16,9 +16,11 @@
903 */
904
905 #include "BankPackets.h"
906+#include "Creature.h"
907 #include "Item.h"
908 #include "DB2Stores.h"
909 #include "Log.h"
910+#include "Map.h"
911 #include "NPCPackets.h"
912 #include "Opcodes.h"
913 #include "Player.h"
914@@ -60,6 +62,11 @@ void WorldSession::HandleAutoBankItemOpcode(WorldPackets::Bank::AutoBankItem& pa
915
916 void WorldSession::HandleBankerActivateOpcode(WorldPackets::NPC::Hello& packet)
917 {
918+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
919+ if (packet.Unit.IsAnyTypeCreature())
920+ if (Creature* creature = _player->GetMap()->GetCreature(packet.Unit))
921+ creature->SendMirrorSound(_player, 0);
922+ #endif
923 Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(packet.Unit, UNIT_NPC_FLAG_BANKER);
924 if (!unit)
925 {
926diff --git a/src/server/game/Handlers/GuildHandler.cpp b/src/server/game/Handlers/GuildHandler.cpp
927index d12e83bb078f..caff078279d2 100644
928--- a/src/server/game/Handlers/GuildHandler.cpp
929+++ b/src/server/game/Handlers/GuildHandler.cpp
930@@ -19,10 +19,12 @@
931 #include "WorldSession.h"
932 #include "AchievementPackets.h"
933 #include "Common.h"
934+#include "Creature.h"
935 #include "Guild.h"
936 #include "GuildMgr.h"
937 #include "GuildPackets.h"
938 #include "Log.h"
939+#include "Map.h"
940 #include "ObjectMgr.h"
941 #include "Opcodes.h"
942 #include "Player.h"
943@@ -35,7 +37,6 @@ void WorldSession::HandleGuildQueryOpcode(WorldPackets::Guild::QueryGuildInfo& q
944 GetPlayerInfo().c_str(), query.GuildGuid.ToString().c_str(), query.PlayerGuid.ToString().c_str());
945
946 if (Guild* guild = sGuildMgr->GetGuildByGuid(query.GuildGuid))
947- if (guild->IsMember(query.PlayerGuid))
948 {
949 guild->SendQueryResponse(this);
950 return;
951@@ -232,6 +233,11 @@ void WorldSession::HandleGuildBankActivate(WorldPackets::Guild::GuildBankActivat
952 TC_LOG_DEBUG("guild", "CMSG_GUILD_BANK_ACTIVATE [%s]: [%s] AllSlots: %u"
953 , GetPlayerInfo().c_str(), packet.Banker.ToString().c_str(), packet.FullUpdate);
954
955+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
956+ if (packet.Banker.IsAnyTypeCreature())
957+ if (Creature* creature = _player->GetMap()->GetCreature(packet.Banker))
958+ creature->SendMirrorSound(_player, 0);
959+#endif
960 GameObject const* const go = GetPlayer()->GetGameObjectIfCanInteractWith(packet.Banker, GAMEOBJECT_TYPE_GUILD_BANK);
961 if (!go)
962 return;
963diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp
964index 0ba24d95ac90..1ca2608fbe02 100644
965--- a/src/server/game/Handlers/ItemHandler.cpp
966+++ b/src/server/game/Handlers/ItemHandler.cpp
967@@ -25,6 +25,7 @@
968 #include "Item.h"
969 #include "ItemPackets.h"
970 #include "Log.h"
971+#include "Map.h"
972 #include "NPCPackets.h"
973 #include "ObjectMgr.h"
974 #include "Opcodes.h"
975@@ -581,6 +582,11 @@ void WorldSession::HandleListInventoryOpcode(WorldPackets::NPC::Hello& packet)
976 if (!GetPlayer()->IsAlive())
977 return;
978
979+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
980+ if (packet.Unit.IsAnyTypeCreature())
981+ if (Creature* creature = _player->GetMap()->GetCreature(packet.Unit))
982+ creature->SendMirrorSound(_player, 0);
983+#endif
984 SendListInventory(packet.Unit);
985 }
986
987diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp
988index 296cd80e3234..a6ae89e83891 100644
989--- a/src/server/game/Handlers/MiscHandler.cpp
990+++ b/src/server/game/Handlers/MiscHandler.cpp
991@@ -27,6 +27,7 @@
992 #include "ClientConfigPackets.h"
993 #include "Common.h"
994 #include "Corpse.h"
995+#include "Creature.h"
996 #include "DatabaseEnv.h"
997 #include "DB2Stores.h"
998 #include "GossipDef.h"
999@@ -37,6 +38,7 @@
1000 #include "InstanceScript.h"
1001 #include "Language.h"
1002 #include "Log.h"
1003+#include "Map.h"
1004 #include "MapManager.h"
1005 #include "MiscPackets.h"
1006 #include "Object.h"
1007@@ -401,6 +403,11 @@ void WorldSession::HandleRequestCemeteryList(WorldPackets::Misc::RequestCemetery
1008
1009 void WorldSession::HandleSetSelectionOpcode(WorldPackets::Misc::SetSelection& packet)
1010 {
1011+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
1012+ if (packet.Selection.IsAnyTypeCreature())
1013+ if (Creature* creature = _player->GetMap()->GetCreature(packet.Selection))
1014+ creature->SendMirrorSound(_player, 0);
1015+#endif
1016 _player->SetSelection(packet.Selection);
1017 }
1018
1019@@ -1160,6 +1167,11 @@ void WorldSession::HandlePvpPrestigeRankUp(WorldPackets::Misc::PvpPrestigeRankUp
1020
1021 void WorldSession::HandleCloseInteraction(WorldPackets::Misc::CloseInteraction& closeInteraction)
1022 {
1023+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
1024+ if (closeInteraction.SourceGuid.IsAnyTypeCreature())
1025+ if (Creature* creature = _player->GetMap()->GetCreature(closeInteraction.SourceGuid))
1026+ creature->SendMirrorSound(_player, 1);
1027+#endif
1028 if (_player->PlayerTalkClass->GetInteractionData().SourceGuid == closeInteraction.SourceGuid)
1029 _player->PlayerTalkClass->GetInteractionData().Reset();
1030 }
1031diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp
1032index ca7b036a89e6..e450b2ee7110 100644
1033--- a/src/server/game/Handlers/NPCHandler.cpp
1034+++ b/src/server/game/Handlers/NPCHandler.cpp
1035@@ -60,6 +60,11 @@ enum StableResultCode
1036
1037 void WorldSession::HandleTabardVendorActivateOpcode(WorldPackets::NPC::Hello& packet)
1038 {
1039+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
1040+ if (packet.Unit.IsAnyTypeCreature())
1041+ if (Creature* creature = _player->GetMap()->GetCreature(packet.Unit))
1042+ creature->SendMirrorSound(_player, 0);
1043+#endif
1044 Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(packet.Unit, UNIT_NPC_FLAG_TABARDDESIGNER);
1045 if (!unit)
1046 {
1047@@ -90,6 +95,11 @@ void WorldSession::SendShowMailBox(ObjectGuid guid)
1048
1049 void WorldSession::HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet)
1050 {
1051+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
1052+ if (packet.Unit.IsAnyTypeCreature())
1053+ if (Creature* creature = _player->GetMap()->GetCreature(packet.Unit))
1054+ creature->SendMirrorSound(_player, 0);
1055+#endif
1056 Creature* npc = GetPlayer()->GetNPCIfCanInteractWith(packet.Unit, UNIT_NPC_FLAG_TRAINER);
1057 if (!npc)
1058 {
1059@@ -152,6 +162,11 @@ void WorldSession::HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpel
1060
1061 void WorldSession::HandleGossipHelloOpcode(WorldPackets::NPC::Hello& packet)
1062 {
1063+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
1064+ if (packet.Unit.IsAnyTypeCreature())
1065+ if (Creature* creature = _player->GetMap()->GetCreature(packet.Unit))
1066+ creature->SendMirrorSound(_player, 0);
1067+#endif
1068 Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(packet.Unit, UNIT_NPC_FLAG_GOSSIP);
1069 if (!unit)
1070 {
1071@@ -359,6 +374,11 @@ void WorldSession::SendBindPoint(Creature* npc)
1072
1073 void WorldSession::HandleRequestStabledPets(WorldPackets::NPC::RequestStabledPets& packet)
1074 {
1075+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
1076+ if (packet.StableMaster.IsAnyTypeCreature())
1077+ if (Creature* creature = _player->GetMap()->GetCreature(packet.StableMaster))
1078+ creature->SendMirrorSound(_player, 0);
1079+#endif
1080 if (!CheckStableMaster(packet.StableMaster))
1081 return;
1082
1083diff --git a/src/server/game/Handlers/PetitionsHandler.cpp b/src/server/game/Handlers/PetitionsHandler.cpp
1084index 922bc519e135..caa759ea066b 100644
1085--- a/src/server/game/Handlers/PetitionsHandler.cpp
1086+++ b/src/server/game/Handlers/PetitionsHandler.cpp
1087@@ -18,11 +18,13 @@
1088
1089 #include "WorldSession.h"
1090 #include "Common.h"
1091+#include "Creature.h"
1092 #include "DatabaseEnv.h"
1093 #include "Guild.h"
1094 #include "GuildMgr.h"
1095 #include "Item.h"
1096 #include "Log.h"
1097+#include "Map.h"
1098 #include "ObjectAccessor.h"
1099 #include "ObjectMgr.h"
1100 #include "Opcodes.h"
1101@@ -575,6 +577,11 @@ void WorldSession::HandleTurnInPetition(WorldPackets::Petition::TurnInPetition&
1102
1103 void WorldSession::HandlePetitionShowList(WorldPackets::Petition::PetitionShowList& packet)
1104 {
1105+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
1106+ if (packet.PetitionUnit.IsAnyTypeCreature())
1107+ if (Creature* creature = _player->GetMap()->GetCreature(packet.PetitionUnit))
1108+ creature->SendMirrorSound(_player, 0);
1109+#endif
1110 SendPetitionShowList(packet.PetitionUnit);
1111 }
1112
1113diff --git a/src/server/game/Handlers/QueryHandler.cpp b/src/server/game/Handlers/QueryHandler.cpp
1114index 273bf2878215..033dcf715a39 100644
1115--- a/src/server/game/Handlers/QueryHandler.cpp
1116+++ b/src/server/game/Handlers/QueryHandler.cpp
1117@@ -95,6 +95,10 @@ void WorldSession::HandleCreatureQuery(WorldPackets::Query::QueryCreature& packe
1118 for (uint32 i = 0; i < MAX_KILL_CREDIT; ++i)
1119 stats.ProxyCreatureID[i] = creatureInfo->KillCredit[i];
1120
1121+ // stats.CreatureDisplayID[0] = sObjectMgr->GetRealDisplayId(creatureInfo->Modelid1);
1122+ // stats.CreatureDisplayID[1] = sObjectMgr->GetRealDisplayId(creatureInfo->Modelid2);
1123+ // stats.CreatureDisplayID[2] = sObjectMgr->GetRealDisplayId(creatureInfo->Modelid3);
1124+ // stats.CreatureDisplayID[3] = sObjectMgr->GetRealDisplayId(creatureInfo->Modelid4);
1125 stats.CreatureDisplayID[0] = creatureInfo->Modelid1;
1126 stats.CreatureDisplayID[1] = creatureInfo->Modelid2;
1127 stats.CreatureDisplayID[2] = creatureInfo->Modelid3;
1128diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp
1129index 4a031305052e..585a74a7f259 100644
1130--- a/src/server/game/Handlers/QuestHandler.cpp
1131+++ b/src/server/game/Handlers/QuestHandler.cpp
1132@@ -27,6 +27,7 @@
1133 #include "GossipDef.h"
1134 #include "Group.h"
1135 #include "Log.h"
1136+#include "Map.h"
1137 #include "ObjectAccessor.h"
1138 #include "ObjectMgr.h"
1139 #include "Player.h"
1140@@ -77,6 +78,11 @@ void WorldSession::HandleQuestgiverHelloOpcode(WorldPackets::Quest::QuestGiverHe
1141 {
1142 TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_HELLO %s", packet.QuestGiverGUID.ToString().c_str());
1143
1144+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
1145+ if (packet.QuestGiverGUID.IsAnyTypeCreature())
1146+ if (Creature* creature = _player->GetMap()->GetCreature(packet.QuestGiverGUID))
1147+ creature->SendMirrorSound(_player, 0);
1148+#endif
1149 Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.QuestGiverGUID, UNIT_NPC_FLAG_QUESTGIVER);
1150 if (!creature)
1151 {
1152diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp
1153index ca011dfd61f4..2cf036e8c585 100644
1154--- a/src/server/game/Handlers/SpellHandler.cpp
1155+++ b/src/server/game/Handlers/SpellHandler.cpp
1156@@ -19,6 +19,7 @@
1157 #include "WorldSession.h"
1158 #include "CollectionMgr.h"
1159 #include "Common.h"
1160+#include "CreatureOutfit.h"
1161 #include "DatabaseEnv.h"
1162 #include "GameObjectAI.h"
1163 #include "GameObjectPackets.h"
1164@@ -501,6 +502,43 @@ void WorldSession::HandleMirrorImageDataRequest(WorldPackets::Spells::GetMirrorI
1165 if (!unit)
1166 return;
1167
1168+ if (Creature* creature = unit->ToCreature())
1169+ {
1170+ if (std::shared_ptr<CreatureOutfit> const & outfit_ptr = creature->GetOutfit())
1171+ {
1172+ CreatureOutfit const& outfit = *outfit_ptr;
1173+ WorldPackets::Spells::MirrorImageComponentedData mirrorImageComponentedData;
1174+ mirrorImageComponentedData.UnitGUID = guid;
1175+ mirrorImageComponentedData.DisplayID = outfit.GetDisplayId();
1176+ mirrorImageComponentedData.RaceID = outfit.GetRace();
1177+ mirrorImageComponentedData.Gender = outfit.GetGender();
1178+ mirrorImageComponentedData.ClassID = outfit.Class;
1179+
1180+ mirrorImageComponentedData.SkinColor = outfit.skin;
1181+ mirrorImageComponentedData.FaceVariation = outfit.face;
1182+ mirrorImageComponentedData.HairVariation = outfit.hair;
1183+ mirrorImageComponentedData.HairColor = outfit.haircolor;
1184+ mirrorImageComponentedData.BeardVariation = outfit.facialhair;
1185+
1186+ static_assert(CreatureOutfit::max_custom_displays == PLAYER_CUSTOM_DISPLAY_SIZE, "Amount of custom displays for player has changed - change it for dressnpcs as well");
1187+ for (uint32 i = 0; i < PLAYER_CUSTOM_DISPLAY_SIZE; ++i)
1188+ mirrorImageComponentedData.CustomDisplay[i] = outfit.customdisplay[i];
1189+ mirrorImageComponentedData.GuildGUID = ObjectGuid::Empty;
1190+ if (outfit.guild)
1191+ {
1192+ if (Guild* guild = sGuildMgr->GetGuildById(outfit.guild))
1193+ mirrorImageComponentedData.GuildGUID = guild->GetGUID();
1194+ }
1195+
1196+ mirrorImageComponentedData.ItemDisplayID.reserve(11);
1197+ for (auto const& slot : CreatureOutfit::item_slots)
1198+ mirrorImageComponentedData.ItemDisplayID.push_back(outfit.outfitdisplays[slot]);
1199+
1200+ SendPacket(mirrorImageComponentedData.Write());
1201+ return;
1202+ }
1203+ }
1204+
1205 if (!unit->HasAuraType(SPELL_AURA_CLONE_CASTER))
1206 return;
1207
1208diff --git a/src/server/game/Handlers/TaxiHandler.cpp b/src/server/game/Handlers/TaxiHandler.cpp
1209index abb05a2a0c94..7b5e66d5968b 100644
1210--- a/src/server/game/Handlers/TaxiHandler.cpp
1211+++ b/src/server/game/Handlers/TaxiHandler.cpp
1212@@ -23,6 +23,7 @@
1213 #include "DatabaseEnv.h"
1214 #include "DB2Stores.h"
1215 #include "Log.h"
1216+#include "Map.h"
1217 #include "ObjectAccessor.h"
1218 #include "ObjectMgr.h"
1219 #include "Player.h"
1220@@ -68,6 +69,11 @@ void WorldSession::SendTaxiStatus(ObjectGuid guid)
1221
1222 void WorldSession::HandleTaxiQueryAvailableNodesOpcode(WorldPackets::Taxi::TaxiQueryAvailableNodes& taxiQueryAvailableNodes)
1223 {
1224+#ifndef DISABLE_DRESSNPCS_CORESOUNDS
1225+ if (taxiQueryAvailableNodes.Unit.IsAnyTypeCreature())
1226+ if (Creature* creature = _player->GetMap()->GetCreature(taxiQueryAvailableNodes.Unit))
1227+ creature->SendMirrorSound(_player, 0);
1228+#endif
1229 // cheating checks
1230 Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(taxiQueryAvailableNodes.Unit, UNIT_NPC_FLAG_FLIGHTMASTER);
1231 if (!unit)
1232diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp
1233index acadaf0d64af..8ea4bc365401 100644
1234--- a/src/server/game/Server/WorldSession.cpp
1235+++ b/src/server/game/Server/WorldSession.cpp
1236@@ -1247,6 +1247,7 @@ uint32 WorldSession::DosProtection::GetMaxPacketCounterAllowed(uint16 opcode) co
1237 case CMSG_CHAT_MESSAGE_YELL: // 0 3.5
1238 case CMSG_INSPECT: // 0 3.5
1239 case CMSG_AREA_SPIRIT_HEALER_QUERY: // not profiled
1240+ case CMSG_GET_MIRROR_IMAGE_DATA: // not profiled
1241 case CMSG_STAND_STATE_CHANGE: // not profiled
1242 case CMSG_RANDOM_ROLL: // not profiled
1243 case CMSG_TIME_SYNC_RESPONSE: // not profiled
1244diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
1245index 4484e3c1f879..07c3740202bf 100644
1246--- a/src/server/game/World/World.cpp
1247+++ b/src/server/game/World/World.cpp
1248@@ -1723,6 +1723,9 @@ void World::SetInitialWorldSettings()
1249 TC_LOG_INFO("server.loading", "Loading Creature Model Based Info Data...");
1250 sObjectMgr->LoadCreatureModelInfo();
1251
1252+ TC_LOG_INFO("server.loading", "Loading Creature template outfits..."); // must be before LoadCreatureTemplates
1253+ sObjectMgr->LoadCreatureOutfits();
1254+
1255 TC_LOG_INFO("server.loading", "Loading Creature templates...");
1256 sObjectMgr->LoadCreatureTemplates();
1257
1258diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp
1259index 2fdf3dde9561..f939b748309a 100644
1260--- a/src/server/scripts/Commands/cs_reload.cpp
1261+++ b/src/server/scripts/Commands/cs_reload.cpp
1262@@ -30,6 +30,8 @@ EndScriptData */
1263 #include "CharacterTemplateDataStore.h"
1264 #include "Chat.h"
1265 #include "ConversationDataStore.h"
1266+#include "Creature.h"
1267+#include "CreatureOutfit.h"
1268 #include "CreatureTextMgr.h"
1269 #include "DatabaseEnv.h"
1270 #include "DisableMgr.h"
1271@@ -96,6 +98,7 @@ class reload_commandscript : public CommandScript
1272 { "creature_queststarter", rbac::RBAC_PERM_COMMAND_RELOAD_CREATURE_QUESTSTARTER, true, &HandleReloadCreatureQuestStarterCommand, "" },
1273 { "creature_summon_groups", rbac::RBAC_PERM_COMMAND_RELOAD_CREATURE_SUMMON_GROUPS, true, &HandleReloadCreatureSummonGroupsCommand, "" },
1274 { "creature_template", rbac::RBAC_PERM_COMMAND_RELOAD_CREATURE_TEMPLATE, true, &HandleReloadCreatureTemplateCommand, "" },
1275+ { "creature_template_outfits", rbac::RBAC_PERM_COMMAND_RELOAD_CREATURE_TEMPLATE, true, &HandleReloadCreatureTemplateOutfitsCommand, "" },
1276 { "criteria_data", rbac::RBAC_PERM_COMMAND_RELOAD_CRITERIA_DATA, true, &HandleReloadCriteriaDataCommand, "" },
1277 { "disables", rbac::RBAC_PERM_COMMAND_RELOAD_DISABLES, true, &HandleReloadDisablesCommand, "" },
1278 { "disenchant_loot_template", rbac::RBAC_PERM_COMMAND_RELOAD_DISENCHANT_LOOT_TEMPLATE, true, &HandleReloadLootTemplatesDisenchantCommand, "" },
1279@@ -203,6 +206,7 @@ class reload_commandscript : public CommandScript
1280 HandleReloadGameTeleCommand(handler, "");
1281
1282 HandleReloadCreatureSummonGroupsCommand(handler, "");
1283+ HandleReloadCreatureTemplateOutfitsCommand(handler, "");
1284
1285 HandleReloadVehicleAccessoryCommand(handler, "");
1286 HandleReloadVehicleTemplateAccessoryCommand(handler, "");
1287@@ -460,6 +464,24 @@ class reload_commandscript : public CommandScript
1288 return true;
1289 }
1290
1291+ static bool HandleReloadCreatureTemplateOutfitsCommand(ChatHandler* handler, const char* /*args*/)
1292+ {
1293+ TC_LOG_INFO("misc", "Loading Creature Outfits... (`creature_template_outfits`)");
1294+ sObjectMgr->LoadCreatureOutfits();
1295+ sMapMgr->DoForAllMaps([](Map* map)
1296+ {
1297+ for (auto e : map->GetCreatureBySpawnIdStore())
1298+ {
1299+ auto const & outfit = e.second->GetOutfit();
1300+ if (outfit && outfit->GetId())
1301+ e.second->SetDisplayId(outfit->GetId());
1302+ }
1303+ });
1304+
1305+ handler->SendGlobalGMSysMessage("DB table `creature_template_outfits` reloaded.");
1306+ return true;
1307+ }
1308+
1309 static bool HandleReloadCreatureQuestStarterCommand(ChatHandler* handler, const char* /*args*/)
1310 {
1311 TC_LOG_INFO("misc", "Loading Quests Relations... (`creature_queststarter`)");
1312diff --git a/src/server/scripts/Custom/DressNPCs/Example.sql b/src/server/scripts/Custom/DressNPCs/Example.sql
1313new file mode 100644
1314index 000000000000..ab0c26a4d468
1315--- /dev/null
1316+++ b/src/server/scripts/Custom/DressNPCs/Example.sql
1317@@ -0,0 +1,5 @@
1318+SET @NPCENTRY := 6;
1319+
1320+INSERT INTO `creature_template_outfits` (`entry`, `race`, `gender`, `skin`, `face`, `hair`, `haircolor`, `facialhair`, `head`, `shoulders`, `body`, `chest`, `waist`, `legs`, `feet`, `wrists`, `hands`, `back`, `tabard`)
1321+VALUES (3000000123, 11, 1, 14, 4, 10, 3, 5, -31286, -43617, 0, -26267, -26270, -26272, 0, 0, -43698, 0, 0);
1322+UPDATE `creature_template` SET `modelid2` = 3000000123 WHERE `entry` = @NPCENTRY;
1323diff --git a/src/server/scripts/Custom/DressNPCs/README.md b/src/server/scripts/Custom/DressNPCs/README.md
1324new file mode 100644
1325index 000000000000..bbb717c671db
1326--- /dev/null
1327+++ b/src/server/scripts/Custom/DressNPCs/README.md
1328@@ -0,0 +1,77 @@
1329+# DressNPCs [](https://travis-ci.org/Rochet2/TrinityCore)
1330+
1331+## About
1332+This patch allows you to dress up armor on NPCs as well as choose their facial features.
1333+Create unique looking NPCs by defining their gender, race, facial features, clothing and much more.
1334+All this is done through the database. No client edits required.
1335+
1336+Source: http://rochet2.github.io/Dress-NPCs.html
1337+
1338+Known bugs:
1339+- Portraits of the NPCs may not work properly at times - a client side visual bug, cannot fix.
1340+- Some skins are not available. These are usually skins that are not available for players for a specific race. This is a client side limitation, cannot fix.
1341+- Normally NPCs have no sound replies when you talk to them. This is a client side limitation and can be fixed with a client patch. Additionally a **workaround has been included** which sends interaction sounds from the core unless disabled in CMake.
1342+
1343+## Installation
1344+
1345+Available as:
1346+- Direct merge: https://github.com/Rochet2/TrinityCore/tree/dressnpcs_7.x
1347+- Diff: https://github.com/Rochet2/TrinityCore/compare/TrinityCore:master...dressnpcs_7.x.diff
1348+- Diff in github view: https://github.com/Rochet2/TrinityCore/compare/TrinityCore:master...dressnpcs_7.x
1349+
1350+Using direct merge:
1351+- open git bash to source location
1352+- do `git remote add rochet2 https://github.com/Rochet2/TrinityCore.git`
1353+- do `git pull rochet2 dressnpcs_7.x`
1354+- use cmake and compile
1355+
1356+Using diff:
1357+- DO NOT COPY THE DIFF DIRECTLY! It causes apply to fail.
1358+- download the diff by __right clicking__ the link and select __Save link as__
1359+- place the downloaded `dressnpcs_7.x.diff` to the source root folder
1360+- open git bash to source location
1361+- do `git apply dressnpcs_7.x.diff`
1362+- use cmake and compile
1363+
1364+After compiling:
1365+- TrinityCore auto updater should run needed SQLs automatically.
1366+- If you do not use the auto updater then run files named `*_dressnpcs.sql` from `\sql\custom` to your databases.
1367+
1368+## Usage
1369+- Before compiling the core you can choose in CMake to disable sound workaround if you have a client patch for the NPC sounds. The option is `DISABLE_DRESSNPCS_CORESOUNDS`.
1370+- Remember to check server startup errors, they tell when you do things wrong.
1371+- For some skins you must use a specific class, like for death knight skins and eyes.
1372+- Unplayable races like naga are possible to be used as race, experiment :).
1373+- The patch also adds `.reload creature_template_outfit` command. You can use it to reload the creature outfit table for testing.
1374+
1375+#### Use through DB
1376+To make an outfit create a row to `creature_template_outfits` table with your desired race, class, gender and equipped items.
1377+- You can freely choose the entry number, but it must be higher than `2147483647`. This is to avoid mixing with existing modelids.
1378+- The items can use positive value as item entry and negative for displayid.
1379+- Appearances are usually between 0 and 10 and they define the look of the item. Different appearances are usually used by mythic and heroic versions of an item. The appearance column takes effect only when using an item entry (a positive value) for the equipped item definition.
1380+- `guildid` refers to an actual guild from characters table and it is used to define the tabard looks of the creature if one is equipped. So you must make a guild and set a tabard for it and use it's ID in the column for the outfit.
1381+- `npcsoundsid` refers to `NPCSounds.dbc/db2`. In this column you can define what gossip replies to use for the NPC with the core side workaround for missing sounds for gossip. To create completely new sound combinations you can use hotfixes database or edit the DBC file.
1382+
1383+You can use the outfit entry as modelid in creature template, smart scripts and elsewhere in the DB and core for setting a modelid/displayid, like `creature->SetDisplayId(outfitid)`.
1384+
1385+#### Use through C++
1386+You can create an outfit in C++, here is an example:
1387+```c++
1388+#include "CreatureOutfit.h" // CreatureOutfit, shared_ptr
1389+#include "Player.h" // EquipmentSlots
1390+#include "SharedDefines.h" // Gender
1391+#include "Creature.h" // Creature
1392+
1393+// Create outfit
1394+std::shared_ptr<CreatureOutfit> outfit(new CreatureOutfit(RACE_NIGHTELF, GENDER_MALE));
1395+outfit->SetItemDisplay(EQUIPMENT_SLOT_SHOULDERS, 43617);
1396+outfit->skin = 2;
1397+
1398+// set it for a creature
1399+creature->SetOutfit(outfit);
1400+```
1401+
1402+In C++ if you absolutely must change the displayid of a creature wears an outfit without removing the outfit you can use `creature->SetDisplayIdRaw(modelid);`.
1403+
1404+## Bugs and Contact
1405+Report issues and similar to http://rochet2.github.io/