From 52ba508b97d7883b256654f896c9664f1aa31421 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Mon, 15 Dec 2025 01:47:08 -0500 Subject: [PATCH 1/2] notifies client of eye/mob changes via network message for determining draw, context menu, verbs --- OpenDreamClient/ClientVerbSystem.cs | 10 +-- .../ContextMenu/ContextMenuPopup.xaml.cs | 4 +- .../Interface/DreamInterfaceManager.cs | 69 ++++++++++++++++--- .../Interface/DummyDreamInterfaceManager.cs | 2 + OpenDreamClient/Rendering/DreamViewOverlay.cs | 18 +++-- OpenDreamRuntime/DreamConnection.cs | 18 ++++- OpenDreamRuntime/DreamManager.Connections.cs | 1 + .../Network/Messages/MsgNotifyMobEyeUpdate.cs | 24 +++++++ 8 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs diff --git a/OpenDreamClient/ClientVerbSystem.cs b/OpenDreamClient/ClientVerbSystem.cs index f92212d5d5..cb3a052e0b 100644 --- a/OpenDreamClient/ClientVerbSystem.cs +++ b/OpenDreamClient/ClientVerbSystem.cs @@ -83,8 +83,10 @@ public IEnumerable GetAllVerbs() { /// The ID, src, and information of every executable verb public IEnumerable<(int Id, ClientObjectReference Src, VerbInfo VerbInfo)> GetExecutableVerbs(bool ignoreHiddenAttr = false) { sbyte? seeInvisibility = null; - if (_playerManager.LocalEntity != null) { - _sightQuery.TryGetComponent(_playerManager.LocalEntity.Value, out var mobSight); + + EntityUid mob = _interfaceManager.MobUid; + if (mob.IsValid()) { + _sightQuery.TryGetComponent(mob, out var mobSight); seeInvisibility = mobSight?.SeeInvisibility; } @@ -120,12 +122,12 @@ public IEnumerable GetAllVerbs() { // Check the verb's "set src" allows us to execute this switch (verb.Accessibility) { case VerbAccessibility.Usr: - if (entity != _playerManager.LocalEntity) + if (entity != mob) continue; break; case VerbAccessibility.InUsr: - if (_transformSystem.GetParentUid(entity) != _playerManager.LocalEntity) + if (_transformSystem.GetParentUid(entity) != mob) continue; break; diff --git a/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs b/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs index 3e90159083..1029540f11 100644 --- a/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs +++ b/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs @@ -1,3 +1,4 @@ +using OpenDreamClient.Interface; using OpenDreamClient.Interface.Controls.UI; using OpenDreamClient.Rendering; using OpenDreamShared.Dream; @@ -18,6 +19,7 @@ internal sealed partial class ContextMenuPopup : Popup { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IUserInterfaceManager _uiManager = default!; + [Dependency] private readonly IDreamInterfaceManager _dreamInterfaceManager = default!; private readonly ClientAppearanceSystem _appearanceSystem; private readonly ClientVerbSystem _verbSystem; private readonly DMISpriteSystem _spriteSystem; @@ -115,7 +117,7 @@ public void SetActiveItem(ContextMenuItem item) { private sbyte GetSeeInvisible() { if (_playerManager.LocalSession == null) return 127; - if (!_mobSightQuery.TryGetComponent(_playerManager.LocalSession.AttachedEntity, out DreamMobSightComponent? sight)) + if (!_mobSightQuery.TryGetComponent(_dreamInterfaceManager.MobUid, out DreamMobSightComponent? sight)) return 127; return sight.SeeInvisibility; diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index ac3ab646de..91ff6e6bde 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -1,28 +1,29 @@ -using System.IO; -using System.Text; -using System.Globalization; -using OpenDreamShared.Network.Messages; -using OpenDreamClient.Interface.Controls; -using OpenDreamShared.Interface.Descriptors; -using OpenDreamShared.Interface.DMF; +using OpenDreamClient.Interface.Controls; using OpenDreamClient.Interface.Prompts; using OpenDreamClient.Resources; using OpenDreamClient.Resources.ResourceTypes; using OpenDreamShared.Dream; +using OpenDreamShared.Interface.Descriptors; +using OpenDreamShared.Interface.DMF; +using OpenDreamShared.Network.Messages; using Robust.Client; using Robust.Client.Graphics; using Robust.Client.Input; +using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.ContentPack; +using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Random; using Robust.Shared.Serialization.Manager; using Robust.Shared.Timing; using Robust.Shared.Utility; using SixLabors.ImageSharp; +using System.Globalization; +using System.IO; using System.Linq; -using Robust.Shared.Map; +using System.Text; namespace OpenDreamClient.Interface; @@ -41,9 +42,12 @@ internal sealed class DreamInterfaceManager : IDreamInterfaceManager { [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IUserInterfaceManager _uiManager = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly ITimerManager _timerManager = default!; [Dependency] private readonly IUriOpener _uriOpener = default!; [Dependency] private readonly IGameController _gameController = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; private readonly ISawmill _sawmill = Logger.GetSawmill("opendream.interface"); @@ -60,6 +64,36 @@ internal sealed class DreamInterfaceManager : IDreamInterfaceManager { private Dictionary ClydeWindowIdToControl { get; } = new(); public CursorHolder Cursors { get; private set; } = default!; + // Current Entity of player's mob, or Invalid if could not be determined. + private EntityUid _mobUid = EntityUid.Invalid; + public EntityUid MobUid { + get { + if (_mobUid.IsValid()) { + return _mobUid; + } else { + return _playerManager.LocalSession?.AttachedEntity.GetValueOrDefault(EntityUid.Invalid) ?? EntityUid.Invalid; + } + } + private set { + _mobUid = value; + } + } + + // Current Entity of player's eye, or Invalid if could not be determined. + private EntityUid _eyeUid = EntityUid.Invalid; + public EntityUid EyeUid { + get { + if (_eyeUid.IsValid()) { + return _eyeUid; + } else { + return _playerManager.LocalSession?.AttachedEntity.GetValueOrDefault(EntityUid.Invalid) ?? EntityUid.Invalid; + } + } + private set { + _eyeUid = value; + } + } + public ViewRange View { get => _view; private set { @@ -131,6 +165,7 @@ public void Initialize() { _netManager.RegisterNetMessage(RxLoadInterface); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(RxUpdateClientInfo); + _netManager.RegisterNetMessage(RxNotifyMobEyeUpdate); _clyde.OnWindowFocused += OnWindowFocused; } @@ -339,6 +374,22 @@ private void RxUpdateClientInfo(MsgUpdateClientInfo msg) { } } + private void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate msg) { + var mob = _entityManager.GetEntity(msg.MobNetEntity); + if (mob.IsValid()) { + MobUid = mob; + } else { + MobUid = _playerManager.LocalSession?.AttachedEntity.GetValueOrDefault(EntityUid.Invalid) ?? EntityUid.Invalid; + } + + var eye = _entityManager.GetEntity(msg.MobNetEntity); + if (eye.IsValid()) { + EyeUid = eye; + } else { + EyeUid = _playerManager.LocalSession?.AttachedEntity.GetValueOrDefault(EntityUid.Invalid) ?? EntityUid.Invalid; + } + } + private void ShowPrompt(PromptWindow prompt) { prompt.Owner = _clyde.MainWindow; prompt.Show(); @@ -1081,6 +1132,8 @@ public interface IDreamInterfaceManager { public bool ShowPopupMenus { get; } public int IconSize { get; } public CursorHolder Cursors { get; } + public EntityUid MobUid { get; } + public EntityUid EyeUid { get; } void Initialize(); void FrameUpdate(FrameEventArgs frameEventArgs); diff --git a/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs b/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs index a849d06e8c..3448944103 100644 --- a/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs @@ -21,6 +21,8 @@ public sealed class DummyDreamInterfaceManager : IDreamInterfaceManager { public bool ShowPopupMenus => true; public int IconSize => 32; public CursorHolder Cursors => null!; + public EntityUid MobUid => EntityUid.Invalid; + public EntityUid EyeUid => EntityUid.Invalid; [Dependency] private readonly IClientNetManager _netManager = default!; diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index e7e48cfb11..279bf60dbe 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -120,15 +120,20 @@ public DreamViewOverlay(RenderTargetPool renderTargetPool) { protected override void Draw(in OverlayDrawArgs args) { using var _ = _prof.Group("Dream View Overlay"); - EntityUid? eye = _playerManager.LocalSession?.AttachedEntity; - if (eye == null) + EntityUid eye = _interfaceManager.EyeUid; + if (!eye.IsValid()) { return; + } + EntityUid mob = _interfaceManager.MobUid; + if (!mob.IsValid()) { + return; + } //Main drawing of sprites happens here try { var viewportSize = (Vector2i)(args.Viewport.Size / args.Viewport.RenderScale); - DrawAll(args, eye.Value, viewportSize); + DrawAll(args, mob, eye, viewportSize); } catch (Exception e) { _sawmill.Error($"Error occurred while rendering frame. Error details:\n{e.Message}\n{e.StackTrace}"); } @@ -145,7 +150,7 @@ protected override void Draw(in OverlayDrawArgs args) { _rendererMetaDataRental.Push(_rendererMetaDataToReturn.Pop()); } - private void DrawAll(OverlayDrawArgs args, EntityUid eye, Vector2i viewportSize) { + private void DrawAll(OverlayDrawArgs args, EntityUid mob, EntityUid eye, Vector2i viewportSize) { if (!_xformQuery.TryGetComponent(eye, out var eyeTransform)) return; @@ -153,9 +158,10 @@ private void DrawAll(OverlayDrawArgs args, EntityUid eye, Vector2i viewportSize) if (!_mapManager.TryFindGridAt(eyeCoords, out var gridUid, out var grid)) return; - _mobSightQuery.TryGetComponent(eye, out var mobSight); + _mobSightQuery.TryGetComponent(mob, out var mobSight); + _mobSightQuery.TryGetComponent(eye, out var eyeSight); var seeVis = mobSight?.SeeInvisibility ?? 127; - var sight = mobSight?.Sight ?? 0; + var sight = eyeSight?.Sight ?? 0; var worldHandle = args.WorldHandle; var worldAABB = args.WorldAABB; diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 8f08d03560..8f0f3d86ce 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -1,5 +1,3 @@ -using System.Threading.Tasks; -using System.Web; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Procs.Native; @@ -10,6 +8,8 @@ using Robust.Shared.Enums; using Robust.Shared.Player; using SpaceWizards.Sodium; +using System.Threading.Tasks; +using System.Web; namespace OpenDreamRuntime; @@ -18,6 +18,7 @@ public sealed class DreamConnection { [Dependency] private readonly DreamObjectTree _objectTree = default!; [Dependency] private readonly DreamResourceManager _resourceManager = default!; [Dependency] private readonly WalkManager _walkManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly ISharedPlayerManager _playerManager = default!; @@ -51,6 +52,8 @@ [ViewVariables] public DreamObjectMob? Mob { Eye = value; } + UpdateMobEye(); + if (_mob != null) { // If the mob is already owned by another player, kick them out if (_mob.Connection != null) @@ -69,6 +72,8 @@ [ViewVariables] public DreamObjectMovable? Eye { set { _eye = value; _playerManager.SetAttachedEntity(Session!, _eye?.Entity); + + UpdateMobEye(); } } @@ -504,4 +509,13 @@ public bool TryConvertPromptResponse(DreamValueType type, object? value, out Dre converted = default; return false; } + private void UpdateMobEye() { + var mobUid = Mob?.Entity.Id ?? EntityUid.Invalid.Id; + var eyeUid = Eye?.Entity.Id ?? EntityUid.Invalid.Id; + var msg = new MsgNotifyMobEyeUpdate() { + MobNetEntity = _entityManager.GetNetEntity(new(mobUid)), + EyeNetEntity = _entityManager.GetNetEntity(new(eyeUid)) + }; + Session?.Channel.SendMessage(msg); + } } diff --git a/OpenDreamRuntime/DreamManager.Connections.cs b/OpenDreamRuntime/DreamManager.Connections.cs index 59583538ae..3958143f3d 100644 --- a/OpenDreamRuntime/DreamManager.Connections.cs +++ b/OpenDreamRuntime/DreamManager.Connections.cs @@ -74,6 +74,7 @@ private void InitializeConnectionManager() { _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(); + _netManager.RegisterNetMessage(); var topicPort = _config.GetCVar(OpenDreamCVars.TopicPort); var worldTopicAddress = new IPEndPoint(IPAddress.Loopback, topicPort); diff --git a/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs b/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs new file mode 100644 index 0000000000..2a93202cdc --- /dev/null +++ b/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs @@ -0,0 +1,24 @@ +using Lidgren.Network; +using Robust.Shared.GameObjects; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace OpenDreamShared.Network.Messages; + +//Server -> Client: Tell the client about the current entity UIDs of its mob and eye +public sealed class MsgNotifyMobEyeUpdate : NetMessage { + public override MsgGroups MsgGroup => MsgGroups.EntityEvent; + + public NetEntity MobNetEntity; + public NetEntity EyeNetEntity; + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { + MobNetEntity = new(buffer.ReadInt32()); + EyeNetEntity = new(buffer.ReadInt32()); + } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { + buffer.Write(MobNetEntity.Id); + buffer.Write(EyeNetEntity.Id); + } +} From ed29795032f70d2416151f2f6f46f4ad55e13f03 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Mon, 15 Dec 2025 09:14:10 -0500 Subject: [PATCH 2/2] feedback --- OpenDreamClient/Interface/DreamInterfaceManager.cs | 12 +++++------- OpenDreamClient/Rendering/DreamViewOverlay.cs | 1 + OpenDreamRuntime/DreamConnection.cs | 1 + 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index 91ff6e6bde..c6cdd73952 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -66,6 +66,7 @@ internal sealed class DreamInterfaceManager : IDreamInterfaceManager { // Current Entity of player's mob, or Invalid if could not be determined. private EntityUid _mobUid = EntityUid.Invalid; + public EntityUid MobUid { get { if (_mobUid.IsValid()) { @@ -74,13 +75,12 @@ public EntityUid MobUid { return _playerManager.LocalSession?.AttachedEntity.GetValueOrDefault(EntityUid.Invalid) ?? EntityUid.Invalid; } } - private set { - _mobUid = value; - } + private set => _mobUid = value; } // Current Entity of player's eye, or Invalid if could not be determined. private EntityUid _eyeUid = EntityUid.Invalid; + public EntityUid EyeUid { get { if (_eyeUid.IsValid()) { @@ -89,9 +89,7 @@ public EntityUid EyeUid { return _playerManager.LocalSession?.AttachedEntity.GetValueOrDefault(EntityUid.Invalid) ?? EntityUid.Invalid; } } - private set { - _eyeUid = value; - } + private set => _eyeUid = value; } public ViewRange View { @@ -382,7 +380,7 @@ private void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate msg) { MobUid = _playerManager.LocalSession?.AttachedEntity.GetValueOrDefault(EntityUid.Invalid) ?? EntityUid.Invalid; } - var eye = _entityManager.GetEntity(msg.MobNetEntity); + var eye = _entityManager.GetEntity(msg.EyeNetEntity); if (eye.IsValid()) { EyeUid = eye; } else { diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 279bf60dbe..6226753ca6 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -124,6 +124,7 @@ protected override void Draw(in OverlayDrawArgs args) { if (!eye.IsValid()) { return; } + EntityUid mob = _interfaceManager.MobUid; if (!mob.IsValid()) { return; diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 8f0f3d86ce..341cccfc9b 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -509,6 +509,7 @@ public bool TryConvertPromptResponse(DreamValueType type, object? value, out Dre converted = default; return false; } + private void UpdateMobEye() { var mobUid = Mob?.Entity.Id ?? EntityUid.Invalid.Id; var eyeUid = Eye?.Entity.Id ?? EntityUid.Invalid.Id;