Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
52ba508
notifies client of eye/mob changes via network message for determinin…
Ruzihm Dec 15, 2025
ed29795
feedback
Ruzihm Dec 15, 2025
d6a4560
Merge branch 'master' into mob-sight
Ruzihm Dec 16, 2025
4a4b7fc
Update OpenDreamClient/Interface/DreamInterfaceManager.cs
Ruzihm Jan 14, 2026
fe4846e
Merge branch 'master' into mob-sight
Ruzihm Jan 14, 2026
434d423
first attempt at supporting turf eye
Ruzihm Jan 15, 2026
63d7ff7
some cleanup
Ruzihm Jan 15, 2026
b5b02bb
lint
Ruzihm Jan 15, 2026
af6dc0a
lint
Ruzihm Jan 15, 2026
952497f
stop changing attached entity for eye changes, store eye reference in…
Ruzihm Jan 15, 2026
f57645d
lint
Ruzihm Jan 15, 2026
2a1b4da
cleanup
Ruzihm Jan 15, 2026
88d4457
makes null eye stop rendering. annoyingly it doesn't clear the screen…
Ruzihm Jan 16, 2026
0cdcc18
lint
Ruzihm Jan 16, 2026
f0cac6d
null eye only shows certain screen elements
Ruzihm Jan 17, 2026
2811882
lint
Ruzihm Jan 17, 2026
c16563c
lint
Ruzihm Jan 17, 2026
20b94b3
lint
Ruzihm Jan 17, 2026
355c938
Merge branch 'master' into mob-sight
Ruzihm Jan 20, 2026
6471787
Merge branch 'master' into mob-sight
Ruzihm Jan 28, 2026
79ceec6
listen for RT eye adding event to overwrite current eye changes
Ruzihm Jan 28, 2026
d562c8d
Merge branch 'master' into mob-sight
Ruzihm May 8, 2026
c3955a5
lint
Ruzihm May 8, 2026
92ca72c
lint
Ruzihm May 8, 2026
8856a58
Merge branch 'master' into mob-sight
Ruzihm May 9, 2026
1fd4e32
lint
Ruzihm May 9, 2026
8e437c9
Update OpenDreamClient/DreamClientSystem.cs
Ruzihm May 16, 2026
cce3159
Update OpenDreamClient/DreamClientSystem.cs
Ruzihm May 16, 2026
93f6cde
remove onlyDrawScreenSprites from DrawMouseMap
Ruzihm May 16, 2026
d1fe093
DreamConnection.Eye from ClientObjectReference to DreamObjectAtom
Ruzihm May 16, 2026
f402d10
lint
Ruzihm May 16, 2026
b5b693f
Keeps mob verbs usable even if mob isn't in view, fixes out of bounds…
Ruzihm May 17, 2026
f020f50
Merge branch 'master' into mob-sight
Ruzihm May 23, 2026
a1ed778
Feedback
Ruzihm May 23, 2026
d8c318f
lint
Ruzihm May 23, 2026
bace554
lint
Ruzihm May 23, 2026
95d4699
lint
Ruzihm May 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions OpenDreamClient/ClientVerbSystem.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using System.Threading.Tasks;
using OpenDreamClient.Interface;
using OpenDreamClient.Rendering;
Expand All @@ -13,10 +14,10 @@ namespace OpenDreamClient;

