mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-15 03:31:30 +01:00
Station maps (#13027)
This commit is contained in:
96
Content.Client/Pinpointer/NavMapSystem.cs
Normal file
96
Content.Client/Pinpointer/NavMapSystem.cs
Normal file
@@ -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<NavMapComponent, ComponentHandleState>(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<NavMapComponent>();
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
333
Content.Client/Pinpointer/UI/NavMapControl.cs
Normal file
333
Content.Client/Pinpointer/UI/NavMapControl.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Displays the nav map data of the specified grid.
|
||||
/// </summary>
|
||||
public sealed class NavMapControl : MapGridControl
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public EntityUid? MapUid;
|
||||
|
||||
|
||||
public Dictionary<EntityCoordinates, (bool Visible, Color Color)> 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<NavMapComponent>(MapUid, out var navMap) ||
|
||||
!_entManager.TryGetComponent<TransformComponent>(MapUid, out var xform) ||
|
||||
!_entManager.TryGetComponent<MapGridComponent>(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<PhysicsComponent>(MapUid, out var physics))
|
||||
{
|
||||
offset += physics.LocalCenter;
|
||||
}
|
||||
|
||||
// Draw tiles
|
||||
if (_entManager.TryGetComponent<FixturesComponent>(MapUid, out var manager))
|
||||
{
|
||||
Span<Vector2> 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;
|
||||
}
|
||||
}
|
||||
35
Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs
Normal file
35
Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs
Normal file
@@ -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<IEntityManager>().TryGetComponent<TransformComponent>(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();
|
||||
}
|
||||
}
|
||||
7
Content.Client/Pinpointer/UI/StationMapWindow.xaml
Normal file
7
Content.Client/Pinpointer/UI/StationMapWindow.xaml
Normal file
@@ -0,0 +1,7 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
|
||||
Title="{Loc 'station-map-window-title'}"
|
||||
Resizable="False">
|
||||
<ui:NavMapControl Name="NavMapScreen"/>
|
||||
</controls:FancyWindow>
|
||||
24
Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs
Normal file
24
Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs
Normal file
@@ -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<IEntityManager>().TryGetComponent<MetaDataComponent>(mapUid, out var metadata))
|
||||
{
|
||||
Title = metadata.EntityName;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user