diff --git a/Source/Game/Entities/Creature/CreatureData.cs b/Source/Game/Entities/Creature/CreatureData.cs index e5023c9c2..92e190b38 100644 --- a/Source/Game/Entities/Creature/CreatureData.cs +++ b/Source/Game/Entities/Creature/CreatureData.cs @@ -410,6 +410,13 @@ namespace Game.Entities } } + public class CreatureSummonedData + { + public uint? CreatureIDVisibleToSummoner; + public uint? GroundMountDisplayID; + public uint? FlyingMountDisplayID; + } + public class CreatureAddon { public uint path_id; diff --git a/Source/Game/Entities/Object/Update/UpdateFields.cs b/Source/Game/Entities/Object/Update/UpdateFields.cs index 2c43856c9..b8824c79d 100644 --- a/Source/Game/Entities/Object/Update/UpdateFields.cs +++ b/Source/Game/Entities/Object/Update/UpdateFields.cs @@ -37,7 +37,7 @@ namespace Game.Entities public void WriteCreate(WorldPacket data, UpdateFieldFlag fieldVisibilityFlags, WorldObject owner, Player receiver) { - data.WriteUInt32(EntryId); + data.WriteUInt32(GetViewerDependentEntryId(this, owner, receiver)); data.WriteUInt32(GetViewerDependentDynamicFlags(this, owner, receiver)); data.WriteFloat(Scale); } @@ -56,7 +56,7 @@ namespace Game.Entities { if (changesMask[1]) { - data.WriteUInt32(EntryId); + data.WriteUInt32(GetViewerDependentEntryId(this, owner, receiver)); } if (changesMask[2]) { @@ -77,6 +77,21 @@ namespace Game.Entities _changesMask.ResetAll(); } + uint GetViewerDependentEntryId(ObjectFieldData objectData, WorldObject obj, Player receiver) + { + uint entryId = objectData.EntryId; + Unit unit = obj.ToUnit(); + if (unit != null) + { + TempSummon summon = unit.ToTempSummon(); + if (summon != null) + if (summon.GetSummonerGUID() == receiver.GetGUID() && summon.GetCreatureIdVisibleToSummoner().HasValue) + entryId = summon.GetCreatureIdVisibleToSummoner().Value; + } + + return entryId; + } + uint GetViewerDependentDynamicFlags(ObjectFieldData objectData, WorldObject obj, Player receiver) { uint unitDynFlags = objectData.DynamicFlags; @@ -2217,6 +2232,18 @@ namespace Game.Entities if (unit.IsCreature()) { CreatureTemplate cinfo = unit.ToCreature().GetCreatureTemplate(); + TempSummon summon = unit.ToTempSummon(); + if (summon != null) + { + if (summon.GetSummonerGUID() == receiver.GetGUID()) + { + if (summon.GetCreatureIdVisibleToSummoner().HasValue) + cinfo = Global.ObjectMgr.GetCreatureTemplate(summon.GetCreatureIdVisibleToSummoner().Value); + + if (summon.GetDisplayIdVisibleToSummoner().HasValue) + displayId = summon.GetDisplayIdVisibleToSummoner().Value; + } + } // this also applies for transform auras SpellInfo transform = Global.SpellMgr.GetSpellInfo(unit.GetTransformSpell(), unit.GetMap().GetDifficultyID()); diff --git a/Source/Game/Entities/TemporarySummon.cs b/Source/Game/Entities/TemporarySummon.cs index b737d33e3..c52f2e7f2 100644 --- a/Source/Game/Entities/TemporarySummon.cs +++ b/Source/Game/Entities/TemporarySummon.cs @@ -193,6 +193,20 @@ namespace Game.Entities if (owner.IsTypeId(TypeId.Player)) m_ControlledByPlayer = true; + if (owner != null && owner.IsPlayer()) + { + CreatureSummonedData summonedData = Global.ObjectMgr.GetCreatureSummonedData(GetEntry()); + if (summonedData != null) + { + m_creatureIdVisibleToSummoner = summonedData.CreatureIDVisibleToSummoner; + if (summonedData.CreatureIDVisibleToSummoner.HasValue) + { + CreatureTemplate creatureTemplateVisibleToSummoner = Global.ObjectMgr.GetCreatureTemplate(summonedData.CreatureIDVisibleToSummoner.Value); + m_displayIdVisibleToSummoner = ObjectManager.ChooseDisplayId(creatureTemplateVisibleToSummoner, null).CreatureDisplayID; + } + } + } + if (m_Properties == null) return; @@ -367,6 +381,9 @@ namespace Game.Entities public uint GetTimer() { return m_timer; } + public uint? GetCreatureIdVisibleToSummoner() { return m_creatureIdVisibleToSummoner; } + public uint? GetDisplayIdVisibleToSummoner() { return m_displayIdVisibleToSummoner; } + public bool CanFollowOwner() { return m_canFollowOwner; } public void SetCanFollowOwner(bool can) { m_canFollowOwner = can; } @@ -375,6 +392,8 @@ namespace Game.Entities uint m_timer; uint m_lifetime; ObjectGuid m_summonerGUID; + uint? m_creatureIdVisibleToSummoner; + uint? m_displayIdVisibleToSummoner; bool m_canFollowOwner; } diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index bad24227d..6b95059c1 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -1705,6 +1705,8 @@ namespace Game // We load the creature models after loading but before checking LoadCreatureTemplateModels(); + LoadCreatureSummonedData(); + // Checking needs to be done after loading because of the difficulty self referencing foreach (var template in creatureTemplateStorage.Values) CheckCreatureTemplate(template); @@ -1940,6 +1942,66 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} creature template models in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } + void LoadCreatureSummonedData() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT CreatureID, CreatureIDVisibleToSummoner, GroundMountDisplayID, FlyingMountDisplayID FROM creature_summoned_data"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creature summoned data definitions. DB table `creature_summoned_data` is empty."); + return; + } + + do + { + uint creatureId = result.Read(0); + if (GetCreatureTemplate(creatureId) == null) + { + Log.outError(LogFilter.Sql, $"Table `creature_summoned_data` references non-existing creature {creatureId}, skipped"); + continue; + } + + if (!creatureSummonedDataStorage.ContainsKey(creatureId)) + creatureSummonedDataStorage[creatureId] = new(); + + CreatureSummonedData summonedData = creatureSummonedDataStorage[creatureId]; + + if (!result.IsNull(1)) + { + summonedData.CreatureIDVisibleToSummoner = result.Read(1); + if (GetCreatureTemplate(summonedData.CreatureIDVisibleToSummoner.Value) == null) + { + Log.outError(LogFilter.Sql, $"Table `creature_summoned_data` references non-existing creature {summonedData.CreatureIDVisibleToSummoner.Value} in CreatureIDVisibleToSummoner for creature {creatureId}, set to 0"); + summonedData.CreatureIDVisibleToSummoner = null; + } + } + + if (!result.IsNull(2)) + { + summonedData.GroundMountDisplayID = result.Read(2); + if (!CliDB.CreatureDisplayInfoStorage.ContainsKey(summonedData.GroundMountDisplayID.Value)) + { + Log.outError(LogFilter.Sql, $"Table `creature_summoned_data` references non-existing display id {summonedData.GroundMountDisplayID.Value} in GroundMountDisplayID for creature {creatureId}, set to 0"); + summonedData.CreatureIDVisibleToSummoner = null; + } + } + + if (!result.IsNull(3)) + { + summonedData.FlyingMountDisplayID = result.Read(3); + if (!CliDB.CreatureDisplayInfoStorage.ContainsKey(summonedData.FlyingMountDisplayID.Value)) + { + Log.outError(LogFilter.Sql, $"Table `creature_summoned_data` references non-existing display id {summonedData.FlyingMountDisplayID.Value} in FlyingMountDisplayID for creature {creatureId}, set to 0"); + summonedData.GroundMountDisplayID = null; + } + } + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, $"Loaded {creatureSummonedDataStorage.Count} creature summoned data definitions in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); + } public void LoadCreatureTemplateAddons() { var time = Time.GetMSTime(); @@ -3894,6 +3956,10 @@ namespace Game { return creatureModelStorage.LookupByKey(modelId); } + public CreatureSummonedData GetCreatureSummonedData(uint entryId) + { + return creatureSummonedDataStorage.LookupByKey(entryId); + } public NpcText GetNpcText(uint textId) { return npcTextStorage.LookupByKey(textId); @@ -10818,6 +10884,7 @@ namespace Game //Creature Dictionary creatureTemplateStorage = new(); Dictionary creatureModelStorage = new(); + Dictionary creatureSummonedDataStorage = new(); Dictionary creatureDataStorage = new(); Dictionary creatureAddonStorage = new(); MultiMap creatureQuestItemStorage = new(); diff --git a/sql/updates/world/master/2022_07_03_00_world.sql b/sql/updates/world/master/2022_07_03_00_world.sql new file mode 100644 index 000000000..c25cda300 --- /dev/null +++ b/sql/updates/world/master/2022_07_03_00_world.sql @@ -0,0 +1,18 @@ +-- +-- Table structure for table `creature_summoned_data` +-- +DROP TABLE IF EXISTS `creature_summoned_data`; +CREATE TABLE `creature_summoned_data` ( + `CreatureID` int unsigned NOT NULL, + `CreatureIDVisibleToSummoner` int DEFAULT NULL, + `GroundMountDisplayID` int unsigned DEFAULT NULL, + `FlyingMountDisplayID` int unsigned DEFAULT NULL, + PRIMARY KEY (`CreatureID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `creature_summoned_data` +-- +INSERT INTO `creature_summoned_data` VALUES +(90382,90240,54563,46930), +(91911,91913,59339,NULL);