368 lines
13 KiB
C#
368 lines
13 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Displays the nav map data of the specified grid.
|
|
/// </summary>
|
|
public sealed class NavMapControl : MapGridControl
|
|
{
|
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
|
private SharedTransformSystem _transform;
|
|
|
|
public EntityUid? MapUid;
|
|
|
|
public Dictionary<EntityCoordinates, (bool Visible, Color Color)> 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<SharedTransformSystem>();
|
|
var cache = IoCManager.Resolve<IClientResourceCache>();
|
|
_font = new VectorFont(cache.GetResource<FontResource>("/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<PhysicsComponent>(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<NavMapComponent>(MapUid, out var navMap) ||
|
|
!_entManager.TryGetComponent<TransformComponent>(MapUid, out var xform) ||
|
|
!_entManager.TryGetComponent<MapGridComponent>(MapUid, out var grid))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var offset = _offset;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|