From be4e69b0c078888d86cfe76835792628efa487ca Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 13 Apr 2023 16:21:24 +1000 Subject: [PATCH] Station maps (#13027) --- .../CrewMonitoringBoundUserInterface.cs | 24 +- .../CrewMonitoring/CrewMonitoringWindow.xaml | 43 ++- .../CrewMonitoringWindow.xaml.cs | 88 ++++- Content.Client/Pinpointer/NavMapSystem.cs | 96 +++++ ...inpointerSystem.cs => PinpointerSystem.cs} | 2 +- Content.Client/Pinpointer/UI/NavMapControl.cs | 333 ++++++++++++++++++ .../UI/StationMapBoundUserInterface.cs | 35 ++ .../Pinpointer/UI/StationMapWindow.xaml | 7 + .../Pinpointer/UI/StationMapWindow.xaml.cs | 24 ++ Content.Client/Shuttles/UI/DockingControl.cs | 17 +- Content.Client/Shuttles/UI/RadarControl.cs | 101 ++---- .../Shuttles/UI/ShuttleConsoleWindow.xaml.cs | 6 +- Content.Client/Stylesheets/StyleNano.cs | 4 +- .../UserInterface/Controls/MapGridControl.cs | 79 +++++ .../CrewMonitoringConsoleSystem.cs | 3 +- .../SuitSensors/SuitSensorComponent.cs | 6 +- .../Medical/SuitSensors/SuitSensorSystem.cs | 67 ++-- Content.Server/Pinpointer/NavMapSystem.cs | 158 +++++++++ ...inpointerSystem.cs => PinpointerSystem.cs} | 2 +- .../Pinpointer/StationMapComponent.cs | 17 + Content.Server/Pinpointer/StationMapSystem.cs | 43 +++ .../CrewMonitoring/CrewMonitoringShared.cs | 4 +- .../Medical/SuitSensor/SharedSuitSensor.cs | 2 +- Content.Shared/Pinpointer/NavMapComponent.cs | 29 ++ .../Pinpointer/SharedNavMapSystem.cs | 45 +++ .../Pinpointer/SharedStationMapSystem.cs | 9 + Content.Shared/Power/SharedPowerDevice.cs | 2 +- .../Locale/en-US/pinpointer/station_map.ftl | 1 + .../Objects/Devices/Circuitboards/misc.yml | 5 + .../Entities/Objects/Devices/station_map.yml | 33 ++ .../Specific/{Mining => Salvage}/ore_bag.yml | 0 .../Structures/Wallmounts/station_map.yml | 76 ++++ .../Objects/Devices/tablets.rsi/generic.png | Bin 0 -> 271 bytes .../Objects/Devices/tablets.rsi/meta.json | 1 + .../Objects/Devices/tablets.rsi/tablet.png | Bin 0 -> 348 bytes .../Objects/Devices/tablets.rsi/tabletsol.png | Bin 0 -> 414 bytes .../Machines/station_map.rsi/meta.json | 1 + .../station_map.rsi/station_map-panel.png | Bin 0 -> 389 bytes .../Machines/station_map.rsi/station_map0.png | Bin 0 -> 484 bytes .../station_map.rsi/station_map_broken.png | Bin 0 -> 687 bytes .../station_map.rsi/station_map_frame0.png | Bin 0 -> 357 bytes .../station_map.rsi/station_map_frame1.png | Bin 0 -> 416 bytes .../station_map.rsi/station_map_frame2.png | Bin 0 -> 639 bytes .../station_map.rsi/station_map_frame3.png | Bin 0 -> 513 bytes .../Machines/station_map.rsi/unshaded.png | Bin 0 -> 6331 bytes 45 files changed, 1210 insertions(+), 153 deletions(-) create mode 100644 Content.Client/Pinpointer/NavMapSystem.cs rename Content.Client/Pinpointer/{ClientPinpointerSystem.cs => PinpointerSystem.cs} (97%) create mode 100644 Content.Client/Pinpointer/UI/NavMapControl.cs create mode 100644 Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs create mode 100644 Content.Client/Pinpointer/UI/StationMapWindow.xaml create mode 100644 Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs create mode 100644 Content.Client/UserInterface/Controls/MapGridControl.cs create mode 100644 Content.Server/Pinpointer/NavMapSystem.cs rename Content.Server/Pinpointer/{ServerPinpointerSystem.cs => PinpointerSystem.cs} (98%) create mode 100644 Content.Server/Pinpointer/StationMapComponent.cs create mode 100644 Content.Server/Pinpointer/StationMapSystem.cs create mode 100644 Content.Shared/Pinpointer/NavMapComponent.cs create mode 100644 Content.Shared/Pinpointer/SharedNavMapSystem.cs create mode 100644 Content.Shared/Pinpointer/SharedStationMapSystem.cs create mode 100644 Resources/Locale/en-US/pinpointer/station_map.ftl create mode 100644 Resources/Prototypes/Entities/Objects/Devices/Circuitboards/misc.yml create mode 100644 Resources/Prototypes/Entities/Objects/Devices/station_map.yml rename Resources/Prototypes/Entities/Objects/Specific/{Mining => Salvage}/ore_bag.yml (100%) create mode 100644 Resources/Prototypes/Entities/Structures/Wallmounts/station_map.yml create mode 100644 Resources/Textures/Objects/Devices/tablets.rsi/generic.png create mode 100644 Resources/Textures/Objects/Devices/tablets.rsi/meta.json create mode 100644 Resources/Textures/Objects/Devices/tablets.rsi/tablet.png create mode 100644 Resources/Textures/Objects/Devices/tablets.rsi/tabletsol.png create mode 100644 Resources/Textures/Structures/Machines/station_map.rsi/meta.json create mode 100644 Resources/Textures/Structures/Machines/station_map.rsi/station_map-panel.png create mode 100644 Resources/Textures/Structures/Machines/station_map.rsi/station_map0.png create mode 100644 Resources/Textures/Structures/Machines/station_map.rsi/station_map_broken.png create mode 100644 Resources/Textures/Structures/Machines/station_map.rsi/station_map_frame0.png create mode 100644 Resources/Textures/Structures/Machines/station_map.rsi/station_map_frame1.png create mode 100644 Resources/Textures/Structures/Machines/station_map.rsi/station_map_frame2.png create mode 100644 Resources/Textures/Structures/Machines/station_map.rsi/station_map_frame3.png create mode 100644 Resources/Textures/Structures/Machines/station_map.rsi/unshaded.png diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs index 8b37687a24..0943f89247 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs @@ -7,15 +7,25 @@ namespace Content.Client.Medical.CrewMonitoring { public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface { + private readonly IEntityManager _entManager; private CrewMonitoringWindow? _menu; - public CrewMonitoringBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] Enum uiKey) : base(owner, uiKey) + public CrewMonitoringBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) { + _entManager = IoCManager.Resolve(); } protected override void Open() { - _menu = new CrewMonitoringWindow(); + EntityUid? gridUid = null; + + if (_entManager.TryGetComponent(Owner.Owner, out var xform)) + { + gridUid = xform.GridUid; + } + + _menu = new CrewMonitoringWindow(gridUid); + _menu.OpenCentered(); _menu.OnClose += Close; } @@ -27,7 +37,15 @@ namespace Content.Client.Medical.CrewMonitoring switch (state) { case CrewMonitoringState st: - _menu?.ShowSensors(st.Sensors, st.WorldPosition, st.Snap, st.Precision); + _entManager.TryGetComponent(Owner.Owner, out var xform); + Vector2 localPosition = Vector2.Zero; + + if (_entManager.TryGetComponent(xform?.GridUid, out var gridXform)) + { + localPosition = gridXform.InvWorldMatrix.Transform(xform.WorldPosition); + } + + _menu?.ShowSensors(st.Sensors, localPosition, st.Snap, st.Precision); break; } } diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml index 3063619161..99224dbec6 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml @@ -1,16 +1,33 @@  - - - - + + + diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs index e52c232100..6e781ed92f 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs @@ -1,11 +1,11 @@ using System.Linq; +using Content.Client.Stylesheets; using Content.Client.UserInterface.Controls; using Content.Shared.Medical.SuitSensor; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Map; using Robust.Shared.Timing; @@ -18,19 +18,32 @@ namespace Content.Client.Medical.CrewMonitoring { private List _rowsContent = new(); private List<(DirectionIcon Icon, Vector2 Position)> _directionIcons = new(); + private readonly IEntityManager _entManager; private readonly IEyeManager _eye; - private readonly IEntityManager _entityManager; + private EntityUid? _stationUid; public static int IconSize = 16; // XAML has a `VSeparationOverride` of 20 for each row. - public CrewMonitoringWindow() + public CrewMonitoringWindow(EntityUid? mapUid) { RobustXamlLoader.Load(this); _eye = IoCManager.Resolve(); - _entityManager = IoCManager.Resolve(); + _entManager = IoCManager.Resolve(); + _stationUid = mapUid; + + if (_entManager.TryGetComponent(mapUid, out var xform)) + { + NavMap.MapUid = xform.GridUid; + } + else + { + NavMap.Visible = false; + SetSize = new Vector2(775, 400); + MinSize = SetSize; + } } - public void ShowSensors(List stSensors, Vector2 worldPosition, bool snap, float precision) + public void ShowSensors(List stSensors, Vector2 localPosition, bool snap, float precision) { ClearAllSensors(); @@ -43,13 +56,21 @@ namespace Content.Client.Medical.CrewMonitoring { // add users name // format: UserName - var nameLabel = new Label() + var nameLabel = new PanelContainer() { - Text = sensor.Name, - HorizontalExpand = true + PanelOverride = new StyleBoxFlat() + { + BackgroundColor = StyleNano.ButtonColorDisabled, + }, + Children = + { + new Label() + { + Text = sensor.Name, + Margin = new Thickness(5f, 5f), + } + } }; - SensorsTable.AddChild(nameLabel); - _rowsContent.Add(nameLabel); // add users job // format: JobName @@ -58,6 +79,9 @@ namespace Content.Client.Medical.CrewMonitoring Text = sensor.Job, HorizontalExpand = true }; + + SensorsTable.AddChild(nameLabel); + _rowsContent.Add(nameLabel); SensorsTable.AddChild(jobLabel); _rowsContent.Add(jobLabel); @@ -79,9 +103,36 @@ namespace Content.Client.Medical.CrewMonitoring // add users positions // format: (x, y) - var box = GetPositionBox(sensor.Coordinates, worldPosition, snap, precision); + var box = GetPositionBox(sensor.Coordinates, localPosition, snap, precision); + SensorsTable.AddChild(box); _rowsContent.Add(box); + + if (sensor.Coordinates != null && NavMap.Visible) + { + NavMap.TrackedCoordinates.Add(sensor.Coordinates.Value, (true, Color.FromHex("#B02E26"))); + nameLabel.MouseFilter = MouseFilterMode.Stop; + + // Hide all others upon mouseover. + nameLabel.OnMouseEntered += args => + { + foreach (var (coord, value) in NavMap.TrackedCoordinates) + { + if (coord == sensor.Coordinates) + continue; + + NavMap.TrackedCoordinates[coord] = (false, value.Color); + } + }; + + nameLabel.OnMouseExited += args => + { + foreach (var (coord, value) in NavMap.TrackedCoordinates) + { + NavMap.TrackedCoordinates[coord] = (true, value.Color); + } + }; + } } } @@ -89,7 +140,7 @@ namespace Content.Client.Medical.CrewMonitoring { var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal }; - if (coordinates == null) + if (coordinates == null || !_entManager.TryGetComponent(_stationUid, out var xform)) { var dirIcon = new DirectionIcon() { @@ -101,17 +152,18 @@ namespace Content.Client.Medical.CrewMonitoring } else { - // todo: add locations names (kitchen, bridge, etc) - var pos = (Vector2i) coordinates.Value.Position; - var mapCoords = coordinates.Value.ToMap(_entityManager).Position; + var position = coordinates.Value.ToMapPos(_entManager); + var local = xform.InvWorldMatrix.Transform(position); + + var displayPos = local.Floored(); var dirIcon = new DirectionIcon(snap, precision) { SetSize = (IconSize, IconSize), Margin = new(0, 0, 4, 0) }; box.AddChild(dirIcon); - box.AddChild(new Label() { Text = pos.ToString() }); - _directionIcons.Add((dirIcon, mapCoords - sensorPosition)); + box.AddChild(new Label() { Text = displayPos.ToString() }); + _directionIcons.Add((dirIcon, local - sensorPosition)); } return box; @@ -134,7 +186,9 @@ namespace Content.Client.Medical.CrewMonitoring { SensorsTable.RemoveChild(child); } + _rowsContent.Clear(); + NavMap.TrackedCoordinates.Clear(); } } } diff --git a/Content.Client/Pinpointer/NavMapSystem.cs b/Content.Client/Pinpointer/NavMapSystem.cs new file mode 100644 index 0000000000..23b7fc8365 --- /dev/null +++ b/Content.Client/Pinpointer/NavMapSystem.cs @@ -0,0 +1,96 @@ +using Content.Shared.Pinpointer; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.GameStates; +using Robust.Shared.Map; + +namespace Content.Client.Pinpointer; + +public sealed class NavMapSystem : SharedNavMapSystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnHandleState); + } + + private void OnHandleState(EntityUid uid, NavMapComponent component, ref ComponentHandleState args) + { + if (args.Current is not NavMapComponentState state) + return; + + component.Chunks.Clear(); + + foreach (var (origin, data) in state.TileData) + { + component.Chunks.Add(origin, new NavMapChunk(origin) + { + TileData = data, + }); + } + } +} + +public sealed class NavMapOverlay : Overlay +{ + private readonly IEntityManager _entManager; + private readonly IMapManager _mapManager; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + public NavMapOverlay(IEntityManager entManager, IMapManager mapManager) + { + _entManager = entManager; + _mapManager = mapManager; + } + + protected override void Draw(in OverlayDrawArgs args) + { + var query = _entManager.GetEntityQuery(); + var xformQuery = _entManager.GetEntityQuery(); + var scale = Matrix3.CreateScale(new Vector2(1f, 1f)); + + foreach (var grid in _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds)) + { + if (!query.TryGetComponent(grid.Owner, out var navMap) || !xformQuery.TryGetComponent(grid.Owner, out var xform)) + continue; + + // TODO: Faster helper method + var (_, _, matrix, invMatrix) = xform.GetWorldPositionRotationMatrixWithInv(); + + var localAABB = invMatrix.TransformBox(args.WorldBounds); + Matrix3.Multiply(in scale, in matrix, out var matty); + + args.WorldHandle.SetTransform(matty); + + for (var x = Math.Floor(localAABB.Left); x <= Math.Ceiling(localAABB.Right); x += SharedNavMapSystem.ChunkSize * grid.TileSize) + { + for (var y = Math.Floor(localAABB.Bottom); y <= Math.Ceiling(localAABB.Top); y += SharedNavMapSystem.ChunkSize * grid.TileSize) + { + var floored = new Vector2i((int) x, (int) y); + + var chunkOrigin = SharedMapSystem.GetChunkIndices(floored, SharedNavMapSystem.ChunkSize); + + if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk)) + continue; + + // TODO: Okay maybe I should just use ushorts lmao... + for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++) + { + var value = (int) Math.Pow(2, i); + + var mask = chunk.TileData & value; + + if (mask == 0x0) + continue; + + var tile = chunk.Origin * SharedNavMapSystem.ChunkSize + SharedNavMapSystem.GetTile(mask); + args.WorldHandle.DrawRect(new Box2(tile * grid.TileSize, (tile + 1) * grid.TileSize), Color.Aqua, false); + } + } + } + } + + args.WorldHandle.SetTransform(Matrix3.Identity); + } +} diff --git a/Content.Client/Pinpointer/ClientPinpointerSystem.cs b/Content.Client/Pinpointer/PinpointerSystem.cs similarity index 97% rename from Content.Client/Pinpointer/ClientPinpointerSystem.cs rename to Content.Client/Pinpointer/PinpointerSystem.cs index f4e79b0d34..1266cbe452 100644 --- a/Content.Client/Pinpointer/ClientPinpointerSystem.cs +++ b/Content.Client/Pinpointer/PinpointerSystem.cs @@ -5,7 +5,7 @@ using Robust.Shared.GameStates; namespace Content.Client.Pinpointer { - public sealed class ClientPinpointerSystem : SharedPinpointerSystem + public sealed class PinpointerSystem : SharedPinpointerSystem { [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs new file mode 100644 index 0000000000..723030b768 --- /dev/null +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -0,0 +1,333 @@ +using Content.Client.Stylesheets; +using Content.Client.UserInterface.Controls; +using Content.Shared.Pinpointer; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Input; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Components; +using Vector2 = Robust.Shared.Maths.Vector2; + +namespace Content.Client.Pinpointer.UI; + +/// +/// Displays the nav map data of the specified grid. +/// +public sealed class NavMapControl : MapGridControl +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + public EntityUid? MapUid; + + + public Dictionary TrackedCoordinates = new(); + + private Vector2 _offset; + private bool _draggin; + + private bool _recentering = false; + + private float _recenterMinimum = 0.05f; + + // TODO: https://github.com/space-wizards/RobustToolbox/issues/3818 + private readonly Label _zoom = new() + { + VerticalAlignment = VAlignment.Top, + Margin = new Thickness(8f, 8f), + }; + + private readonly Button _recenter = new() + { + Text = "Recentre", + VerticalAlignment = VAlignment.Top, + HorizontalAlignment = HAlignment.Right, + Margin = new Thickness(8f, 4f), + Disabled = true, + }; + + public NavMapControl() : base(8f, 128f, 48f) + { + IoCManager.InjectDependencies(this); + RectClipContent = true; + HorizontalExpand = true; + VerticalExpand = true; + + var topPanel = new PanelContainer() + { + PanelOverride = new StyleBoxFlat() + { + BackgroundColor = StyleNano.ButtonColorContext.WithAlpha(1f), + BorderColor = StyleNano.PanelDark + }, + VerticalExpand = false, + Children = + { + _zoom, + _recenter, + } + }; + + var topContainer = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + Children = + { + topPanel, + new Control() + { + Name = "DrawingControl", + VerticalExpand = true, + Margin = new Thickness(5f, 5f) + } + } + }; + + AddChild(topContainer); + topPanel.Measure(Vector2.Infinity); + + _recenter.OnPressed += args => + { + _recentering = true; + }; + } + + protected override void KeyBindDown(GUIBoundKeyEventArgs args) + { + base.KeyBindDown(args); + + if (args.Function == EngineKeyFunctions.Use) + { + _draggin = true; + } + } + + protected override void KeyBindUp(GUIBoundKeyEventArgs args) + { + base.KeyBindUp(args); + + if (args.Function == EngineKeyFunctions.Use) + { + _draggin = false; + } + } + + protected override void MouseMove(GUIMouseMoveEventArgs args) + { + base.MouseMove(args); + + if (!_draggin) + return; + + _recentering = false; + _offset -= new Vector2(args.Relative.X, -args.Relative.Y) / MidPoint * WorldRange; + + if (_offset != Vector2.Zero) + { + _recenter.Disabled = false; + } + else + { + _recenter.Disabled = true; + } + } + + protected override void Draw(DrawingHandleScreen handle) + { + base.Draw(handle); + + if (_recentering) + { + var frameTime = Timing.FrameTime; + var diff = _offset * (float) frameTime.TotalSeconds; + + if (_offset.LengthSquared < _recenterMinimum) + { + _offset = Vector2.Zero; + _recentering = false; + _recenter.Disabled = true; + } + else + { + _offset -= diff * 5f; + } + } + + _zoom.Text = $"Zoom: {(WorldRange / WorldMaxRange * 100f):0.00}%"; + + if (!_entManager.TryGetComponent(MapUid, out var navMap) || + !_entManager.TryGetComponent(MapUid, out var xform) || + !_entManager.TryGetComponent(MapUid, out var grid)) + { + return; + } + + var offset = _offset; + var tileColor = new Color(30, 67, 30); + var lineColor = new Color(102, 217, 102); + + if (_entManager.TryGetComponent(MapUid, out var physics)) + { + offset += physics.LocalCenter; + } + + // Draw tiles + if (_entManager.TryGetComponent(MapUid, out var manager)) + { + Span verts = new Vector2[8]; + + foreach (var fixture in manager.Fixtures.Values) + { + if (fixture.Shape is not PolygonShape poly) + continue; + + for (var i = 0; i < poly.VertexCount; i++) + { + var vert = poly.Vertices[i] - offset; + + verts[i] = Scale(new Vector2(vert.X, -vert.Y)); + } + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..poly.VertexCount], tileColor); + } + } + + // Draw the wall data + var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset); + var tileSize = new Vector2(grid.TileSize, -grid.TileSize); + + for (var x = Math.Floor(area.Left); x <= Math.Ceiling(area.Right); x += SharedNavMapSystem.ChunkSize * grid.TileSize) + { + for (var y = Math.Floor(area.Bottom); y <= Math.Ceiling(area.Top); y += SharedNavMapSystem.ChunkSize * grid.TileSize) + { + var floored = new Vector2i((int) x, (int) y); + + var chunkOrigin = SharedMapSystem.GetChunkIndices(floored, SharedNavMapSystem.ChunkSize); + + if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk)) + continue; + + // TODO: Okay maybe I should just use ushorts lmao... + for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++) + { + var value = (int) Math.Pow(2, i); + + var mask = chunk.TileData & value; + + if (mask == 0x0) + continue; + + // Alright now we'll work out our edges + var relativeTile = SharedNavMapSystem.GetTile(mask); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize - offset; + var position = new Vector2(tile.X, -tile.Y); + NavMapChunk? neighborChunk; + bool neighbor; + + // North edge + if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1) + { + neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) && + (neighborChunk.TileData & + SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0; + } + else + { + var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1)); + neighbor = (chunk.TileData & flag) != 0x0; + } + + if (!neighbor) + { + handle.DrawLine(Scale(position + new Vector2(0f, -grid.TileSize)), Scale(position + tileSize), lineColor); + } + + // East edge + if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1) + { + neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) && + (neighborChunk.TileData & + SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0; + } + else + { + var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0)); + neighbor = (chunk.TileData & flag) != 0x0; + } + + if (!neighbor) + { + handle.DrawLine(Scale(position + tileSize), Scale(position + new Vector2(grid.TileSize, 0f)), lineColor); + } + + // South edge + if (relativeTile.Y == 0) + { + neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(0, -1), out neighborChunk) && + (neighborChunk.TileData & + SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0; + } + else + { + var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, -1)); + neighbor = (chunk.TileData & flag) != 0x0; + } + + if (!neighbor) + { + handle.DrawLine(Scale(position + new Vector2(grid.TileSize, 0f)), Scale(position), lineColor); + } + + // West edge + if (relativeTile.X == 0) + { + neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(-1, 0), out neighborChunk) && + (neighborChunk.TileData & + SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0; + } + else + { + var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(-1, 0)); + neighbor = (chunk.TileData & flag) != 0x0; + } + + if (!neighbor) + { + handle.DrawLine(Scale(position), Scale(position + new Vector2(0f, -grid.TileSize)), lineColor); + } + + // Draw a diagonal line for interiors. + handle.DrawLine(Scale(position + new Vector2(0f, -grid.TileSize)), Scale(position + new Vector2(grid.TileSize, 0f)), lineColor); + } + } + } + + var curTime = Timing.RealTime; + var blinkFrequency = 1f / 1f; + var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f; + + foreach (var (coord, value) in TrackedCoordinates) + { + if (lit && value.Visible) + { + var mapPos = coord.ToMap(_entManager); + + if (mapPos.MapId != MapId.Nullspace) + { + var position = xform.InvWorldMatrix.Transform(mapPos.Position) - offset; + position = Scale(new Vector2(position.X, -position.Y)); + + handle.DrawCircle(position, MinimapScale / 2f, value.Color); + } + } + } + } + + private Vector2 Scale(Vector2 position) + { + return position * MinimapScale + MidPoint; + } +} diff --git a/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs b/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs new file mode 100644 index 0000000000..e4c6a2a1d5 --- /dev/null +++ b/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs @@ -0,0 +1,35 @@ +using Robust.Client.GameObjects; +using Robust.Client.Player; + +namespace Content.Client.Pinpointer.UI; + +public sealed class StationMapBoundUserInterface : BoundUserInterface +{ + private StationMapWindow? _window; + + public StationMapBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + _window?.Close(); + EntityUid? gridUid = null; + + if (IoCManager.Resolve().TryGetComponent(Owner.Owner, out var xform)) + { + gridUid = xform.GridUid; + } + + _window = new StationMapWindow(gridUid, Owner.Owner); + _window.OpenCentered(); + _window.OnClose += Close; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _window?.Dispose(); + } +} diff --git a/Content.Client/Pinpointer/UI/StationMapWindow.xaml b/Content.Client/Pinpointer/UI/StationMapWindow.xaml new file mode 100644 index 0000000000..86d2dc3244 --- /dev/null +++ b/Content.Client/Pinpointer/UI/StationMapWindow.xaml @@ -0,0 +1,7 @@ + + + diff --git a/Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs b/Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs new file mode 100644 index 0000000000..205987ca35 --- /dev/null +++ b/Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs @@ -0,0 +1,24 @@ +using Content.Client.UserInterface.Controls; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Map; + +namespace Content.Client.Pinpointer.UI; + +[GenerateTypedNameReferences] +public sealed partial class StationMapWindow : FancyWindow +{ + public StationMapWindow(EntityUid? mapUid, EntityUid? trackedEntity) + { + RobustXamlLoader.Load(this); + NavMapScreen.MapUid = mapUid; + + if (trackedEntity != null) + NavMapScreen.TrackedCoordinates.Add(new EntityCoordinates(trackedEntity.Value, Vector2.Zero), (true, Color.Red)); + + if (IoCManager.Resolve().TryGetComponent(mapUid, out var metadata)) + { + Title = metadata.EntityName; + } + } +} diff --git a/Content.Client/Shuttles/UI/DockingControl.cs b/Content.Client/Shuttles/UI/DockingControl.cs index a0bfe63740..44d205a8b7 100644 --- a/Content.Client/Shuttles/UI/DockingControl.cs +++ b/Content.Client/Shuttles/UI/DockingControl.cs @@ -1,3 +1,4 @@ +using Content.Client.UserInterface.Controls; using Content.Shared.Shuttles.BUIStates; using Robust.Client.Graphics; using Robust.Client.UserInterface; @@ -20,11 +21,9 @@ public class DockingControl : Control private float _rangeSquared = 0f; private const float GridLinesDistance = 32f; - private int MinimapRadius => (int) Math.Min(Size.X, Size.Y) / 2; - - private Vector2 MidPoint => (Size / 2) * UIScale; - private int SizeFull => (int) (MinimapRadius * 2 * UIScale); - private int ScaledMinimapRadius => (int) (MinimapRadius * UIScale); + private int MidPoint => SizeFull / 2; + private int SizeFull => (int) (MapGridControl.UIDisplayRadius * 2 * UIScale); + private int ScaledMinimapRadius => (int) (MapGridControl.UIDisplayRadius * UIScale); private float MinimapScale => _range != 0 ? ScaledMinimapRadius / _range : 0f; public EntityUid? ViewedDock; @@ -52,8 +51,8 @@ public class DockingControl : Control var fakeAA = new Color(0.08f, 0.08f, 0.08f); - handle.DrawCircle((MidPoint.X, MidPoint.Y), ScaledMinimapRadius + 1, fakeAA); - handle.DrawCircle((MidPoint.X, MidPoint.Y), ScaledMinimapRadius, Color.Black); + 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; @@ -61,14 +60,14 @@ public class DockingControl : Control for (var i = 1; i < gridLinesEquatorial + 1; i++) { - handle.DrawCircle((MidPoint.X, MidPoint.Y), GridLinesDistance * MinimapScale * i, gridLines, false); + 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.X, MidPoint.Y) - aExtent, (MidPoint.X, MidPoint.Y) + aExtent, gridLines); + handle.DrawLine((MidPoint, MidPoint) - aExtent, (MidPoint, MidPoint) + aExtent, gridLines); } if (Coordinates == null || diff --git a/Content.Client/Shuttles/UI/RadarControl.cs b/Content.Client/Shuttles/UI/RadarControl.cs index 7c18c98ffa..bc5f48e3f2 100644 --- a/Content.Client/Shuttles/UI/RadarControl.cs +++ b/Content.Client/Shuttles/UI/RadarControl.cs @@ -1,3 +1,4 @@ +using Content.Client.UserInterface.Controls; using Content.Shared.Shuttles.BUIStates; using Content.Shared.Shuttles.Components; using JetBrains.Annotations; @@ -13,7 +14,6 @@ using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; -using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Client.Shuttles.UI; @@ -21,13 +21,11 @@ namespace Content.Client.Shuttles.UI; /// /// Displays nearby grids inside of a control. /// -public sealed class RadarControl : Control +public sealed class RadarControl : MapGridControl { [Dependency] private readonly IEntityManager _entManager = default!; - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IMapManager _mapManager = default!; - private const float ScrollSensitivity = 8f; private const float GridLinesDistance = 32f; /// @@ -37,26 +35,6 @@ public sealed class RadarControl : Control private Angle? _rotation; - private float _radarMinRange = SharedRadarConsoleSystem.DefaultMinRange; - private float _radarMaxRange = SharedRadarConsoleSystem.DefaultMaxRange; - public float RadarRange { get; private set; } = SharedRadarConsoleSystem.DefaultMinRange; - - /// - /// We'll lerp between the radarrange and actual range - /// - private float _actualRadarRange = SharedRadarConsoleSystem.DefaultMinRange; - - /// - /// Controls the maximum distance that IFF labels will display. - /// - public float MaxRadarRange { get; private set; } = 256f * 10f; - - private int MinimapRadius => (int) Math.Min(Size.X, Size.Y) / 2; - private Vector2 MidPoint => (Size / 2) * UIScale; - private int SizeFull => (int) (MinimapRadius * 2 * UIScale); - private int ScaledMinimapRadius => (int) (MinimapRadius * UIScale); - private float MinimapScale => RadarRange != 0 ? ScaledMinimapRadius / RadarRange : 0f; - /// /// Shows a label on each radar object. /// @@ -79,11 +57,9 @@ public sealed class RadarControl : Control /// public Action? OnRadarClick; - public RadarControl() + public RadarControl() : base(64f, 256f, 256f) { - IoCManager.InjectDependencies(this); - MinSize = (SizeFull, SizeFull); - RectClipContent = true; + } public void SetMatrix(EntityCoordinates? coordinates, Angle? angle) @@ -92,25 +68,6 @@ public sealed class RadarControl : Control _rotation = angle; } - public void UpdateState(RadarConsoleBoundInterfaceState ls) - { - _radarMaxRange = ls.MaxRange; - - if (_radarMaxRange < _radarMinRange) - _radarMinRange = _radarMaxRange; - - _actualRadarRange = Math.Clamp(_actualRadarRange, _radarMinRange, _radarMaxRange); - - _docks.Clear(); - - foreach (var state in ls.Docks) - { - var coordinates = state.Coordinates; - var grid = _docks.GetOrNew(coordinates.EntityId); - grid.Add(state); - } - } - protected override void KeyBindUp(GUIBoundKeyEventArgs args) { base.KeyBindUp(args); @@ -148,32 +105,38 @@ public sealed class RadarControl : Control return coords; } - protected override void MouseWheel(GUIMouseWheelEventArgs args) + public void UpdateState(RadarConsoleBoundInterfaceState ls) { - base.MouseWheel(args); - AddRadarRange(-args.Delta.Y * 1f / ScrollSensitivity * RadarRange); - } + WorldMaxRange = ls.MaxRange; - public void AddRadarRange(float value) - { - _actualRadarRange = Math.Clamp(_actualRadarRange + value, _radarMinRange, _radarMaxRange); + if (WorldMaxRange < WorldRange) + { + ActualRadarRange = WorldMaxRange; + } + + if (WorldMaxRange < WorldMinRange) + WorldMinRange = WorldMaxRange; + + ActualRadarRange = Math.Clamp(ActualRadarRange, WorldMinRange, WorldMaxRange); + + _docks.Clear(); + + foreach (var state in ls.Docks) + { + var coordinates = state.Coordinates; + var grid = _docks.GetOrNew(coordinates.EntityId); + grid.Add(state); + } } protected override void Draw(DrawingHandleScreen handle) { - if (!_actualRadarRange.Equals(RadarRange)) - { - var diff = _actualRadarRange - RadarRange; - var lerpRate = 10f; - - RadarRange += (float) Math.Clamp(diff, -lerpRate * MathF.Abs(diff) * _timing.FrameTime.TotalSeconds, lerpRate * MathF.Abs(diff) * _timing.FrameTime.TotalSeconds); - OnRadarRangeChanged?.Invoke(RadarRange); - } + base.Draw(handle); var fakeAA = new Color(0.08f, 0.08f, 0.08f); - handle.DrawCircle((MidPoint.X, MidPoint.Y), ScaledMinimapRadius + 1, fakeAA); - handle.DrawCircle((MidPoint.X, MidPoint.Y), ScaledMinimapRadius, Color.Black); + handle.DrawCircle((MidPoint, MidPoint), ScaledMinimapRadius + 1, fakeAA); + handle.DrawCircle((MidPoint, MidPoint), ScaledMinimapRadius, Color.Black); // No data if (_coordinates == null || _rotation == null) @@ -184,18 +147,18 @@ public sealed class RadarControl : Control var gridLines = new Color(0.08f, 0.08f, 0.08f); var gridLinesRadial = 8; - var gridLinesEquatorial = (int) Math.Floor(RadarRange / GridLinesDistance); + var gridLinesEquatorial = (int) Math.Floor(WorldRange / GridLinesDistance); for (var i = 1; i < gridLinesEquatorial + 1; i++) { - handle.DrawCircle((MidPoint.X, MidPoint.Y), GridLinesDistance * MinimapScale * i, gridLines, false); + 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.X, MidPoint.Y) - aExtent, (MidPoint.X, MidPoint.Y) + aExtent, gridLines); + handle.DrawLine((MidPoint, MidPoint) - aExtent, (MidPoint, MidPoint) + aExtent, gridLines); } var metaQuery = _entManager.GetEntityQuery(); @@ -368,7 +331,7 @@ public sealed class RadarControl : Control var position = state.Coordinates.Position; var uiPosition = matrix.Transform(position); - if (uiPosition.Length > RadarRange - DockScale) continue; + if (uiPosition.Length > WorldRange - DockScale) continue; var color = HighlightedDock == ent ? state.HighlightedColor : state.Color; @@ -446,7 +409,7 @@ public sealed class RadarControl : Control var adjustedStart = matrix.Transform(start); var adjustedEnd = matrix.Transform(end); - if (adjustedStart.Length > RadarRange || adjustedEnd.Length > RadarRange) + if (adjustedStart.Length > ActualRadarRange || adjustedEnd.Length > ActualRadarRange) continue; start = ScalePosition(new Vector2(adjustedStart.X, -adjustedStart.Y)); diff --git a/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml.cs b/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml.cs index 634920b41a..61f861bbc7 100644 --- a/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleConsoleWindow.xaml.cs @@ -52,8 +52,8 @@ public sealed partial class ShuttleConsoleWindow : FancyWindow, _entManager = IoCManager.Resolve(); _timing = IoCManager.Resolve(); - OnRadarRangeChange(RadarScreen.RadarRange); - RadarScreen.OnRadarRangeChanged += OnRadarRangeChange; + WorldRangeChange(RadarScreen.WorldRange); + RadarScreen.WorldRangeChanged += WorldRangeChange; IFFToggle.OnToggled += OnIFFTogglePressed; IFFToggle.Pressed = RadarScreen.ShowIFF; @@ -64,7 +64,7 @@ public sealed partial class ShuttleConsoleWindow : FancyWindow, UndockButton.OnPressed += OnUndockPressed; } - private void OnRadarRangeChange(float value) + private void WorldRangeChange(float value) { RadarRange.Text = $"{value:0}"; } diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs index 2c1bbb7e92..a377b74340 100644 --- a/Content.Client/Stylesheets/StyleNano.cs +++ b/Content.Client/Stylesheets/StyleNano.cs @@ -81,6 +81,8 @@ namespace Content.Client.Stylesheets public const string StyleClassPopupMessageLarge = "PopupMessageLarge"; public const string StyleClassPopupMessageLargeCaution = "PopupMessageLargeCaution"; + public static readonly Color PanelDark = Color.FromHex("#1E1E22"); + public static readonly Color NanoGold = Color.FromHex("#A88B5E"); public static readonly Color GoodGreenFore = Color.FromHex("#31843E"); public static readonly Color ConcerningOrangeFore = Color.FromHex("#A5762F"); @@ -453,7 +455,7 @@ namespace Content.Client.Stylesheets var sliderBackBox = new StyleBoxTexture { Texture = sliderFillTex, - Modulate = Color.FromHex("#1E1E22") + Modulate = PanelDark, }; var sliderForeBox = new StyleBoxTexture diff --git a/Content.Client/UserInterface/Controls/MapGridControl.cs b/Content.Client/UserInterface/Controls/MapGridControl.cs new file mode 100644 index 0000000000..d44424912e --- /dev/null +++ b/Content.Client/UserInterface/Controls/MapGridControl.cs @@ -0,0 +1,79 @@ +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Shared.Timing; + +namespace Content.Client.UserInterface.Controls; + +/// +/// Handles generic grid-drawing data, with zoom and dragging. +/// +public abstract class MapGridControl : Control +{ + [Dependency] protected readonly IGameTiming Timing = default!; + + protected const float ScrollSensitivity = 8f; + + /// + /// UI pixel radius. + /// + public const int UIDisplayRadius = 320; + protected const int MinimapMargin = 4; + + protected float WorldMinRange; + protected float WorldMaxRange; + public float WorldRange; + + /// + /// We'll lerp between the radarrange and actual range + /// + protected float ActualRadarRange; + + /// + /// Controls the maximum distance that will display. + /// + public float MaxRadarRange { get; private set; } = 256f * 10f; + + protected int MidPoint => SizeFull / 2; + protected int SizeFull => (int) ((UIDisplayRadius + MinimapMargin) * 2 * UIScale); + protected int ScaledMinimapRadius => (int) (UIDisplayRadius * UIScale); + protected float MinimapScale => WorldRange != 0 ? ScaledMinimapRadius / WorldRange : 0f; + + public event Action? WorldRangeChanged; + + public MapGridControl(float minRange, float maxRange, float range) + { + IoCManager.InjectDependencies(this); + SetSize = (SizeFull, SizeFull); + RectClipContent = true; + MouseFilter = MouseFilterMode.Stop; + ActualRadarRange = WorldRange; + WorldMinRange = minRange; + WorldMaxRange = maxRange; + WorldRange = range; + ActualRadarRange = range; + } + + protected override void MouseWheel(GUIMouseWheelEventArgs args) + { + base.MouseWheel(args); + AddRadarRange(-args.Delta.Y * 1f / ScrollSensitivity * ActualRadarRange); + } + + public void AddRadarRange(float value) + { + ActualRadarRange = Math.Clamp(ActualRadarRange + value, WorldMinRange, WorldMaxRange); + } + + protected override void Draw(DrawingHandleScreen handle) + { + base.Draw(handle); + if (!ActualRadarRange.Equals(WorldRange)) + { + var diff = ActualRadarRange - WorldRange; + const float lerpRate = 10f; + + WorldRange += (float) Math.Clamp(diff, -lerpRate * MathF.Abs(diff) * Timing.FrameTime.TotalSeconds, lerpRate * MathF.Abs(diff) * Timing.FrameTime.TotalSeconds); + WorldRangeChanged?.Invoke(WorldRange); + } + } +} diff --git a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs index 98c9c2d8cf..034f0e7e8a 100644 --- a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs +++ b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs @@ -60,9 +60,8 @@ namespace Content.Server.Medical.CrewMonitoring return; // update all sensors info - var xform = Transform(uid); var allSensors = component.ConnectedSensors.Values.ToList(); - var uiState = new CrewMonitoringState(allSensors, xform.WorldPosition, component.Snap, component.Precision); + var uiState = new CrewMonitoringState(allSensors, component.Snap, component.Precision); ui.SetState(uiState); } } diff --git a/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs b/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs index 891a5a8a67..17d1feffa3 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Medical.SuitSensor; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.Medical.SuitSensors { @@ -53,9 +54,10 @@ namespace Content.Server.Medical.SuitSensors public EntityUid? User = null; /// - /// Last time when sensor updated owners status + /// Next time when sensor updated owners status /// - public TimeSpan LastUpdate = TimeSpan.Zero; + [DataField("nextUpdate", customTypeSerializer:typeof(TimeOffsetSerializer))] + public TimeSpan NextUpdate = TimeSpan.Zero; /// /// The station this suit sensor belongs to. If it's null the suit didn't spawn on a station and the sensor doesn't work. diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs index eecb894255..7786b349ba 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs @@ -21,23 +21,21 @@ namespace Content.Server.Medical.SuitSensors { public sealed class SuitSensorSystem : EntitySystem { - [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly CrewMonitoringServerSystem _monitoringServerSystem = default!; + [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; [Dependency] private readonly IdCardSystem _idCardSystem = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly CrewMonitoringServerSystem _monitoringServerSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly StationSystem _stationSystem = default!; - [Dependency] private readonly SharedTransformSystem _xform = default!; - - private const float UpdateRate = 1f; - private float _updateDif; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnUnpaused); SubscribeLocalEvent(OnEquipped); SubscribeLocalEvent(OnUnequipped); SubscribeLocalEvent(OnExamine); @@ -46,30 +44,29 @@ namespace Content.Server.Medical.SuitSensors SubscribeLocalEvent(OnRemove); } + private void OnUnpaused(EntityUid uid, SuitSensorComponent component, ref EntityUnpausedEvent args) + { + component.NextUpdate += args.PausedTime; + } + public override void Update(float frameTime) { base.Update(frameTime); - // check update rate - _updateDif += frameTime; - if (_updateDif < UpdateRate) - return; - - _updateDif -= UpdateRate; - var curTime = _gameTiming.CurTime; - var sensors = EntityManager.EntityQuery(); - foreach (var (sensor, device) in sensors) + var sensors = EntityManager.EntityQueryEnumerator(); + + while (sensors.MoveNext(out var sensor, out var device)) { - if (!device.TransmitFrequency.HasValue || !sensor.StationId.HasValue) + if (device.TransmitFrequency is null || !sensor.StationId.HasValue) continue; // check if sensor is ready to update - if (curTime - sensor.LastUpdate < sensor.UpdateRate) + if (curTime < sensor.NextUpdate) continue; - // Add a random offset to the next update time that isn't longer than the sensors update rate - sensor.LastUpdate = curTime.Add(TimeSpan.FromSeconds(_random.Next(0, sensor.UpdateRate.Seconds))); + // TODO: This would cause imprecision at different tick rates. + sensor.NextUpdate = curTime + sensor.UpdateRate; // get sensor status var status = GetSensorState(sensor.Owner, sensor); @@ -278,9 +275,6 @@ namespace Content.Server.Medical.SuitSensors totalDamage = damageable.TotalDamage.Int(); // finally, form suit sensor status - var xForm = Transform(sensor.User.Value); - var xFormQuery = GetEntityQuery(); - var coords = _xform.GetMoverCoordinates(xForm, xFormQuery); var status = new SuitSensorStatus(userName, userJob); switch (sensor.Mode) { @@ -294,7 +288,26 @@ namespace Content.Server.Medical.SuitSensors case SuitSensorMode.SensorCords: status.IsAlive = isAlive; status.TotalDamage = totalDamage; - status.Coordinates = coords; + EntityCoordinates coordinates; + var xformQuery = GetEntityQuery(); + + if (transform.GridUid != null) + { + coordinates = new EntityCoordinates(transform.GridUid.Value, + _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery) + .Transform(_transform.GetWorldPosition(transform, xformQuery))); + } + else if (transform.MapUid != null) + { + coordinates = new EntityCoordinates(transform.MapUid.Value, + _transform.GetWorldPosition(transform, xformQuery)); + } + else + { + coordinates = EntityCoordinates.Invalid; + } + + status.Coordinates = coordinates; break; } @@ -317,7 +330,7 @@ namespace Content.Server.Medical.SuitSensors if (status.TotalDamage != null) payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage); if (status.Coordinates != null) - payload.Add(SuitSensorConstants.NET_CORDINATES, status.Coordinates); + payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates); return payload; @@ -341,7 +354,7 @@ namespace Content.Server.Medical.SuitSensors // try get total damage and cords (optionals) payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage); - payload.TryGetValue(SuitSensorConstants.NET_CORDINATES, out EntityCoordinates? cords); + payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out EntityCoordinates? cords); var status = new SuitSensorStatus(name, job) { diff --git a/Content.Server/Pinpointer/NavMapSystem.cs b/Content.Server/Pinpointer/NavMapSystem.cs new file mode 100644 index 0000000000..81de3a4368 --- /dev/null +++ b/Content.Server/Pinpointer/NavMapSystem.cs @@ -0,0 +1,158 @@ +using Content.Shared.Pinpointer; +using Content.Shared.Tag; +using Robust.Shared.GameStates; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; + +namespace Content.Server.Pinpointer; + +/// +/// Handles data to be used for in-grid map displays. +/// +public sealed class NavMapSystem : SharedNavMapSystem +{ + [Dependency] private readonly TagSystem _tags = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnAnchorChange); + SubscribeLocalEvent(OnReAnchor); + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnNavMapSplit); + } + + private void OnNavMapSplit(EntityUid uid, NavMapComponent component, ref GridSplitEvent args) + { + var physicsQuery = GetEntityQuery(); + var tagQuery = GetEntityQuery(); + var gridQuery = GetEntityQuery(); + + foreach (var grid in args.NewGrids) + { + var newComp = EnsureComp(grid); + RefreshGrid(newComp, gridQuery.GetComponent(grid), physicsQuery, tagQuery); + } + + RefreshGrid(component, gridQuery.GetComponent(uid), physicsQuery, tagQuery); + } + + private void RefreshGrid(NavMapComponent component, MapGridComponent grid, EntityQuery physicsQuery, EntityQuery tagQuery) + { + component.Chunks.Clear(); + + var tiles = grid.GetAllTilesEnumerator(); + + while (tiles.MoveNext(out var tile)) + { + var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.Value.GridIndices, ChunkSize); + + if (!component.Chunks.TryGetValue(chunkOrigin, out var chunk)) + { + chunk = new NavMapChunk(chunkOrigin); + } + + RefreshTile(grid, component, chunk, tile.Value.GridIndices, physicsQuery, tagQuery); + } + } + + private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args) + { + var data = new Dictionary(component.Chunks.Count); + foreach (var (index, chunk) in component.Chunks) + { + data.Add(index, chunk.TileData); + } + + // TODO: Diffs + args.State = new NavMapComponentState() + { + TileData = data, + }; + } + + private void OnReAnchor(ref ReAnchorEvent ev) + { + if (TryComp(ev.OldGrid, out var oldGrid) && + TryComp(ev.OldGrid, out var navMap)) + { + var chunkOrigin = SharedMapSystem.GetChunkIndices(ev.TilePos, ChunkSize); + + if (navMap.Chunks.TryGetValue(chunkOrigin, out var chunk)) + { + var physicsQuery = GetEntityQuery(); + var tagQuery = GetEntityQuery(); + RefreshTile(oldGrid, navMap, chunk, ev.TilePos, physicsQuery, tagQuery); + } + } + + HandleAnchor(ev.Xform); + } + + private void OnAnchorChange(ref AnchorStateChangedEvent ev) + { + HandleAnchor(ev.Transform); + } + + private void HandleAnchor(TransformComponent xform) + { + if (!TryComp(xform.GridUid, out var navMap) || + !TryComp(xform.GridUid, out var grid)) + return; + + var tile = grid.LocalToTile(xform.Coordinates); + var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize); + var physicsQuery = GetEntityQuery(); + var tagQuery = GetEntityQuery(); + + if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk)) + { + chunk = new NavMapChunk(chunkOrigin); + navMap.Chunks[chunkOrigin] = chunk; + } + + RefreshTile(grid, navMap, chunk, tile, physicsQuery, tagQuery); + } + + private void RefreshTile(MapGridComponent grid, NavMapComponent component, NavMapChunk chunk, Vector2i tile, + EntityQuery physicsQuery, + EntityQuery tagQuery) + { + var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); + + var existing = chunk.TileData; + var flag = GetFlag(relative); + + chunk.TileData &= ~flag; + + var enumerator = grid.GetAnchoredEntitiesEnumerator(tile); + // TODO: Use something to get convex poly. + + while (enumerator.MoveNext(out var ent)) + { + if (!physicsQuery.TryGetComponent(ent, out var body) || + !body.CanCollide || + !body.Hard || + body.BodyType != BodyType.Static || + (!_tags.HasTag(ent.Value, "Wall", tagQuery) && + !_tags.HasTag(ent.Value, "Window", tagQuery))) + { + continue; + } + + chunk.TileData |= flag; + break; + } + + if (chunk.TileData == 0) + { + component.Chunks.Remove(chunk.Origin); + } + + if (existing == chunk.TileData) + return; + + Dirty(component); + } +} diff --git a/Content.Server/Pinpointer/ServerPinpointerSystem.cs b/Content.Server/Pinpointer/PinpointerSystem.cs similarity index 98% rename from Content.Server/Pinpointer/ServerPinpointerSystem.cs rename to Content.Server/Pinpointer/PinpointerSystem.cs index 3ff5425c74..21b96a98e3 100644 --- a/Content.Server/Pinpointer/ServerPinpointerSystem.cs +++ b/Content.Server/Pinpointer/PinpointerSystem.cs @@ -6,7 +6,7 @@ using Content.Server.Shuttles.Events; namespace Content.Server.Pinpointer { - public sealed class ServerPinpointerSystem : SharedPinpointerSystem + public sealed class PinpointerSystem : SharedPinpointerSystem { [Dependency] private readonly SharedTransformSystem _transform = default!; diff --git a/Content.Server/Pinpointer/StationMapComponent.cs b/Content.Server/Pinpointer/StationMapComponent.cs new file mode 100644 index 0000000000..0e60289876 --- /dev/null +++ b/Content.Server/Pinpointer/StationMapComponent.cs @@ -0,0 +1,17 @@ +namespace Content.Server.Pinpointer; + +[RegisterComponent] +public sealed class StationMapComponent : Component +{ + +} + +/// +/// Added to an entity using station map so when its parent changes we reset it. +/// +[RegisterComponent] +public sealed class StationMapUserComponent : Component +{ + [DataField("mapUid")] + public EntityUid Map; +} diff --git a/Content.Server/Pinpointer/StationMapSystem.cs b/Content.Server/Pinpointer/StationMapSystem.cs new file mode 100644 index 0000000000..3b5eea07cf --- /dev/null +++ b/Content.Server/Pinpointer/StationMapSystem.cs @@ -0,0 +1,43 @@ +using Content.Shared.Interaction.Events; +using Content.Shared.Pinpointer; +using Robust.Server.GameObjects; + +namespace Content.Server.Pinpointer; + +public sealed class StationMapSystem : EntitySystem +{ + [Dependency] private readonly UserInterfaceSystem _ui = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnUserParentChanged); + SubscribeLocalEvent(OnStationMapOpened); + SubscribeLocalEvent(OnStationMapClosed); + } + + private void OnStationMapClosed(EntityUid uid, StationMapComponent component, BoundUIClosedEvent args) + { + if (!Equals(args.UiKey, StationMapUiKey.Key) || args.Session.AttachedEntity == null) + return; + + RemCompDeferred(args.Session.AttachedEntity.Value); + } + + private void OnUserParentChanged(EntityUid uid, StationMapUserComponent component, ref EntParentChangedMessage args) + { + if (TryComp(uid, out var actor)) + { + _ui.TryClose(component.Map, StationMapUiKey.Key, actor.PlayerSession); + } + } + + private void OnStationMapOpened(EntityUid uid, StationMapComponent component, BoundUIOpenedEvent args) + { + if (args.Session.AttachedEntity == null) + return; + + var comp = EnsureComp(args.Session.AttachedEntity.Value); + comp.Map = uid; + } +} diff --git a/Content.Shared/Medical/CrewMonitoring/CrewMonitoringShared.cs b/Content.Shared/Medical/CrewMonitoring/CrewMonitoringShared.cs index ddf943e58d..7e5c00558b 100644 --- a/Content.Shared/Medical/CrewMonitoring/CrewMonitoringShared.cs +++ b/Content.Shared/Medical/CrewMonitoring/CrewMonitoringShared.cs @@ -13,14 +13,12 @@ namespace Content.Shared.Medical.CrewMonitoring public sealed class CrewMonitoringState : BoundUserInterfaceState { public List Sensors; - public readonly Vector2 WorldPosition; public readonly bool Snap; public readonly float Precision; - public CrewMonitoringState(List sensors, Vector2 worldPosition, bool snap, float precision) + public CrewMonitoringState(List sensors, bool snap, float precision) { Sensors = sensors; - WorldPosition = worldPosition; Snap = snap; Precision = precision; } diff --git a/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs b/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs index cdf3ee070c..5f59836182 100644 --- a/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs +++ b/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs @@ -50,7 +50,7 @@ namespace Content.Shared.Medical.SuitSensor public const string NET_JOB = "job"; public const string NET_IS_ALIVE = "alive"; public const string NET_TOTAL_DAMAGE = "vitals"; - public const string NET_CORDINATES = "cords"; + public const string NET_COORDINATES = "coords"; ///Used by the CrewMonitoringServerSystem to send the status of all connected suit sensors to each crew monitor public const string NET_STATUS_COLLECTION = "suit-status-collection"; diff --git a/Content.Shared/Pinpointer/NavMapComponent.cs b/Content.Shared/Pinpointer/NavMapComponent.cs new file mode 100644 index 0000000000..67b118c7ce --- /dev/null +++ b/Content.Shared/Pinpointer/NavMapComponent.cs @@ -0,0 +1,29 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Timing; + +namespace Content.Shared.Pinpointer; + +/// +/// Used to store grid poly data to be used for UIs. +/// +[RegisterComponent, NetworkedComponent] +public sealed class NavMapComponent : Component +{ + [ViewVariables] + public readonly Dictionary Chunks = new(); +} + +public sealed class NavMapChunk +{ + public readonly Vector2i Origin; + + /// + /// Bitmask for tiles, 1 for occupied and 0 for empty. + /// + public int TileData; + + public NavMapChunk(Vector2i origin) + { + Origin = origin; + } +} diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs new file mode 100644 index 0000000000..3601ae9dfa --- /dev/null +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -0,0 +1,45 @@ +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Pinpointer; + +public abstract class SharedNavMapSystem : EntitySystem +{ + public const byte ChunkSize = 4; + + /// + /// Converts the chunk's tile into a bitflag for the slot. + /// + public static int GetFlag(Vector2i relativeTile) + { + return 1 << (relativeTile.X * ChunkSize + relativeTile.Y); + } + + /// + /// Converts the chunk's tile into a bitflag for the slot. + /// + public static Vector2i GetTile(int flag) + { + var value = Math.Log2(flag); + var x = (int) value / ChunkSize; + var y = (int) value % ChunkSize; + var result = new Vector2i(x, y); + + DebugTools.Assert(GetFlag(result) == flag); + + return new Vector2i(x, y); + } + + [Serializable, NetSerializable] + protected sealed class NavMapComponentState : ComponentState + { + public Dictionary TileData = new(); + } + + [Serializable, NetSerializable] + protected sealed class NavMapDiffComponentState : ComponentState + { + public Dictionary TileData = new(); + public List RemovedChunks = new(); + } +} diff --git a/Content.Shared/Pinpointer/SharedStationMapSystem.cs b/Content.Shared/Pinpointer/SharedStationMapSystem.cs new file mode 100644 index 0000000000..13ec5dcd58 --- /dev/null +++ b/Content.Shared/Pinpointer/SharedStationMapSystem.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Pinpointer; + +[Serializable, NetSerializable] +public enum StationMapUiKey : byte +{ + Key, +} diff --git a/Content.Shared/Power/SharedPowerDevice.cs b/Content.Shared/Power/SharedPowerDevice.cs index 8a99dc7d91..4274dde60f 100644 --- a/Content.Shared/Power/SharedPowerDevice.cs +++ b/Content.Shared/Power/SharedPowerDevice.cs @@ -3,7 +3,7 @@ using Robust.Shared.Serialization; namespace Content.Shared.Power { [Serializable, NetSerializable] - public enum PowerDeviceVisuals + public enum PowerDeviceVisuals : byte { VisualState, Powered diff --git a/Resources/Locale/en-US/pinpointer/station_map.ftl b/Resources/Locale/en-US/pinpointer/station_map.ftl new file mode 100644 index 0000000000..d8172eb729 --- /dev/null +++ b/Resources/Locale/en-US/pinpointer/station_map.ftl @@ -0,0 +1 @@ +station-map-window-title = Station map \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/misc.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/misc.yml new file mode 100644 index 0000000000..afce7da5f9 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/misc.yml @@ -0,0 +1,5 @@ +- type: entity + parent: BaseElectronics + id: StationMapCircuitboard + name: station map circuit board + description: A printed circuit board for a station map. diff --git a/Resources/Prototypes/Entities/Objects/Devices/station_map.yml b/Resources/Prototypes/Entities/Objects/Devices/station_map.yml new file mode 100644 index 0000000000..f88cd896ef --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/station_map.yml @@ -0,0 +1,33 @@ +- type: entity + id: StationMap + name: station map + description: Displays a readout of the current station. + parent: BaseItem + suffix: Handheld + components: + - type: StationMap + - type: Sprite + netsync: false + sprite: Objects/Devices/tablets.rsi + layers: + - state: tablet + - state: generic + shader: unshaded + - type: ActivatableUI + inHandsOnly: true + singleUser: true + key: enum.StationMapUiKey.Key + - type: Damageable + damageContainer: Inorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: UserInterface + interfaces: + - key: enum.StationMapUiKey.Key + type: StationMapBoundUserInterface diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mining/ore_bag.yml b/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml similarity index 100% rename from Resources/Prototypes/Entities/Objects/Specific/Mining/ore_bag.yml rename to Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/station_map.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/station_map.yml new file mode 100644 index 0000000000..5fa2f4467a --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/station_map.yml @@ -0,0 +1,76 @@ +- type: entity + id: WallStationMapBroken + name: station map + description: A virtual map of the surrounding station. + suffix: Wall broken + placement: + mode: SnapgridCenter + components: + - type: InteractionOutline + - type: Clickable + - type: Sprite + netsync: false + sprite: Structures/Machines/station_map.rsi + drawdepth: WallMountedItems + layers: + - state: station_map_broken + - type: Damageable + damageContainer: Inorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: GlassBreak + - !type:DoActsBehavior + acts: [ "Destruction" ] + +- type: entity + id: WallStationMap + name: station map + parent: WallStationMapBroken + suffix: Wall + placement: + mode: SnapgridCenter + components: + - type: StationMap + - type: Transform + anchored: true + - type: Sprite + layers: + - state: station_map0 + - state: unshaded + map: [ "enum.PowerDeviceVisualLayers.Powered" ] + shader: unshaded + - type: ApcPowerReceiver + powerLoad: 200 + priority: Low + - type: WallMount + arc: 360 + - type: ExtensionCableReceiver + - type: ActivatableUIRequiresPower + - type: ActivatableUI + key: enum.StationMapUiKey.Key + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: GlassBreak + - !type:SpawnEntitiesBehavior + spawn: + WallStationMapBroken: + min: 1 + max: 1 + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: UserInterface + interfaces: + - key: enum.StationMapUiKey.Key + type: StationMapBoundUserInterface diff --git a/Resources/Textures/Objects/Devices/tablets.rsi/generic.png b/Resources/Textures/Objects/Devices/tablets.rsi/generic.png new file mode 100644 index 0000000000000000000000000000000000000000..5e1130fcc1c4917fe17a8a6df0e75647397e19a6 GIT binary patch literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVDb50q$YKTtz9S&aI8~cZ8Ypa(O1SNd0SJlGQ;KEF->@S5w}1!vw4HQDpR=&`IegS291r;J6zm3x09 z-&!+P2<`g5L@&}hq3W~(-wT$#)(fO-=Gxav`!V=9z4{rg{wVHf_};BoEf1{kKC$KW zUZxlSk{lVFL_l^yz)uD!bhxuxqG9m~c98J>t;}*I=}#>u?X&`MJzf1=);T3K0RTN7 BV=n*z literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/tablets.rsi/meta.json b/Resources/Textures/Objects/Devices/tablets.rsi/meta.json new file mode 100644 index 0000000000..11d1d586eb --- /dev/null +++ b/Resources/Textures/Objects/Devices/tablets.rsi/meta.json @@ -0,0 +1 @@ +{"version":1,"license":"CC-BY-SA-3.0","copyright":"https://github.com/Baystation12/Baystation12/tree/17e84546562b97d775cb26790a08e6d87e7f8077","size":{"x":32,"y":32},"states":[{"name":"tablet"},{"name":"tabletsol"},{"name":"generic","delays":[[0.3,0.3]]}]} \ No newline at end of file diff --git a/Resources/Textures/Objects/Devices/tablets.rsi/tablet.png b/Resources/Textures/Objects/Devices/tablets.rsi/tablet.png new file mode 100644 index 0000000000000000000000000000000000000000..417d44fbcc00fadef9961a926bc30d7b2b4ca088 GIT binary patch literal 348 zcmV-i0i*tjP)VQ!P3~mSL=$rgU=3lvZhEZKp4=i@``Xxr1 zIl$S?7i`JPm(MZ8VPY^fq=LP>HlsKk!vP?J4NWZ>Xa#^C_zwz6oDP72J2$UU-EtLm zEetUX2Y`b3-09vhj~_BVQ!P3~mSL=$rgU=3lvZhEZKp4=i@``Xxr1 zIl$S?7i`JPm(MZ8VPY^fq=LP>HlsKk!vP?J4NWZ>s0BLm+8NYa>cH%qHBAf#ZYKXh zA&JugFmUJQHHs}?Gj$`1EKq)|O4PkWvHbt}qBc~h~bpS2^01-HWy*qxisQ>@~07*qo IM6N<$f+kj`u>b%7 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/station_map.rsi/meta.json b/Resources/Textures/Structures/Machines/station_map.rsi/meta.json new file mode 100644 index 0000000000..493b112b27 --- /dev/null +++ b/Resources/Textures/Structures/Machines/station_map.rsi/meta.json @@ -0,0 +1 @@ +{"version":1,"license":"CC-BY-SA-3.0","copyright":"https://github.com/vgstation-coders/vgstation13/tree/c5d2df28ee81ca4c7d57f4a2a3dd548a7995c084","size":{"x":32,"y":32},"states":[{"name":"station_map_frame0"},{"name":"station_map_frame1"},{"name":"station_map_frame2"},{"name":"station_map_frame3"},{"name":"unshaded"},{"name":"station_map_broken"},{"name":"station_map0"},{"name":"station_map-panel"}]} \ No newline at end of file diff --git a/Resources/Textures/Structures/Machines/station_map.rsi/station_map-panel.png b/Resources/Textures/Structures/Machines/station_map.rsi/station_map-panel.png new file mode 100644 index 0000000000000000000000000000000000000000..90f21168483a7086fc4351b9f6c91f0d6517a9aa GIT binary patch literal 389 zcmV;00eb$4P)2O!APg;uw;c^T8u&`iqMF7F!WQ>_fSlf&w zczhbN&53$GkTs{-r{n#d-HbngnU(CfngD>{a1Q5`w|E-k$-&yf4)+;`Ar*%HzX?QK^`gk-5hw}Y5%KsxS_9~3-%?zO08%D>T!lbsAfr`_@EJbdhSNEa z*##ww(C?Ezhkm#SyN<7yPbC7SfEN)Rv6=wHhqZ=E6an^uj!0!QUIQcJreFz78OS^b zAVxw(v{XDxJ2fYYC>1A40P+b=qGV82nY!o`(xAvWB-S^ j->dc21ipohz((K#tXPmxH3?eQ00000NkvXXu0mjfHQJ?j literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/station_map.rsi/station_map0.png b/Resources/Textures/Structures/Machines/station_map.rsi/station_map0.png new file mode 100644 index 0000000000000000000000000000000000000000..4a217e218c24496971b6cc497a4a180224c1f76f GIT binary patch literal 484 zcmVMzhBi_MhOGSqK7`NYL->QP88TFg zIz$mtRRO8b#g4Jhh1fLCTWpMd_k6xHj}#)~eh8FhdA^m?s;V9=0*Jb<|J`{}6r*SB z2+}l#kKJx@$FJKhv~6pRTLh%@K7WMdIf3r86SI=_Z=bCg^rv3ElkI5y$k%XR>fQcZ z*nP?KX#xUoPr%;4po0qu)0x@oq!7Auv2OeyK|-?$$Z+GLEX#rj=$n~M07vDbrfDDqGKpQFk6?XCrqBpTd#+n< z@(4mB;B^G5ydXRRvP@lYm*KD|Gy?LD@Gis92zZsDj?f59VUoZSRd6){J#m)Efvc+^ zdJaUt2XsWq0iE>{-vX~AkV!aa7k|Mk359bmMnz>;5POU~tXP~5mc=YZ*arY2O+ zM@_>Da=6M02*!HjYDnXrZ^*IotPrz8~vSg%ivFA<*mfw#Nb; zjYgFO0hGmJ@#o;pW^?B|5rS&93LjDQ>%bR`!Dh2b=o178d<|o0`L{3+X8c>tSqC7R zsP8nrd8P(kZuh*=$R@zS*%oS1B|fZ4Xs`(fv&VhlWX~n;Du|8YA>bsmTFoQ(=-Mmn zOdw611k_DY$N=8bBAL+9`QEoAQCFT>2F|T+0@~eDAziEh^MH>$<1!eE0tASJ03(eg z0Ub+kfcpk`WZ5Y~z)U7!76~v&Sm5)(6YGrjX?x{E5E;ff8BHP#hkXCwt*yaP3{RB{ z0hN~`{Okr>nD!sYayiDO7umx{M?iOHmwMO%n|E0VnyfD;W*dy5l z7$q4crpLhuT+e4RJR&FXOdd(C0?Gk=ShU+cx$cPyh{2c7XRv->iMT)MN|*;SA!R}r zssb|y#Cy;&vRw$pxppbaRL5PGDFc!D2S3FrPcF&z-6!_~u$7_JnP2)T&MV^0UnLjz z)P5#`ou~LfA#WBAfx1CTBavImBq1Kc4koKDvqxt~gm!pOQGh@maH} zGQ>n7<^zs14}|nDj7Dn8^O~rUMqo?joF@fF5a#8Vx~?Sypw{^ST%pEH+nXYwJk#oK zRbY%TKOYK!QYZk5B|93REK8O`$4`Mw(4IvBfH7r9v{Y(xc=Q-e!{% zc+(RUik1N_i=j}vsiw9=63Mk>$#MKPw)~m*^oEf}3?t)q1SCmvxE5)erZY_dATUa8*3?GjA1WCaN;MY!C{cmIHo*LUEp8Xq>mjQMX`ZVuGsSOevDu5L>B!aW7+ zO2~Xu2HCQ$1^krX;5!cNs`2v+uv~icPoNCN0PUHT03r_A5jhEH)^?jMxC>2tx3CpJ z-77VaZ9QBcEa1=;(?_Q6)fXmmO~B=$;Tkv!I6W?nB48|xF5nj%(QOGk2bQn^0000< KMNUMnLSTXr zS%A)2=_-f_om58fsi@0q)lMA8CO7spR8PT#^6n*AjaX`32TaaYz2_cr0{BOwoT=#; z(KyBfg20P-7Gj5{@iKshG0+4u;$3jfdcA+R%wB-`Ebf(6b(o$9n27Ik=O`=JjaCHE zu*_qVUJ^wR)A=4tzy_>}f{2$1EdG(`$$}7%I(Mwdjz#S>R1q1GTd0Io3>TsCzyLarn7k12b$R32U%57gck;e&;2hM;eLGs+=(gfPVd<9#UHJ=ng z@By3qwQ!E7(3>Z%?%qnEjGyEi=#l~~)3txcTka67z>UZ`qBa3cGvDXV3_;igXb0zr z0nzLAF~Awi-LVslXXyzvtOMv+?P}$=t82qLnVg7B&=QcfUAEwS28{p8uUxB8TXWta zv+=H9mjF($I6$g1oG7D_@?Egh8`SCh&|oUYAL*vow$M4R5GcPioC7%l^>wKe0$rhV Z0>4$f>S;?tZCC&R002ovPDHLkV1gb^B>eyY literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/station_map.rsi/station_map_frame3.png b/Resources/Textures/Structures/Machines/station_map.rsi/station_map_frame3.png new file mode 100644 index 0000000000000000000000000000000000000000..695418e66553f1ab3da5f5ec72eaded3fbf3062e GIT binary patch literal 513 zcmV+c0{;DpP)m`33jG6cVTqH)kPZ7_C$u2;0b1OnwI^A4|tF~;8 z^*Bw_Or@D`!xv*KTrl<2P6QZZs|a9g;^+Yda3f^BC*KmFE>JHaIo5!}S_J^v5fi&H zOJJ=6bFdOXKg*Wl+bG~n0FtRb_M(7}Efj#F4;P^~1ymbu^B!>Q0=)>gcn{E>5VJ+- zr3-+vQ5`(c7=n&ED;o5;$C~m@d71jn>+C$`ek`$4fM2BuiDT{jY7GZJ*cie7#=J_v`(Bygo^8F7|RV8ZrKz0!mmlTf$#W-%L)jb_oe=~;d68@Y3PdpI5J*JVxje@H7Y6e2 zGZlNc#;Nc2#itCtlaKl|JxT)oGL1Avb<*_#( zUie(8ZuT#j7+zqdj<3x@Jk8H-FV=cgdkCM?K>FD6?8ALr`1bN^)2}TWEu-nGaKJfV z_fS;upm|G~Nj%Y8%c&7*zRT_S#S)J4OLaH$*5{*JZf?|#4V-#W#EO{;z1J`8zS zX1*I~W1gxXP)v@^OTTMeyX&@wpAi%vYtgFltZktA;(HB2(}ujrs4A&u-!*%5eFR0i zW6aXN>GjWo>Z@#!)C$?v4+K03rCQxJIn=(P_n-XuYZMn%lvg^`pP$w_kvvl%ISJ7o zS-$UzLR5ab7*dHeKY3C~()zyrWT%ftTvYnv`4H@W9g2*$s5(Tky5?a@l-<>IVaY|egLA481yeoA zNB8$?`a6|N4||q&Fvjc{5i?^k!hxdZ9i|WEYdQ_iZ9DsPdl#f+eELFCMwhjIz@CfK zUa~nGrN1a^UiCbEGt-GSe4Ca3LEd-R�dlujLI8Ng)}9Th5oB%U{`%TWB8WqaQdL zQ>OmGD_U$O^T|$BbUM?YWT3fo-u>Llpv!V7!+8h7)VF=ac^s_Uko}pC5^POXU~v9_{Th|0sy2A!(+ToWRke+L+_oN* zSI4ddo1jW)Rt%p|kNnfP6=Xe)%~RK4dS=h-$?kf_Pv%@l-ZaFuS%yz)hfJV%hb)nQ zx3-?0oKSQ%dR!9U-F~aebMVzvZ|&`YwW9`%!c|B5>C3KND7HNGuCnjOZgFilO`F|a zPfAH2)XqQXK&4#H#OfnAI{>4(zSe!cGUaGZ(mptL@x}_OcGHcezLF%j`H;Nu4PEFKm|<($|zcx^#22a!Gbae|`1Zy7>O_ z`vduilsu0bqqvrUC--b^4p@7PHHEAV*G@QTx8`G@38V6OYR_Q>z1;;ThZ3K9%MsF^ z_4O;|##9`r#tbE4Wqp-~7;(ub8(eYG8a=wsz9?j#c9vU>Y)1bhsSbzI z{95wvT%0vYM%=B)Ypl6J3i~+*He4$C*5$UPLVCfe!Uj?UeR6x7Hh1QRl#sOf%Y#LN zjuYcyi()i}9LeQI>$l~9N_xR%H|@IJ<5%6RHFQQ7AQnFF&})!s;^jVT!VF+a*0D3t z(k9B5Z_?js&PPjHKbrKba3=ZfivVz|rA~hOc(5Ed+&*CS#H6>XdW+Y~LhoA{$`^_&NGLfq zW@xtLy&I=T1GD-qB}WqR>-hEb6=B^m6mi;x$ksDk!qbX4$sd{XC9)>Q?yN8!*<2eg zkyY|FG8duIv!~7IHbedC`5V+otROBZeW7{7nt{}O&EC5}tsCY+;L(bGth@Szq&BUV zZk4S8?Ci;w(YAR#>mQY?_2xYs=zgsyN4b4aby?GiTl32w`v@{Iy=(iw>I%ru>e_B) z9oXaHu!Ea^RaYRkdk0(Lt-R1%*ymymSzJHXF_o5{9wBC4x5<`k<$o7=^Q8e*+cU!vaWc!0A)ymFBj#02?AK%&wBN7&vrjBgF0q_!&AsxAJAgrP{Tbd`#Uvt`kvC##YtjmEE88J=3?EcMZL0AHx7 zGAT9LR9L`$wk+*@`6iky1 zSFeS6(@SbvT29zoM`-f-0{=HU>`Dn)%aN?b_^hM*-HgP?{oWBHefv~~eYitymJK5k z83mi*y%6a=*FBTU+by2;v|C)+5#9B6O3cBHvwy`>-cfMaRAYccr6@#Ycx(a!5gs0H9ByLF;`kv@csw3~L?h5>IA{Uq zMh5f95%6HHo(SR_hAqIQau{qLgB1)FVUj7VP@V-02I`?d@?)}z#GmlN-0v)ad>|so zYy`>}iC{7jzk6_bc3~jM_k{kd2iF6f%@OVZmlevP0(N0QFi-Dy2paXLKRc8YG+Pdh ziU5KDCTPk9qoV!@Y41pM`{^N~z>mRX&w7Dm|6$2v(0`HjM{Xj`Y&pLt0=oai{lof4 z?X$+96_H4=Wl=*#={ee3z(ny0G!~UXBh3Ca!vHuFG}Q!-p`!6{j0ph1DHIAFP6f>H zWK$%@6o;q(2IUyc<&lG_fCvf%H)eo17!xYZjEXab(@o9Da108-!tqo*1&+be$!1iP z2_0uf{td#F!vMRI9Q1ouA}AUNg~i}7bUGCWH#I}k;TQ~=0z%Qv;A8-6f+8bvNIVrg z3q_+6*04BCGPs-!CfN@_u!H?(6(Yh3tK1wdU}$6HFNs?anMVg5z&*eSrm@1gzf>L! zCP3nmMSP-grYIB=Z-T~|nVI4+Cclg}030sZiy~AM(im+vs}U^>0W1fkmMrR25MWjg zRzq0L0mwWS$AiTRvVe(_f{Hw68ysr>ZCM-`T+kv?)broFo&GpG=K(|-Gm0mqp5T_ zicX~>(G)7i4Ee2$pXgi`ofl5#0IU4KO2KNt7MiUFy8JttEB?$T+#e7zg#`CE3jE+a zkVpa&O+eyci0{QBM0@&2wdRQb!-x5-!Ec8E==V(q9xmWnh4^_`edkL=;s5aUeI5RX z5kTmFLH>&0e{}t$>#rF2E8~CN^^dN{@8(lKL9(RCXZ~_VkA7(z{7Qn#= zAt{QJy)9%$^gdQolnqMeu^l#XArSdnqDw5tP+k)hO7k3vcG53p739RFY}%(%KvA=! z?J5t?o3HnypE#~IL>)TJp>cqEF4CN`!IA2eRCD|Fu3%BkfYYnY5$=F%f zNB3wc??c7TS?Vj_tifh?In!q(w33*JhDacMqxs5FS@x2;EpzFJK!Z_@ZS{?B%KHXg z=4>!``n36z>@6$is-_f&*mEnB5(^aP<&C<)C<~J%n^K#*zVH_e-`0QVE>