diff --git a/Source/Game/Entities/Object/SmoothPhasing.cs b/Source/Game/Entities/Object/SmoothPhasing.cs new file mode 100644 index 000000000..bab8c0f5a --- /dev/null +++ b/Source/Game/Entities/Object/SmoothPhasing.cs @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012-2020 CypherCore + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; + +namespace Game.Entities +{ + public class SmoothPhasing + { + SmoothPhasingInfo smoothPhasingInfoSingle; + Dictionary smoothPhasingInfoViewerDependent = new(); + + public void SetViewerDependentInfo(ObjectGuid seer, SmoothPhasingInfo info) + { + smoothPhasingInfoViewerDependent[seer] = info; + } + + public void ClearViewerDependentInfo(ObjectGuid seer) + { + smoothPhasingInfoViewerDependent.Remove(seer); + } + + public void SetSingleInfo(SmoothPhasingInfo info) + { + smoothPhasingInfoSingle = info; + } + + public bool IsReplacing(ObjectGuid guid) + { + return smoothPhasingInfoSingle != null && smoothPhasingInfoSingle.ReplaceObject == guid; + } + + public bool IsBeingReplacedForSeer(ObjectGuid seer) + { + SmoothPhasingInfo smoothPhasingInfo = smoothPhasingInfoViewerDependent.LookupByKey(seer); + if (smoothPhasingInfo != null) + return !smoothPhasingInfo.Disabled; + + return false; + } + + public SmoothPhasingInfo GetInfoForSeer(ObjectGuid seer) + { + if (smoothPhasingInfoViewerDependent.TryGetValue(seer, out SmoothPhasingInfo value)) + return value; + + return smoothPhasingInfoSingle; + } + + public void DisableReplacementForSeer(ObjectGuid seer) + { + SmoothPhasingInfo smoothPhasingInfo = smoothPhasingInfoViewerDependent.LookupByKey(seer); + if (smoothPhasingInfo != null) + smoothPhasingInfo.Disabled = true; + } + } + + public class SmoothPhasingInfo + { + // Fields visible on client + public ObjectGuid? ReplaceObject; + public bool ReplaceActive = true; + public bool StopAnimKits = true; + + // Serverside fields + public bool Disabled = false; + + public SmoothPhasingInfo(ObjectGuid replaceObject, bool replaceActive, bool stopAnimKits) + { + ReplaceObject = replaceObject; + ReplaceActive = replaceActive; + StopAnimKits = stopAnimKits; + } + } +} diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index b8f0f0949..c8318bf3a 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -107,8 +107,6 @@ namespace Game.Entities if (!IsInWorld) return; - RestoreReplacedObject(); - if (!ObjectTypeMask.HasAnyFlag(TypeMask.Item | TypeMask.Container)) UpdateObjectVisibilityOnDestroy(); @@ -159,7 +157,7 @@ namespace Game.Entities if (GetAIAnimKitId() != 0 || GetMovementAnimKitId() != 0 || GetMeleeAnimKitId() != 0) flags.AnimKit = true; - if (IsReplacingObjectFor(target)) + if (GetSmoothPhasing()?.GetInfoForSeer(target.GetGUID()) != null) flags.SmoothPhasing = true; Unit unit = ToUnit(); @@ -558,14 +556,15 @@ namespace Game.Entities if (flags.SmoothPhasing) { - ReplaceObjectInfo replacedObjectInfo = GetReplacedObjectFor(target); - Cypher.Assert(replacedObjectInfo != null); + SmoothPhasingInfo smoothPhasingInfo = GetSmoothPhasing().GetInfoForSeer(target.GetGUID()); + Cypher.Assert(smoothPhasingInfo != null); - data.WriteBit(true); // ReplaceActive - data.WriteBit(replacedObjectInfo.StopAnimKits); - data.WriteBit(true); + data.WriteBit(smoothPhasingInfo.ReplaceActive); + data.WriteBit(smoothPhasingInfo.StopAnimKits); + data.WriteBit(smoothPhasingInfo.ReplaceObject.HasValue); data.FlushBits(); - data.WritePackedGuid(replacedObjectInfo.ReplaceObject); + if (smoothPhasingInfo.ReplaceObject.HasValue) + data.WritePackedGuid(smoothPhasingInfo.ReplaceObject.Value); } if (flags.SceneObject) @@ -1095,77 +1094,16 @@ namespace Game.Entities return false; } - void SetReplacedObject(ObjectGuid seer, ObjectGuid replacedObject, bool stopAnimKits = true) + public SmoothPhasing GetOrCreateSmoothPhasing() { - ReplaceObjectInfo replaceObjectInfo = new(); - replaceObjectInfo.ReplaceObject = replacedObject; - replaceObjectInfo.StopAnimKits = stopAnimKits; - _replacedObjects[seer] = replaceObjectInfo; + if (_smoothPhasing == null) + _smoothPhasing = new(); + + return _smoothPhasing; } - public void ReplaceWith(WorldObject seer, WorldObject replaceWithObject, bool stopAnimKits = true) - { - _objectsWhichReplaceMeForSeer[seer.GetGUID()] = replaceWithObject.GetGUID(); - replaceWithObject.SetReplacedObject(seer.GetGUID(), GetGUID(), stopAnimKits); - } - - void ReplaceWith(ObjectGuid seerGuid, ObjectGuid replaceWithObjectGuid, bool stopAnimKits = true) - { - WorldObject replaceWithObject = Global.ObjAccessor.GetWorldObject(this, replaceWithObjectGuid); - if (replaceWithObject == null) - return; - - _objectsWhichReplaceMeForSeer[seerGuid] = replaceWithObjectGuid; - replaceWithObject.SetReplacedObject(seerGuid, GetGUID(), stopAnimKits); - } - - void RestoreReplacedObject() - { - if (_replacedObjects.Empty() || !IsPrivateObject()) - return; - - var itr = _replacedObjects.FirstOrDefault(); - WorldObject replacedObject = Global.ObjAccessor.GetWorldObject(this, itr.Value.ReplaceObject); - if (replacedObject == null) - return; - - ReplaceWith(itr.Key, itr.Value.ReplaceObject, itr.Value.StopAnimKits); - replacedObject.RemoveObjectWhichReplacesMe(itr.Key); - _replacedObjects.Remove(itr.Key); - - Player player = Global.ObjAccessor.FindPlayer(itr.Key); - if (player == null) - return; - - player.UpdateVisibilityOf(new[] { replacedObject, this }); - } - - void RemoveObjectWhichReplacesMe(WorldObject seer) { RemoveObjectWhichReplacesMe(seer.GetGUID()); } - - void RemoveObjectWhichReplacesMe(ObjectGuid seerGuid) { _objectsWhichReplaceMeForSeer.Remove(seerGuid); } - - ReplaceObjectInfo GetReplacedObjectFor(WorldObject seer) - { - return _replacedObjects.LookupByKey(seer.GetGUID()); - } - - bool IsReplacingObjectFor(WorldObject seer) { return GetReplacedObjectFor(seer) != null; } - - bool IsBeingReplacedFor(WorldObject seer) { return _objectsWhichReplaceMeForSeer.ContainsKey(seer.GetGUID()); } + public SmoothPhasing GetSmoothPhasing() { return _smoothPhasing; } - bool CheckReplacedObjectVisibility(WorldObject seer) - { - Creature creature = ToCreature(); - if (creature != null) - { - Player player = seer.ToPlayer(); - if (player != null && IsBeingReplacedFor(player)) - return false; - } - - return true; - } - public bool CanSeeOrDetect(WorldObject obj, bool ignoreStealth = false, bool distanceCheck = false, bool checkAlert = false) { if (this == obj) @@ -1180,7 +1118,8 @@ namespace Game.Entities if (!obj.CheckPrivateObjectOwnerVisibility(this)) return false; - if (!obj.CheckReplacedObjectVisibility(this)) + SmoothPhasing smoothPhasing = obj.GetSmoothPhasing(); + if (smoothPhasing != null && smoothPhasing.IsBeingReplacedForSeer(GetGUID())) return false; if (!Global.ConditionMgr.IsObjectMeetingVisibilityByObjectIdConditions((uint)obj.GetTypeId(), obj.GetEntry(), this)) @@ -1539,7 +1478,7 @@ namespace Game.Entities Map map = GetMap(); if (map != null) { - TempSummon summon = map.SummonCreature(GetEntry(), pos, null, (uint)despawnTime.TotalMilliseconds, this, spellId, vehId, privateObjectOwner, GetGUID()); + TempSummon summon = map.SummonCreature(GetEntry(), pos, null, (uint)despawnTime.TotalMilliseconds, this, spellId, vehId, privateObjectOwner, new SmoothPhasingInfo(GetGUID(), true, true)); if (summon != null) { summon.SetTempSummonType(despawnType); @@ -2896,7 +2835,7 @@ namespace Game.Entities public virtual void UpdateObjectVisibility(bool force = true) { //updates object's visibility for nearby players - var notifier = new VisibleChangesNotifier(this); + var notifier = new VisibleChangesNotifier(new[] { this }); Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); } @@ -2971,7 +2910,8 @@ namespace Game.Entities public bool IsDynObject() { return GetTypeId() == TypeId.DynamicObject; } public bool IsAreaTrigger() { return GetTypeId() == TypeId.AreaTrigger; } public bool IsConversation() { return GetTypeId() == TypeId.Conversation; } - + public bool IsSceneObject() { return GetTypeId() == TypeId.SceneObject; } + public Creature ToCreature() { return IsCreature() ? (this as Creature) : null; } public Player ToPlayer() { return IsPlayer() ? (this as Player) : null; } public GameObject ToGameObject() { return IsGameObject() ? (this as GameObject) : null; } @@ -2980,6 +2920,7 @@ namespace Game.Entities public DynamicObject ToDynamicObject() { return IsDynObject() ? (this as DynamicObject) : null; } public AreaTrigger ToAreaTrigger() { return IsAreaTrigger() ? (this as AreaTrigger) : null; } public Conversation ToConversation() { return IsConversation() ? (this as Conversation) : null; } + public SceneObject ToSceneObject() { return IsSceneObject() ? (this as SceneObject) : null; } public virtual void Update(uint diff) { } @@ -3690,8 +3631,7 @@ namespace Game.Entities ObjectGuid _privateObjectOwner; - Dictionary _replacedObjects = new(); - Dictionary _objectsWhichReplaceMeForSeer = new(); + SmoothPhasing _smoothPhasing; public FlaggedArray m_stealth = new(2); public FlaggedArray m_stealthDetect = new(2); @@ -3925,10 +3865,4 @@ namespace Game.Entities player.SendPacket(i_message); } } - - class ReplaceObjectInfo - { - public ObjectGuid ReplaceObject; - public bool StopAnimKits = true; - } } \ No newline at end of file diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 5a6b319f4..c3344898b 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -6063,16 +6063,49 @@ namespace Game.Entities ((Pet)obj).Remove(PetSaveMode.NotInSlot, true); } - public void UpdateVisibilityOf(WorldObject[] targets) + public void UpdateVisibilityOf(ICollection targets) { if (targets.Empty()) return; UpdateData udata = new(GetMapId()); - List visibleNow = new(); + List newVisibleUnits = new(); foreach (WorldObject target in targets) - UpdateVisibilityOf(target, udata, visibleNow); + { + if (target == this) + continue; + + switch (target.GetTypeId()) + { + case TypeId.Unit: + UpdateVisibilityOf(target.ToCreature(), udata, newVisibleUnits); + break; + case TypeId.Player: + UpdateVisibilityOf(target.ToPlayer(), udata, newVisibleUnits); + break; + case TypeId.GameObject: + UpdateVisibilityOf(target.ToGameObject(), udata, newVisibleUnits); + break; + case TypeId.DynamicObject: + UpdateVisibilityOf(target.ToDynamicObject(), udata, newVisibleUnits); + break; + case TypeId.Corpse: + UpdateVisibilityOf(target.ToCorpse(), udata, newVisibleUnits); + break; + case TypeId.AreaTrigger: + UpdateVisibilityOf(target.ToAreaTrigger(), udata, newVisibleUnits); + break; + case TypeId.SceneObject: + UpdateVisibilityOf(target.ToSceneObject(), udata, newVisibleUnits); + break; + case TypeId.Conversation: + UpdateVisibilityOf(target.ToConversation(), udata, newVisibleUnits); + break; + default: + break; + } + } if (!udata.HasData()) return; @@ -6080,8 +6113,8 @@ namespace Game.Entities udata.BuildPacket(out UpdateObject packet); SendPacket(packet); - foreach (var target in visibleNow) - SendInitialVisiblePackets(target); + foreach (var visibleUnit in newVisibleUnits) + SendInitialVisiblePackets(visibleUnit); } public void UpdateVisibilityOf(WorldObject target) diff --git a/Source/Game/Entities/TemporarySummon.cs b/Source/Game/Entities/TemporarySummon.cs index 06df0ab3d..49156ae49 100644 --- a/Source/Game/Entities/TemporarySummon.cs +++ b/Source/Game/Entities/TemporarySummon.cs @@ -20,6 +20,7 @@ using Framework.Dynamic; using Game.DataStorage; using System; using System.Collections.Generic; +using Game.Maps; namespace Game.Entities { @@ -241,7 +242,45 @@ namespace Game.Entities public override void UpdateObjectVisibilityOnCreate() { - UpdateObjectVisibility(true); + List objectsToUpdate = new(); + objectsToUpdate.Add(this); + + SmoothPhasing smoothPhasing = GetSmoothPhasing(); + WorldObject original = GetSummoner(); + if (original != null) + if (smoothPhasing != null && smoothPhasing.IsReplacing(original.GetGUID())) + objectsToUpdate.Add(original); + + VisibleChangesNotifier notifier = new(objectsToUpdate); + Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); + } + + public override void UpdateObjectVisibilityOnDestroy() + { + List objectsToUpdate = new(); + objectsToUpdate.Add(this); + + WorldObject original = GetSummoner(); + SmoothPhasing smoothPhasing = GetSmoothPhasing(); + if (original != null && smoothPhasing != null && smoothPhasing.IsReplacing(original.GetGUID())) + { + objectsToUpdate.Add(original); + + // disable replacement without removing - it is still needed for next step (visibility update) + SmoothPhasing originalSmoothPhasing = original.GetSmoothPhasing(); + if (originalSmoothPhasing != null) + originalSmoothPhasing.DisableReplacementForSeer(GetDemonCreatorGUID()); + } + + VisibleChangesNotifier notifier = new(objectsToUpdate); + Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); + + if (original != null && smoothPhasing != null && smoothPhasing.IsReplacing(original.GetGUID())) + { + SmoothPhasing originalSmoothPhasing = original.GetSmoothPhasing(); + if (originalSmoothPhasing != null) + originalSmoothPhasing.ClearViewerDependentInfo(GetDemonCreatorGUID()); + } } public void SetTempSummonType(TempSummonType type) diff --git a/Source/Game/Entities/Unit/Unit.cs b/Source/Game/Entities/Unit/Unit.cs index 7b75cfa26..5048af40d 100644 --- a/Source/Game/Entities/Unit/Unit.cs +++ b/Source/Game/Entities/Unit/Unit.cs @@ -1887,7 +1887,9 @@ namespace Game.Entities public void SetCritterGUID(ObjectGuid guid) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.Critter), guid); } public ObjectGuid GetBattlePetCompanionGUID() { return m_unitData.BattlePetCompanionGUID; } public void SetBattlePetCompanionGUID(ObjectGuid guid) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.BattlePetCompanionGUID), guid); } - + public ObjectGuid GetDemonCreatorGUID() { return m_unitData.DemonCreator; } + public void SetDemonCreatorGUID(ObjectGuid guid) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.DemonCreator), guid); } + public ObjectGuid GetCharmerGUID() { return m_unitData.CharmedBy; } public Unit GetCharmer() { return m_charmer; } diff --git a/Source/Game/Maps/GridNotifiers.cs b/Source/Game/Maps/GridNotifiers.cs index a35bd8ad4..7d88a7dca 100644 --- a/Source/Game/Maps/GridNotifiers.cs +++ b/Source/Game/Maps/GridNotifiers.cs @@ -167,9 +167,11 @@ namespace Game.Maps public class VisibleChangesNotifier : Notifier { - public VisibleChangesNotifier(WorldObject obj) + ICollection i_objects; + + public VisibleChangesNotifier(ICollection objects) { - i_object = obj; + i_objects = objects; } public override void Visit(IList objs) @@ -177,19 +179,14 @@ namespace Game.Maps for (var i = 0; i < objs.Count; ++i) { Player player = objs[i]; - if (player.GetGUID() == i_object.GetGUID()) - return; - player.UpdateVisibilityOf(i_object); + player.UpdateVisibilityOf(i_objects); - if (player.HasSharedVision()) + foreach (var visionPlayer in player.GetSharedVisionList()) { - foreach (var visionPlayer in player.GetSharedVisionList()) - { - if (visionPlayer.seerView == player) - visionPlayer.UpdateVisibilityOf(i_object); - } - } + if (visionPlayer.seerView == player) + visionPlayer.UpdateVisibilityOf(i_objects); + } } } @@ -198,12 +195,9 @@ namespace Game.Maps for (var i = 0; i < objs.Count; ++i) { Creature creature = objs[i]; - if (creature.HasSharedVision()) - { - foreach (var visionPlayer in creature.GetSharedVisionList()) - if (visionPlayer.seerView == creature) - visionPlayer.UpdateVisibilityOf(i_object); - } + foreach (var visionPlayer in creature.GetSharedVisionList()) + if (visionPlayer.seerView == creature) + visionPlayer.UpdateVisibilityOf(i_objects); } } @@ -217,12 +211,10 @@ namespace Game.Maps { Player pl = caster.ToPlayer(); if (pl && pl.seerView == dynamicObject) - pl.UpdateVisibilityOf(i_object); + pl.UpdateVisibilityOf(i_objects); } } } - - WorldObject i_object; } public class PlayerRelocationNotifier : VisibleNotifier diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 91af3cbba..a136bf381 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -4022,7 +4022,7 @@ namespace Game.Maps } } - public TempSummon SummonCreature(uint entry, Position pos, SummonPropertiesRecord properties = null, uint duration = 0, WorldObject summoner = null, uint spellId = 0, uint vehId = 0, ObjectGuid privateObjectOwner = default, ObjectGuid replaceObject = default) + public TempSummon SummonCreature(uint entry, Position pos, SummonPropertiesRecord properties = null, uint duration = 0, WorldObject summoner = null, uint spellId = 0, uint vehId = 0, ObjectGuid privateObjectOwner = default, SmoothPhasingInfo smoothPhasingInfo = null) { var mask = UnitTypeMask.Summon; if (properties != null) @@ -4072,10 +4072,6 @@ namespace Game.Maps } } - WorldObject objectOwner = Global.ObjAccessor.GetWorldObject(summoner, privateObjectOwner); - if (objectOwner != null) - summoner = objectOwner; - Unit summonerUnit = summoner != null ? summoner.ToUnit() : null; TempSummon summon; @@ -4112,8 +4108,19 @@ namespace Game.Maps summon.InitStats(duration); summon.SetPrivateObjectOwner(privateObjectOwner); - if (summoner != null && !replaceObject.IsEmpty()) - PhasingHandler.ReplaceObject(summoner, summon, replaceObject); + if (smoothPhasingInfo != null) + { + if (summoner != null && smoothPhasingInfo.ReplaceObject.HasValue) + { + SmoothPhasingInfo originalSmoothPhasingInfo = smoothPhasingInfo; + originalSmoothPhasingInfo.ReplaceObject = summon.GetGUID(); + summoner.GetOrCreateSmoothPhasing().SetViewerDependentInfo(privateObjectOwner, originalSmoothPhasingInfo); + + summon.SetDemonCreatorGUID(privateObjectOwner); + } + + summon.GetOrCreateSmoothPhasing().SetSingleInfo(smoothPhasingInfo); + } if (!AddToMap(summon.ToCreature())) { diff --git a/Source/Game/Phasing/PhasingHandler.cs b/Source/Game/Phasing/PhasingHandler.cs index c420b747d..0d04c5e2d 100644 --- a/Source/Game/Phasing/PhasingHandler.cs +++ b/Source/Game/Phasing/PhasingHandler.cs @@ -662,19 +662,6 @@ namespace Game return false; } - - public static void ReplaceObject(WorldObject obj, WorldObject newObject, ObjectGuid replacedObjectGuid, bool stopAnimKits = true) - { - WorldObject replacedObject = Global.ObjAccessor.GetWorldObject(obj, replacedObjectGuid); - if (replacedObject == null) - return; - - replacedObject.ReplaceWith(obj, newObject, stopAnimKits); - - Player player = obj.ToPlayer(); - if (player != null) - player.UpdateVisibilityOf(new[] { newObject, replacedObject }); - } static void UpdateVisibilityIfNeeded(WorldObject obj, bool updateVisibility, bool changed) { diff --git a/Source/Scripts/Spells/Shaman.cs b/Source/Scripts/Spells/Shaman.cs index 2165c227b..497e90121 100644 --- a/Source/Scripts/Spells/Shaman.cs +++ b/Source/Scripts/Spells/Shaman.cs @@ -24,7 +24,6 @@ using Game.Scripting; using Game.Spells; using System; using System.Collections.Generic; -using System.Linq; namespace Scripts.Spells.Shaman {