|
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp |
|
index fd8aaff..3e91556 100644 |
|
--- a/src/server/game/Entities/Creature/Creature.cpp |
|
+++ b/src/server/game/Entities/Creature/Creature.cpp |
|
@@ -963,6 +963,10 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) |
|
displayId == cinfo->Modelid3 || displayId == cinfo->Modelid4) |
|
displayId = 0; |
|
|
|
+ // Avoid bug when reloading and then saving a creature causing invalid modelID to save |
|
+ if (GetUInt32Value(UNIT_FIELD_FLAGS_2) & UNIT_FLAG2_MIRROR_IMAGE) |
|
+ displayId = 0; |
|
+ |
|
if (npcflag == cinfo->npcflag) |
|
npcflag = 0; |
|
|
|
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp |
|
index 278460e..99be9d2 100644 |
|
--- a/src/server/game/Globals/ObjectMgr.cpp |
|
+++ b/src/server/game/Globals/ObjectMgr.cpp |
|
@@ -7905,6 +7905,93 @@ SkillRangeType GetSkillRangeType(SkillRaceClassInfoEntry const* rcEntry) |
|
return SKILL_RANGE_LEVEL; |
|
} |
|
|
|
+void ObjectMgr::LoadCreatureOutfits() |
|
+{ |
|
+ uint32 oldMSTime = getMSTime(); |
|
+ |
|
+ _creatureOutfitStore.clear(); // for reload case (test only) |
|
+ |
|
+ // 0 1 2 3 4 5 6 7 |
|
+ QueryResult result = WorldDatabase.Query("SELECT entry, race, gender, skin, face, hair, haircolor, facialhair, " |
|
+ //8 9 10 11 12 13 14 15 16 17 18 |
|
+ "head, shoulders, body, chest, waist, legs, feet, wrists, hands, back, tabard FROM creature_template_outfits"); |
|
+ |
|
+ if (!result) |
|
+ { |
|
+ TC_LOG_ERROR("server.loading", ">> Loaded 0 creature outfits. DB table `creature_template_outfits` is empty!"); |
|
+ return; |
|
+ } |
|
+ |
|
+ uint32 count = 0; |
|
+ |
|
+ do |
|
+ { |
|
+ Field* fields = result->Fetch(); |
|
+ |
|
+ uint32 i = 0; |
|
+ uint32 entry = fields[i++].GetUInt32(); |
|
+ |
|
+ if (!GetCreatureTemplate(entry)) |
|
+ { |
|
+ TC_LOG_ERROR("server.loading", ">> Creature entry %u in `creature_template_outfits`, but not in `creature_template`!", entry); |
|
+ continue; |
|
+ } |
|
+ |
|
+ CreatureOutfit co; // const, shouldnt be changed after saving |
|
+ co.race = fields[i++].GetUInt8(); |
|
+ const ChrRacesEntry* rEntry = sChrRacesStore.LookupEntry(co.race); |
|
+ if (!rEntry) |
|
+ { |
|
+ TC_LOG_ERROR("server.loading", ">> Creature entry %u in `creature_template_outfits` has incorrect race (%u).", entry, uint32(co.race)); |
|
+ continue; |
|
+ } |
|
+ co.gender = fields[i++].GetUInt8(); |
|
+ // Set correct displayId |
|
+ switch (co.gender) |
|
+ { |
|
+ case GENDER_FEMALE: _creatureTemplateStore[entry].Modelid1 = rEntry->model_f; break; |
|
+ case GENDER_MALE: _creatureTemplateStore[entry].Modelid1 = rEntry->model_m; break; |
|
+ default: |
|
+ TC_LOG_ERROR("server.loading", ">> Creature entry %u in `creature_template_outfits` has invalid gender %u", entry, uint32(co.gender)); |
|
+ continue; |
|
+ } |
|
+ _creatureTemplateStore[entry].Modelid2 = 0; |
|
+ _creatureTemplateStore[entry].Modelid3 = 0; |
|
+ _creatureTemplateStore[entry].Modelid4 = 0; |
|
+ _creatureTemplateStore[entry].unit_flags2 |= UNIT_FLAG2_MIRROR_IMAGE; // Needed so client requests mirror packet |
|
+ |
|
+ co.skin = fields[i++].GetUInt8(); |
|
+ co.face = fields[i++].GetUInt8(); |
|
+ co.hair = fields[i++].GetUInt8(); |
|
+ co.haircolor = fields[i++].GetUInt8(); |
|
+ co.facialhair = fields[i++].GetUInt8(); |
|
+ for (uint32 j = 0; j < MAX_CREATURE_OUTFIT_DISPLAYS; ++j) |
|
+ { |
|
+ int32 displayInfo = fields[i + j].GetInt32(); |
|
+ if (displayInfo > 0) // entry |
|
+ { |
|
+ ItemTemplate const* proto = sObjectMgr->GetItemTemplate(uint32(displayInfo)); |
|
+ if (proto) |
|
+ co.outfit[j] = proto->DisplayInfoID; |
|
+ else |
|
+ { |
|
+ TC_LOG_ERROR("server.loading", ">> Creature entry %u in `creature_template_outfits` has invalid item entry %u", entry, uint32(displayInfo)); |
|
+ co.outfit[j] = 0; |
|
+ } |
|
+ } |
|
+ else // display |
|
+ co.outfit[j] = uint32(-displayInfo); |
|
+ } |
|
+ |
|
+ _creatureOutfitStore[entry] = co; |
|
+ |
|
+ ++count; |
|
+ } |
|
+ while (result->NextRow()); |
|
+ |
|
+ TC_LOG_INFO("server.loading", ">> Loaded %u creature outfits in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); |
|
+} |
|
+ |
|
void ObjectMgr::LoadGameTele() |
|
{ |
|
uint32 oldMSTime = getMSTime(); |
|
diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h |
|
index 3f8013b..0396942 100644 |
|
--- a/src/server/game/Globals/ObjectMgr.h |
|
+++ b/src/server/game/Globals/ObjectMgr.h |
|
@@ -144,6 +144,21 @@ struct GameTele |
|
|
|
typedef std::unordered_map<uint32, GameTele > GameTeleContainer; |
|
|
|
+#define MAX_CREATURE_OUTFIT_DISPLAYS 11 |
|
+struct CreatureOutfit |
|
+{ |
|
+ uint8 race; |
|
+ uint8 gender; |
|
+ uint8 face; |
|
+ uint8 skin; |
|
+ uint8 hair; |
|
+ uint8 facialhair; |
|
+ uint8 haircolor; |
|
+ uint32 outfit[MAX_CREATURE_OUTFIT_DISPLAYS]; |
|
+}; |
|
+ |
|
+typedef std::unordered_map<uint32, CreatureOutfit> CreatureOutfitContainer; |
|
+ |
|
enum ScriptsType |
|
{ |
|
SCRIPTS_FIRST = 1, |
|
@@ -1011,6 +1026,7 @@ class ObjectMgr |
|
|
|
void LoadNPCSpellClickSpells(); |
|
|
|
+ void LoadCreatureOutfits(); |
|
void LoadGameTele(); |
|
|
|
void LoadGossipMenu(); |
|
@@ -1219,6 +1235,8 @@ class ObjectMgr |
|
bool AddGameTele(GameTele& data); |
|
bool DeleteGameTele(std::string const& name); |
|
|
|
+ CreatureOutfitContainer* GetCreatureOutfitMap() { return &_creatureOutfitStore; } |
|
+ |
|
TrainerSpellData const* GetNpcTrainerSpells(uint32 entry) const |
|
{ |
|
CacheTrainerSpellContainer::const_iterator iter = _cacheTrainerSpellStore.find(entry); |
|
@@ -1365,6 +1383,8 @@ class ObjectMgr |
|
PageTextContainer _pageTextStore; |
|
InstanceTemplateContainer _instanceTemplateStore; |
|
|
|
+ CreatureOutfitContainer _creatureOutfitStore; |
|
+ |
|
private: |
|
void LoadScripts(ScriptsType type); |
|
void CheckScripts(ScriptsType type, std::set<int32>& ids); |
|
diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp |
|
index ba30803..afd5e44 100644 |
|
--- a/src/server/game/Handlers/SpellHandler.cpp |
|
+++ b/src/server/game/Handlers/SpellHandler.cpp |
|
@@ -568,6 +568,34 @@ void WorldSession::HandleMirrorImageDataRequest(WorldPacket& recvData) |
|
if (!unit) |
|
return; |
|
|
|
+ if (unit->ToCreature()) |
|
+ { |
|
+ CreatureOutfitContainer* outfits = sObjectMgr->GetCreatureOutfitMap(); |
|
+ CreatureOutfitContainer::const_iterator it = outfits->find(unit->GetEntry()); |
|
+ if (it != outfits->end()) |
|
+ { |
|
+ WorldPacket data(SMSG_MIRRORIMAGE_DATA, 68); |
|
+ data << uint64(guid); |
|
+ data << uint32(unit->GetDisplayId()); // displayId |
|
+ data << uint8(it->second.race); // race |
|
+ data << uint8(it->second.gender); // gender |
|
+ data << uint8(1); // class |
|
+ data << uint8(it->second.skin); // skin |
|
+ data << uint8(it->second.face); // face |
|
+ data << uint8(it->second.hair); // hair |
|
+ data << uint8(it->second.haircolor); // haircolor |
|
+ data << uint8(it->second.facialhair); // facialhair |
|
+ data << uint32(0); // guildId |
|
+ |
|
+ // item displays |
|
+ for (uint32 i = 0; i < MAX_CREATURE_OUTFIT_DISPLAYS; ++i) |
|
+ data << uint32(it->second.outfit[i]); |
|
+ |
|
+ SendPacket(&data); |
|
+ return; |
|
+ } |
|
+ } |
|
+ |
|
if (!unit->HasAuraType(SPELL_AURA_CLONE_CASTER)) |
|
return; |
|
|
|
diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp |
|
index 9f8b378..6e214ca 100644 |
|
--- a/src/server/game/Server/WorldSession.cpp |
|
+++ b/src/server/game/Server/WorldSession.cpp |
|
@@ -1378,6 +1378,7 @@ uint32 WorldSession::DosProtection::GetMaxPacketCounterAllowed(uint16 opcode) co |
|
case CMSG_MESSAGECHAT: // 0 3.5 |
|
case CMSG_INSPECT: // 0 3.5 |
|
case CMSG_AREA_SPIRIT_HEALER_QUERY: // not profiled |
|
+ case CMSG_GET_MIRRORIMAGE_DATA: // not profiled |
|
{ |
|
// "0" is a magic number meaning there's no limit for the opcode. |
|
// All the opcodes above must cause little CPU usage and no sync/async database queries at all |
|
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp |
|
index 78470d4..8a4ba99 100644 |
|
--- a/src/server/game/World/World.cpp |
|
+++ b/src/server/game/World/World.cpp |
|
@@ -1443,6 +1443,9 @@ void World::SetInitialWorldSettings() |
|
TC_LOG_INFO("server.loading", "Loading Creature templates..."); |
|
sObjectMgr->LoadCreatureTemplates(); |
|
|
|
+ TC_LOG_INFO("server.loading", "Loading Creature template outfits..."); // must be after LoadCreatureTemplates |
|
+ sObjectMgr->LoadCreatureOutfits(); |
|
+ |
|
TC_LOG_INFO("server.loading", "Loading Equipment templates..."); // must be after LoadCreatureTemplates |
|
sObjectMgr->LoadEquipmentTemplates(); |
|
|
|
diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp |
|
index 89dc08d..09052cc 100644 |
|
--- a/src/server/scripts/Commands/cs_reload.cpp |
|
+++ b/src/server/scripts/Commands/cs_reload.cpp |
|
@@ -86,6 +86,7 @@ public: |
|
{ "creature_queststarter", rbac::RBAC_PERM_COMMAND_RELOAD_CREATURE_QUESTSTARTER, true, &HandleReloadCreatureQuestStarterCommand, "", NULL }, |
|
{ "creature_summon_groups", rbac::RBAC_PERM_COMMAND_RELOAD_CREATURE_SUMMON_GROUPS, true, &HandleReloadCreatureSummonGroupsCommand, "", NULL }, |
|
{ "creature_template", rbac::RBAC_PERM_COMMAND_RELOAD_CREATURE_TEMPLATE, true, &HandleReloadCreatureTemplateCommand, "", NULL }, |
|
+ { "creature_template_outfits", rbac::RBAC_PERM_COMMAND_RELOAD_CREATURE_TEMPLATE, true, &HandleReloadCreatureTemplateOutfitsCommand, "", NULL }, |
|
//{ "db_script_string", rbac::RBAC_PERM_COMMAND_RELOAD_, true, &HandleReloadDbScriptStringCommand, "", NULL }, |
|
{ "disables", rbac::RBAC_PERM_COMMAND_RELOAD_DISABLES, true, &HandleReloadDisablesCommand, "", NULL }, |
|
{ "disenchant_loot_template", rbac::RBAC_PERM_COMMAND_RELOAD_DISENCHANT_LOOT_TEMPLATE, true, &HandleReloadLootTemplatesDisenchantCommand, "", NULL }, |
|
@@ -195,6 +196,7 @@ public: |
|
HandleReloadGameTeleCommand(handler, ""); |
|
|
|
HandleReloadCreatureSummonGroupsCommand(handler, ""); |
|
+ HandleReloadCreatureTemplateOutfitsCommand(handler, ""); |
|
|
|
HandleReloadVehicleAccessoryCommand(handler, ""); |
|
HandleReloadVehicleTemplateAccessoryCommand(handler, ""); |
|
@@ -529,6 +531,14 @@ public: |
|
return true; |
|
} |
|
|
|
+ static bool HandleReloadCreatureTemplateOutfitsCommand(ChatHandler* handler, const char* /*args*/) |
|
+ { |
|
+ TC_LOG_INFO("misc", "Loading Creature Outfits... (`creature_template_outfits`)"); |
|
+ sObjectMgr->LoadCreatureOutfits(); |
|
+ handler->SendGlobalGMSysMessage("DB table `creature_template_outfits` reloaded."); |
|
+ return true; |
|
+ } |
|
+ |
|
static bool HandleReloadCreatureQuestStarterCommand(ChatHandler* handler, const char* /*args*/) |
|
{ |
|
TC_LOG_INFO("misc", "Loading Quests Relations... (`creature_queststarter`)"); |