using System.Numerics; using Content.Client.Stylesheets; using Content.Client.UserInterface.Controls; using Content.Shared.Pinpointer; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; 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; 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!; private SharedTransformSystem _transform; public EntityUid? MapUid; public Dictionary TrackedCoordinates = new(); private Vector2 _offset; private bool _draggin; private bool _recentering = false; private readonly float _recenterMinimum = 0.05f; private readonly Font _font; private static readonly Color TileColor = new(30, 67, 30); private static readonly Color BeaconColor = Color.FromSrgb(TileColor.WithAlpha(0.8f)); // 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); _transform = _entManager.System(); var cache = IoCManager.Resolve(); _font = new VectorFont(cache.GetResource("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 16); 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(Vector2Helpers.Infinity); _recenter.OnPressed += args => { _recentering = true; }; } public void CenterToCoordinates(EntityCoordinates coordinates) { if (_entManager.TryGetComponent(MapUid, out var physics)) { _offset = new Vector2(coordinates.X, coordinates.Y) - physics.LocalCenter; } _recenter.Disabled = false; } 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 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); } } } // Beacons var labelOffset = new Vector2(0.5f, 0.5f) * MinimapScale; var rectBuffer = new Vector2(5f, 3f); foreach (var beacon in navMap.Beacons) { var position = beacon.Position - offset; position = Scale(position with { Y = -position.Y }); handle.DrawCircle(position, MinimapScale / 2f, beacon.Color); var textDimensions = handle.GetDimensions(_font, beacon.Text, 1f); var labelPosition = position + labelOffset; handle.DrawRect(new UIBox2(labelPosition, labelPosition + textDimensions + rectBuffer * 2), BeaconColor); handle.DrawString(_font, labelPosition + rectBuffer, beacon.Text, beacon.Color); } } private Vector2 Scale(Vector2 position) { return position * MinimapScale + MidpointVector; } }