diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index 5ab34d0cb..b8f0f0949 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -28,6 +28,7 @@ using Game.Scenarios; using Game.Spells; using System; using System.Collections.Generic; +using System.Linq; using System.Numerics; namespace Game.Entities @@ -106,6 +107,8 @@ namespace Game.Entities if (!IsInWorld) return; + RestoreReplacedObject(); + if (!ObjectTypeMask.HasAnyFlag(TypeMask.Item | TypeMask.Container)) UpdateObjectVisibilityOnDestroy(); @@ -156,6 +159,9 @@ namespace Game.Entities if (GetAIAnimKitId() != 0 || GetMovementAnimKitId() != 0 || GetMeleeAnimKitId() != 0) flags.AnimKit = true; + if (IsReplacingObjectFor(target)) + flags.SmoothPhasing = true; + Unit unit = ToUnit(); if (unit) { @@ -169,7 +175,7 @@ namespace Game.Entities buffer.WritePackedGuid(GetGUID()); buffer.WriteUInt8((byte)tempObjectType); - BuildMovementUpdate(buffer, flags); + BuildMovementUpdate(buffer, flags, target); BuildValuesCreate(buffer, target); data.AddUpdateBlock(buffer); } @@ -240,7 +246,7 @@ namespace Game.Entities target.SendPacket(packet); } - public void BuildMovementUpdate(WorldPacket data, CreateObjectBits flags) + public void BuildMovementUpdate(WorldPacket data, CreateObjectBits flags, Player target) { int PauseTimesCount = 0; @@ -550,15 +556,17 @@ namespace Game.Entities data.WriteUInt32(Int1); } - //if (flags.SmoothPhasing) - //{ - // data.WriteBit(ReplaceActive); - // data.WriteBit(StopAnimKits); - // data.WriteBit(HasReplaceObjectt); - // data.FlushBits(); - // if (HasReplaceObject) - // *data << ObjectGuid(ReplaceObject); - //} + if (flags.SmoothPhasing) + { + ReplaceObjectInfo replacedObjectInfo = GetReplacedObjectFor(target); + Cypher.Assert(replacedObjectInfo != null); + + data.WriteBit(true); // ReplaceActive + data.WriteBit(replacedObjectInfo.StopAnimKits); + data.WriteBit(true); + data.FlushBits(); + data.WritePackedGuid(replacedObjectInfo.ReplaceObject); + } if (flags.SceneObject) { @@ -1087,6 +1095,77 @@ namespace Game.Entities return false; } + void SetReplacedObject(ObjectGuid seer, ObjectGuid replacedObject, bool stopAnimKits = true) + { + ReplaceObjectInfo replaceObjectInfo = new(); + replaceObjectInfo.ReplaceObject = replacedObject; + replaceObjectInfo.StopAnimKits = stopAnimKits; + _replacedObjects[seer] = replaceObjectInfo; + } + + 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()); } + + 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) @@ -1101,6 +1180,9 @@ namespace Game.Entities if (!obj.CheckPrivateObjectOwnerVisibility(this)) return false; + if (!obj.CheckReplacedObjectVisibility(this)) + return false; + if (!Global.ConditionMgr.IsObjectMeetingVisibilityByObjectIdConditions((uint)obj.GetTypeId(), obj.GetEntry(), this)) return false; @@ -1457,7 +1539,7 @@ namespace Game.Entities Map map = GetMap(); if (map != null) { - TempSummon summon = map.SummonCreature(GetEntry(), pos, null, (uint)despawnTime.TotalMilliseconds, this, spellId, vehId, privateObjectOwner); + TempSummon summon = map.SummonCreature(GetEntry(), pos, null, (uint)despawnTime.TotalMilliseconds, this, spellId, vehId, privateObjectOwner, GetGUID()); if (summon != null) { summon.SetTempSummonType(despawnType); @@ -3608,6 +3690,9 @@ namespace Game.Entities ObjectGuid _privateObjectOwner; + Dictionary _replacedObjects = new(); + Dictionary _objectsWhichReplaceMeForSeer = new(); + public FlaggedArray m_stealth = new(2); public FlaggedArray m_stealthDetect = new(2); @@ -3840,4 +3925,10 @@ 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 e7da166f8..5a6b319f4 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -6062,6 +6062,28 @@ namespace Game.Entities if (p.GetPetGUID() == obj.GetGUID() && obj.ToCreature().IsPet()) ((Pet)obj).Remove(PetSaveMode.NotInSlot, true); } + + public void UpdateVisibilityOf(WorldObject[] targets) + { + if (targets.Empty()) + return; + + UpdateData udata = new(GetMapId()); + List visibleNow = new(); + + foreach (WorldObject target in targets) + UpdateVisibilityOf(target, udata, visibleNow); + + if (!udata.HasData()) + return; + + udata.BuildPacket(out UpdateObject packet); + SendPacket(packet); + + foreach (var target in visibleNow) + SendInitialVisiblePackets(target); + } + public void UpdateVisibilityOf(WorldObject target) { if (HaveAtClient(target)) @@ -6093,6 +6115,7 @@ namespace Game.Entities } } } + public void UpdateVisibilityOf(T target, UpdateData data, List visibleNow) where T : WorldObject { if (HaveAtClient(target)) @@ -6118,6 +6141,7 @@ namespace Game.Entities } } } + void UpdateVisibilityOf_helper(List s64, T target, List v) where T : WorldObject { switch (target.GetTypeId()) diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 9c3d493cb..91af3cbba 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) + 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) { var mask = UnitTypeMask.Summon; if (properties != null) @@ -4072,6 +4072,10 @@ namespace Game.Maps } } + WorldObject objectOwner = Global.ObjAccessor.GetWorldObject(summoner, privateObjectOwner); + if (objectOwner != null) + summoner = objectOwner; + Unit summonerUnit = summoner != null ? summoner.ToUnit() : null; TempSummon summon; @@ -4108,7 +4112,15 @@ namespace Game.Maps summon.InitStats(duration); summon.SetPrivateObjectOwner(privateObjectOwner); - AddToMap(summon.ToCreature()); + if (summoner != null && !replaceObject.IsEmpty()) + PhasingHandler.ReplaceObject(summoner, summon, replaceObject); + + if (!AddToMap(summon.ToCreature())) + { + summon.Dispose(); + return null; + } + summon.InitSummon(); // call MoveInLineOfSight for nearby creatures diff --git a/Source/Game/Phasing/PhasingHandler.cs b/Source/Game/Phasing/PhasingHandler.cs index 45691a907..c420b747d 100644 --- a/Source/Game/Phasing/PhasingHandler.cs +++ b/Source/Game/Phasing/PhasingHandler.cs @@ -663,6 +663,19 @@ 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) { if (changed && obj.IsInWorld)