public sealed partial class ClientVerbSystem : VerbSystem {
[Dependency] private IDreamInterfaceManager _interfaceManager = default!;
[Dependency] private IPlayerManager _playerManager = default!;
[Dependency] private IEntityManager _entityManager = default!;
[Dependency] private ITaskManager _taskManager = default!;
[Dependency] private IOverlayManager _overlayManager = default!;
[Dependency] private DreamClientSystem _dreamClientSystem = default!;
[Dependency] private TransformSystem _transformSystem = default!;

private EntityQuery<DMISpriteComponent> _spriteQuery;
Expand Down Expand Up @@ -80,10 +81,18 @@ public IEnumerable<VerbInfo> GetAllVerbs() {
/// <returns>The ID, src, and information of every executable verb</returns>
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 = _dreamClientSystem.MobUid;

var viewOverlay = _overlayManager.GetOverlay<DreamViewOverlay>();
var entitiesToCheck = viewOverlay.EntitiesInView.AsEnumerable();

if (mob.IsValid()) {
_sightQuery.TryGetComponent(mob, out var mobSight);

seeInvisibility = mobSight?.SeeInvisibility;

entitiesToCheck = entitiesToCheck.Prepend(mob);
}

// First, the verbs attached to our client
Expand All @@ -98,9 +107,8 @@ public IEnumerable<VerbInfo> GetAllVerbs() {
}
}

// Then, the verbs on objects around us
var viewOverlay = _overlayManager.GetOverlay<DreamViewOverlay>();
foreach (var entity in viewOverlay.EntitiesInView) {
// Then, the verbs on our mob (if it is valid) and the objects around us
foreach (var entity in entitiesToCheck) {
if (!_spriteQuery.TryGetComponent(entity, out var sprite))
continue;
if (sprite.Icon.Appearance is not { } appearance)
Expand All @@ -117,12 +125,12 @@ public IEnumerable<VerbInfo> 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;
Expand Down
53 changes: 53 additions & 0 deletions OpenDreamClient/DreamClientSystem.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,71 @@
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;

internal sealed partial class DreamClientSystem : EntitySystem {
[Dependency] private IEntityManager _entityManager = default!;
[Dependency] private IDreamInterfaceManager _interfaceManager = default!;
[Dependency] private IEyeManager _eyeManager = default!;
[Dependency] private TransformSystem _transformSystem = default!;

// Current NetEntityof player's mob, or Invalid if could not be determined.
Comment thread Fixed
private NetEntity _mobNet = NetEntity.Invalid;

// Current Entity of player's mob, or Invalid if could not be determined.
public EntityUid MobUid => _entityManager.GetEntity(_mobNet);

// 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 {
_eyeManager.CurrentEye = new DreamClientEye(_eyeManager.CurrentEye, value, _entityManager, _transformSystem);
_eyeRef = value;
}
}

public bool IsEyeMissing() {
return EyeRef.Type == ClientObjectReference.RefType.Client;
}

public override void Initialize() {
SubscribeLocalEvent<LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<EyeComponent, EyeAttachedEvent>(OnEyeAttachedEvent);
}

// override basic RT eye changes
private void OnEyeAttachedEvent(EntityUid uid, EyeComponent comp, ref EyeAttachedEvent e) {
_eyeManager.CurrentEye = new DreamClientEye(_eyeManager.CurrentEye, _eyeRef, _entityManager, _transformSystem);
}

private void OnPlayerAttached(LocalPlayerAttachedEvent e) {
// The active input context gets reset to "common" when a new player is attached
// So we have to set it again
_interfaceManager.DefaultWindow?.Macro.SetActive();
}

public void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate msg) {
_mobNet = msg.MobNetEntity;

ClientObjectReference incomingEyeRef = msg.EyeRef.GetValueOrDefault(new());

if (incomingEyeRef.Type == ClientObjectReference.RefType.Entity && !incomingEyeRef.Entity.IsValid()) {
EyeRef = new(msg.MobNetEntity);
} else {
EyeRef = incomingEyeRef;
}
}
}
4 changes: 3 additions & 1 deletion OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal sealed partial class ContextMenuPopup : Popup {
[Dependency] private IEntitySystemManager _entitySystemManager = default!;
[Dependency] private IUserInterfaceManager _uiManager = default!;
private readonly ClientAppearanceSystem _appearanceSystem;
private readonly DreamClientSystem _dreamClientSystem;
private readonly ClientVerbSystem _verbSystem;
private readonly DMISpriteSystem _spriteSystem;
private readonly EntityLookupSystem _lookupSystem;
Expand All @@ -37,6 +38,7 @@ public ContextMenuPopup() {

_verbSystem = _entitySystemManager.GetEntitySystem<ClientVerbSystem>();
_appearanceSystem = _entitySystemManager.GetEntitySystem<ClientAppearanceSystem>();
_dreamClientSystem = _entitySystemManager.GetEntitySystem<DreamClientSystem>();
_spriteSystem = _entitySystemManager.GetEntitySystem<DMISpriteSystem>();
_lookupSystem = _entitySystemManager.GetEntitySystem<EntityLookupSystem>();
_mouseInputSystem = _entitySystemManager.GetEntitySystem<MouseInputSystem>();
Expand Down Expand Up @@ -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(_dreamClientSystem.MobUid, out DreamMobSightComponent? sight))
return 127;

return sight.SeeInvisibility;
Expand Down
6 changes: 6 additions & 0 deletions OpenDreamClient/Input/MouseInputSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ internal sealed partial class MouseInputSystem : SharedMouseInputSystem {
[Dependency] private IConfigurationManager _configurationManager = default!;
[Dependency] private IDreamInterfaceManager _dreamInterfaceManager = default!;
[Dependency] private ClientAppearanceSystem _appearanceSystem = default!;
[Dependency] private DreamClientSystem _dreamClientSystem = default!;
[Dependency] private IClyde _clyde = default!;
[Dependency] private ILogManager _logManager = default!;
private ISawmill _sawmill = default!;
Expand Down Expand Up @@ -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);

Expand Down
3 changes: 1 addition & 2 deletions OpenDreamClient/Interface/Controls/ControlInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,8 @@ public void RefreshVerbs(IEnumerable<(int, ClientObjectReference, VerbSystem.Ver
_verbButtons[key] = verbButton;
_grid.AddChild(verbButton);
verbButton.SetPositionInParent(gridIndex);
gridIndex++;
}

gridIndex++;
}

foreach (var key in _verbButtons.Keys) {
Expand Down
21 changes: 13 additions & 8 deletions OpenDreamClient/Interface/DreamInterfaceManager.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
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.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;

Expand Down Expand Up @@ -135,9 +135,14 @@ public void Initialize() {
_netManager.RegisterNetMessage<MsgLoadInterface>(RxLoadInterface);
_netManager.RegisterNetMessage<MsgAckLoadInterface>();
_netManager.RegisterNetMessage<MsgUpdateClientInfo>(RxUpdateClientInfo);
_netManager.RegisterNetMessage<MsgNotifyMobEyeUpdate>(RxNotifyMobEyeUpdate);
Comment thread
Ruzihm marked this conversation as resolved.
_clyde.OnWindowFocused += OnWindowFocused;
}

private void RxNotifyMobEyeUpdate(MsgNotifyMobEyeUpdate message) {
_entitySystemManager.GetEntitySystem<DreamClientSystem>().RxNotifyMobEyeUpdate(message);
}

private void RxUpdateStatPanels(MsgUpdateStatPanels message) {
DefaultInfo?.UpdateStatPanels(message);
}
Expand Down
146 changes: 146 additions & 0 deletions OpenDreamClient/Rendering/DreamClientEye.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using OpenDreamShared.Dream;
using OpenDreamShared.Rendering;
using Robust.Client.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using System.Diagnostics.CodeAnalysis;

namespace OpenDreamClient.Rendering;

public sealed class DreamClientEye : IEye {
private readonly ClientObjectReference _eyeRef;
private readonly IEntityManager _entityManager;
private readonly TransformSystem _transformSystem;

private readonly EntityQuery<TransformComponent> _xformQuery;
private readonly EntityQuery<DreamMobSightComponent> _mobSightQuery;

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;

_xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
_mobSightQuery = _entityManager.GetEntityQuery<DreamMobSightComponent>();
}

[ViewVariables(VVAccess.ReadOnly)]
public MapCoordinates Position {
get {
switch (_eyeRef.Type) {
case ClientObjectReference.RefType.Entity:
var ent = _entityManager.GetEntity(_eyeRef.Entity);
if (_entityManager.TryGetComponent<TransformComponent>(ent, out var pos)) {
return _transformSystem.GetMapCoordinates(pos);
}

break;
Comment thread Fixed

case ClientObjectReference.RefType.Turf:
return new(new(_eyeRef.TurfX, _eyeRef.TurfY), new(_eyeRef.TurfZ));
}

// Nullspace position stops all rendering but we still want to render certain screen space objects...
//return MapCoordinates.Nullspace;
return new(0, 0, new(1));
}
}

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;

[ViewVariables(VVAccess.ReadWrite)]
public bool DrawFov { get; set; }

[ViewVariables]
public bool DrawLight { get; set; }

[ViewVariables(VVAccess.ReadWrite)]
public Vector2 Offset { get; set; }

[ViewVariables(VVAccess.ReadWrite)]
public Angle Rotation { get; set; }

[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;
}

public bool IsNullEye => _eyeRef.Type == ClientObjectReference.RefType.Client;

public bool TryGetEyeCoords([NotNullWhen(true)] out MapCoordinates? coords) {
switch (_eyeRef.Type) {
default:
coords = null;
return false;
case ClientObjectReference.RefType.Turf:
coords = new(new(_eyeRef.TurfX, _eyeRef.TurfY), new(_eyeRef.TurfZ));
return true;
case ClientObjectReference.RefType.Entity:
var eyeUid = _entityManager.GetEntity(_eyeRef.Entity);
if (!_xformQuery.TryGetComponent(eyeUid, out var eyeTransform)) {
coords = null;
return false;
}

coords = _transformSystem.GetMapCoordinates(eyeUid, eyeTransform);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return true;
}
}

public DreamMobSightComponent? GetSight(EntityUid mob) {
DreamMobSightComponent? res;
switch (_eyeRef.Type) {
default:
return null;
case ClientObjectReference.RefType.Turf:
_mobSightQuery.TryGetComponent(mob, out res);
return res;
case ClientObjectReference.RefType.Entity:
var eyeUid = _entityManager.GetEntity(_eyeRef.Entity);
_mobSightQuery.TryGetComponent(eyeUid, out res);
return res;
}
}
}
Loading
Loading