using System.Numerics; using Content.Shared.Shuttles.BUIStates; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Systems; using JetBrains.Annotations; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.XAML; using Robust.Shared.Collections; using Robust.Shared.Input; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Utility; namespace Content.Client.Shuttles.UI; [GenerateTypedNameReferences] public sealed partial class ShuttleNavControl : BaseShuttleControl { [Dependency] private readonly IMapManager _mapManager = default!; private readonly SharedShuttleSystem _shuttles; private readonly SharedTransformSystem _transform; /// /// Used to transform all of the radar objects. Typically is a shuttle console parented to a grid. /// private EntityCoordinates? _coordinates; private Angle? _rotation; private Dictionary> _docks = new(); public bool ShowIFF { get; set; } = true; public bool ShowDocks { get; set; } = true; /// /// Raised if the user left-clicks on the radar control with the relevant entitycoordinates. /// public Action? OnRadarClick; private List> _grids = new(); public ShuttleNavControl() : base(64f, 256f, 256f) { RobustXamlLoader.Load(this); _shuttles = EntManager.System(); _transform = EntManager.System(); } public void SetMatrix(EntityCoordinates? coordinates, Angle? angle) { _coordinates = coordinates; _rotation = angle; } protected override void KeyBindUp(GUIBoundKeyEventArgs args) { base.KeyBindUp(args); if (_coordinates == null || _rotation == null || args.Function != EngineKeyFunctions.UIClick || OnRadarClick == null) { return; } var a = InverseScalePosition(args.RelativePosition); var relativeWorldPos = new Vector2(a.X, -a.Y); relativeWorldPos = _rotation.Value.RotateVec(relativeWorldPos); var coords = _coordinates.Value.Offset(relativeWorldPos); OnRadarClick?.Invoke(coords); } /// /// Gets the entitycoordinates of where the mouseposition is, relative to the control. /// [PublicAPI] public EntityCoordinates GetMouseCoordinates(ScreenCoordinates screen) { if (_coordinates == null || _rotation == null) { return EntityCoordinates.Invalid; } var pos = screen.Position / UIScale - GlobalPosition; var a = InverseScalePosition(pos); var relativeWorldPos = new Vector2(a.X, -a.Y); relativeWorldPos = _rotation.Value.RotateVec(relativeWorldPos); var coords = _coordinates.Value.Offset(relativeWorldPos); return coords; } public void UpdateState(NavInterfaceState state) { SetMatrix(EntManager.GetCoordinates(state.Coordinates), state.Angle); WorldMaxRange = state.MaxRange; if (WorldMaxRange < WorldRange) { ActualRadarRange = WorldMaxRange; } if (WorldMaxRange < WorldMinRange) WorldMinRange = WorldMaxRange; ActualRadarRange = Math.Clamp(ActualRadarRange, WorldMinRange, WorldMaxRange); _docks = state.Docks; } protected override void Draw(DrawingHandleScreen handle) { base.Draw(handle); DrawBacking(handle); DrawCircles(handle); // No data if (_coordinates == null || _rotation == null) { return; } var xformQuery = EntManager.GetEntityQuery(); var fixturesQuery = EntManager.GetEntityQuery(); var bodyQuery = EntManager.GetEntityQuery(); if (!xformQuery.TryGetComponent(_coordinates.Value.EntityId, out var xform) || xform.MapID == MapId.Nullspace) { return; } var mapPos = _transform.ToMapCoordinates(_coordinates.Value); var offset = _coordinates.Value.Position; var posMatrix = Matrix3.CreateTransform(offset, _rotation.Value); var (_, ourEntRot, ourEntMatrix) = _transform.GetWorldPositionRotationMatrix(_coordinates.Value.EntityId); Matrix3.Multiply(posMatrix, ourEntMatrix, out var ourWorldMatrix); var ourWorldMatrixInvert = ourWorldMatrix.Invert(); // Draw our grid in detail var ourGridId = xform.GridUid; if (EntManager.TryGetComponent(ourGridId, out var ourGrid) && fixturesQuery.HasComponent(ourGridId.Value)) { var ourGridMatrix = _transform.GetWorldMatrix(ourGridId.Value); Matrix3.Multiply(in ourGridMatrix, in ourWorldMatrixInvert, out var matrix); var color = _shuttles.GetIFFColor(ourGridId.Value, self: true); DrawGrid(handle, matrix, (ourGridId.Value, ourGrid), color); DrawDocks(handle, ourGridId.Value, matrix); } var invertedPosition = _coordinates.Value.Position - offset; invertedPosition.Y = -invertedPosition.Y; // Don't need to transform the InvWorldMatrix again as it's already offset to its position. // Draw radar position on the station var radarPos = invertedPosition; const float radarVertRadius = 2f; var radarPosVerts = new Vector2[] { ScalePosition(radarPos + new Vector2(0f, -radarVertRadius)), ScalePosition(radarPos + new Vector2(radarVertRadius / 2f, 0f)), ScalePosition(radarPos + new Vector2(0f, radarVertRadius)), ScalePosition(radarPos + new Vector2(radarVertRadius / -2f, 0f)), }; handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, radarPosVerts, Color.Lime); var rot = ourEntRot + _rotation.Value; var viewBounds = new Box2Rotated(new Box2(-WorldRange, -WorldRange, WorldRange, WorldRange).Translated(mapPos.Position), rot, mapPos.Position); var viewAABB = viewBounds.CalcBoundingBox(); _grids.Clear(); _mapManager.FindGridsIntersecting(xform.MapID, new Box2(mapPos.Position - MaxRadarRangeVector, mapPos.Position + MaxRadarRangeVector), ref _grids, approx: true, includeMap: false); // Draw other grids... differently foreach (var grid in _grids) { var gUid = grid.Owner; if (gUid == ourGridId || !fixturesQuery.HasComponent(gUid)) continue; var gridBody = bodyQuery.GetComponent(gUid); EntManager.TryGetComponent(gUid, out var iff); if (!_shuttles.CanDraw(gUid, gridBody, iff)) continue; var gridMatrix = _transform.GetWorldMatrix(gUid); Matrix3.Multiply(in gridMatrix, in ourWorldMatrixInvert, out var matty); var color = _shuttles.GetIFFColor(grid, self: false, iff); // Others default: // Color.FromHex("#FFC000FF") // Hostile default: Color.Firebrick var labelName = _shuttles.GetIFFLabel(grid, self: false, iff); if (ShowIFF && labelName != null) { var gridBounds = grid.Comp.LocalAABB; var gridCentre = matty.Transform(gridBody.LocalCenter); gridCentre.Y = -gridCentre.Y; var distance = gridCentre.Length(); var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName), ("distance", $"{distance:0.0}")); var labelDimensions = handle.GetDimensions(Font, labelText, UIScale); // y-offset the control to always render below the grid (vertically) var yOffset = Math.Max(gridBounds.Height, gridBounds.Width) * MinimapScale / 1.8f / UIScale; // The actual position in the UI. We offset the matrix position to render it off by half its width // plus by the offset. var uiPosition = ScalePosition(gridCentre) / UIScale - new Vector2(labelDimensions.X / 2f, -yOffset); // Look this is uggo so feel free to cleanup. We just need to clamp the UI position to within the viewport. uiPosition = new Vector2(Math.Clamp(uiPosition.X, 0f, Width - labelDimensions.X), Math.Clamp(uiPosition.Y, 0f, Height - labelDimensions.Y)); handle.DrawString(Font, uiPosition, labelText, color); } // Detailed view var gridAABB = gridMatrix.TransformBox(grid.Comp.LocalAABB); // Skip drawing if it's out of range. if (!gridAABB.Intersects(viewAABB)) continue; DrawGrid(handle, matty, grid, color); DrawDocks(handle, gUid, matty); } } private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3 matrix) { if (!ShowDocks) return; const float DockScale = 0.6f; var nent = EntManager.GetNetEntity(uid); if (_docks.TryGetValue(nent, out var docks)) { foreach (var state in docks) { var position = state.Coordinates.Position; var uiPosition = matrix.Transform(position); if (uiPosition.Length() > (WorldRange * 2f) - DockScale) continue; var color = Color.ToSrgb(Color.Magenta); var verts = new[] { matrix.Transform(position + new Vector2(-DockScale, -DockScale)), matrix.Transform(position + new Vector2(DockScale, -DockScale)), matrix.Transform(position + new Vector2(DockScale, DockScale)), matrix.Transform(position + new Vector2(-DockScale, DockScale)), }; for (var i = 0; i < verts.Length; i++) { var vert = verts[i]; vert.Y = -vert.Y; verts[i] = ScalePosition(vert); } handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, color.WithAlpha(0.8f)); handle.DrawPrimitives(DrawPrimitiveTopology.LineStrip, verts, color); } } } private Vector2 InverseScalePosition(Vector2 value) { return (value - MidPointVector) / MinimapScale; } }