]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Hotfix: Camera offset for "Separated Chat" HUD fix & targetting fix (#35087)
authorSlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Mon, 18 Aug 2025 18:22:02 +0000 (20:22 +0200)
committerGitHub <noreply@github.com>
Mon, 18 Aug 2025 18:22:02 +0000 (11:22 -0700)
* Initial commit

* Fix everything

* Comment and remove unused dependencies

* Update comments for consistency

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
Content.Client/Movement/Systems/ContentEyeSystem.cs
Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs
Content.Client/Wieldable/WieldableSystem.cs

index 518a4a1bd40afaf32ab5b95be64c7c2df5079390..a332d25f9a16d58adb61793eb6b4860ca7b88bad 100644 (file)
@@ -1,7 +1,6 @@
 using System.Numerics;
 using Content.Shared.Movement.Components;
 using Content.Shared.Movement.Systems;
-using Robust.Client.GameObjects;
 using Robust.Client.Player;
 
 namespace Content.Client.Movement.Systems;
@@ -63,4 +62,15 @@ public sealed class ContentEyeSystem : SharedContentEyeSystem
             UpdateEyeOffset((entity, eyeComponent));
         }
     }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+        // TODO: Ideally we wouldn't want this to run in both FrameUpdate and Update, but we kind of have to since the visual update happens in FrameUpdate, but interaction update happens in Update. It's a workaround and a better solution should be found.
+        var eyeEntities = AllEntityQuery<ContentEyeComponent, EyeComponent>();
+        while (eyeEntities.MoveNext(out var entity, out ContentEyeComponent? contentComponent, out EyeComponent? eyeComponent))
+        {
+            UpdateEyeOffset((entity, eyeComponent));
+        }
+    }
 }
index eb524cf4ee2d02e6ac974702d658d179f67a05d5..174ae2dd970c6a740e1fe6398072bd81d128e53c 100644 (file)
@@ -1,10 +1,10 @@
 using System.Numerics;
 using Content.Client.Movement.Components;
+using Content.Client.Viewport;
 using Content.Shared.Camera;
 using Robust.Client.Graphics;
 using Robust.Client.Input;
 using Robust.Shared.Map;
-using Robust.Client.Player;
 
 namespace Content.Client.Movement.Systems;
 
@@ -12,13 +12,10 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
 {
     [Dependency] private readonly IEyeManager _eyeManager = default!;
     [Dependency] private readonly IInputManager _inputManager = default!;
-    [Dependency] private readonly IPlayerManager _player = default!;
-    [Dependency] private readonly SharedTransformSystem _transform = default!;
-    [Dependency] private readonly IClyde _clyde = default!;
 
     // This value is here to make sure the user doesn't have to move their mouse
     // all the way out to the edge of the screen to get the full offset.
-    static private float _edgeOffset = 0.9f;
+    private static float _edgeOffset = 0.8f;
 
     public override void Initialize()
     {
@@ -38,25 +35,29 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
 
     public Vector2? OffsetAfterMouse(EntityUid uid, EyeCursorOffsetComponent? component)
     {
-        var localPlayer = _player.LocalEntity;
-        var mousePos = _inputManager.MouseScreenPosition;
-        var screenSize = _clyde.MainWindow.Size;
-        var minValue = MathF.Min(screenSize.X / 2, screenSize.Y / 2) * _edgeOffset;
+        // We need the main viewport where the game content is displayed, as certain UI layouts (e.g. Separated HUD) can make it a different size to the game window.
+        if (_eyeManager.MainViewport is not ScalingViewport vp)
+            return null;
 
-        var mouseNormalizedPos = new Vector2(-(mousePos.X - screenSize.X / 2) / minValue, (mousePos.Y - screenSize.Y / 2) / minValue); // X needs to be inverted here for some reason, otherwise it ends up flipped.
+        var mousePos = _inputManager.MouseScreenPosition.Position; // TODO: If we ever get a right-aligned Separated HUD setting, this might need to be adjusted for that.
 
-        if (localPlayer == null)
-            return null;
+        var viewportSize = vp.PixelSize; // The size of the game viewport, including black bars - does not include the chatbox in Separated HUD view.
+        var scalingViewportSize = vp.ViewportSize * vp.CurrentRenderScale; // The size of the viewport in which the game is rendered (i.e. not including black bars). Note! Can extend outside the game window with certain zoom settings!
+        var visibleViewportSize = Vector2.Min(viewportSize, scalingViewportSize); // The size of the game viewport that is "actually visible" to the player, cutting off over-extensions and not counting black bar padding.
+
+        Matrix3x2.Invert(_eyeManager.MainViewport.GetLocalToScreenMatrix(), out var matrix);
+        var mouseCoords = Vector2.Transform(mousePos, matrix); // Gives the mouse position inside of the *scaling viewport*, i.e. 0,0 is inside the black bars. Note! 0,0 can be outside the game window with certain zoom settings!
 
-        var playerPos = _transform.GetWorldPosition(localPlayer.Value);
+        var boundedMousePos = Vector2.Clamp(Vector2.Min(mouseCoords, mousePos), Vector2.Zero, visibleViewportSize); // Mouse position inside the visible game viewport's bounds.
+
+        var offsetRadius = MathF.Min(visibleViewportSize.X / 2f, visibleViewportSize.Y / 2f) * _edgeOffset;
+        var mouseNormalizedPos = new Vector2(-(boundedMousePos.X - visibleViewportSize.X / 2f) / offsetRadius, (boundedMousePos.Y - visibleViewportSize.Y / 2f) / offsetRadius);
 
         if (component == null)
-        {
             component = EnsureComp<EyeCursorOffsetComponent>(uid);
-        }
 
         // Doesn't move the offset if the mouse has left the game window!
-        if (mousePos.Window != WindowId.Invalid)
+        if (_inputManager.MouseScreenPosition.Window != WindowId.Invalid)
         {
             // The offset must account for the in-world rotation.
             var eyeRotation = _eyeManager.CurrentEye.Rotation;
@@ -77,7 +78,7 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
                 Vector2 vectorOffset = component.TargetPosition - component.CurrentPosition;
                 if (vectorOffset.Length() > component.OffsetSpeed)
                 {
-                    vectorOffset = vectorOffset.Normalized() * component.OffsetSpeed;
+                    vectorOffset = vectorOffset.Normalized() * component.OffsetSpeed; // TODO: Probably needs to properly account for time delta or something.
                 }
                 component.CurrentPosition += vectorOffset;
             }
index 2de837923c877d70b00ee7565db616f3ea064c14..e40544e39dc3c82a2a4261716129f47c759a396e 100644 (file)
@@ -29,7 +29,10 @@ public sealed class WieldableSystem : SharedWieldableSystem
             return;
 
         if (_gameTiming.IsFirstTimePredicted)
+        {
             cursorOffsetComp.CurrentPosition = Vector2.Zero;
+            cursorOffsetComp.TargetPosition = Vector2.Zero;
+        }
     }
 
     public void OnGetEyeOffset(Entity<CursorOffsetRequiresWieldComponent> entity, ref HeldRelayedEvent<GetEyeOffsetRelayedEvent> args)