From 52ba508b97d7883b256654f896c9664f1aa31421 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Mon, 15 Dec 2025 01:47:08 -0500 Subject: [PATCH 01/16] 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 02/16] 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; From 4a4b7fc7c85fb2cb563f5deb5bf721edf34844b4 Mon Sep 17 00:00:00 2001 From: Richard Van Tassel Date: Tue, 13 Jan 2026 22:37:53 -0500 Subject: [PATCH 03/16] Update OpenDreamClient/Interface/DreamInterfaceManager.cs Co-authored-by: wixoa --- OpenDreamClient/Interface/DreamInterfaceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index c6cdd73952..df035c4053 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -384,7 +384,7 @@ private void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate msg) { if (eye.IsValid()) { EyeUid = eye; } else { - EyeUid = _playerManager.LocalSession?.AttachedEntity.GetValueOrDefault(EntityUid.Invalid) ?? EntityUid.Invalid; + EyeUid = MobUid; } } From 434d423d69e0d66aff34bc1fc7cdda30fc624e27 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Wed, 14 Jan 2026 21:49:54 -0500 Subject: [PATCH 04/16] first attempt at supporting turf eye --- OpenDreamClient/ClientVerbSystem.cs | 3 +- OpenDreamClient/DreamClientSystem.cs | 71 +++++++++++++++++++ .../ContextMenu/ContextMenuPopup.xaml.cs | 5 +- .../Interface/DreamInterfaceManager.cs | 52 ++------------ OpenDreamClient/Rendering/DreamViewOverlay.cs | 65 +++++++++++------ OpenDreamRuntime/DreamConnection.cs | 50 ++++++++++--- .../Objects/Types/DreamObjectClient.cs | 29 ++++++-- .../Network/Messages/MsgNotifyMobEyeUpdate.cs | 36 +++++++++- 8 files changed, 218 insertions(+), 93 deletions(-) diff --git a/OpenDreamClient/ClientVerbSystem.cs b/OpenDreamClient/ClientVerbSystem.cs index cb3a052e0b..2ae6dee122 100644 --- a/OpenDreamClient/ClientVerbSystem.cs +++ b/OpenDreamClient/ClientVerbSystem.cs @@ -18,6 +18,7 @@ public sealed class ClientVerbSystem : VerbSystem { [Dependency] private readonly ITaskManager _taskManager = default!; [Dependency] private readonly ITimerManager _timerManager = default!; [Dependency] private readonly IOverlayManager _overlayManager = default!; + [Dependency] private readonly DreamClientSystem _dreamClientSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; private EntityQuery _spriteQuery; @@ -84,7 +85,7 @@ public IEnumerable GetAllVerbs() { public IEnumerable<(int Id, ClientObjectReference Src, VerbInfo VerbInfo)> GetExecutableVerbs(bool ignoreHiddenAttr = false) { sbyte? seeInvisibility = null; - EntityUid mob = _interfaceManager.MobUid; + EntityUid mob = _dreamClientSystem.MobUid; if (mob.IsValid()) { _sightQuery.TryGetComponent(mob, out var mobSight); diff --git a/OpenDreamClient/DreamClientSystem.cs b/OpenDreamClient/DreamClientSystem.cs index a2d1c288ae..35d8efd91f 100644 --- a/OpenDreamClient/DreamClientSystem.cs +++ b/OpenDreamClient/DreamClientSystem.cs @@ -1,10 +1,52 @@ using OpenDreamClient.Interface; +using OpenDreamShared.Dream; +using OpenDreamShared.Network.Messages; +using Robust.Client.Player; +using Robust.Shared.Network; using Robust.Shared.Player; +using YamlDotNet.Core.Tokens; namespace OpenDreamClient; internal sealed class DreamClientSystem : EntitySystem { + [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IDreamInterfaceManager _interfaceManager = default!; + [Dependency] private readonly IClientNetManager _netManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + + + // Current NetEntityof player's mob, or Invalid if could not be determined. + private NetEntity _mobNet = NetEntity.Invalid; + + // Current Entity of player's mob, or Invalid if could not be determined. + private EntityUid _mobUid = EntityUid.Invalid; + + // Sometimes we get mob info before we know all the net entities, so store the net entity and refer to it + public EntityUid MobUid { + get { + // if entity of mob is invalid but net entity isn't, try referring to known net entities + if (! _mobUid.IsValid() && _mobNet.IsValid() && _entityManager.TryGetEntity(_mobNet, out var ent)) { + _mobUid = ent.GetValueOrDefault(EntityUid.Invalid); + } + + return _mobUid; + } + } + + // Current Entity of player's eye, or Invalid if could not be determined. + private ClientObjectReference _eyeRef = new(NetEntity.Invalid); + + public ClientObjectReference EyeRef { + get { + if (_eyeRef.Type == ClientObjectReference.RefType.Entity && !_eyeRef.Entity.IsValid()) { + return new(_entityManager.GetNetEntity(MobUid)); + } else { + return _eyeRef; + } + } + private set => _eyeRef = value; + } + public override void Initialize() { SubscribeLocalEvent(OnPlayerAttached); @@ -15,4 +57,33 @@ private void OnPlayerAttached(LocalPlayerAttachedEvent e) { // So we have to set it again _interfaceManager.DefaultWindow?.Macro.SetActive(); } + + public void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate msg) { + var prevMobNet = _mobNet; + _mobNet = msg.MobNetEntity; + + if (prevMobNet != _mobNet) { + // mark mob cache as dirty/invalid + _mobUid = EntityUid.Invalid; + } + + + var incomingEyeRef = msg.EyeRef; + + switch (incomingEyeRef.Type) { + default: + EyeRef = new(msg.MobNetEntity); + break; + case ClientObjectReference.RefType.Entity: + if (incomingEyeRef.Entity.IsValid()) { + EyeRef = incomingEyeRef; + } else { + EyeRef = new(msg.MobNetEntity); + } + break; + case ClientObjectReference.RefType.Turf: + EyeRef = incomingEyeRef; + break; + } + } } diff --git a/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs b/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs index 1029540f11..61e776eb40 100644 --- a/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs +++ b/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs @@ -19,8 +19,8 @@ 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 DreamClientSystem _dreamClientSystem; private readonly ClientVerbSystem _verbSystem; private readonly DMISpriteSystem _spriteSystem; private readonly EntityLookupSystem _lookupSystem; @@ -39,6 +39,7 @@ public ContextMenuPopup() { _verbSystem = _entitySystemManager.GetEntitySystem(); _appearanceSystem = _entitySystemManager.GetEntitySystem(); + _dreamClientSystem = _entitySystemManager.GetEntitySystem(); _spriteSystem = _entitySystemManager.GetEntitySystem(); _lookupSystem = _entitySystemManager.GetEntitySystem(); _mouseInputSystem = _entitySystemManager.GetEntitySystem(); @@ -117,7 +118,7 @@ public void SetActiveItem(ContextMenuItem item) { private sbyte GetSeeInvisible() { if (_playerManager.LocalSession == null) return 127; - if (!_mobSightQuery.TryGetComponent(_dreamInterfaceManager.MobUid, out DreamMobSightComponent? sight)) + if (!_mobSightQuery.TryGetComponent(_dreamClientSystem.MobUid, out DreamMobSightComponent? sight)) return 127; return sight.SeeInvisibility; diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index 44856b23d4..c4b650c949 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -42,12 +42,10 @@ 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"); @@ -64,34 +62,6 @@ 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 { @@ -167,6 +137,10 @@ public void Initialize() { _clyde.OnWindowFocused += OnWindowFocused; } + private void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate message) { + _entitySystemManager.GetEntitySystem().RxNotifyMobEyeUpdate(message); + } + private void RxUpdateStatPanels(MsgUpdateStatPanels message) { DefaultInfo?.UpdateStatPanels(message); } @@ -372,22 +346,6 @@ 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.EyeNetEntity); - if (eye.IsValid()) { - EyeUid = eye; - } else { - EyeUid = MobUid; - } - } - private void ShowPrompt(PromptWindow prompt) { prompt.Owner = _clyde.MainWindow; prompt.Show(); @@ -1130,8 +1088,6 @@ 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/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index c132218148..44d61728d1 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -1,20 +1,21 @@ -using System.Linq; -using OpenDreamClient.Interface; -using Robust.Client.Graphics; -using Robust.Client.Player; -using Robust.Shared.Map; +using OpenDreamClient.Interface; +using OpenDreamClient.Rendering.Particles; using OpenDreamShared.Dream; -using Robust.Shared.Console; -using Robust.Shared.Prototypes; using OpenDreamShared.Rendering; -using OpenDreamClient.Rendering.Particles; using Robust.Client.GameObjects; -using Robust.Shared.Map.Components; -using Robust.Shared.Profiling; -using Matrix3x2 = System.Numerics.Matrix3x2; +using Robust.Client.Graphics; +using Robust.Client.Player; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface.RichText; +using Robust.Shared.Console; using Robust.Shared.Enums; +using Robust.Shared.Graphics; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Profiling; +using Robust.Shared.Prototypes; +using System.Linq; +using Matrix3x2 = System.Numerics.Matrix3x2; namespace OpenDreamClient.Rendering; @@ -60,6 +61,7 @@ internal sealed partial class DreamViewOverlay : Overlay { private readonly ClientScreenOverlaySystem _screenOverlaySystem; private readonly ClientImagesSystem _imagesSystem; private readonly DMISpriteSystem _spriteSystem; + private readonly DreamClientSystem _dreamClientSystem; private readonly EntityQuery _spriteQuery; private readonly EntityQuery _xformQuery; @@ -90,8 +92,9 @@ public DreamViewOverlay(RenderTargetPool renderTargetPool) { _screenOverlaySystem = _entitySystemManager.GetEntitySystem(); _imagesSystem = _entitySystemManager.GetEntitySystem(); _spriteSystem = _entitySystemManager.GetEntitySystem(); + _dreamClientSystem = _entitySystemManager.GetEntitySystem(); - _spriteQuery = _entityManager.GetEntityQuery(); + _spriteQuery = _entityManager.GetEntityQuery(); _xformQuery = _entityManager.GetEntityQuery(); _mobSightQuery = _entityManager.GetEntityQuery(); @@ -120,12 +123,12 @@ public DreamViewOverlay(RenderTargetPool renderTargetPool) { protected override void Draw(in OverlayDrawArgs args) { using var _ = _prof.Group("Dream View Overlay"); - EntityUid eye = _interfaceManager.EyeUid; - if (!eye.IsValid()) { + var eyeRef = _dreamClientSystem.EyeRef; + if (eyeRef.Type == ClientObjectReference.RefType.Entity && !eyeRef.Entity.IsValid()) { return; } - EntityUid mob = _interfaceManager.MobUid; + EntityUid mob = _dreamClientSystem.MobUid; if (!mob.IsValid()) { return; } @@ -134,7 +137,7 @@ protected override void Draw(in OverlayDrawArgs args) { try { var viewportSize = (Vector2i)(args.Viewport.Size / args.Viewport.RenderScale); - DrawAll(args, mob, eye, viewportSize); + DrawAll(args, mob, eyeRef, viewportSize); } catch (Exception e) { _sawmill.Error($"Error occurred while rendering frame. Error details:\n{e.Message}\n{e.StackTrace}"); } @@ -151,21 +154,39 @@ protected override void Draw(in OverlayDrawArgs args) { _rendererMetaDataRental.Push(_rendererMetaDataToReturn.Pop()); } - private void DrawAll(OverlayDrawArgs args, EntityUid mob, EntityUid eye, Vector2i viewportSize) { - if (!_xformQuery.TryGetComponent(eye, out var eyeTransform)) - return; + private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference eyeRef, Vector2i viewportSize) { + MapCoordinates eyeCoords; + DreamMobSightComponent? eyeSight; + Box2 worldAABB; + + switch (eyeRef.Type) { + default: + return; + case ClientObjectReference.RefType.Turf: + eyeCoords = new MapCoordinates(new(eyeRef.TurfX, eyeRef.TurfY), new(eyeRef.TurfZ)); + _mobSightQuery.TryGetComponent(mob, out eyeSight); + worldAABB = args.WorldAABB; + worldAABB = worldAABB.Translated(eyeCoords.Position - worldAABB.Center); + break; + case ClientObjectReference.RefType.Entity: + var eyeUid = _entityManager.GetEntity(new(eyeRef.Entity.Id)); + if (!_xformQuery.TryGetComponent(eyeUid, out var eyeTransform)) + return; + eyeCoords = _transformSystem.GetMapCoordinates(eyeUid, eyeTransform); + + _mobSightQuery.TryGetComponent(eyeUid, out eyeSight); + worldAABB = args.WorldAABB; + break; + } - var eyeCoords = _transformSystem.GetMapCoordinates(eye, eyeTransform); if (!_mapManager.TryFindGridAt(eyeCoords, out var gridUid, out var grid)) return; _mobSightQuery.TryGetComponent(mob, out var mobSight); - _mobSightQuery.TryGetComponent(eye, out var eyeSight); var seeVis = mobSight?.SeeInvisibility ?? 127; var sight = eyeSight?.Sight ?? 0; var worldHandle = args.WorldHandle; - var worldAABB = args.WorldAABB; using (_prof.Group("lookup")) { //TODO use a sprite tree. diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 1b057d1ec8..f7a6aae74e 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -1,15 +1,18 @@ using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; +using OpenDreamRuntime.Procs.DebugAdapter.Protocol; using OpenDreamRuntime.Procs.Native; using OpenDreamRuntime.Rendering; using OpenDreamRuntime.Resources; using OpenDreamShared.Dream; using OpenDreamShared.Network.Messages; using Robust.Shared.Enums; +using Robust.Shared.Graphics; using Robust.Shared.Player; using SpaceWizards.Sodium; using System.Threading.Tasks; using System.Web; +using YamlDotNet.Core.Tokens; namespace OpenDreamRuntime; @@ -48,8 +51,18 @@ [ViewVariables] public DreamObjectMob? Mob { } StatObj = new(value); - if (Eye != null && Eye == oldMob) { - Eye = value; + + // If eye is equivalent to old mob, and the new mob is different from old mob, set eye to new mob. + if (oldMob != null) { + var oldMobNetEntity = _entityManager.GetNetEntity(oldMob.Entity); + if (Eye != null && Eye.Value.Entity == oldMobNetEntity) { + if (_mob == null) { + Eye = null; + } else { + var newMobNetEntity = _entityManager.GetNetEntity(_mob.Entity); + Eye = new(newMobNetEntity); + } + } } UpdateMobEye(); @@ -67,12 +80,17 @@ [ViewVariables] public DreamObjectMob? Mob { } } - [ViewVariables] public DreamObjectMovable? Eye { + [ViewVariables] public ClientObjectReference? Eye { get => _eye; set { _eye = value; - _playerManager.SetAttachedEntity(Session!, _eye?.Entity); - + if (_eye?.Type == ClientObjectReference.RefType.Entity) { + var ent = _entityManager.GetEntity(_eye?.Entity); + _playerManager.SetAttachedEntity(Session!, ent); + } else { + _playerManager.SetAttachedEntity(Session!, EntityUid.Invalid); + } + UpdateMobEye(); } } @@ -86,7 +104,7 @@ [ViewVariables] public DreamObjectMovable? Eye { [ViewVariables] private int _nextPromptEvent = 1; private readonly Dictionary _permittedBrowseRscFiles = new(); private DreamObjectMob? _mob; - private DreamObjectMovable? _eye; + private ClientObjectReference? _eye; private readonly ISawmill _sawmill = Logger.GetSawmill("opendream.connection"); @@ -554,11 +572,23 @@ public bool TryConvertPromptResponse(DreamValueType type, object? value, out Dre } private void UpdateMobEye() { - var mobUid = Mob?.Entity.Id ?? EntityUid.Invalid.Id; - var eyeUid = Eye?.Entity.Id ?? EntityUid.Invalid.Id; + var mobUid = Mob?.Entity ?? EntityUid.Invalid; + ClientObjectReference eyeRef; + switch (Eye?.Type) { + default: + eyeRef = new(_entityManager.GetNetEntity(mobUid)); + break; + case ClientObjectReference.RefType.Entity when Eye.HasValue: + eyeRef = new(_entityManager.GetNetEntity(new(Eye.Value.Entity.Id))); + break; + case ClientObjectReference.RefType.Turf: + eyeRef = new(new(Eye.Value.TurfX, Eye.Value.TurfY), Eye.Value.TurfZ); + break; + } + var msg = new MsgNotifyMobEyeUpdate() { - MobNetEntity = _entityManager.GetNetEntity(new(mobUid)), - EyeNetEntity = _entityManager.GetNetEntity(new(eyeUid)) + MobNetEntity = _entityManager.GetNetEntity(mobUid), + EyeRef = eyeRef }; Session?.Channel.SendMessage(msg); } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs index 52fd7cdb11..a828770952 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs @@ -1,9 +1,11 @@ -using System.Security.Cryptography; -using System.Text; +using OpenDreamRuntime.Procs.DebugAdapter.Protocol; using OpenDreamRuntime.Procs.Native; using OpenDreamRuntime.Rendering; using OpenDreamRuntime.Resources; using OpenDreamShared.Dream; +using Robust.Shared.Graphics; +using System.Security.Cryptography; +using System.Text; namespace OpenDreamRuntime.Objects.Types; @@ -55,7 +57,12 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = Connection.StatObj; return true; case "eye": - value = new(Connection.Eye); + if (Connection.Eye == null) { + value = DreamValue.Null; + return true; + } + + value = new(DreamManager.GetFromClientReference(Connection, Connection.Eye.Value)); return true; case "view": // Number if square & centerable, string representation otherwise @@ -124,11 +131,19 @@ protected override void SetVar(string varName, DreamValue value) { break; case "eye": { value.TryGetValueAsDreamObject(out var newEye); - if (newEye is not (DreamObjectMovable or null)) { - throw new Exception($"Cannot set eye to non-movable {value}"); // TODO: You can set it to a turf + switch (newEye) { + case DreamObjectMovable movable: + Connection.Eye = new(EntityManager.GetNetEntity(movable.Entity)); + break; + case DreamObjectTurf turf: + Connection.Eye = new(new(turf.X, turf.Y), turf.Z); + break; + case null: + Connection.Eye = null; + break; + default: + throw new Exception($"Cannot set eye to non-movable, non-turf {value}"); } - - Connection.Eye = newEye as DreamObjectMovable; break; } case "view": { diff --git a/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs b/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs index 2a93202cdc..7e9300874f 100644 --- a/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs +++ b/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs @@ -1,7 +1,9 @@ using Lidgren.Network; +using OpenDreamShared.Dream; using Robust.Shared.GameObjects; using Robust.Shared.Network; using Robust.Shared.Serialization; +using static OpenDreamShared.Dream.ClientObjectReference; namespace OpenDreamShared.Network.Messages; @@ -10,15 +12,43 @@ public sealed class MsgNotifyMobEyeUpdate : NetMessage { public override MsgGroups MsgGroup => MsgGroups.EntityEvent; public NetEntity MobNetEntity; - public NetEntity EyeNetEntity; + public ClientObjectReference EyeRef; public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { MobNetEntity = new(buffer.ReadInt32()); - EyeNetEntity = new(buffer.ReadInt32()); + + RefType eyeType = (RefType)buffer.ReadInt32(); + switch (eyeType) { + case RefType.Entity: + EyeRef = new(new NetEntity(buffer.ReadInt32())); + break; + case RefType.Turf: + int x = buffer.ReadInt32(); + int y = buffer.ReadInt32(); + int z = buffer.ReadInt32(); + EyeRef = new(new(x, y), z); + break; + default: + EyeRef = new(); + break; + } } public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { buffer.Write(MobNetEntity.Id); - buffer.Write(EyeNetEntity.Id); + + buffer.Write((int)EyeRef.Type); + switch (EyeRef.Type) { + case RefType.Entity: + buffer.Write(EyeRef.Entity.Id); + break; + case RefType.Turf: + buffer.Write(EyeRef.TurfX); + buffer.Write(EyeRef.TurfY); + buffer.Write(EyeRef.TurfZ); + break; + default: + break; + } } } From 63d7ff7a7df4fc7f66dceb223f5e84a88b9b9171 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Wed, 14 Jan 2026 22:02:24 -0500 Subject: [PATCH 05/16] some cleanup --- OpenDreamClient/DreamClientSystem.cs | 8 -------- .../Input/ContextMenu/ContextMenuPopup.xaml.cs | 1 - OpenDreamClient/Interface/DreamInterfaceManager.cs | 2 -- OpenDreamClient/Interface/DummyDreamInterfaceManager.cs | 2 -- OpenDreamClient/Rendering/DreamViewOverlay.cs | 3 +-- OpenDreamRuntime/DreamConnection.cs | 3 --- OpenDreamRuntime/Objects/Types/DreamObjectClient.cs | 4 +--- 7 files changed, 2 insertions(+), 21 deletions(-) diff --git a/OpenDreamClient/DreamClientSystem.cs b/OpenDreamClient/DreamClientSystem.cs index 35d8efd91f..0473388f90 100644 --- a/OpenDreamClient/DreamClientSystem.cs +++ b/OpenDreamClient/DreamClientSystem.cs @@ -1,19 +1,13 @@ using OpenDreamClient.Interface; using OpenDreamShared.Dream; using OpenDreamShared.Network.Messages; -using Robust.Client.Player; -using Robust.Shared.Network; using Robust.Shared.Player; -using YamlDotNet.Core.Tokens; namespace OpenDreamClient; internal sealed class DreamClientSystem : EntitySystem { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IDreamInterfaceManager _interfaceManager = default!; - [Dependency] private readonly IClientNetManager _netManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - // Current NetEntityof player's mob, or Invalid if could not be determined. private NetEntity _mobNet = NetEntity.Invalid; @@ -47,7 +41,6 @@ public ClientObjectReference EyeRef { private set => _eyeRef = value; } - public override void Initialize() { SubscribeLocalEvent(OnPlayerAttached); } @@ -66,7 +59,6 @@ public void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate msg) { // mark mob cache as dirty/invalid _mobUid = EntityUid.Invalid; } - var incomingEyeRef = msg.EyeRef; diff --git a/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs b/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs index 61e776eb40..19a90d5fc9 100644 --- a/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs +++ b/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs @@ -1,4 +1,3 @@ -using OpenDreamClient.Interface; using OpenDreamClient.Interface.Controls.UI; using OpenDreamClient.Rendering; using OpenDreamShared.Dream; diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index c4b650c949..9889946ac5 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -9,7 +9,6 @@ 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; @@ -42,7 +41,6 @@ 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 ITimerManager _timerManager = default!; [Dependency] private readonly IUriOpener _uriOpener = default!; [Dependency] private readonly IGameController _gameController = default!; diff --git a/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs b/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs index 3448944103..a849d06e8c 100644 --- a/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs @@ -21,8 +21,6 @@ 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 44d61728d1..824200780d 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -9,7 +9,6 @@ using Robust.Client.UserInterface.RichText; using Robust.Shared.Console; using Robust.Shared.Enums; -using Robust.Shared.Graphics; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Profiling; @@ -94,7 +93,7 @@ public DreamViewOverlay(RenderTargetPool renderTargetPool) { _spriteSystem = _entitySystemManager.GetEntitySystem(); _dreamClientSystem = _entitySystemManager.GetEntitySystem(); - _spriteQuery = _entityManager.GetEntityQuery(); + _spriteQuery = _entityManager.GetEntityQuery(); _xformQuery = _entityManager.GetEntityQuery(); _mobSightQuery = _entityManager.GetEntityQuery(); diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index f7a6aae74e..97ca9cd085 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -1,18 +1,15 @@ using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; -using OpenDreamRuntime.Procs.DebugAdapter.Protocol; using OpenDreamRuntime.Procs.Native; using OpenDreamRuntime.Rendering; using OpenDreamRuntime.Resources; using OpenDreamShared.Dream; using OpenDreamShared.Network.Messages; using Robust.Shared.Enums; -using Robust.Shared.Graphics; using Robust.Shared.Player; using SpaceWizards.Sodium; using System.Threading.Tasks; using System.Web; -using YamlDotNet.Core.Tokens; namespace OpenDreamRuntime; diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs index a828770952..ec6755f7cb 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs @@ -1,9 +1,7 @@ -using OpenDreamRuntime.Procs.DebugAdapter.Protocol; -using OpenDreamRuntime.Procs.Native; +using OpenDreamRuntime.Procs.Native; using OpenDreamRuntime.Rendering; using OpenDreamRuntime.Resources; using OpenDreamShared.Dream; -using Robust.Shared.Graphics; using System.Security.Cryptography; using System.Text; From b5b02bbe26ba1d79b4e398be27479a5fd310e04e Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Wed, 14 Jan 2026 22:11:46 -0500 Subject: [PATCH 06/16] lint --- OpenDreamClient/DreamClientSystem.cs | 1 + OpenDreamRuntime/DreamConnection.cs | 9 ++++----- .../Network/Messages/MsgNotifyMobEyeUpdate.cs | 2 -- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/OpenDreamClient/DreamClientSystem.cs b/OpenDreamClient/DreamClientSystem.cs index 0473388f90..eb258fc7a0 100644 --- a/OpenDreamClient/DreamClientSystem.cs +++ b/OpenDreamClient/DreamClientSystem.cs @@ -72,6 +72,7 @@ public void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate msg) { } else { EyeRef = new(msg.MobNetEntity); } + break; case ClientObjectReference.RefType.Turf: EyeRef = incomingEyeRef; diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 97ca9cd085..813811b0a9 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -78,11 +78,11 @@ [ViewVariables] public DreamObjectMob? Mob { } [ViewVariables] public ClientObjectReference? Eye { - get => _eye; + get => field; set { - _eye = value; - if (_eye?.Type == ClientObjectReference.RefType.Entity) { - var ent = _entityManager.GetEntity(_eye?.Entity); + field = value; + if (field?.Type == ClientObjectReference.RefType.Entity) { + var ent = _entityManager.GetEntity(field?.Entity); _playerManager.SetAttachedEntity(Session!, ent); } else { _playerManager.SetAttachedEntity(Session!, EntityUid.Invalid); @@ -101,7 +101,6 @@ [ViewVariables] public ClientObjectReference? Eye { [ViewVariables] private int _nextPromptEvent = 1; private readonly Dictionary _permittedBrowseRscFiles = new(); private DreamObjectMob? _mob; - private ClientObjectReference? _eye; private readonly ISawmill _sawmill = Logger.GetSawmill("opendream.connection"); diff --git a/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs b/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs index 7e9300874f..12c34bbd2f 100644 --- a/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs +++ b/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs @@ -47,8 +47,6 @@ public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer buffer.Write(EyeRef.TurfY); buffer.Write(EyeRef.TurfZ); break; - default: - break; } } } From af6dc0ab6fbc2448dfd108bb183ab6c987f282f7 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Wed, 14 Jan 2026 22:33:45 -0500 Subject: [PATCH 07/16] lint --- OpenDreamRuntime/DreamConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 813811b0a9..10aafafd82 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -78,7 +78,7 @@ [ViewVariables] public DreamObjectMob? Mob { } [ViewVariables] public ClientObjectReference? Eye { - get => field; + get; set { field = value; if (field?.Type == ClientObjectReference.RefType.Entity) { From 952497ff12f13c95076a6afb2ec427ed48c82a56 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Thu, 15 Jan 2026 03:06:41 -0500 Subject: [PATCH 08/16] stop changing attached entity for eye changes, store eye reference in an ieye --- OpenDreamClient/DreamClientSystem.cs | 11 +- OpenDreamClient/Rendering/DreamClientEye.cs | 101 ++++++++++++++++++ OpenDreamClient/Rendering/DreamViewOverlay.cs | 13 ++- OpenDreamRuntime/DreamConnection.cs | 19 ++-- 4 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 OpenDreamClient/Rendering/DreamClientEye.cs diff --git a/OpenDreamClient/DreamClientSystem.cs b/OpenDreamClient/DreamClientSystem.cs index eb258fc7a0..1c09d98ffb 100644 --- a/OpenDreamClient/DreamClientSystem.cs +++ b/OpenDreamClient/DreamClientSystem.cs @@ -1,6 +1,9 @@ using OpenDreamClient.Interface; +using OpenDreamClient.Rendering; using OpenDreamShared.Dream; using OpenDreamShared.Network.Messages; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; using Robust.Shared.Player; namespace OpenDreamClient; @@ -8,6 +11,8 @@ namespace OpenDreamClient; internal sealed class DreamClientSystem : EntitySystem { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IDreamInterfaceManager _interfaceManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly TransformSystem _transformSystem = default!; // Current NetEntityof player's mob, or Invalid if could not be determined. private NetEntity _mobNet = NetEntity.Invalid; @@ -38,7 +43,10 @@ public ClientObjectReference EyeRef { return _eyeRef; } } - private set => _eyeRef = value; + private set { + _eyeManager.CurrentEye = new DreamClientEye(_eyeManager.CurrentEye, value, _entityManager, _transformSystem); + _eyeRef = value; + } } public override void Initialize() { @@ -72,7 +80,6 @@ public void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate msg) { } else { EyeRef = new(msg.MobNetEntity); } - break; case ClientObjectReference.RefType.Turf: EyeRef = incomingEyeRef; diff --git a/OpenDreamClient/Rendering/DreamClientEye.cs b/OpenDreamClient/Rendering/DreamClientEye.cs new file mode 100644 index 0000000000..dce41ed7ff --- /dev/null +++ b/OpenDreamClient/Rendering/DreamClientEye.cs @@ -0,0 +1,101 @@ +using OpenDreamShared.Dream; +using Robust.Client.GameObjects; +using Robust.Shared.Graphics; +using Robust.Shared.Map; + +namespace OpenDreamClient.Rendering; + +public sealed class DreamClientEye: IEye { + private ClientObjectReference _eyeRef; + private IEntityManager _entityManager; + private TransformSystem _transformSystem; + + public DreamClientEye(IEye baseEye, ClientObjectReference eyeRef, IEntityManager entityManager, TransformSystem transformSystem) { + Rotation = baseEye.Rotation; + Scale = baseEye.Scale; + DrawFov = baseEye.DrawFov; + DrawLight = baseEye.DrawLight; + + _eyeRef = eyeRef; + _entityManager = entityManager; + _transformSystem = transformSystem; + } + + public MapCoordinates Position { + get { + switch (_eyeRef.Type) { + case ClientObjectReference.RefType.Entity: + var ent = _entityManager.GetEntity(_eyeRef.Entity); + if (_entityManager.TryGetComponent(ent, out var pos)) { + return _transformSystem.GetMapCoordinates(pos); + } + break; + + case ClientObjectReference.RefType.Turf: + return new(new(_eyeRef.TurfX, _eyeRef.TurfY), new(_eyeRef.TurfZ)); + } + + return MapCoordinates.Nullspace; + } + } + + public void GetViewMatrix(out Matrix3x2 viewMatrix, Vector2 renderScale) { + var pos = Position; + viewMatrix = Matrix3Helpers.CreateInverseTransform( + pos.X + Offset.X, + pos.Y + Offset.Y, + (float)-Rotation.Theta, + 1 / (Scale.X * renderScale.X), + 1 / (Scale.Y * renderScale.Y)); + } + + public void GetViewMatrixNoOffset(out Matrix3x2 viewMatrix, Vector2 renderScale) { + var pos = Position; + viewMatrix = Matrix3Helpers.CreateInverseTransform( + pos.X, + pos.Y, + (float)-Rotation.Theta, + 1 / (Scale.X * renderScale.X), + 1 / (Scale.Y * renderScale.Y)); + } + + public void GetViewMatrixInv(out Matrix3x2 viewMatrixInv, Vector2 renderScale) { + var pos = Position; + viewMatrixInv = Matrix3Helpers.CreateTransform( + pos.X + Offset.X, + pos.Y + Offset.Y, + (float)-Rotation.Theta, + 1 / (Scale.X * renderScale.X), + 1 / (Scale.Y * renderScale.Y)); + } + + private Vector2 _scale = Vector2.One / 2f; + private Angle _rotation = Angle.Zero; + + [ViewVariables(VVAccess.ReadWrite)] + public bool DrawFov { get; set; } = true; + + [ViewVariables] + public bool DrawLight { get; set; } = true; + + [ViewVariables(VVAccess.ReadWrite)] + public Vector2 Offset { get; set; } + + [ViewVariables(VVAccess.ReadWrite)] + public Angle Rotation { + get => _rotation; + set => _rotation = value; + } + + [ViewVariables(VVAccess.ReadWrite)] + public Vector2 Zoom { + get => new(1 / _scale.X, 1 / _scale.Y); + set => _scale = new Vector2(1 / value.X, 1 / value.Y); + } + + [ViewVariables(VVAccess.ReadWrite)] + public Vector2 Scale { + get => _scale; + set => _scale = value; + } +} diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 824200780d..424081b54c 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -157,23 +157,28 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference MapCoordinates eyeCoords; DreamMobSightComponent? eyeSight; Box2 worldAABB; + MapId mapId; + EntitiesInView.Clear(); + switch (eyeRef.Type) { default: return; case ClientObjectReference.RefType.Turf: - eyeCoords = new MapCoordinates(new(eyeRef.TurfX, eyeRef.TurfY), new(eyeRef.TurfZ)); + eyeCoords = new(new(eyeRef.TurfX, eyeRef.TurfY), new(eyeRef.TurfZ)); _mobSightQuery.TryGetComponent(mob, out eyeSight); worldAABB = args.WorldAABB; - worldAABB = worldAABB.Translated(eyeCoords.Position - worldAABB.Center); + mapId = new(eyeRef.TurfZ); + //worldAABB = worldAABB.Translated(eyeCoords.Position - worldAABB.Center); break; case ClientObjectReference.RefType.Entity: - var eyeUid = _entityManager.GetEntity(new(eyeRef.Entity.Id)); + var eyeUid = _entityManager.GetEntity(eyeRef.Entity); if (!_xformQuery.TryGetComponent(eyeUid, out var eyeTransform)) return; eyeCoords = _transformSystem.GetMapCoordinates(eyeUid, eyeTransform); _mobSightQuery.TryGetComponent(eyeUid, out eyeSight); + mapId = args.MapId; worldAABB = args.WorldAABB; break; } @@ -190,7 +195,7 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference using (_prof.Group("lookup")) { //TODO use a sprite tree. //the scaling is to attempt to prevent pop-in, by getting sprites that are *just* offscreen - _lookupSystem.GetEntitiesIntersecting(args.MapId, worldAABB.Scale(1.2f), EntitiesInView, MapLookupFlags); + _lookupSystem.GetEntitiesIntersecting(mapId, worldAABB.Scale(1.2f), EntitiesInView, MapLookupFlags); } var eyeTile = _mapSystem.GetTileRef(gridUid, grid, eyeCoords); diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 10aafafd82..1cca702657 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -54,10 +54,10 @@ [ViewVariables] public DreamObjectMob? Mob { var oldMobNetEntity = _entityManager.GetNetEntity(oldMob.Entity); if (Eye != null && Eye.Value.Entity == oldMobNetEntity) { if (_mob == null) { - Eye = null; + _eye = null; } else { var newMobNetEntity = _entityManager.GetNetEntity(_mob.Entity); - Eye = new(newMobNetEntity); + _eye = new(newMobNetEntity); } } } @@ -65,6 +65,8 @@ [ViewVariables] public DreamObjectMob? Mob { UpdateMobEye(); if (_mob != null) { + _playerManager.SetAttachedEntity(Session!, _mob.Entity); + // If the mob is already owned by another player, kick them out if (_mob.Connection != null) _mob.Connection.Mob = null; @@ -77,17 +79,12 @@ [ViewVariables] public DreamObjectMob? Mob { } } + + private ClientObjectReference? _eye; [ViewVariables] public ClientObjectReference? Eye { - get; + get => _eye; set { - field = value; - if (field?.Type == ClientObjectReference.RefType.Entity) { - var ent = _entityManager.GetEntity(field?.Entity); - _playerManager.SetAttachedEntity(Session!, ent); - } else { - _playerManager.SetAttachedEntity(Session!, EntityUid.Invalid); - } - + _eye = value; UpdateMobEye(); } } From f57645d334743b687ca8c026c9322e3c12ccbed1 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Thu, 15 Jan 2026 03:19:11 -0500 Subject: [PATCH 09/16] lint --- OpenDreamClient/Rendering/DreamClientEye.cs | 12 +++++++----- OpenDreamRuntime/DreamConnection.cs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/OpenDreamClient/Rendering/DreamClientEye.cs b/OpenDreamClient/Rendering/DreamClientEye.cs index dce41ed7ff..564cb8ea9c 100644 --- a/OpenDreamClient/Rendering/DreamClientEye.cs +++ b/OpenDreamClient/Rendering/DreamClientEye.cs @@ -6,9 +6,9 @@ namespace OpenDreamClient.Rendering; public sealed class DreamClientEye: IEye { - private ClientObjectReference _eyeRef; - private IEntityManager _entityManager; - private TransformSystem _transformSystem; + private readonly ClientObjectReference _eyeRef; + private readonly IEntityManager _entityManager; + private readonly TransformSystem _transformSystem; public DreamClientEye(IEye baseEye, ClientObjectReference eyeRef, IEntityManager entityManager, TransformSystem transformSystem) { Rotation = baseEye.Rotation; @@ -21,6 +21,7 @@ public DreamClientEye(IEye baseEye, ClientObjectReference eyeRef, IEntityManager _transformSystem = transformSystem; } + [ViewVariables(VVAccess.ReadOnly)] public MapCoordinates Position { get { switch (_eyeRef.Type) { @@ -29,6 +30,7 @@ public MapCoordinates Position { if (_entityManager.TryGetComponent(ent, out var pos)) { return _transformSystem.GetMapCoordinates(pos); } + break; case ClientObjectReference.RefType.Turf: @@ -73,10 +75,10 @@ public void GetViewMatrixInv(out Matrix3x2 viewMatrixInv, Vector2 renderScale) { private Angle _rotation = Angle.Zero; [ViewVariables(VVAccess.ReadWrite)] - public bool DrawFov { get; set; } = true; + public bool DrawFov { get; set; } [ViewVariables] - public bool DrawLight { get; set; } = true; + public bool DrawLight { get; set; } [ViewVariables(VVAccess.ReadWrite)] public Vector2 Offset { get; set; } diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 1cca702657..4a82b851a7 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -79,8 +79,8 @@ [ViewVariables] public DreamObjectMob? Mob { } } - private ClientObjectReference? _eye; + [ViewVariables] public ClientObjectReference? Eye { get => _eye; set { From 2a1b4da6246f82248e78bd79f48a076e0ead730c Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Thu, 15 Jan 2026 09:33:51 -0500 Subject: [PATCH 10/16] cleanup --- OpenDreamClient/Rendering/DreamClientEye.cs | 6 +----- OpenDreamClient/Rendering/DreamViewOverlay.cs | 11 +++-------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/OpenDreamClient/Rendering/DreamClientEye.cs b/OpenDreamClient/Rendering/DreamClientEye.cs index 564cb8ea9c..ebded5c8c1 100644 --- a/OpenDreamClient/Rendering/DreamClientEye.cs +++ b/OpenDreamClient/Rendering/DreamClientEye.cs @@ -72,7 +72,6 @@ public void GetViewMatrixInv(out Matrix3x2 viewMatrixInv, Vector2 renderScale) { } private Vector2 _scale = Vector2.One / 2f; - private Angle _rotation = Angle.Zero; [ViewVariables(VVAccess.ReadWrite)] public bool DrawFov { get; set; } @@ -84,10 +83,7 @@ public void GetViewMatrixInv(out Matrix3x2 viewMatrixInv, Vector2 renderScale) { public Vector2 Offset { get; set; } [ViewVariables(VVAccess.ReadWrite)] - public Angle Rotation { - get => _rotation; - set => _rotation = value; - } + public Angle Rotation { get; set; } [ViewVariables(VVAccess.ReadWrite)] public Vector2 Zoom { diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 424081b54c..24d57e422a 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -156,8 +156,7 @@ protected override void Draw(in OverlayDrawArgs args) { private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference eyeRef, Vector2i viewportSize) { MapCoordinates eyeCoords; DreamMobSightComponent? eyeSight; - Box2 worldAABB; - MapId mapId; + Box2 worldAABB = args.WorldAABB; EntitiesInView.Clear(); @@ -167,22 +166,18 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference case ClientObjectReference.RefType.Turf: eyeCoords = new(new(eyeRef.TurfX, eyeRef.TurfY), new(eyeRef.TurfZ)); _mobSightQuery.TryGetComponent(mob, out eyeSight); - worldAABB = args.WorldAABB; - mapId = new(eyeRef.TurfZ); - //worldAABB = worldAABB.Translated(eyeCoords.Position - worldAABB.Center); break; case ClientObjectReference.RefType.Entity: var eyeUid = _entityManager.GetEntity(eyeRef.Entity); if (!_xformQuery.TryGetComponent(eyeUid, out var eyeTransform)) return; eyeCoords = _transformSystem.GetMapCoordinates(eyeUid, eyeTransform); - _mobSightQuery.TryGetComponent(eyeUid, out eyeSight); - mapId = args.MapId; - worldAABB = args.WorldAABB; break; } + MapId mapId = eyeCoords.MapId; + if (!_mapManager.TryFindGridAt(eyeCoords, out var gridUid, out var grid)) return; From 88d4457d0965a64826581caeebfd633d0f75c17e Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Fri, 16 Jan 2026 00:22:02 -0500 Subject: [PATCH 11/16] makes null eye stop rendering. annoyingly it doesn't clear the screen buffer... --- OpenDreamClient/DreamClientSystem.cs | 18 ++++-------------- OpenDreamRuntime/DreamConnection.cs | 3 +++ .../Network/Messages/MsgNotifyMobEyeUpdate.cs | 2 ++ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/OpenDreamClient/DreamClientSystem.cs b/OpenDreamClient/DreamClientSystem.cs index 1c09d98ffb..734fc6f087 100644 --- a/OpenDreamClient/DreamClientSystem.cs +++ b/OpenDreamClient/DreamClientSystem.cs @@ -70,20 +70,10 @@ public void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate msg) { var incomingEyeRef = msg.EyeRef; - switch (incomingEyeRef.Type) { - default: - EyeRef = new(msg.MobNetEntity); - break; - case ClientObjectReference.RefType.Entity: - if (incomingEyeRef.Entity.IsValid()) { - EyeRef = incomingEyeRef; - } else { - EyeRef = new(msg.MobNetEntity); - } - break; - case ClientObjectReference.RefType.Turf: - EyeRef = incomingEyeRef; - break; + if (incomingEyeRef.Type == ClientObjectReference.RefType.Entity && !incomingEyeRef.Entity.IsValid()) { + EyeRef = new(msg.MobNetEntity); + } else { + EyeRef = incomingEyeRef; } } } diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 4a82b851a7..821428c824 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -571,6 +571,9 @@ private void UpdateMobEye() { default: eyeRef = new(_entityManager.GetNetEntity(mobUid)); break; + case null: + eyeRef = new(); + break; case ClientObjectReference.RefType.Entity when Eye.HasValue: eyeRef = new(_entityManager.GetNetEntity(new(Eye.Value.Entity.Id))); break; diff --git a/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs b/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs index 12c34bbd2f..7c23305580 100644 --- a/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs +++ b/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs @@ -12,6 +12,7 @@ public sealed class MsgNotifyMobEyeUpdate : NetMessage { public override MsgGroups MsgGroup => MsgGroups.EntityEvent; public NetEntity MobNetEntity; + // Type = Client -> null public ClientObjectReference EyeRef; public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { @@ -29,6 +30,7 @@ public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer EyeRef = new(new(x, y), z); break; default: + // null eye EyeRef = new(); break; } From 0cdcc18d2d7193287bd8bac5fa77d32f0a583544 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Fri, 16 Jan 2026 00:27:23 -0500 Subject: [PATCH 12/16] lint --- OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs b/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs index 7c23305580..b4283c3131 100644 --- a/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs +++ b/OpenDreamShared/Network/Messages/MsgNotifyMobEyeUpdate.cs @@ -12,6 +12,7 @@ public sealed class MsgNotifyMobEyeUpdate : NetMessage { public override MsgGroups MsgGroup => MsgGroups.EntityEvent; public NetEntity MobNetEntity; + // Type = Client -> null public ClientObjectReference EyeRef; From f0cac6df528d4531d8d414eace6f3e5499a0bd65 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Sat, 17 Jan 2026 02:01:02 -0500 Subject: [PATCH 13/16] null eye only shows certain screen elements --- OpenDreamClient/DreamClientSystem.cs | 4 + OpenDreamClient/Input/MouseInputSystem.cs | 6 ++ OpenDreamClient/Rendering/DreamClientEye.cs | 4 +- OpenDreamClient/Rendering/DreamPlane.cs | 5 +- OpenDreamClient/Rendering/DreamViewOverlay.cs | 81 +++++++++++++------ 5 files changed, 74 insertions(+), 26 deletions(-) diff --git a/OpenDreamClient/DreamClientSystem.cs b/OpenDreamClient/DreamClientSystem.cs index 734fc6f087..ec2cadbcd0 100644 --- a/OpenDreamClient/DreamClientSystem.cs +++ b/OpenDreamClient/DreamClientSystem.cs @@ -49,6 +49,10 @@ private set { } } + public bool IsEyeMissing() { + return EyeRef.Type == ClientObjectReference.RefType.Client; + } + public override void Initialize() { SubscribeLocalEvent(OnPlayerAttached); } diff --git a/OpenDreamClient/Input/MouseInputSystem.cs b/OpenDreamClient/Input/MouseInputSystem.cs index 0d6375f346..85daeaffef 100644 --- a/OpenDreamClient/Input/MouseInputSystem.cs +++ b/OpenDreamClient/Input/MouseInputSystem.cs @@ -27,6 +27,7 @@ internal sealed class MouseInputSystem : SharedMouseInputSystem { [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IDreamInterfaceManager _dreamInterfaceManager = default!; [Dependency] private readonly ClientAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly DreamClientSystem _dreamClientSystem = default!; [Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly ILogManager _logManager = default!; private ISawmill _sawmill = default!; @@ -144,6 +145,11 @@ public void HandleAtomMouseMove(ScalingViewport viewport, Vector2 relativePos, C } public (ClientObjectReference Atom, Vector2i IconPosition)? GetTurfUnderMouse(MapCoordinates mapCoords, out uint? turfId) { + if (_dreamClientSystem.IsEyeMissing()) { + turfId = null; + return null; + } + // Grid coordinates are half a meter off from entity coordinates mapCoords = new MapCoordinates(mapCoords.Position + new Vector2(0.5f), mapCoords.MapId); diff --git a/OpenDreamClient/Rendering/DreamClientEye.cs b/OpenDreamClient/Rendering/DreamClientEye.cs index ebded5c8c1..17e7b5e267 100644 --- a/OpenDreamClient/Rendering/DreamClientEye.cs +++ b/OpenDreamClient/Rendering/DreamClientEye.cs @@ -37,7 +37,9 @@ public MapCoordinates Position { return new(new(_eyeRef.TurfX, _eyeRef.TurfY), new(_eyeRef.TurfZ)); } - return MapCoordinates.Nullspace; + // Nullspace position stops all rendering but we still want to render certain screen space objects... + //return MapCoordinates.Nullspace; + return new(0, 0, new(1)); } } diff --git a/OpenDreamClient/Rendering/DreamPlane.cs b/OpenDreamClient/Rendering/DreamPlane.cs index e0a89d5d2c..2de04cf29a 100644 --- a/OpenDreamClient/Rendering/DreamPlane.cs +++ b/OpenDreamClient/Rendering/DreamPlane.cs @@ -83,12 +83,15 @@ public void Draw(DreamViewOverlay overlay, DrawingHandleWorld handle, Box2 world /// /// Draws this plane's mouse map onto the current render target /// - public void DrawMouseMap(DrawingHandleWorld handle, DreamViewOverlay overlay, Vector2i renderTargetSize, Box2 worldAABB) { + public void DrawMouseMap(DrawingHandleWorld handle, DreamViewOverlay overlay, Vector2i renderTargetSize, Box2 worldAABB, bool onlyDrawScreenSprites = false) { if (Master?.MouseOpacity == MouseOpacity.Transparent || !Enabled) return; handle.UseShader(overlay.BlockColorInstance); foreach (var sprite in Sprites) { + if (onlyDrawScreenSprites && !sprite.IsScreen) + continue; + if (sprite.MouseOpacity == MouseOpacity.Transparent || sprite.ShouldPassMouse) continue; diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 24d57e422a..e12713ba8d 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -157,11 +157,18 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference MapCoordinates eyeCoords; DreamMobSightComponent? eyeSight; Box2 worldAABB = args.WorldAABB; + DreamMobSightComponent? mobSight; + SightFlags sight; + sbyte seeVis; EntitiesInView.Clear(); switch (eyeRef.Type) { default: + _mobSightQuery.TryGetComponent(mob, out mobSight); + seeVis = mobSight?.SeeInvisibility ?? 127; + sight = mobSight?.Sight ?? 0; + DrawNullEyeSprites(args, viewportSize, seeVis, sight); return; case ClientObjectReference.RefType.Turf: eyeCoords = new(new(eyeRef.TurfX, eyeRef.TurfY), new(eyeRef.TurfZ)); @@ -181,9 +188,9 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference if (!_mapManager.TryFindGridAt(eyeCoords, out var gridUid, out var grid)) return; - _mobSightQuery.TryGetComponent(mob, out var mobSight); - var seeVis = mobSight?.SeeInvisibility ?? 127; - var sight = eyeSight?.Sight ?? 0; + _mobSightQuery.TryGetComponent(mob, out mobSight); + seeVis = mobSight?.SeeInvisibility ?? 127; + sight = eyeSight?.Sight ?? 0; var worldHandle = args.WorldHandle; @@ -211,6 +218,25 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference args.WorldAABB.BottomLeft); } + // Only draw sprites in screen space and also on non-negative planes + private void DrawNullEyeSprites(OverlayDrawArgs args, Vector2i viewportSize, sbyte seeVis, SightFlags sight) { + RefreshRenderTargets(args.WorldHandle, viewportSize); + + if (ScreenOverlayEnabled) { + CollectScreenSpaceSprites(seeVis, args.WorldAABB); + } + ClearPlanes(); + ProcessSprites(args.WorldHandle, viewportSize, args.WorldAABB, true); + + //Final draw + DrawPlanes(args.WorldHandle, args.WorldAABB); + + //At this point all the sprites have been rendered to the base target, now we just draw it to the viewport! + args.WorldHandle.DrawTexture( + MouseMapRenderEnabled ? _mouseMapRenderTarget!.Texture : _baseRenderTarget!.Texture, + args.WorldAABB.BottomLeft); + } + //handles underlays, overlays, appearance flags, images. Adds them to the result list, so they can be sorted and drawn with DrawIcon() private void ProcessIconComponents(DreamIcon icon, Vector2 position, EntityUid uid, bool isScreen, ref int tieBreaker, List result, sbyte seeVis, RendererMetaData? parentIcon = null, bool keepTogether = false, Vector3? turfCoords = null, ClientAppearanceSystem.Flick? flick = null) { if (icon.Appearance is null) //in the event that appearance hasn't loaded yet @@ -557,11 +583,14 @@ private DreamPlane GetPlane(int planeIndex, Vector2i viewportSize) { return plane; } - private void ProcessSprites(DrawingHandleWorld handle, Vector2i viewportSize, Box2 worldAABB) { + private void ProcessSprites(DrawingHandleWorld handle, Vector2i viewportSize, Box2 worldAABB, bool skipNegativePlanes = false) { using var _ = _prof.Group("process sprites / draw render targets"); //all sprites with render targets get handled first - these are ordered by sprites.Sort(), so we can just iterate normally foreach (var sprite in _spriteContainer) { + if (skipNegativePlanes && sprite.Plane < 0) + continue; + var plane = GetPlane(sprite.Plane, viewportSize); if (!string.IsNullOrEmpty(sprite.RenderTarget)) { @@ -699,26 +728,7 @@ private void CollectVisibleSprites(ViewAlgorithm.Tile?[,] tiles, EntityUid gridU // Screen objects if (ScreenOverlayEnabled) { - using var _ = _prof.Group("screen objects"); - - foreach (EntityUid uid in _screenOverlaySystem.ScreenObjects) { - if (!_entityManager.TryGetComponent(uid, out DMISpriteComponent? sprite) || sprite.ScreenLocation == null) - continue; - if (!_spriteSystem.IsVisible(sprite, null, seeVis, null)) - continue; - if (sprite.ScreenLocation.MapControl != null) // Don't render screen objects meant for other map controls - continue; - - Vector2i dmiIconSize = sprite.Icon.DMI?.IconSize ?? new(IconSize, IconSize); - Vector2 position = sprite.ScreenLocation.GetViewPosition(worldAABB.BottomLeft, _interfaceManager.View, IconSize, dmiIconSize); - Vector2 iconSize = sprite.Icon.DMI == null ? Vector2.Zero : sprite.Icon.DMI.IconSize / (float)IconSize; - for (int x = 0; x < sprite.ScreenLocation.RepeatX; x++) { - for (int y = 0; y < sprite.ScreenLocation.RepeatY; y++) { - tValue = 0; - ProcessIconComponents(sprite.Icon, position + iconSize * new Vector2(x, y), uid, true, ref tValue, _spriteContainer, seeVis); - } - } - } + CollectScreenSpaceSprites(seeVis, worldAABB); } using (_prof.Group("sort sprites")) { @@ -726,6 +736,29 @@ private void CollectVisibleSprites(ViewAlgorithm.Tile?[,] tiles, EntityUid gridU } } + private void CollectScreenSpaceSprites(sbyte seeVis, Box2 worldAABB) { + using var _ = _prof.Group("screen objects"); + int tValue; + foreach (EntityUid uid in _screenOverlaySystem.ScreenObjects) { + if (!_entityManager.TryGetComponent(uid, out DMISpriteComponent? sprite) || sprite.ScreenLocation == null) + continue; + if (!_spriteSystem.IsVisible(sprite, null, seeVis, null)) + continue; + if (sprite.ScreenLocation.MapControl != null) // Don't render screen objects meant for other map controls + continue; + + Vector2i dmiIconSize = sprite.Icon.DMI?.IconSize ?? new(IconSize, IconSize); + Vector2 position = sprite.ScreenLocation.GetViewPosition(worldAABB.BottomLeft, _interfaceManager.View, IconSize, dmiIconSize); + Vector2 iconSize = sprite.Icon.DMI == null ? Vector2.Zero : sprite.Icon.DMI.IconSize / (float)IconSize; + for (int x = 0; x < sprite.ScreenLocation.RepeatX; x++) { + for (int y = 0; y < sprite.ScreenLocation.RepeatY; y++) { + tValue = 0; + ProcessIconComponents(sprite.Icon, position + iconSize * new Vector2(x, y), uid, true, ref tValue, _spriteContainer, seeVis); + } + } + } + } + private RendererMetaData RentRendererMetaData() { RendererMetaData result; if (_rendererMetaDataRental.Count == 0) From 28118828fd1414812c7d0d34220632ee4a8a074d Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Sat, 17 Jan 2026 02:13:58 -0500 Subject: [PATCH 14/16] lint --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index e12713ba8d..ebf2720f79 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -168,7 +168,7 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference _mobSightQuery.TryGetComponent(mob, out mobSight); seeVis = mobSight?.SeeInvisibility ?? 127; sight = mobSight?.Sight ?? 0; - DrawNullEyeSprites(args, viewportSize, seeVis, sight); + DrawNullEyeSprites(args, viewportSize, seeVis); return; case ClientObjectReference.RefType.Turf: eyeCoords = new(new(eyeRef.TurfX, eyeRef.TurfY), new(eyeRef.TurfZ)); @@ -207,7 +207,7 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference CollectVisibleSprites(tiles, gridUid, grid, eyeTile, seeVis, sight, worldAABB); ClearPlanes(); - ProcessSprites(worldHandle, viewportSize, worldAABB); + ProcessSprites(worldHandle, viewportSize); //Final draw DrawPlanes(worldHandle, worldAABB); @@ -219,14 +219,15 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference } // Only draw sprites in screen space and also on non-negative planes - private void DrawNullEyeSprites(OverlayDrawArgs args, Vector2i viewportSize, sbyte seeVis, SightFlags sight) { + private void DrawNullEyeSprites(OverlayDrawArgs args, Vector2i viewportSize, sbyte seeVis) { RefreshRenderTargets(args.WorldHandle, viewportSize); if (ScreenOverlayEnabled) { CollectScreenSpaceSprites(seeVis, args.WorldAABB); } + ClearPlanes(); - ProcessSprites(args.WorldHandle, viewportSize, args.WorldAABB, true); + ProcessSprites(args.WorldHandle, viewportSize, true); //Final draw DrawPlanes(args.WorldHandle, args.WorldAABB); @@ -583,7 +584,7 @@ private DreamPlane GetPlane(int planeIndex, Vector2i viewportSize) { return plane; } - private void ProcessSprites(DrawingHandleWorld handle, Vector2i viewportSize, Box2 worldAABB, bool skipNegativePlanes = false) { + private void ProcessSprites(DrawingHandleWorld handle, Vector2i viewportSize, bool skipNegativePlanes = false) { using var _ = _prof.Group("process sprites / draw render targets"); //all sprites with render targets get handled first - these are ordered by sprites.Sort(), so we can just iterate normally @@ -738,7 +739,6 @@ private void CollectVisibleSprites(ViewAlgorithm.Tile?[,] tiles, EntityUid gridU private void CollectScreenSpaceSprites(sbyte seeVis, Box2 worldAABB) { using var _ = _prof.Group("screen objects"); - int tValue; foreach (EntityUid uid in _screenOverlaySystem.ScreenObjects) { if (!_entityManager.TryGetComponent(uid, out DMISpriteComponent? sprite) || sprite.ScreenLocation == null) continue; @@ -752,7 +752,7 @@ private void CollectScreenSpaceSprites(sbyte seeVis, Box2 worldAABB) { Vector2 iconSize = sprite.Icon.DMI == null ? Vector2.Zero : sprite.Icon.DMI.IconSize / (float)IconSize; for (int x = 0; x < sprite.ScreenLocation.RepeatX; x++) { for (int y = 0; y < sprite.ScreenLocation.RepeatY; y++) { - tValue = 0; + int tValue = 0; ProcessIconComponents(sprite.Icon, position + iconSize * new Vector2(x, y), uid, true, ref tValue, _spriteContainer, seeVis); } } From c16563c76c520916d7f409a167e1ce3742870ed5 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Sat, 17 Jan 2026 02:20:43 -0500 Subject: [PATCH 15/16] lint --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index ebf2720f79..1c0c4c06f6 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -167,7 +167,6 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference default: _mobSightQuery.TryGetComponent(mob, out mobSight); seeVis = mobSight?.SeeInvisibility ?? 127; - sight = mobSight?.Sight ?? 0; DrawNullEyeSprites(args, viewportSize, seeVis); return; case ClientObjectReference.RefType.Turf: From 20b94b34896be8601d769bc0a6de10e8464412e8 Mon Sep 17 00:00:00 2001 From: Ruzihm Date: Sat, 17 Jan 2026 02:35:22 -0500 Subject: [PATCH 16/16] lint --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 1c0c4c06f6..b1aa6f8c8c 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -155,10 +155,8 @@ protected override void Draw(in OverlayDrawArgs args) { private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference eyeRef, Vector2i viewportSize) { MapCoordinates eyeCoords; - DreamMobSightComponent? eyeSight; + DreamMobSightComponent? eyeSight, mobSight; Box2 worldAABB = args.WorldAABB; - DreamMobSightComponent? mobSight; - SightFlags sight; sbyte seeVis; EntitiesInView.Clear(); @@ -189,7 +187,7 @@ private void DrawAll(OverlayDrawArgs args, EntityUid mob, ClientObjectReference _mobSightQuery.TryGetComponent(mob, out mobSight); seeVis = mobSight?.SeeInvisibility ?? 127; - sight = eyeSight?.Sight ?? 0; + var sight = eyeSight?.Sight ?? 0; var worldHandle = args.WorldHandle;