From 0b2621b34816e65319179a625bcc4d2f3d97dcab Mon Sep 17 00:00:00 2001 From: hondacrx Date: Fri, 21 Oct 2022 17:34:52 -0400 Subject: [PATCH] Core/GameObjects: Implemented per player gameobject state and visibility for looted non-consumable chests Port From (https://github.com/TrinityCore/TrinityCore/commit/879c0cccfcb9224f9e15cbed926c57a4e010a070) --- Source/Game/Entities/Creature/Creature.cs | 4 +- Source/Game/Entities/GameObject/GameObject.cs | 111 +++++++++++++++++- .../Entities/Object/Update/UpdateFields.cs | 9 +- Source/Game/Entities/Object/WorldObject.cs | 4 +- Source/Game/Handlers/LootHandler.cs | 2 + .../Networking/Packets/GameObjectPackets.cs | 14 +++ 6 files changed, 135 insertions(+), 9 deletions(-) diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index f1ce8e90e..0b067fd3b 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -1731,9 +1731,9 @@ namespace Game.Entities return true; } - public override bool IsInvisibleDueToDespawn() + public override bool IsInvisibleDueToDespawn(WorldObject seer) { - if (base.IsInvisibleDueToDespawn()) + if (base.IsInvisibleDueToDespawn(seer)) return true; if (IsAlive() || m_corpseRemoveTime > GameTime.GetGameTime()) diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs index 03ddbda2f..029bf0b8b 100644 --- a/Source/Game/Entities/GameObject/GameObject.cs +++ b/Source/Game/Entities/GameObject/GameObject.cs @@ -426,6 +426,40 @@ namespace Game.Entities if (m_goTypeImpl != null) m_goTypeImpl.Update(diff); + if (m_perPlayerState != null) + { + foreach (var (guid, playerState) in m_perPlayerState.ToList()) + { + if (playerState.ValidUntil > GameTime.GetSystemTime()) + continue; + + Player seer = Global.ObjAccessor.GetPlayer(this, guid); + bool needsStateUpdate = playerState.State != GetGoState(); + bool despawned = playerState.Despawned; + + m_perPlayerState.Remove(guid); + + if (seer) + { + if (despawned) + { + seer.UpdateVisibilityOf(this); + } + else if (needsStateUpdate) + { + ObjectFieldData objMask = new(); + GameObjectFieldData goMask = new(); + goMask.MarkChanged(m_gameObjectData.State); + + UpdateData udata = new(GetMapId()); + BuildValuesUpdateForPlayerWithMask(udata, objMask.GetUpdateMask(), goMask.GetUpdateMask(), seer); + udata.BuildPacket(out UpdateObject packet); + seer.SendPacket(packet); + } + } + } + } + switch (m_lootState) { case LootState.NotReady: @@ -1311,15 +1345,22 @@ namespace Game.Entities return false; } - public override bool IsInvisibleDueToDespawn() + public override bool IsInvisibleDueToDespawn(WorldObject seer) { - if (base.IsInvisibleDueToDespawn()) + if (base.IsInvisibleDueToDespawn(seer)) return true; // Despawned if (!IsSpawned()) return true; + if (m_perPlayerState != null) + { + PerPlayerState state = m_perPlayerState.LookupByKey(seer.GetGUID()); + if (state != null && state.Despawned) + return true; + } + return false; } @@ -2797,7 +2838,30 @@ namespace Game.Entities return true; } - + + public void OnLootRelease(Player looter) + { + switch (GetGoType()) + { + case GameObjectTypes.Chest: + { + GameObjectTemplate goInfo = GetGoInfo(); + if (goInfo.Chest.consumable == 0 && goInfo.Chest.chestPersonalLoot != 0) + { + PerPlayerState perPlayerState = GetOrCreatePerPlayerStates()[looter.GetGUID()]; + perPlayerState.ValidUntil = GameTime.GetSystemTime() + (goInfo.Chest.chestRestockTime != 0 + ? TimeSpan.FromSeconds(goInfo.Chest.chestRestockTime) + : TimeSpan.FromSeconds(m_respawnDelayTime)); // not hiding this object permanently to prevent infinite growth of m_perPlayerState + // while also maintaining some sort of cheater protection (not getting rid of entries on logout) + perPlayerState.Despawned = true; + + looter.UpdateVisibilityOf(this); + } + break; + } + } + } + public void SetGoState(GameObjectState state) { GameObjectState oldState = GetGoState(); @@ -2822,6 +2886,30 @@ namespace Game.Entities } } + public GameObjectState GetGoStateFor(ObjectGuid viewer) + { + if (m_perPlayerState != null) + { + PerPlayerState state = m_perPlayerState.LookupByKey(viewer); + if (state != null && state.State.HasValue) + return state.State.Value; + } + + return GetGoState(); + } + + void SetGoStateFor(GameObjectState state, Player viewer) + { + PerPlayerState perPlayerState = GetOrCreatePerPlayerStates()[viewer.GetGUID()]; + perPlayerState.ValidUntil = GameTime.GetSystemTime() + TimeSpan.FromSeconds(m_respawnDelayTime); + perPlayerState.State = state; + + GameObjectSetStateLocal setStateLocal = new(); + setStateLocal.ObjectGUID = GetGUID(); + setStateLocal.State = (byte)state; + viewer.SendPacket(setStateLocal); + } + public void SetDisplayId(uint displayid) { SetUpdateFieldValue(m_values.ModifyValue(m_gameObjectData).ModifyValue(m_gameObjectData.DisplayID), displayid); @@ -3289,6 +3377,14 @@ namespace Game.Entities || m_goValue.CapturePoint.State == BattlegroundCapturePointState.HordeCaptured; } + Dictionary GetOrCreatePerPlayerStates() + { + if (m_perPlayerState == null) + m_perPlayerState = new(); + + return m_perPlayerState; + } + public override ushort GetAIAnimKitId() { return _animKitId; } public uint GetWorldEffectID() { return _worldEffectID; } @@ -3516,6 +3612,8 @@ namespace Game.Entities ushort _animKitId; uint _worldEffectID; + Dictionary m_perPlayerState; + GameObjectState m_prevGoState; // What state to set whenever resetting Dictionary ChairListSlots = new(); @@ -4024,4 +4122,11 @@ namespace Game.Entities } } } + + public class PerPlayerState + { + public DateTime ValidUntil = DateTime.MinValue; + public GameObjectState? State; + public bool Despawned; + } } \ No newline at end of file diff --git a/Source/Game/Entities/Object/Update/UpdateFields.cs b/Source/Game/Entities/Object/Update/UpdateFields.cs index 0f35857b5..c38efa3bd 100644 --- a/Source/Game/Entities/Object/Update/UpdateFields.cs +++ b/Source/Game/Entities/Object/Update/UpdateFields.cs @@ -4997,7 +4997,7 @@ namespace Game.Entities data.WriteFloat(rotation.Z); data.WriteFloat(rotation.W); data.WriteUInt32(FactionTemplate); - data.WriteInt8(State); + data.WriteInt8(GetViewerGameObjectState(this, owner, receiver)); data.WriteInt8(TypeID); data.WriteUInt8(PercentHealth); data.WriteUInt32(ArtKit); @@ -5104,7 +5104,7 @@ namespace Game.Entities } if (changesMask[14]) { - data.WriteInt8(State); + data.WriteInt8(GetViewerGameObjectState(this, owner, receiver)); } if (changesMask[15]) { @@ -5167,6 +5167,11 @@ namespace Game.Entities return flags; } + + sbyte GetViewerGameObjectState(GameObjectFieldData gameObjectData, GameObject gameObject, Player receiver) + { + return (sbyte)gameObject.GetGoStateFor(receiver.GetGUID()); + } } public class DynamicObjectData : BaseUpdateData diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index ec6afe4d2..ff1f4fe37 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -1218,7 +1218,7 @@ namespace Game.Entities return false; } - if (obj.IsInvisibleDueToDespawn()) + if (obj.IsInvisibleDueToDespawn(this)) return false; if (!CanDetect(obj, ignoreStealth, checkAlert)) @@ -3038,7 +3038,7 @@ namespace Game.Entities public virtual bool IsNeverVisibleFor(WorldObject seer) { return !IsInWorld || IsDestroyedObject(); } public virtual bool IsAlwaysVisibleFor(WorldObject seer) { return false; } - public virtual bool IsInvisibleDueToDespawn() { return false; } + public virtual bool IsInvisibleDueToDespawn(WorldObject seer) { return false; } public virtual bool IsAlwaysDetectableFor(WorldObject seer) { return false; } public virtual bool LoadFromDB(ulong spawnId, Map map, bool addToMap, bool allowDuplicate) { return true; } diff --git a/Source/Game/Handlers/LootHandler.cs b/Source/Game/Handlers/LootHandler.cs index 3e158f543..b4121e6fc 100644 --- a/Source/Game/Handlers/LootHandler.cs +++ b/Source/Game/Handlers/LootHandler.cs @@ -298,6 +298,8 @@ namespace Game } else if (go.IsFullyLooted()) go.SetLootState(LootState.JustDeactivated); + + go.OnLootRelease(player); } else { diff --git a/Source/Game/Networking/Packets/GameObjectPackets.cs b/Source/Game/Networking/Packets/GameObjectPackets.cs index 5929ad086..468f240f3 100644 --- a/Source/Game/Networking/Packets/GameObjectPackets.cs +++ b/Source/Game/Networking/Packets/GameObjectPackets.cs @@ -167,4 +167,18 @@ namespace Game.Networking.Packets public ObjectGuid ActivatorGUID; public uint SpellVisualID; } + + class GameObjectSetStateLocal : ServerPacket + { + public GameObjectSetStateLocal() : base(ServerOpcodes.GameObjectSetStateLocal, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ObjectGUID); + _worldPacket.WriteUInt8(State); + } + + public ObjectGuid ObjectGUID; + public byte State; + } }