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;
};
}
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 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;
}
}