diff --git a/Content.Client/Radar/RadarConsoleWindow.xaml b/Content.Client/Radar/RadarConsoleWindow.xaml deleted file mode 100644 index 92e25bf7fcfb..000000000000 --- a/Content.Client/Radar/RadarConsoleWindow.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/Content.Client/Radar/RadarConsoleWindow.xaml.cs b/Content.Client/Radar/RadarConsoleWindow.xaml.cs deleted file mode 100644 index 048541ebe4b9..000000000000 --- a/Content.Client/Radar/RadarConsoleWindow.xaml.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using Content.Client.Computer; -using Content.Shared.Radar; -using JetBrains.Annotations; -using Robust.Client.AutoGenerated; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.CustomControls; -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Maths; - -namespace Content.Client.Radar; - -[GenerateTypedNameReferences] -public sealed partial class RadarConsoleWindow : DefaultWindow, IComputerWindow -{ - public RadarConsoleWindow() - { - RobustXamlLoader.Load(this); - } - - public void SetupComputerWindow(ComputerBoundUserInterfaceBase cb) - { - - } - - public void UpdateState(RadarConsoleBoundInterfaceState scc) - { - Radar.UpdateState(scc); - } -} - - -public sealed class RadarControl : Control -{ - private const int MinimapRadius = 256; - private const int MinimapMargin = 4; - private const float GridLinesDistance = 32f; - - private float _radarRange = 256f; - private RadarConsoleBoundInterfaceState _lastState = new(256f, Array.Empty()); - - private int SizeFull => (int) ((MinimapRadius + MinimapMargin) * 2 * UIScale); - private int ScaledMinimapRadius => (int) (MinimapRadius * UIScale); - private float MinimapScale => _radarRange != 0 ? ScaledMinimapRadius / _radarRange : 0f; - - public RadarControl() - { - MinSize = (SizeFull, SizeFull); - } - - public void UpdateState(RadarConsoleBoundInterfaceState ls) - { - if (!_radarRange.Equals(ls.Range)) - { - _radarRange = ls.Range; - } - - _lastState = ls; - } - - protected override void Draw(DrawingHandleScreen handle) - { - var point = SizeFull / 2; - var fakeAA = new Color(0.08f, 0.08f, 0.08f); - var gridLines = new Color(0.08f, 0.08f, 0.08f); - var gridLinesRadial = 8; - var gridLinesEquatorial = (int) Math.Floor(_radarRange / GridLinesDistance); - - handle.DrawCircle((point, point), ScaledMinimapRadius + 1, fakeAA); - handle.DrawCircle((point, point), ScaledMinimapRadius, Color.Black); - - for (var i = 1; i < gridLinesEquatorial + 1; i++) - { - handle.DrawCircle((point, point), GridLinesDistance * MinimapScale * i, gridLines, false); - } - - for (var i = 0; i < gridLinesRadial; i++) - { - Angle angle = (Math.PI / gridLinesRadial) * i; - var aExtent = angle.ToVec() * ScaledMinimapRadius; - handle.DrawLine((point, point) - aExtent, (point, point) + aExtent, gridLines); - } - - handle.DrawLine((point, point) + new Vector2(8, 8), (point, point) - new Vector2(0, 8), Color.Yellow); - handle.DrawLine((point, point) + new Vector2(-8, 8), (point, point) - new Vector2(0, 8), Color.Yellow); - - foreach (var obj in _lastState.Objects) - { - var minimapPos = obj.Position * MinimapScale; - var radius = obj.Radius * MinimapScale; - - if (minimapPos.Length + radius > ScaledMinimapRadius) - continue; - - switch (obj.Shape) - { - case RadarObjectShape.CircleFilled: - case RadarObjectShape.Circle: - { - handle.DrawCircle(minimapPos + point, radius, obj.Color, obj.Shape == RadarObjectShape.CircleFilled); - break; - } - default: - throw new NotImplementedException(); - } - } - } -} - -[UsedImplicitly] -public sealed class RadarConsoleBoundUserInterface : ComputerBoundUserInterface -{ - public RadarConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {} -} diff --git a/Content.Client/Shuttles/BUI/RadarConsoleBoundUserInterface.cs b/Content.Client/Shuttles/BUI/RadarConsoleBoundUserInterface.cs new file mode 100644 index 000000000000..4c5b4edea8ab --- /dev/null +++ b/Content.Client/Shuttles/BUI/RadarConsoleBoundUserInterface.cs @@ -0,0 +1,29 @@ +using Content.Client.Shuttles.UI; +using Content.Shared.Shuttles.BUIStates; +using JetBrains.Annotations; +using Robust.Client.GameObjects; + +namespace Content.Client.Shuttles.BUI; + +[UsedImplicitly] +public sealed class RadarConsoleBoundUserInterface : BoundUserInterface +{ + private RadarConsoleWindow? _window; + + public RadarConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {} + + protected override void Open() + { + base.Open(); + _window = new RadarConsoleWindow(); + _window?.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (state is not RadarConsoleBoundInterfaceState cState) return; + + _window?.UpdateState(cState); + } +} diff --git a/Content.Client/Shuttles/BUI/ShuttleConsoleBoundUserInterface.cs b/Content.Client/Shuttles/BUI/ShuttleConsoleBoundUserInterface.cs new file mode 100644 index 000000000000..cb60508c310c --- /dev/null +++ b/Content.Client/Shuttles/BUI/ShuttleConsoleBoundUserInterface.cs @@ -0,0 +1,70 @@ +using Content.Client.Shuttles.UI; +using Content.Shared.Shuttles.BUIStates; +using Content.Shared.Shuttles.Components; +using Content.Shared.Shuttles.Events; +using JetBrains.Annotations; +using Robust.Client.GameObjects; + +namespace Content.Client.Shuttles.BUI; + +[UsedImplicitly] +public sealed class ShuttleConsoleBoundUserInterface : BoundUserInterface +{ + private ShuttleConsoleWindow? _window; + + public ShuttleConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {} + + protected override void Open() + { + base.Open(); + _window = new ShuttleConsoleWindow(); + _window.ShuttleModePressed += OnShuttleModePressed; + _window.UndockPressed += OnUndockPressed; + _window.StartAutodockPressed += OnAutodockPressed; + _window.StopAutodockPressed += OnStopAutodockPressed; + _window.OpenCentered(); + _window.OnClose += OnClose; + } + + private void OnClose() + { + Close(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _window?.Dispose(); + } + } + + private void OnStopAutodockPressed(EntityUid obj) + { + SendMessage(new StopAutodockRequestMessage() {Entity = obj}); + } + + private void OnAutodockPressed(EntityUid obj) + { + SendMessage(new AutodockRequestMessage() {Entity = obj}); + } + + private void OnUndockPressed(EntityUid obj) + { + SendMessage(new UndockRequestMessage() {Entity = obj}); + } + + private void OnShuttleModePressed(ShuttleMode obj) + { + SendMessage(new ShuttleModeRequestMessage() {Mode = obj}); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (state is not ShuttleConsoleBoundInterfaceState cState) return; + _window?.UpdateState(cState); + } +} diff --git a/Content.Client/Shuttles/ShuttleConsoleComponent.cs b/Content.Client/Shuttles/ShuttleConsoleComponent.cs index 86c4410b8698..da6b9147948b 100644 --- a/Content.Client/Shuttles/ShuttleConsoleComponent.cs +++ b/Content.Client/Shuttles/ShuttleConsoleComponent.cs @@ -1,13 +1,7 @@ -using Content.Shared.Shuttles; using Content.Shared.Shuttles.Components; -using Robust.Shared.GameObjects; namespace Content.Client.Shuttles { [RegisterComponent] - [ComponentReference(typeof(SharedShuttleConsoleComponent))] - internal sealed class ShuttleConsoleComponent : SharedShuttleConsoleComponent - { - - } + public sealed class ShuttleConsoleComponent : SharedShuttleConsoleComponent {} } diff --git a/Content.Client/Shuttles/ShuttleConsoleSystem.cs b/Content.Client/Shuttles/ShuttleConsoleSystem.cs deleted file mode 100644 index cb0f6f7b0c1e..000000000000 --- a/Content.Client/Shuttles/ShuttleConsoleSystem.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Shared.Shuttles; - -namespace Content.Client.Shuttles -{ - internal sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem - { - - } -} diff --git a/Content.Client/Shuttles/Systems/DockingSystem.cs b/Content.Client/Shuttles/Systems/DockingSystem.cs new file mode 100644 index 000000000000..61f0b8d06c96 --- /dev/null +++ b/Content.Client/Shuttles/Systems/DockingSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared.Shuttles.Events; + +namespace Content.Client.Shuttles.Systems; + +public sealed class DockingSystem : EntitySystem +{ + public void StartAutodock(EntityUid uid) + { + RaiseNetworkEvent(new AutodockRequestMessage {Entity = uid}); + } + + public void StopAutodock(EntityUid uid) + { + RaiseNetworkEvent(new StopAutodockRequestMessage() {Entity = uid}); + } + + public void Undock(EntityUid uid) + { + RaiseNetworkEvent(new UndockRequestMessage() {Entity = uid}); + } +} diff --git a/Content.Client/Shuttles/Systems/RadarConsoleSystem.cs b/Content.Client/Shuttles/Systems/RadarConsoleSystem.cs new file mode 100644 index 000000000000..0a0ddcf2eef4 --- /dev/null +++ b/Content.Client/Shuttles/Systems/RadarConsoleSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Shuttles.Systems; + +namespace Content.Client.Shuttles.Systems; + +public sealed class RadarConsoleSystem : SharedRadarConsoleSystem +{ + +} diff --git a/Content.Client/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Client/Shuttles/Systems/ShuttleConsoleSystem.cs new file mode 100644 index 000000000000..7ae419c358b3 --- /dev/null +++ b/Content.Client/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -0,0 +1,37 @@ +using Content.Shared.Shuttles.Components; +using Content.Shared.Shuttles.Events; +using Content.Shared.Shuttles.Systems; +using Robust.Shared.GameStates; + +namespace Content.Client.Shuttles.Systems +{ + public sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem + { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnHandleState); + } + + private void OnHandleState(EntityUid uid, PilotComponent component, ref ComponentHandleState args) + { + if (args.Current is not PilotComponentState state) return; + + var console = state.Console.GetValueOrDefault(); + if (!console.IsValid()) + { + component.Console = null; + return; + } + + if (!TryComp(console, out var shuttleConsoleComponent)) + { + Logger.Warning($"Unable to set Helmsman console to {console}"); + return; + } + + component.Console = shuttleConsoleComponent; + ActionBlockerSystem.UpdateCanMove(uid); + } + } +} diff --git a/Content.Client/Shuttles/UI/DockingControl.cs b/Content.Client/Shuttles/UI/DockingControl.cs new file mode 100644 index 000000000000..dfa5023a8a57 --- /dev/null +++ b/Content.Client/Shuttles/UI/DockingControl.cs @@ -0,0 +1,251 @@ +using Content.Shared.Shuttles.BUIStates; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; + +namespace Content.Client.Shuttles.UI; + +/// +/// Displays the docking view from a specific docking port +/// +[Virtual] +public class DockingControl : Control +{ + private readonly IEntityManager _entManager; + private readonly IMapManager _mapManager; + + private const int MinimapRadius = 384; + private const int MinimapMargin = 4; + + private float _range = 8f; + private float _rangeSquared = 0f; + private const float GridLinesDistance = 32f; + + private int MidPoint => SizeFull / 2; + private int SizeFull => (int) ((MinimapRadius + MinimapMargin) * 2 * UIScale); + private int ScaledMinimapRadius => (int) (MinimapRadius * UIScale); + private float MinimapScale => _range != 0 ? ScaledMinimapRadius / _range : 0f; + + public EntityUid? ViewedDock; + public EntityUid? GridEntity; + + /// + /// Stored by GridID then by docks + /// + public Dictionary> Docks = new(); + + public DockingControl() + { + _entManager = IoCManager.Resolve(); + _mapManager = IoCManager.Resolve(); + _rangeSquared = _range * _range; + MinSize = (SizeFull, SizeFull); + } + + protected override void Draw(DrawingHandleScreen handle) + { + base.Draw(handle); + + var fakeAA = new Color(0.08f, 0.08f, 0.08f); + + handle.DrawCircle((MidPoint, MidPoint), ScaledMinimapRadius + 1, fakeAA); + handle.DrawCircle((MidPoint, MidPoint), ScaledMinimapRadius, Color.Black); + + var gridLines = new Color(0.08f, 0.08f, 0.08f); + var gridLinesRadial = 8; + var gridLinesEquatorial = (int) Math.Floor(_range / GridLinesDistance); + + for (var i = 1; i < gridLinesEquatorial + 1; i++) + { + handle.DrawCircle((MidPoint, MidPoint), GridLinesDistance * MinimapScale * i, gridLines, false); + } + + for (var i = 0; i < gridLinesRadial; i++) + { + Angle angle = (Math.PI / gridLinesRadial) * i; + var aExtent = angle.ToVec() * ScaledMinimapRadius; + handle.DrawLine((MidPoint, MidPoint) - aExtent, (MidPoint, MidPoint) + aExtent, gridLines); + } + + if (!_entManager.TryGetComponent(ViewedDock, out var xform) || + !_entManager.TryGetComponent(GridEntity, out var gridXform)) return; + + var rotation = Matrix3.CreateRotation(xform.LocalRotation); + var matrix = Matrix3.CreateTranslation(-xform.LocalPosition); + + // Draw the fixtures around the dock before drawing it + if (_entManager.TryGetComponent(GridEntity, out var fixtures)) + { + foreach (var (_, fixture) in fixtures.Fixtures) + { + var poly = (PolygonShape) fixture.Shape; + + for (var i = 0; i < poly.VertexCount; i++) + { + var start = matrix.Transform(poly.Vertices[i]); + var end = matrix.Transform(poly.Vertices[(i + 1) % poly.VertexCount]); + + var startOut = start.LengthSquared > _rangeSquared; + var endOut = end.LengthSquared > _rangeSquared; + + // We need to draw to the radar border so we'll cap the range, + // but if none of the verts are in range then just leave it. + if (startOut && endOut) + continue; + + start.Y = -start.Y; + end.Y = -end.Y; + + // If start is outside we draw capped from end to start + if (startOut) + { + // It's called Jobseeker now. + if (!MathHelper.TryGetIntersecting(start, end, _range, out var newStart)) continue; + start = newStart.Value; + } + // otherwise vice versa + else if (endOut) + { + if (!MathHelper.TryGetIntersecting(end, start, _range, out var newEnd)) continue; + end = newEnd.Value; + } + + handle.DrawLine(ScalePosition(start), ScalePosition(end), Color.Goldenrod); + } + } + } + + // Draw the dock's collision + handle.DrawRect(new UIBox2( + ScalePosition(rotation.Transform(new Vector2(-0.2f, -0.7f))), + ScalePosition(rotation.Transform(new Vector2(0.2f, -0.5f)))), Color.Aquamarine); + + // Draw the dock itself + handle.DrawRect(new UIBox2( + ScalePosition(rotation.Transform(new Vector2(-0.5f, 0.5f))), + ScalePosition(rotation.Transform(new Vector2(0.5f, -0.5f)))), Color.Green); + + // Draw nearby grids + var worldPos = gridXform.WorldMatrix.Transform(xform.LocalPosition); + var gridInvMatrix = gridXform.InvWorldMatrix; + Matrix3.Multiply(in gridInvMatrix, in matrix, out var invMatrix); + + // TODO: Getting some overdraw so need to fix that. + + foreach (var grid in _mapManager.FindGridsIntersecting(gridXform.MapID, + new Box2(worldPos - _range, worldPos + _range))) + { + if (grid.GridEntityId == GridEntity) continue; + + // Draw the fixtures before drawing any docks in range. + if (!_entManager.TryGetComponent(grid.GridEntityId, out var gridFixtures)) continue; + + var gridMatrix = grid.WorldMatrix; + + Matrix3.Multiply(in gridMatrix, in invMatrix, out var matty); + + foreach (var (_, fixture) in gridFixtures.Fixtures) + { + var poly = (PolygonShape) fixture.Shape; + + for (var i = 0; i < poly.VertexCount; i++) + { + // This is because the same line might be on different fixtures so we don't want to draw it twice. + var startPos = poly.Vertices[i]; + var endPos = poly.Vertices[(i + 1) % poly.VertexCount]; + + var start = matty.Transform(startPos); + var end = matty.Transform(endPos); + + var startOut = start.LengthSquared > _rangeSquared; + var endOut = end.LengthSquared > _rangeSquared; + + // We need to draw to the radar border so we'll cap the range, + // but if none of the verts are in range then just leave it. + if (startOut && endOut) + continue; + + start.Y = -start.Y; + end.Y = -end.Y; + + // If start is outside we draw capped from end to start + if (startOut) + { + // It's called Jobseeker now. + if (!MathHelper.TryGetIntersecting(start, end, _range, out var newStart)) continue; + start = newStart.Value; + } + // otherwise vice versa + else if (endOut) + { + if (!MathHelper.TryGetIntersecting(end, start, _range, out var newEnd)) continue; + end = newEnd.Value; + } + + handle.DrawLine(ScalePosition(start), ScalePosition(end), Color.Aquamarine); + } + } + + // Draw any docks on that grid + if (Docks.TryGetValue(grid.GridEntityId, out var gridDocks)) + { + foreach (var dock in gridDocks) + { + var position = matty.Transform(dock.Coordinates.Position); + + if (position.Length > _range - 0.8f) continue; + + var otherDockRotation = Matrix3.CreateRotation(dock.Angle); + + // Draw the dock's collision + var verts = new[] + { + matty.Transform(dock.Coordinates.Position + + otherDockRotation.Transform(new Vector2(-0.2f, -0.7f))), + matty.Transform(dock.Coordinates.Position + + otherDockRotation.Transform(new Vector2(0.2f, -0.7f))), + matty.Transform(dock.Coordinates.Position + + otherDockRotation.Transform(new Vector2(0.2f, -0.5f))), + matty.Transform(dock.Coordinates.Position + + otherDockRotation.Transform(new Vector2(-0.2f, -0.5f))), + }; + + for (var i = 0; i < verts.Length; i++) + { + var vert = verts[i]; + vert.Y = -vert.Y; + verts[i] = ScalePosition(vert); + } + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, Color.Turquoise); + + // Draw the dock itself + verts = new[] + { + matty.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f)), + matty.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f)), + matty.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f)), + matty.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f)), + }; + + for (var i = 0; i < verts.Length; i++) + { + var vert = verts[i]; + vert.Y = -vert.Y; + verts[i] = ScalePosition(vert); + } + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, Color.Green); + } + } + } + + } + + private Vector2 ScalePosition(Vector2 value) + { + return value * MinimapScale + MidPoint; + } +} diff --git a/Content.Client/Shuttles/UI/RadarConsoleWindow.xaml b/Content.Client/Shuttles/UI/RadarConsoleWindow.xaml new file mode 100644 index 000000000000..7591034b921c --- /dev/null +++ b/Content.Client/Shuttles/UI/RadarConsoleWindow.xaml @@ -0,0 +1,6 @@ + + + diff --git a/Content.Client/Shuttles/UI/RadarConsoleWindow.xaml.cs b/Content.Client/Shuttles/UI/RadarConsoleWindow.xaml.cs new file mode 100644 index 000000000000..7274cc23c8f7 --- /dev/null +++ b/Content.Client/Shuttles/UI/RadarConsoleWindow.xaml.cs @@ -0,0 +1,22 @@ +using Content.Client.Computer; +using Content.Client.UserInterface; +using Content.Shared.Shuttles.BUIStates; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Shuttles.UI; + +[GenerateTypedNameReferences] +public sealed partial class RadarConsoleWindow : FancyWindow, + IComputerWindow +{ + public RadarConsoleWindow() + { + RobustXamlLoader.Load(this); + } + + public void UpdateState(RadarConsoleBoundInterfaceState scc) + { + RadarScreen.UpdateState(scc); + } +} diff --git a/Content.Client/Shuttles/UI/RadarControl.cs b/Content.Client/Shuttles/UI/RadarControl.cs new file mode 100644 index 000000000000..f8d2f8c6e87f --- /dev/null +++ b/Content.Client/Shuttles/UI/RadarControl.cs @@ -0,0 +1,348 @@ +using Content.Client.Stylesheets; +using Content.Shared.Shuttles.BUIStates; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Utility; + +namespace Content.Client.Shuttles.UI; + +/// +/// Displays nearby grids inside of a control. +/// +public sealed class RadarControl : Control +{ + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + + private const float ScrollSensitivity = 8f; + + private const int MinimapRadius = 384; + private const int MinimapMargin = 4; + private const float GridLinesDistance = 32f; + + /// + /// Entity used to transform all of the radar objects. + /// + private EntityUid? _entity; + + private float _radarMinRange = 64f; + private float _radarMaxRange = 256f; + public float RadarRange { get; private set; } = 256f; + + private int MidPoint => SizeFull / 2; + private int SizeFull => (int) ((MinimapRadius + MinimapMargin) * 2 * UIScale); + private int ScaledMinimapRadius => (int) (MinimapRadius * UIScale); + private float MinimapScale => RadarRange != 0 ? ScaledMinimapRadius / RadarRange : 0f; + + /// + /// Shows a label on each radar object. + /// + private Dictionary _iffControls = new(); + + private Dictionary> _docks = new(); + + public bool ShowIFF { get; set; } = true; + public bool ShowDocks { get; set; } = true; + + /// + /// Currently hovered docked to show on the map. + /// + public EntityUid? HighlightedDock; + + public Action? OnRadarRangeChanged; + + public RadarControl() + { + IoCManager.InjectDependencies(this); + MinSize = (SizeFull, SizeFull); + } + + public void UpdateState(RadarConsoleBoundInterfaceState ls) + { + _radarMaxRange = ls.MaxRange; + + if (_radarMaxRange < RadarRange) + { + RadarRange = _radarMaxRange; + OnRadarRangeChanged?.Invoke(RadarRange); + } + + if (_radarMaxRange < _radarMinRange) + _radarMinRange = _radarMaxRange; + + _entity = ls.Entity; + _docks.Clear(); + + foreach (var state in ls.Docks) + { + var coordinates = state.Coordinates; + var grid = _docks.GetOrNew(coordinates.EntityId); + grid.Add(state.Entity, state); + } + } + + protected override void MouseWheel(GUIMouseWheelEventArgs args) + { + base.MouseWheel(args); + AddRadarRange(-args.Delta.Y * ScrollSensitivity); + } + + public void AddRadarRange(float value) + { + var oldValue = RadarRange; + RadarRange = MathF.Max(0f, MathF.Max(_radarMinRange, MathF.Min(RadarRange + value, _radarMaxRange))); + + if (oldValue.Equals(RadarRange)) return; + + OnRadarRangeChanged?.Invoke(RadarRange); + } + + protected override void Draw(DrawingHandleScreen handle) + { + var fakeAA = new Color(0.08f, 0.08f, 0.08f); + + handle.DrawCircle((MidPoint, MidPoint), ScaledMinimapRadius + 1, fakeAA); + handle.DrawCircle((MidPoint, MidPoint), ScaledMinimapRadius, Color.Black); + + // No data + if (_entity == null) + { + Clear(); + return; + } + + var gridLines = new Color(0.08f, 0.08f, 0.08f); + var gridLinesRadial = 8; + var gridLinesEquatorial = (int) Math.Floor(RadarRange / GridLinesDistance); + + for (var i = 1; i < gridLinesEquatorial + 1; i++) + { + handle.DrawCircle((MidPoint, MidPoint), GridLinesDistance * MinimapScale * i, gridLines, false); + } + + for (var i = 0; i < gridLinesRadial; i++) + { + Angle angle = (Math.PI / gridLinesRadial) * i; + var aExtent = angle.ToVec() * ScaledMinimapRadius; + handle.DrawLine((MidPoint, MidPoint) - aExtent, (MidPoint, MidPoint) + aExtent, gridLines); + } + + var metaQuery = _entManager.GetEntityQuery(); + var xformQuery = _entManager.GetEntityQuery(); + var fixturesQuery = _entManager.GetEntityQuery(); + var bodyQuery = _entManager.GetEntityQuery(); + var xform = xformQuery.GetComponent(_entity.Value); + var mapPosition = xform.MapPosition; + + if (mapPosition.MapId == MapId.Nullspace) + { + Clear(); + return; + } + + // Can also use ourGridBody.LocalCenter + var offset = xform.Coordinates.Position; + var offsetMatrix = Matrix3.CreateTranslation(-offset); + Matrix3 matrix; + + // Draw our grid in detail + var ourGridId = xform.GridID; + if (ourGridId != GridId.Invalid) + { + matrix = xform.InvWorldMatrix; + var ourGridFixtures = fixturesQuery.GetComponent(ourGridId); + // Draw our grid; use non-filled boxes so it doesn't look awful. + DrawGrid(handle, offsetMatrix, ourGridFixtures, Color.Yellow); + + DrawDocks(handle, xform.GridEntityId, offsetMatrix); + } + else + { + matrix = Matrix3.CreateTranslation(-offset); + } + + var invertedPosition = xform.Coordinates.Position - offset; + invertedPosition.Y = -invertedPosition.Y; + // Don't need to transform the InvWorldMatrix again as it's already offset to its position. + + // Draw radar position on the station + handle.DrawCircle(ScalePosition(invertedPosition), 5f, Color.Lime); + + var shown = new HashSet(); + + // Draw other grids... differently + foreach (var grid in _mapManager.FindGridsIntersecting(mapPosition.MapId, + new Box2(mapPosition.Position - RadarRange, mapPosition.Position + RadarRange))) + { + if (grid.Index == ourGridId) continue; + + var gridBody = bodyQuery.GetComponent(grid.GridEntityId); + if (gridBody.Mass < 10f) + { + ClearLabel(grid.GridEntityId); + continue; + } + + shown.Add(grid.GridEntityId); + var name = metaQuery.GetComponent(grid.GridEntityId).EntityName; + + if (name == string.Empty) + name = Loc.GetString("shuttle-console-unknown"); + + var gridXform = xformQuery.GetComponent(grid.GridEntityId); + var gridFixtures = fixturesQuery.GetComponent(grid.GridEntityId); + var gridMatrix = gridXform.WorldMatrix; + Matrix3.Multiply(in gridMatrix, in matrix, out var matty); + + if (ShowIFF) + { + if (!_iffControls.TryGetValue(grid.GridEntityId, out var control)) + { + var label = new Label() + { + HorizontalAlignment = HAlignment.Left, + }; + + control = new PanelContainer() + { + HorizontalAlignment = HAlignment.Left, + VerticalAlignment = VAlignment.Top, + Children = { label }, + StyleClasses = { StyleNano.StyleClassTooltipPanel }, + }; + + _iffControls[grid.GridEntityId] = control; + AddChild(control); + } + + var gridCentre = matty.Transform(gridBody.LocalCenter); + gridCentre.Y = -gridCentre.Y; + + // TODO: When we get IFF or whatever we can show controls at a further distance; for now + // we don't do that because it would immediately reveal nukies. + if (gridCentre.Length < RadarRange) + { + control.Visible = true; + var label = (Label) control.GetChild(0); + label.Text = Loc.GetString("shuttle-console-iff-label", ("name", name), ("distance", $"{gridCentre.Length:0.0}")); + LayoutContainer.SetPosition(control, ScalePosition(gridCentre) / UIScale); + } + else + { + control.Visible = false; + } + } + else + { + ClearLabel(grid.GridEntityId); + } + + // Detailed view + DrawGrid(handle, matty, gridFixtures, Color.Aquamarine); + + DrawDocks(handle, grid.GridEntityId, matty); + } + + foreach (var (ent, _) in _iffControls) + { + if (shown.Contains(ent)) continue; + ClearLabel(ent); + } + } + + private void Clear() + { + foreach (var (_, label) in _iffControls) + { + label.Dispose(); + } + + _iffControls.Clear(); + } + + private void ClearLabel(EntityUid uid) + { + if (!_iffControls.TryGetValue(uid, out var label)) return; + label.Dispose(); + _iffControls.Remove(uid); + } + + private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3 matrix) + { + if (!ShowDocks) return; + + const float DockScale = 1.2f; + + if (_docks.TryGetValue(uid, out var docks)) + { + foreach (var (ent, state) in docks) + { + var position = state.Coordinates.Position; + var uiPosition = matrix.Transform(position); + + if (uiPosition.Length > RadarRange - DockScale) continue; + + var color = HighlightedDock == ent ? Color.Magenta : Color.DarkViolet; + + uiPosition.Y = -uiPosition.Y; + + var verts = new[] + { + matrix.Transform(position + new Vector2(-DockScale, -DockScale)), + matrix.Transform(position + new Vector2(DockScale, -DockScale)), + matrix.Transform(position + new Vector2(DockScale, DockScale)), + matrix.Transform(position + new Vector2(-DockScale, DockScale)), + }; + + for (var i = 0; i < verts.Length; i++) + { + var vert = verts[i]; + vert.Y = -vert.Y; + verts[i] = ScalePosition(vert); + } + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, color); + } + } + } + + private void DrawGrid(DrawingHandleScreen handle, Matrix3 matrix, FixturesComponent component, Color color) + { + foreach (var (_, fixture) in component.Fixtures) + { + // If the fixture has any points out of range we won't draw any of it. + var invalid = false; + var poly = (PolygonShape) fixture.Shape; + var verts = new Vector2[poly.VertexCount + 1]; + + for (var i = 0; i < poly.VertexCount; i++) + { + var vert = matrix.Transform(poly.Vertices[i]); + + if (vert.Length > RadarRange) + { + invalid = true; + break; + } + + vert.Y = -vert.Y; + verts[i] = ScalePosition(vert); + } + + if (invalid) continue; + + // Closed list + verts[poly.VertexCount] = verts[0]; + handle.DrawPrimitives(DrawPrimitiveTopology.LineStrip, verts, color); + } + } + + private Vector2 ScalePosition(Vector2 value) + { + return value * MinimapScale + MidPoint; + } +} diff --git a/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml b/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml new file mode 100644 index 000000000000..58d06e575ae1 --- /dev/null +++ b/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + +