Instantly share code, notes, and snippets.

@Rochet2 /!DressNPCs.md Secret
Last active Mar 25, 2016

Embed
What would you like to do?

####About You can make an NPC, set the displays or items you want him to have and his race and gender (defines displayID) as well as facial features and skin color. All this is done in the database. The values for skins and features range from 0 to 12 or something .. not quite sure how many there are. (it varies for races)
Note The items can use positive value as item entry and negative for displayid.
Source: http://rochet2.github.io/Dress-NPCs.html

Known bugs: Portraits dont work properly. A client side visual bug

####Installation Download gist
Apply the the diff with git bash using command git apply DressNPCs.diff
Run world_table.sql to your world database
Supported TC version: https://github.com/TrinityCore/TrinityCore/commit/2042b095266cabd79b26d8325d4dee600260d4c9

####Usage Create an NPC. Create a row to creature_template_outfits table with your desired race, gender and equipped items. Clear wow cache folder, restart server and spawn the NPC. The patch also adds .reload creature_template_outfit command. You can use it to reload the creature outfit table again for testing. You should be able to reload the table with new entries of ingame creatures. Relog to update the visual look of creatures with the reloaded data.

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`)");
SET @NPCENTRY := 6;
INSERT INTO `creature_template_outfits` (`entry`, `race`, `gender`, `skin`, `face`, `hair`, `haircolor`, `facialhair`, `head`, `shoulders`, `body`, `chest`, `waist`, `legs`, `feet`, `wrists`, `hands`, `back`, `tabard`)
VALUES (@NPCENTRY, 11, 1, 14, 4, 10, 3, 5, -31286, -43617, 0, -26267, -26270, -26272, 0, 0, -43698, 0, 0);
INSERT INTO `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`)
VALUES (@NPCENTRY, 1, 32946, 32945, 0);
CREATE TABLE `creature_template_outfits` (
`entry` INT(10) UNSIGNED NOT NULL,
`race` TINYINT(3) UNSIGNED NOT NULL DEFAULT '1',
`gender` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0 for male, 1 for female',
`skin` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
`face` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
`hair` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
`haircolor` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
`facialhair` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
`head` INT(10) NOT NULL DEFAULT '0',
`shoulders` INT(10) NOT NULL DEFAULT '0',
`body` INT(10) NOT NULL DEFAULT '0',
`chest` INT(10) NOT NULL DEFAULT '0',
`waist` INT(10) NOT NULL DEFAULT '0',
`legs` INT(10) NOT NULL DEFAULT '0',
`feet` INT(10) NOT NULL DEFAULT '0',
`wrists` INT(10) NOT NULL DEFAULT '0',
`hands` INT(10) NOT NULL DEFAULT '0',
`back` INT(10) NOT NULL DEFAULT '0',
`tabard` INT(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`entry`)
)
COMMENT='Use positive values for item entries and negative to use item displayid for head, shoulders etc.'
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment