diff --git a/data/sql/updates/pending_db_world/display-probabilities.sql b/data/sql/updates/pending_db_world/display-probabilities.sql new file mode 100644 index 000000000..296f17fb8 --- /dev/null +++ b/data/sql/updates/pending_db_world/display-probabilities.sql @@ -0,0 +1,24 @@ +DROP TABLE IF EXISTS `creature_template_model`; +CREATE TABLE `creature_template_model`( + `CreatureID` int unsigned NOT NULL, + `Idx` smallint unsigned NOT NULL DEFAULT '0', + `CreatureDisplayID` int unsigned NOT NULL, + `DisplayScale` float NOT NULL DEFAULT '1', + `Probability` float NOT NULL DEFAULT '0', + `VerifiedBuild` smallint unsigned, + PRIMARY KEY (`CreatureID`,`Idx`), + CONSTRAINT creature_template_model_chk_1 CHECK (`Idx` <= 3) +) ENGINE=InnoDB CHARSET=utf8mb4; + +INSERT IGNORE INTO `creature_template_model` (`CreatureID`,`Idx`,`CreatureDisplayID`,`DisplayScale`,`Probability`,`VerifiedBuild`) SELECT `entry`,0,`modelid1`,`scale`,1,`VerifiedBuild` FROM `creature_template` WHERE `modelid1`!=0; +INSERT IGNORE INTO `creature_template_model` (`CreatureID`,`Idx`,`CreatureDisplayID`,`DisplayScale`,`Probability`,`VerifiedBuild`) SELECT `entry`,1,`modelid2`,`scale`,1,`VerifiedBuild` FROM `creature_template` WHERE `modelid2`!=0; +INSERT IGNORE INTO `creature_template_model` (`CreatureID`,`Idx`,`CreatureDisplayID`,`DisplayScale`,`Probability`,`VerifiedBuild`) SELECT `entry`,2,`modelid3`,`scale`,1,`VerifiedBuild` FROM `creature_template` WHERE `modelid3`!=0; +INSERT IGNORE INTO `creature_template_model` (`CreatureID`,`Idx`,`CreatureDisplayID`,`DisplayScale`,`Probability`,`VerifiedBuild`) SELECT `entry`,3,`modelid4`,`scale`,1,`VerifiedBuild` FROM `creature_template` WHERE `modelid4`!=0; + +UPDATE `creature_template` SET `scale`=1; + +ALTER TABLE `creature_template` + DROP `modelid1`, + DROP `modelid2`, + DROP `modelid3`, + DROP `modelid4`; diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index e6fa97751..c6e1c5d05 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -77,7 +77,7 @@ void WorldDatabaseConnection::DoPrepareStatements() PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, security, help FROM command", CONNECTION_SYNCH); - PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); + PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id1 = ? OR id2 = ? OR id3 = ?", CONNECTION_SYNCH); diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index a91489dae..cabedbe4a 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -424,10 +424,10 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u { if (CreatureTemplate const* ci = sObjectMgr->GetCreatureTemplate(e.action.morphOrMount.creature)) { - uint32 displayId = ObjectMgr::ChooseDisplayId(ci); - target->ToCreature()->SetDisplayId(displayId); + CreatureModel const* model = ObjectMgr::ChooseDisplayId(ci); + target->ToCreature()->SetDisplayId(model->CreatureDisplayID, model->DisplayScale); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry {}, GuidLow {} set displayid to {}", - target->GetEntry(), target->GetGUID().ToString(), displayId); + target->GetEntry(), target->GetGUID().ToString(), model->CreatureDisplayID); } } //if no param1, then use value from param2 (modelId) @@ -1316,7 +1316,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (e.action.morphOrMount.creature > 0) { if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(e.action.morphOrMount.creature)) - target->ToUnit()->Mount(ObjectMgr::ChooseDisplayId(cInfo)); + target->ToUnit()->Mount(ObjectMgr::ChooseDisplayId(cInfo)->CreatureDisplayID); } else target->ToUnit()->Mount(e.action.morphOrMount.model); diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index fbebfb85c..1c84cb317 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -116,51 +116,104 @@ VendorItem const* VendorItemData::FindItemCostPair(uint32 item_id, uint32 extend return nullptr; } -uint32 CreatureTemplate::GetRandomValidModelId() const +CreatureModel const CreatureModel::DefaultInvisibleModel(11686, 1.0f, 1.0f); +CreatureModel const CreatureModel::DefaultVisibleModel(17519, 1.0f, 1.0f); + +CreatureModel const* CreatureTemplate::GetModelByIdx(uint32 idx) const { - uint8 c = 0; - uint32 modelIDs[4]; - - if (Modelid1) modelIDs[c++] = Modelid1; - if (Modelid2) modelIDs[c++] = Modelid2; - if (Modelid3) modelIDs[c++] = Modelid3; - if (Modelid4) modelIDs[c++] = Modelid4; - - return ((c > 0) ? modelIDs[urand(0, c - 1)] : 0); + return idx < Models.size() ? &Models[idx] : nullptr; } -uint32 CreatureTemplate::GetFirstValidModelId() const +CreatureModel const* CreatureTemplate::GetRandomValidModel() const { - if (Modelid1) return Modelid1; - if (Modelid2) return Modelid2; - if (Modelid3) return Modelid3; - if (Modelid4) return Modelid4; - return 0; + if (!Models.size()) + return nullptr; + + // If only one element, ignore the Probability (even if 0) + if (Models.size() == 1) + return &Models[0]; + + auto selectedItr = Acore::Containers::SelectRandomWeightedContainerElement(Models, [](CreatureModel const& model) + { + return model.Probability; + }); + + return &(*selectedItr); +} + +CreatureModel const* CreatureTemplate::GetFirstValidModel() const +{ + for (CreatureModel const& model : Models) + if (model.CreatureDisplayID) + return &model; + + return nullptr; +} + +CreatureModel const* CreatureTemplate::GetModelWithDisplayId(uint32 displayId) const +{ + for (CreatureModel const& model : Models) + if (displayId == model.CreatureDisplayID) + return &model; + + return nullptr; +} + +CreatureModel const* CreatureTemplate::GetFirstInvisibleModel() const +{ + for (CreatureModel const& model : Models) + if (CreatureModelInfo const* modelInfo = sObjectMgr->GetCreatureModelInfo(model.CreatureDisplayID)) + if (modelInfo && modelInfo->is_trigger) + return &model; + + return &CreatureModel::DefaultInvisibleModel; +} + +CreatureModel const* CreatureTemplate::GetFirstVisibleModel() const +{ + for (CreatureModel const& model : Models) + if (CreatureModelInfo const* modelInfo = sObjectMgr->GetCreatureModelInfo(model.CreatureDisplayID)) + if (modelInfo && !modelInfo->is_trigger) + return &model; + + return &CreatureModel::DefaultVisibleModel; } void CreatureTemplate::InitializeQueryData() { queryData.Initialize(SMSG_CREATURE_QUERY_RESPONSE, 1); - queryData << uint32(Entry); // creature entry + queryData << uint32(Entry); // creature entry queryData << Name; - queryData << uint8(0) << uint8(0) << uint8(0); // name2, name3, name4, always empty + queryData << uint8(0) << uint8(0) << uint8(0); // name2, name3, name4, always empty queryData << SubName; - queryData << IconName; // "Directions" for guard, string for Icons 2.3.0 - queryData << uint32(type_flags); // flags - queryData << uint32(type); // CreatureType.dbc - queryData << uint32(family); // CreatureFamily.dbc - queryData << uint32(rank); // Creature Rank (elite, boss, etc) - queryData << uint32(KillCredit[0]); // new in 3.1, kill credit - queryData << uint32(KillCredit[1]); // new in 3.1, kill credit - queryData << uint32(Modelid1); // Modelid1 - queryData << uint32(Modelid2); // Modelid2 - queryData << uint32(Modelid3); // Modelid3 - queryData << uint32(Modelid4); // Modelid4 - queryData << float(ModHealth); // dmg/hp modifier - queryData << float(ModMana); // dmg/mana modifier + queryData << IconName; // "Directions" for guard, string for Icons 2.3.0 + queryData << uint32(type_flags); // flags + queryData << uint32(type); // CreatureType.dbc + queryData << uint32(family); // CreatureFamily.dbc + queryData << uint32(rank); // Creature Rank (elite, boss, etc) + queryData << uint32(KillCredit[0]); // new in 3.1, kill credit + queryData << uint32(KillCredit[1]); // new in 3.1, kill credit + if (GetModelByIdx(0)) + queryData << uint32(GetModelByIdx(0)->CreatureDisplayID); // Modelid1 + else + queryData << uint32(0); // Modelid1 + if (GetModelByIdx(1)) + queryData << uint32(GetModelByIdx(1)->CreatureDisplayID); // Modelid2 + else + queryData << uint32(0); // Modelid2 + if (GetModelByIdx(2)) + queryData << uint32(GetModelByIdx(2)->CreatureDisplayID); // Modelid3 + else + queryData << uint32(0); // Modelid3 + if (GetModelByIdx(3)) + queryData << uint32(GetModelByIdx(3)->CreatureDisplayID); // Modelid4 + else + queryData << uint32(0); // Modelid4 + queryData << float(ModHealth); // dmg/hp modifier + queryData << float(ModMana); // dmg/mana modifier queryData << uint8(RacialLeader); - queryData << uint32(movementId); // CreatureMovementInfo.dbc + queryData << uint32(movementId); // CreatureMovementInfo.dbc } bool AssistDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) @@ -423,21 +476,22 @@ bool Creature::InitEntry(uint32 Entry, const CreatureData* data) SetByteValue(UNIT_FIELD_BYTES_0, 1, uint8(cinfo->unit_class)); // Cancel load if no model defined - if (!(cinfo->GetFirstValidModelId())) + if (!(cinfo->GetFirstValidModel())) { - LOG_ERROR("sql.sql", "Creature (Entry: {}) has no model defined in table `creature_template`, can't load. ", Entry); + LOG_ERROR("sql.sql", "Creature (Entry: {}) has no model defined in table `creature_template_model`, can't load. ", Entry); return false; } - uint32 displayID = ObjectMgr::ChooseDisplayId(GetCreatureTemplate(), data); - if (!sObjectMgr->GetCreatureModelRandomGender(&displayID)) // Cancel load if no model defined + CreatureModel model = *ObjectMgr::ChooseDisplayId(cinfo, data); + CreatureModelInfo const* mInfo = sObjectMgr->GetCreatureModelRandomGender(&model, cinfo); + if (!mInfo) // Cancel load if no model defined { - LOG_ERROR("sql.sql", "Creature (Entry: {}) has no model defined in table `creature_template`, can't load. ", Entry); + LOG_ERROR("sql.sql", "Creature (Entry: {}) has no model {} defined in table `creature_template_model`, can't load. ", Entry, model.CreatureDisplayID); return false; } - SetDisplayId(displayID); - SetNativeDisplayId(displayID); + SetDisplayId(model.CreatureDisplayID, model.DisplayScale); + SetNativeDisplayId(model.CreatureDisplayID); // Load creature equipment if (!data) @@ -1121,11 +1175,12 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u break; } - uint32 displayID = GetNativeDisplayId(); - if (sObjectMgr->GetCreatureModelRandomGender(&displayID) && !IsTotem()) // Cancel load if no model defined or if totem + CreatureModel display(GetNativeDisplayId(), GetNativeObjectScale(), 1.0f); + CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&display, cinfo); + if (minfo && !IsTotem()) // Cancel load if no model defined or if totem { - SetDisplayId(displayID); - SetNativeDisplayId(displayID); + SetDisplayId(display.CreatureDisplayID, display.DisplayScale); + SetNativeDisplayId(display.CreatureDisplayID); } LoadCreaturesAddon(); @@ -1360,9 +1415,9 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) CreatureTemplate const* cinfo = GetCreatureTemplate(); if (cinfo) { - if (displayId == cinfo->Modelid1 || displayId == cinfo->Modelid2 || - displayId == cinfo->Modelid3 || displayId == cinfo->Modelid4) - displayId = 0; + for (CreatureModel model : cinfo->Models) + if (displayId && displayId == model.CreatureDisplayID) + displayId = 0; if (npcflag == cinfo->npcflag) npcflag = 0; @@ -2031,11 +2086,12 @@ void Creature::Respawn(bool force) // Do not override transform auras if (GetAuraEffectsByType(SPELL_AURA_TRANSFORM).empty()) { - uint32 displayID = GetNativeDisplayId(); - if (sObjectMgr->GetCreatureModelRandomGender(&displayID)) // Cancel load if no model defined + CreatureModel display(GetNativeDisplayId(), GetNativeObjectScale(), 1.0f); + CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&display, GetCreatureTemplate()); + if (minfo) // Cancel load if no model defined { - SetDisplayId(displayID); - SetNativeDisplayId(displayID); + SetDisplayId(display.CreatureDisplayID, display.DisplayScale); + SetNativeDisplayId(display.CreatureDisplayID); } } @@ -3414,9 +3470,9 @@ void Creature::SetObjectScale(float scale) SetFloatValue(UNIT_FIELD_COMBATREACH, combatReach * scale); } -void Creature::SetDisplayId(uint32 modelId) +void Creature::SetDisplayId(uint32 modelId, float displayScale /*= 1.f*/) { - Unit::SetDisplayId(modelId); + Unit::SetDisplayId(modelId, displayScale); float combatReach = DEFAULT_WORLD_OBJECT_SIZE; @@ -3430,9 +3486,17 @@ void Creature::SetDisplayId(uint32 modelId) if (IsPet()) combatReach = DEFAULT_COMBAT_REACH; + SetObjectScale(displayScale); + SetFloatValue(UNIT_FIELD_COMBATREACH, combatReach * GetObjectScale()); } +void Creature::SetDisplayFromModel(uint32 modelIdx) +{ + if (CreatureModel const* model = GetCreatureTemplate()->GetModelByIdx(modelIdx)) + SetDisplayId(model->CreatureDisplayID, model->DisplayScale); +} + void Creature::SetTarget(ObjectGuid guid) { if (!_focusSpell) diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index c4e067a56..a29ae43de 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -53,7 +53,8 @@ public: float GetNativeObjectScale() const override; void SetObjectScale(float scale) override; - void SetDisplayId(uint32 modelId) override; + void SetDisplayId(uint32 displayId, float displayScale = 1.f) override; + void SetDisplayFromModel(uint32 modelIdx); void DisappearAndDie(); diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index 3b8cb2cfc..2903e8f5a 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -171,16 +171,29 @@ struct CreatureMovementData std::string ToString() const; }; +struct CreatureModel +{ + static CreatureModel const DefaultInvisibleModel; + static CreatureModel const DefaultVisibleModel; + + CreatureModel() : + CreatureDisplayID(0), DisplayScale(0.0f), Probability(0.0f) { } + + CreatureModel(uint32 creatureDisplayID, float displayScale, float probability) : + CreatureDisplayID(creatureDisplayID), DisplayScale(displayScale), Probability(probability) { } + + uint32 CreatureDisplayID; + float DisplayScale; + float Probability; +}; + // from `creature_template` table struct CreatureTemplate { uint32 Entry; uint32 DifficultyEntry[MAX_DIFFICULTY - 1]; uint32 KillCredit[MAX_KILL_CREDIT]; - uint32 Modelid1; - uint32 Modelid2; - uint32 Modelid3; - uint32 Modelid4; + std::vector Models; std::string Name; std::string SubName; std::string IconName; @@ -239,8 +252,12 @@ struct CreatureTemplate uint32 flags_extra; uint32 ScriptID; WorldPacket queryData; // pussywizard - [[nodiscard]] uint32 GetRandomValidModelId() const; - [[nodiscard]] uint32 GetFirstValidModelId() const; + CreatureModel const* GetModelByIdx(uint32 idx) const; + CreatureModel const* GetRandomValidModel() const; + CreatureModel const* GetFirstValidModel() const; + CreatureModel const* GetModelWithDisplayId(uint32 displayId) const; + CreatureModel const* GetFirstInvisibleModel() const; + CreatureModel const* GetFirstVisibleModel() const; // helpers [[nodiscard]] SkillType GetRequiredLootSkill() const @@ -389,6 +406,7 @@ struct CreatureModelInfo float combat_reach; uint8 gender; uint32 modelid_other_gender; + float is_trigger; }; // Benchmarked: Faster than std::map (insert/find) diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index f7d56dcb4..a998afa57 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -2418,9 +2418,9 @@ void Pet::SynchronizeLevelWithOwner() } } -void Pet::SetDisplayId(uint32 modelId) +void Pet::SetDisplayId(uint32 modelId, float displayScale /*= 1.f*/) { - Guardian::SetDisplayId(modelId); + Guardian::SetDisplayId(modelId, displayScale); if (!isControlled()) return; diff --git a/src/server/game/Entities/Pet/Pet.h b/src/server/game/Entities/Pet/Pet.h index d32f04fc6..323f489ae 100644 --- a/src/server/game/Entities/Pet/Pet.h +++ b/src/server/game/Entities/Pet/Pet.h @@ -46,7 +46,7 @@ public: void RemoveFromWorld() override; float GetNativeObjectScale() const override; - void SetDisplayId(uint32 modelId) override; + void SetDisplayId(uint32 modelId, float displayScale = 1.f) override; PetType getPetType() const { return m_petType; } void setPetType(PetType type) { m_petType = type; } diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 0e679cfcc..7e346a5e3 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -17034,13 +17034,16 @@ void Unit::RecalculateObjectScale() SetObjectScale(std::max(scale, scaleMin)); } -void Unit::SetDisplayId(uint32 modelId) +void Unit::SetDisplayId(uint32 modelId, float displayScale /*=1.f*/) { SetUInt32Value(UNIT_FIELD_DISPLAYID, modelId); + // Set Gender by modelId if (CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelInfo(modelId)) SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender); + SetObjectScale(displayScale); + sScriptMgr->OnDisplayIdChange(this, modelId); } @@ -21233,17 +21236,11 @@ void Unit::PatchValuesUpdate(ByteBuffer& valuesUpdateBuf, BuildValuesCachePosPoi { if (target->IsGameMaster() && target->GetSession()->IsGMAccount()) { - if (cinfo->Modelid1) - displayId = cinfo->Modelid1; // Modelid1 is a visible model for gms - else - displayId = 17519; // world visible trigger's model + displayId = cinfo->GetFirstVisibleModel()->CreatureDisplayID; } else { - if (cinfo->Modelid2) - displayId = cinfo->Modelid2; // Modelid2 is an invisible model for players - else - displayId = 11686; // world invisible trigger's model + displayId = cinfo->GetFirstInvisibleModel()->CreatureDisplayID; } } } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 3fb0e55ab..16dc4f605 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -2261,10 +2261,10 @@ public: virtual float GetNativeObjectScale() const { return 1.0f; } virtual void RecalculateObjectScale(); [[nodiscard]] uint32 GetDisplayId() const { return GetUInt32Value(UNIT_FIELD_DISPLAYID); } - virtual void SetDisplayId(uint32 modelId); + virtual void SetDisplayId(uint32 modelId, float displayScale = 1.f); [[nodiscard]] uint32 GetNativeDisplayId() const { return GetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID); } void RestoreDisplayId(); - void SetNativeDisplayId(uint32 modelId) { SetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID, modelId); } + void SetNativeDisplayId(uint32 displayId) { SetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID, displayId); } void setTransForm(uint32 spellid) { m_transform = spellid;} [[nodiscard]] uint32 getTransForm() const { return m_transform;} diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 3eb3bdf2d..ea2332b58 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -36,6 +36,7 @@ #include "LFGMgr.h" #include "Log.h" #include "MapMgr.h" +#include #include "Pet.h" #include "PoolMgr.h" #include "ReputationMgr.h" @@ -576,20 +577,20 @@ void ObjectMgr::LoadCreatureTemplates() { uint32 oldMSTime = getMSTime(); -// 0 1 2 3 4 5 6 7 8 - QueryResult result = WorldDatabase.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, " -// 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - "modelid4, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, " -// 23 24 25 26 27 28 29 30 31 32 33 34 - "detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, " -// 35 36 37 38 39 40 41 - "dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, " -// 42 43 44 45 46 47 48 49 50 51 - "type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, " -// 52 53 54 55 56 57 58 59 60 61 62 63 - "ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, " -// 64 65 66 67 68 69 70 - "RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName " +// 0 1 2 3 4 5 6 7 8 + QueryResult result = WorldDatabase.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, " +// 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + "gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, " +// 23 24 25 26 27 28 29 30 31 32 33 34 + "DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, " +// 35 36 37 38 39 40 41 + "trainer_class, trainer_race, type, type_flags, lootid, pickpocketloot, skinloot, " +// 42 43 44 45 46 47 48 49 50 51 + "PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, " +// 52 53 54 55 56 57 58 59 60 61 62 63 + "ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, " +// 64 65 66 + "spell_school_immune_mask, flags_extra, ScriptName " "FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId ORDER BY entry DESC;"); if (!result) @@ -609,6 +610,9 @@ void ObjectMgr::LoadCreatureTemplates() ++count; } while (result->NextRow()); + // We load the creature models after loading but before checking + LoadCreatureTemplateModels(); + sScriptMgr->OnAfterDatabaseLoadCreatureTemplates(_creatureTemplateStoreFast); LoadCreatureTemplateResistances(); @@ -658,47 +662,42 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook) { creatureTemplate.KillCredit[i] = fields[4 + i].Get(); } - - creatureTemplate.Modelid1 = fields[6].Get(); - creatureTemplate.Modelid2 = fields[7].Get(); - creatureTemplate.Modelid3 = fields[8].Get(); - creatureTemplate.Modelid4 = fields[9].Get(); - creatureTemplate.Name = fields[10].Get(); - creatureTemplate.SubName = fields[11].Get(); - creatureTemplate.IconName = fields[12].Get(); - creatureTemplate.GossipMenuId = fields[13].Get(); - creatureTemplate.minlevel = fields[14].Get(); - creatureTemplate.maxlevel = fields[15].Get(); - creatureTemplate.expansion = uint32(fields[16].Get()); - creatureTemplate.faction = uint32(fields[17].Get()); - creatureTemplate.npcflag = fields[18].Get(); - creatureTemplate.speed_walk = fields[19].Get(); - creatureTemplate.speed_run = fields[20].Get(); - creatureTemplate.speed_swim = fields[21].Get(); - creatureTemplate.speed_flight = fields[22].Get(); - creatureTemplate.detection_range = fields[23].Get(); - creatureTemplate.scale = fields[24].Get(); - creatureTemplate.rank = uint32(fields[25].Get()); - creatureTemplate.dmgschool = uint32(fields[26].Get()); - creatureTemplate.DamageModifier = fields[27].Get(); - creatureTemplate.BaseAttackTime = fields[28].Get(); - creatureTemplate.RangeAttackTime = fields[29].Get(); - creatureTemplate.BaseVariance = fields[30].Get(); - creatureTemplate.RangeVariance = fields[31].Get(); - creatureTemplate.unit_class = uint32(fields[32].Get()); - creatureTemplate.unit_flags = fields[33].Get(); - creatureTemplate.unit_flags2 = fields[34].Get(); - creatureTemplate.dynamicflags = fields[35].Get(); - creatureTemplate.family = uint32(fields[36].Get()); - creatureTemplate.trainer_type = uint32(fields[37].Get()); - creatureTemplate.trainer_spell = fields[38].Get(); - creatureTemplate.trainer_class = uint32(fields[39].Get()); - creatureTemplate.trainer_race = uint32(fields[40].Get()); - creatureTemplate.type = uint32(fields[41].Get()); - creatureTemplate.type_flags = fields[42].Get(); - creatureTemplate.lootid = fields[43].Get(); - creatureTemplate.pickpocketLootId = fields[44].Get(); - creatureTemplate.SkinLootId = fields[45].Get(); + creatureTemplate.Name = fields[6].Get(); + creatureTemplate.SubName = fields[7].Get(); + creatureTemplate.IconName = fields[8].Get(); + creatureTemplate.GossipMenuId = fields[9].Get(); + creatureTemplate.minlevel = fields[10].Get(); + creatureTemplate.maxlevel = fields[11].Get(); + creatureTemplate.expansion = uint32(fields[12].Get()); + creatureTemplate.faction = uint32(fields[13].Get()); + creatureTemplate.npcflag = fields[14].Get(); + creatureTemplate.speed_walk = fields[15].Get(); + creatureTemplate.speed_run = fields[16].Get(); + creatureTemplate.speed_swim = fields[17].Get(); + creatureTemplate.speed_flight = fields[18].Get(); + creatureTemplate.detection_range = fields[19].Get(); + creatureTemplate.scale = fields[20].Get(); + creatureTemplate.rank = uint32(fields[21].Get()); + creatureTemplate.dmgschool = uint32(fields[22].Get()); + creatureTemplate.DamageModifier = fields[23].Get(); + creatureTemplate.BaseAttackTime = fields[24].Get(); + creatureTemplate.RangeAttackTime = fields[25].Get(); + creatureTemplate.BaseVariance = fields[26].Get(); + creatureTemplate.RangeVariance = fields[27].Get(); + creatureTemplate.unit_class = uint32(fields[28].Get()); + creatureTemplate.unit_flags = fields[29].Get(); + creatureTemplate.unit_flags2 = fields[30].Get(); + creatureTemplate.dynamicflags = fields[31].Get(); + creatureTemplate.family = uint32(fields[32].Get()); + creatureTemplate.trainer_type = uint32(fields[33].Get()); + creatureTemplate.trainer_spell = fields[34].Get(); + creatureTemplate.trainer_class = uint32(fields[35].Get()); + creatureTemplate.trainer_race = uint32(fields[36].Get()); + creatureTemplate.type = uint32(fields[37].Get()); + creatureTemplate.type_flags = fields[38].Get(); + creatureTemplate.lootid = fields[39].Get(); + creatureTemplate.pickpocketLootId = fields[40].Get(); + creatureTemplate.SkinLootId = fields[41].Get(); for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) { @@ -710,49 +709,49 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook) creatureTemplate.spells[i] = 0; } - creatureTemplate.PetSpellDataId = fields[46].Get(); - creatureTemplate.VehicleId = fields[47].Get(); - creatureTemplate.mingold = fields[48].Get(); - creatureTemplate.maxgold = fields[49].Get(); - creatureTemplate.AIName = fields[50].Get(); - creatureTemplate.MovementType = uint32(fields[51].Get()); + creatureTemplate.PetSpellDataId = fields[42].Get(); + creatureTemplate.VehicleId = fields[43].Get(); + creatureTemplate.mingold = fields[44].Get(); + creatureTemplate.maxgold = fields[45].Get(); + creatureTemplate.AIName = fields[46].Get(); // stopped here, fix it + creatureTemplate.MovementType = uint32(fields[47].Get()); + if (!fields[48].IsNull()) + { + creatureTemplate.Movement.Ground = static_cast(fields[48].Get()); + } + + creatureTemplate.Movement.Swim = fields[49].Get(); + if (!fields[50].IsNull()) + { + creatureTemplate.Movement.Flight = static_cast(fields[50].Get()); + } + + creatureTemplate.Movement.Rooted = fields[51].Get(); if (!fields[52].IsNull()) { - creatureTemplate.Movement.Ground = static_cast(fields[52].Get()); + creatureTemplate.Movement.Chase = static_cast(fields[52].Get()); + } + if (!fields[53].IsNull()) + { + creatureTemplate.Movement.Random = static_cast(fields[53].Get()); } - - creatureTemplate.Movement.Swim = fields[53].Get(); if (!fields[54].IsNull()) { - creatureTemplate.Movement.Flight = static_cast(fields[54].Get()); + creatureTemplate.Movement.InteractionPauseTimer = fields[54].Get(); } - creatureTemplate.Movement.Rooted = fields[55].Get(); - if (!fields[56].IsNull()) - { - creatureTemplate.Movement.Chase = static_cast(fields[56].Get()); - } - if (!fields[57].IsNull()) - { - creatureTemplate.Movement.Random = static_cast(fields[57].Get()); - } - if (!fields[58].IsNull()) - { - creatureTemplate.Movement.InteractionPauseTimer = fields[58].Get(); - } - - creatureTemplate.HoverHeight = fields[59].Get(); - creatureTemplate.ModHealth = fields[60].Get(); - creatureTemplate.ModMana = fields[61].Get(); - creatureTemplate.ModArmor = fields[62].Get(); - creatureTemplate.ModExperience = fields[63].Get(); - creatureTemplate.RacialLeader = fields[64].Get(); - creatureTemplate.movementId = fields[65].Get(); - creatureTemplate.RegenHealth = fields[66].Get(); - creatureTemplate.MechanicImmuneMask = fields[67].Get(); - creatureTemplate.SpellSchoolImmuneMask = fields[68].Get(); - creatureTemplate.flags_extra = fields[69].Get(); - creatureTemplate.ScriptID = GetScriptId(fields[70].Get()); + creatureTemplate.HoverHeight = fields[55].Get(); + creatureTemplate.ModHealth = fields[56].Get(); + creatureTemplate.ModMana = fields[57].Get(); + creatureTemplate.ModArmor = fields[58].Get(); + creatureTemplate.ModExperience = fields[59].Get(); + creatureTemplate.RacialLeader = fields[60].Get(); + creatureTemplate.movementId = fields[61].Get(); + creatureTemplate.RegenHealth = fields[62].Get(); + creatureTemplate.MechanicImmuneMask = fields[63].Get(); + creatureTemplate.SpellSchoolImmuneMask = fields[64].Get(); + creatureTemplate.flags_extra = fields[65].Get(); + creatureTemplate.ScriptID = GetScriptId(fields[66].Get()); // useful if the creature template load is being triggered from outside this class if (triggerHook) @@ -762,6 +761,58 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook) } +void ObjectMgr::LoadCreatureTemplateModels() +{ + uint32 oldMSTime = getMSTime(); + + // 0 1 2 3 + QueryResult result = WorldDatabase.Query("SELECT CreatureID, CreatureDisplayID, DisplayScale, Probability FROM creature_template_model ORDER BY Idx ASC"); + + if (!result) + { + LOG_INFO("server.loading", ">> Loaded 0 creature template model definitions. DB table `creature_template_model` is empty."); + return; + } + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + + uint32 creatureId = fields[0].Get(); + uint32 creatureDisplayId = fields[1].Get(); + float displayScale = fields[2].Get(); + float probability = fields[3].Get(); + + CreatureTemplate const* cInfo = GetCreatureTemplate(creatureId); + if (!cInfo) + { + LOG_ERROR("sql.sql", "Creature template (Entry: {}) does not exist but has a record in `creature_template_model`", creatureId); + continue; + } + + CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(creatureDisplayId); + if (!displayEntry) + { + LOG_ERROR("sql.sql", "Creature (Entry: {}) lists non-existing CreatureDisplayID id ({}), this can crash the client.", creatureId, creatureDisplayId); + continue; + } + + CreatureModelInfo const* modelInfo = GetCreatureModelInfo(creatureDisplayId); + if (!modelInfo) + LOG_ERROR("sql.sql", "No model data exist for `CreatureDisplayID` = {} listed by creature (Entry: {%u}}).", creatureDisplayId, creatureId); + + if (displayScale <= 0.0f) + displayScale = 1.0f; + + const_cast(cInfo)->Models.emplace_back(creatureDisplayId, displayScale, probability); + + ++count; + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} creature template models in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); +} + void ObjectMgr::LoadCreatureTemplateResistances() { uint32 oldMSTime = getMSTime(); @@ -1112,76 +1163,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo) if (!factionTemplate) LOG_ERROR("sql.sql", "Creature (Entry: {}) has non-existing faction template ({}).", cInfo->Entry, cInfo->faction); - // used later for scale - CreatureDisplayInfoEntry const* displayScaleEntry = nullptr; - - if (cInfo->Modelid1) - { - CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid1); - if (!displayEntry) - { - LOG_ERROR("sql.sql", "Creature (Entry: {}) lists non-existing Modelid1 id ({}), this can crash the client.", cInfo->Entry, cInfo->Modelid1); - const_cast(cInfo)->Modelid1 = 0; - } - else if (!displayScaleEntry) - displayScaleEntry = displayEntry; - - CreatureModelInfo const* modelInfo = GetCreatureModelInfo(cInfo->Modelid1); - if (!modelInfo) - LOG_ERROR("sql.sql", "No model data exist for `Modelid1` = {} listed by creature (Entry: {}).", cInfo->Modelid1, cInfo->Entry); - } - - if (cInfo->Modelid2) - { - CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid2); - if (!displayEntry) - { - LOG_ERROR("sql.sql", "Creature (Entry: {}) lists non-existing Modelid2 id ({}), this can crash the client.", cInfo->Entry, cInfo->Modelid2); - const_cast(cInfo)->Modelid2 = 0; - } - else if (!displayScaleEntry) - displayScaleEntry = displayEntry; - - CreatureModelInfo const* modelInfo = GetCreatureModelInfo(cInfo->Modelid2); - if (!modelInfo) - LOG_ERROR("sql.sql", "No model data exist for `Modelid2` = {} listed by creature (Entry: {}).", cInfo->Modelid2, cInfo->Entry); - } - - if (cInfo->Modelid3) - { - CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid3); - if (!displayEntry) - { - LOG_ERROR("sql.sql", "Creature (Entry: {}) lists non-existing Modelid3 id ({}), this can crash the client.", cInfo->Entry, cInfo->Modelid3); - const_cast(cInfo)->Modelid3 = 0; - } - else if (!displayScaleEntry) - displayScaleEntry = displayEntry; - - CreatureModelInfo const* modelInfo = GetCreatureModelInfo(cInfo->Modelid3); - if (!modelInfo) - LOG_ERROR("sql.sql", "No model data exist for `Modelid3` = {} listed by creature (Entry: {}).", cInfo->Modelid3, cInfo->Entry); - } - - if (cInfo->Modelid4) - { - CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid4); - if (!displayEntry) - { - LOG_ERROR("sql.sql", "Creature (Entry: {}) lists non-existing Modelid4 id ({}), this can crash the client.", cInfo->Entry, cInfo->Modelid4); - const_cast(cInfo)->Modelid4 = 0; - } - else if (!displayScaleEntry) - displayScaleEntry = displayEntry; - - CreatureModelInfo const* modelInfo = GetCreatureModelInfo(cInfo->Modelid4); - if (!modelInfo) - LOG_ERROR("sql.sql", "No model data exist for `Modelid4` = {} listed by creature (Entry: {}).", cInfo->Modelid4, cInfo->Entry); - } - - if (!displayScaleEntry) - LOG_ERROR("sql.sql", "Creature (Entry: {}) does not have any existing display id in Modelid1/Modelid2/Modelid3/Modelid4.", cInfo->Entry); - for (int k = 0; k < MAX_KILL_CREDIT; ++k) { if (cInfo->KillCredit[k]) @@ -1194,6 +1175,11 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo) } } + if (!cInfo->Models.size()) + LOG_ERROR("sql.sql", "Creature (Entry: {}) does not have any existing display id in creature_template_model.", cInfo->Entry); + else if (std::accumulate(cInfo->Models.begin(), cInfo->Models.end(), 0.0f, [](float sum, CreatureModel const& model) { return sum + model.Probability; }) <= 0.0f) + LOG_ERROR("sql.sql", "Creature (Entry: {}) has zero total chance for all models in creature_template_model.", cInfo->Entry); + if (!cInfo->unit_class || ((1 << (cInfo->unit_class - 1)) & CLASSMASK_ALL_CREATURES) == 0) { LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid unit_class ({}) in creature_template. Set to 1 (UNIT_CLASS_WARRIOR).", cInfo->Entry, cInfo->unit_class); @@ -1280,15 +1266,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo) const_cast(cInfo)->MovementType = IDLE_MOTION_TYPE; } - /// if not set custom creature scale then load scale from CreatureDisplayInfo.dbc - if (cInfo->scale <= 0.0f) - { - if (displayScaleEntry) - const_cast(cInfo)->scale = displayScaleEntry->scale; - else - const_cast(cInfo)->scale = 1.0f; - } - if (cInfo->expansion > (MAX_EXPANSIONS - 1)) { LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: {}) with expansion {}. Ignored and set to 0.", cInfo->Entry, cInfo->expansion); @@ -1722,13 +1699,19 @@ CreatureModelInfo const* ObjectMgr::GetCreatureModelInfo(uint32 modelId) const return nullptr; } -uint32 ObjectMgr::ChooseDisplayId(CreatureTemplate const* cinfo, CreatureData const* data /*= nullptr*/) +CreatureModel const* ObjectMgr::ChooseDisplayId(CreatureTemplate const* cinfo, CreatureData const* data /*= nullptr*/) { // Load creature model (display id) if (data && data->displayid) - return data->displayid; + if (CreatureModel const* model = cinfo->GetModelWithDisplayId(data->displayid)) + return model; - return cinfo->GetRandomValidModelId(); + if (!(cinfo->flags_extra & CREATURE_FLAG_EXTRA_TRIGGER)) + if (CreatureModel const* model = cinfo->GetRandomValidModel()) + return model; + + // Triggers by default receive the invisible model + return cinfo->GetFirstInvisibleModel(); } void ObjectMgr::ChooseCreatureFlags(const CreatureTemplate* cinfo, uint32& npcflag, uint32& unit_flags, uint32& dynamicflags, const CreatureData* data /*= nullptr*/) @@ -1750,9 +1733,9 @@ void ObjectMgr::ChooseCreatureFlags(const CreatureTemplate* cinfo, uint32& npcfl } } -CreatureModelInfo const* ObjectMgr::GetCreatureModelRandomGender(uint32* displayID) +CreatureModelInfo const* ObjectMgr::GetCreatureModelRandomGender(CreatureModel* model, CreatureTemplate const* creatureTemplate) const { - CreatureModelInfo const* modelInfo = GetCreatureModelInfo(*displayID); + CreatureModelInfo const* modelInfo = GetCreatureModelInfo(model->CreatureDisplayID); if (!modelInfo) return nullptr; @@ -1761,11 +1744,20 @@ CreatureModelInfo const* ObjectMgr::GetCreatureModelRandomGender(uint32* display { CreatureModelInfo const* minfo_tmp = GetCreatureModelInfo(modelInfo->modelid_other_gender); if (!minfo_tmp) - LOG_ERROR("sql.sql", "Model (Entry: {}) has modelid_other_gender {} not found in table `creature_model_info`. ", *displayID, modelInfo->modelid_other_gender); + LOG_ERROR("sql.sql", "Model (Entry: {}) has modelid_other_gender {} not found in table `creature_model_info`. ", model->CreatureDisplayID, modelInfo->modelid_other_gender); else { // Model ID changed - *displayID = modelInfo->modelid_other_gender; + model->CreatureDisplayID = modelInfo->modelid_other_gender; + if (creatureTemplate) + { + auto itr = std::find_if(creatureTemplate->Models.begin(), creatureTemplate->Models.end(), [&](CreatureModel const& templateModel) + { + return templateModel.CreatureDisplayID == modelInfo->modelid_other_gender; + }); + if (itr != creatureTemplate->Models.end()) + *model = *itr; + } return minfo_tmp; } } @@ -1790,10 +1782,15 @@ void ObjectMgr::LoadCreatureModelInfo() _creatureModelStore.rehash(result->GetRowCount()); uint32 count = 0; + // List of ModelDataIDs that use Invisible models + uint32 triggerCreatureModelDataID[14] = { 1731, 1752, 2206, 2296, 2372, 2382, 2481, 2512, 2513, 2611, 2636, 2790, 3230, 3274 }; + do { Field* fields = result->Fetch(); + uint32 displayId = fields[0].Get(); + CreatureDisplayInfoEntry const* creatureDisplay = sCreatureDisplayInfoStore.LookupEntry(displayId); uint32 modelId = fields[0].Get(); CreatureModelInfo& modelInfo = _creatureModelStore[modelId]; @@ -1802,6 +1799,7 @@ void ObjectMgr::LoadCreatureModelInfo() modelInfo.combat_reach = fields[2].Get(); modelInfo.gender = fields[3].Get(); modelInfo.modelid_other_gender = fields[4].Get(); + modelInfo.is_trigger = false; // Checks @@ -1823,6 +1821,18 @@ void ObjectMgr::LoadCreatureModelInfo() if (modelInfo.combat_reach < 0.1f) modelInfo.combat_reach = DEFAULT_COMBAT_REACH; + if (CreatureModelDataEntry const* modelData = sCreatureModelDataStore.LookupEntry(creatureDisplay->ModelId)) + { + for (uint32 i = 0; i < 14; i++) + { + if (modelData->Id == triggerCreatureModelDataID[i]) + { + modelInfo.is_trigger = true; + break; + } + } + } + ++count; } while (result->NextRow()); @@ -6586,7 +6596,8 @@ void ObjectMgr::GetTaxiPath(uint32 source, uint32 destination, uint32& path, uin uint32 ObjectMgr::GetTaxiMountDisplayId(uint32 id, TeamId teamId, bool allowed_alt_team /* = false */) { - uint32 mount_id = 0; + CreatureModel mountModel; + CreatureTemplate const* mount_info = nullptr; // select mount creature id TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(id); @@ -6602,22 +6613,23 @@ uint32 ObjectMgr::GetTaxiMountDisplayId(uint32 id, TeamId teamId, bool allowed_a mount_entry = node->MountCreatureID[teamId]; } - CreatureTemplate const* mount_info = GetCreatureTemplate(mount_entry); + mount_info = GetCreatureTemplate(mount_entry); if (mount_info) { - mount_id = mount_info->GetRandomValidModelId(); - if (!mount_id) + CreatureModel const* model = mount_info->GetRandomValidModel(); + if (!model) { LOG_ERROR("sql.sql", "No displayid found for the taxi mount with the entry {}! Can't load it!", mount_entry); return 0; } + mountModel = *model; } } // minfo is not actually used but the mount_id was updated - GetCreatureModelRandomGender(&mount_id); + GetCreatureModelRandomGender(&mountModel, mount_info); - return mount_id; + return mountModel.CreatureDisplayID; } void ObjectMgr::LoadAreaTriggers() diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 4b70e9ffe..48a359156 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -770,8 +770,8 @@ public: CreatureTemplate const* GetCreatureTemplate(uint32 entry); [[nodiscard]] CreatureTemplateContainer const* GetCreatureTemplates() const { return &_creatureTemplateStore; } CreatureModelInfo const* GetCreatureModelInfo(uint32 modelId) const; - CreatureModelInfo const* GetCreatureModelRandomGender(uint32* displayID); - static uint32 ChooseDisplayId(CreatureTemplate const* cinfo, CreatureData const* data = nullptr); + CreatureModelInfo const* GetCreatureModelRandomGender(CreatureModel* model, CreatureTemplate const* creatureTemplate) const; + static CreatureModel const* ChooseDisplayId(CreatureTemplate const* cinfo, CreatureData const* data = nullptr); static void ChooseCreatureFlags(CreatureTemplate const* cinfo, uint32& npcflag, uint32& unit_flags, uint32& dynamicflags, CreatureData const* data = nullptr); EquipmentInfo const* GetEquipmentInfo(uint32 entry, int8& id); CreatureAddon const* GetCreatureAddon(ObjectGuid::LowType lowguid); @@ -1021,6 +1021,7 @@ public: void LoadCreatureLocales(); void LoadCreatureTemplates(); void LoadCreatureTemplate(Field* fields, bool triggerHook = false); + void LoadCreatureTemplateModels(); void LoadCreatureTemplateAddons(); void LoadCreatureTemplateResistances(); void LoadCreatureTemplateSpells(); diff --git a/src/server/game/Handlers/QueryHandler.cpp b/src/server/game/Handlers/QueryHandler.cpp index df6f4ff27..f38df27fc 100644 --- a/src/server/game/Handlers/QueryHandler.cpp +++ b/src/server/game/Handlers/QueryHandler.cpp @@ -116,23 +116,35 @@ void WorldSession::HandleCreatureQueryOpcode(WorldPacket& recvData) } // guess size WorldPacket data(SMSG_CREATURE_QUERY_RESPONSE, 100); - data << uint32(entry); // creature entry + data << uint32(entry); // creature entry data << Name; - data << uint8(0) << uint8(0) << uint8(0); // name2, name3, name4, always empty + data << uint8(0) << uint8(0) << uint8(0); // name2, name3, name4, always empty data << Title; - data << ci->IconName; // "Directions" for guard, string for Icons 2.3.0 - data << uint32(ci->type_flags); // flags - data << uint32(ci->type); // CreatureType.dbc - data << uint32(ci->family); // CreatureFamily.dbc - data << uint32(ci->rank); // Creature Rank (elite, boss, etc) - data << uint32(ci->KillCredit[0]); // new in 3.1, kill credit - data << uint32(ci->KillCredit[1]); // new in 3.1, kill credit - data << uint32(ci->Modelid1); // Modelid1 - data << uint32(ci->Modelid2); // Modelid2 - data << uint32(ci->Modelid3); // Modelid3 - data << uint32(ci->Modelid4); // Modelid4 - data << float(ci->ModHealth); // dmg/hp modifier - data << float(ci->ModMana); // dmg/mana modifier + data << ci->IconName; // "Directions" for guard, string for Icons 2.3.0 + data << uint32(ci->type_flags); // flags + data << uint32(ci->type); // CreatureType.dbc + data << uint32(ci->family); // CreatureFamily.dbc + data << uint32(ci->rank); // Creature Rank (elite, boss, etc) + data << uint32(ci->KillCredit[0]); // new in 3.1, kill credit + data << uint32(ci->KillCredit[1]); // new in 3.1, kill credit + if (ci->GetModelByIdx(0)) + data << uint32(ci->GetModelByIdx(0)->CreatureDisplayID); // Modelid1 + else + data << uint32(0); // Modelid1 + if (ci->GetModelByIdx(1)) + data << uint32(ci->GetModelByIdx(1)->CreatureDisplayID); // Modelid2 + else + data << uint32(0); // Modelid2 + if (ci->GetModelByIdx(2)) + data << uint32(ci->GetModelByIdx(2)->CreatureDisplayID); // Modelid3 + else + data << uint32(0); // Modelid3 + if (ci->GetModelByIdx(3)) + data << uint32(ci->GetModelByIdx(3)->CreatureDisplayID); // Modelid4 + else + data << uint32(0); // Modelid4 + data << float(ci->ModHealth); // dmg/hp modifier + data << float(ci->ModMana); // dmg/mana modifier data << uint8(ci->RacialLeader); CreatureQuestItemList const* items = sObjectMgr->GetCreatureQuestItemList(entry); @@ -143,7 +155,7 @@ void WorldSession::HandleCreatureQueryOpcode(WorldPacket& recvData) for (size_t i = 0; i < MAX_CREATURE_QUEST_ITEMS; ++i) data << uint32(0); - data << uint32(ci->movementId); // CreatureMovementInfo.dbc + data << uint32(ci->movementId); // CreatureMovementInfo.dbc SendPacket(&data); } else diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 16000a27b..5a533f307 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -2705,7 +2705,7 @@ void AuraEffect::HandleAuraTransform(AuraApplication const* aurApp, uint8 mode, { uint32 model_id = 0; - if (uint32 modelid = ci->GetRandomValidModelId()) + if (uint32 modelid = ObjectMgr::ChooseDisplayId(ci)->CreatureDisplayID) model_id = modelid; // Will use the default model here // Polymorph (sheep) @@ -2757,10 +2757,10 @@ void AuraEffect::HandleAuraTransform(AuraApplication const* aurApp, uint8 mode, uint32 cr_id = target->GetAuraEffectsByType(SPELL_AURA_MOUNTED).front()->GetMiscValue(); if (CreatureTemplate const* ci = sObjectMgr->GetCreatureTemplate(cr_id)) { - uint32 displayID = ObjectMgr::ChooseDisplayId(ci); - sObjectMgr->GetCreatureModelRandomGender(&displayID); + CreatureModel model = *ObjectMgr::ChooseDisplayId(ci); + sObjectMgr->GetCreatureModelRandomGender(&model, ci); - target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, displayID); + target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, model.CreatureDisplayID); } } } @@ -3272,15 +3272,9 @@ void AuraEffect::HandleAuraMounted(AuraApplication const* aurApp, uint8 mode, bo if (CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creatureEntry)) { - if (GetMiscValueB() > 0) // Choose proper modelid - { - displayId = GetMiscValueB() == 2 && creatureInfo->Modelid2 > 0 ? creatureInfo->Modelid2 : creatureInfo->Modelid1; - } - else // Should we choose random modelid in this case? - { - displayId = ObjectMgr::ChooseDisplayId(creatureInfo); - } - sObjectMgr->GetCreatureModelRandomGender(&displayId); + CreatureModel model = *ObjectMgr::ChooseDisplayId(creatureInfo); + sObjectMgr->GetCreatureModelRandomGender(&model, creatureInfo); + displayId = model.CreatureDisplayID; vehicleId = creatureInfo->VehicleId; @@ -5793,10 +5787,10 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool if (CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creatureEntry)) { - uint32 displayID = ObjectMgr::ChooseDisplayId(creatureInfo); - sObjectMgr->GetCreatureModelRandomGender(&displayID); + CreatureModel model = *ObjectMgr::ChooseDisplayId(creatureInfo); + sObjectMgr->GetCreatureModelRandomGender(&model, creatureInfo); - target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, displayID); + target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, model.CreatureDisplayID); } } break; @@ -5823,10 +5817,10 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool if (CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creatureEntry)) { - uint32 displayID = ObjectMgr::ChooseDisplayId(creatureInfo); - sObjectMgr->GetCreatureModelRandomGender(&displayID); + CreatureModel model = *ObjectMgr::ChooseDisplayId(creatureInfo); + sObjectMgr->GetCreatureModelRandomGender(&model, creatureInfo); - target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, displayID); + target->SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, model.CreatureDisplayID); } } break; diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp index 0d55b9f4c..cef361a80 100644 --- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp @@ -63,7 +63,7 @@ struct npc_eye_of_acherus : public ScriptedAI { npc_eye_of_acherus(Creature* creature) : ScriptedAI(creature) { - creature->SetDisplayId(creature->GetCreatureTemplate()->Modelid1); + creature->SetDisplayFromModel(0); creature->SetReactState(REACT_PASSIVE); } @@ -968,7 +968,7 @@ public: { me->SetImmuneToAll(true); me->SetFaction(FACTION_FRIENDLY); - me->SetDisplayId(me->GetCreatureTemplate()->Modelid1); // Modelid2 is a horse. + me->SetDisplayFromModel(0); // Modelid2 is a horse. } ObjectGuid minerGUID;