* Fix grids and docks being culled from display prematurely * Fix inconsistent disabling of "Undock" buttons * Add a radar icon to indicate where the controlling console is * Tidy up math Remove lots of sketchy transforms-of-transforms, which should have been as single matrix multiply. Assign proper names to matrices. Remove some redundant calculations. * Feedback
453 lines
17 KiB
C#
453 lines
17 KiB
C#
using System.Numerics;
|
|
using Content.Client.Shuttles.Systems;
|
|
using Content.Shared.Shuttles.BUIStates;
|
|
using Content.Shared.Shuttles.Components;
|
|
using Content.Shared.Shuttles.Systems;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.XAML;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Map.Components;
|
|
using Robust.Shared.Timing;
|
|
|
|
namespace Content.Client.Shuttles.UI;
|
|
|
|
[GenerateTypedNameReferences]
|
|
public sealed partial class ShuttleDockControl : BaseShuttleControl
|
|
{
|
|
[Dependency] private readonly IGameTiming _timing = default!;
|
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
private readonly DockingSystem _dockSystem;
|
|
private readonly SharedShuttleSystem _shuttles;
|
|
private readonly SharedTransformSystem _xformSystem;
|
|
|
|
public NetEntity? HighlightedDock;
|
|
|
|
public NetEntity? ViewedDock => _viewedState?.Entity;
|
|
private DockingPortState? _viewedState;
|
|
|
|
public EntityUid? GridEntity;
|
|
|
|
private EntityCoordinates? _coordinates;
|
|
private Angle? _angle;
|
|
|
|
public DockingInterfaceState? DockState = null;
|
|
|
|
private List<Entity<MapGridComponent>> _grids = new();
|
|
|
|
private readonly HashSet<DockingPortState> _drawnDocks = new();
|
|
private readonly Dictionary<DockingPortState, Button> _dockButtons = new();
|
|
|
|
/// <summary>
|
|
/// Store buttons for every other dock
|
|
/// </summary>
|
|
private readonly Dictionary<DockingPortState, Control> _dockContainers = new();
|
|
|
|
private static readonly TimeSpan DockChangeCooldown = TimeSpan.FromSeconds(0.5);
|
|
|
|
/// <summary>
|
|
/// Rate-limiting for docking changes
|
|
/// </summary>
|
|
private TimeSpan _nextDockChange;
|
|
|
|
public event Action<NetEntity>? OnViewDock;
|
|
public event Action<NetEntity, NetEntity>? DockRequest;
|
|
public event Action<NetEntity>? UndockRequest;
|
|
|
|
public ShuttleDockControl() : base(2f, 32f, 8f)
|
|
{
|
|
RobustXamlLoader.Load(this);
|
|
_dockSystem = EntManager.System<DockingSystem>();
|
|
_shuttles = EntManager.System<SharedShuttleSystem>();
|
|
_xformSystem = EntManager.System<SharedTransformSystem>();
|
|
MinSize = new Vector2(SizeFull, SizeFull);
|
|
}
|
|
|
|
public void SetViewedDock(DockingPortState? dockState)
|
|
{
|
|
_viewedState = dockState;
|
|
|
|
if (dockState != null)
|
|
{
|
|
_coordinates = EntManager.GetCoordinates(dockState.Coordinates);
|
|
_angle = dockState.Angle;
|
|
OnViewDock?.Invoke(dockState.Entity);
|
|
}
|
|
else
|
|
{
|
|
_coordinates = null;
|
|
_angle = null;
|
|
}
|
|
}
|
|
|
|
protected override void FrameUpdate(FrameEventArgs args)
|
|
{
|
|
base.FrameUpdate(args);
|
|
HideDocks();
|
|
_drawnDocks.Clear();
|
|
}
|
|
|
|
protected override void Draw(DrawingHandleScreen handle)
|
|
{
|
|
base.Draw(handle);
|
|
|
|
DrawBacking(handle);
|
|
|
|
if (_coordinates == null ||
|
|
_angle == null ||
|
|
DockState == null ||
|
|
!EntManager.TryGetComponent<TransformComponent>(GridEntity, out var gridXform))
|
|
{
|
|
DrawNoSignal(handle);
|
|
return;
|
|
}
|
|
|
|
DrawCircles(handle);
|
|
var gridNent = EntManager.GetNetEntity(GridEntity);
|
|
var mapPos = _xformSystem.ToMapCoordinates(_coordinates.Value);
|
|
var ourGridToWorld = _xformSystem.GetWorldMatrix(GridEntity.Value);
|
|
var selectedDockToOurGrid = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, Angle.Zero);
|
|
var selectedDockToWorld = Matrix3x2.Multiply(selectedDockToOurGrid, ourGridToWorld);
|
|
|
|
Box2 viewBoundsWorld = Matrix3Helpers.TransformBox(selectedDockToWorld, new Box2(-WorldRangeVector, WorldRangeVector));
|
|
|
|
Matrix3x2.Invert(selectedDockToWorld, out var worldToSelectedDock);
|
|
var selectedDockToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
|
|
|
|
// Draw nearby grids
|
|
var controlBounds = PixelSizeBox;
|
|
_grids.Clear();
|
|
_mapManager.FindGridsIntersecting(gridXform.MapID, viewBoundsWorld, ref _grids);
|
|
|
|
// offset the dotted-line position to the bounds.
|
|
Vector2? viewedDockPos = _viewedState != null ? MidPointVector : null;
|
|
|
|
if (viewedDockPos != null)
|
|
{
|
|
viewedDockPos = viewedDockPos.Value + _angle.Value.RotateVec(new Vector2(0f,-0.6f) * MinimapScale);
|
|
}
|
|
|
|
var canDockChange = _timing.CurTime > _nextDockChange;
|
|
var lineOffset = (float) _timing.RealTime.TotalSeconds * 30f;
|
|
|
|
foreach (var grid in _grids)
|
|
{
|
|
EntManager.TryGetComponent(grid.Owner, out IFFComponent? iffComp);
|
|
|
|
if (grid.Owner != GridEntity && !_shuttles.CanDraw(grid.Owner, iffComp: iffComp))
|
|
continue;
|
|
|
|
var curGridToWorld = _xformSystem.GetWorldMatrix(grid.Owner);
|
|
var curGridToView = curGridToWorld * worldToSelectedDock * selectedDockToView;
|
|
var color = _shuttles.GetIFFColor(grid.Owner, grid.Owner == GridEntity, component: iffComp);
|
|
|
|
DrawGrid(handle, curGridToView, grid, color);
|
|
|
|
// Draw any docks on that grid
|
|
if (!DockState.Docks.TryGetValue(EntManager.GetNetEntity(grid), out var gridDocks))
|
|
continue;
|
|
|
|
foreach (var dock in gridDocks)
|
|
{
|
|
if (ViewedDock == dock.Entity)
|
|
continue;
|
|
|
|
var otherDockRotation = Matrix3Helpers.CreateRotation(dock.Angle);
|
|
|
|
// This box is the AABB of all the vertices we draw below.
|
|
var dockRenderBoundsLocal = new Box2(-0.5f, -0.7f, 0.5f, 0.5f);
|
|
var currentDockToCurGrid = Matrix3Helpers.CreateTransform(dock.Coordinates.Position, dock.Angle);
|
|
var currentDockToWorld = Matrix3x2.Multiply(currentDockToCurGrid, curGridToWorld);
|
|
var dockRenderBoundsWorld = Matrix3Helpers.TransformBox(currentDockToWorld, dockRenderBoundsLocal);
|
|
if (!viewBoundsWorld.Intersects(dockRenderBoundsWorld))
|
|
continue;
|
|
|
|
var collisionBL = Vector2.Transform(dock.Coordinates.Position +
|
|
Vector2.Transform(new Vector2(-0.2f, -0.7f), otherDockRotation), curGridToView);
|
|
var collisionBR = Vector2.Transform(dock.Coordinates.Position +
|
|
Vector2.Transform(new Vector2(0.2f, -0.7f), otherDockRotation), curGridToView);
|
|
var collisionTR = Vector2.Transform(dock.Coordinates.Position +
|
|
Vector2.Transform(new Vector2(0.2f, -0.5f), otherDockRotation), curGridToView);
|
|
var collisionTL = Vector2.Transform(dock.Coordinates.Position +
|
|
Vector2.Transform(new Vector2(-0.2f, -0.5f), otherDockRotation), curGridToView);
|
|
|
|
var verts = new[]
|
|
{
|
|
collisionBL,
|
|
collisionBR,
|
|
collisionBR,
|
|
collisionTR,
|
|
collisionTR,
|
|
collisionTL,
|
|
collisionTL,
|
|
collisionBL,
|
|
};
|
|
|
|
var collisionCenter = verts[0] + verts[1] + verts[3] + verts[5];
|
|
|
|
var otherDockConnection = Color.ToSrgb(Color.Pink);
|
|
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, otherDockConnection.WithAlpha(0.2f));
|
|
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts, otherDockConnection);
|
|
|
|
// Draw the dock itself
|
|
var dockBL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f), curGridToView);
|
|
var dockBR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f), curGridToView);
|
|
var dockTR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f), curGridToView);
|
|
var dockTL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f), curGridToView);
|
|
|
|
verts = new[]
|
|
{
|
|
dockBL,
|
|
dockBR,
|
|
dockBR,
|
|
dockTR,
|
|
dockTR,
|
|
dockTL,
|
|
dockTL,
|
|
dockBL
|
|
};
|
|
|
|
Color otherDockColor;
|
|
|
|
if (HighlightedDock == dock.Entity)
|
|
{
|
|
otherDockColor = Color.ToSrgb(Color.Magenta);
|
|
}
|
|
else
|
|
{
|
|
otherDockColor = Color.ToSrgb(Color.Purple);
|
|
}
|
|
|
|
/*
|
|
* Can draw in these conditions:
|
|
* 1. Same grid
|
|
* 2. It's in range
|
|
*
|
|
* We don't want to draw stuff far away that's docked because it will just overlap our buttons
|
|
*/
|
|
|
|
var canDraw = grid.Owner == GridEntity;
|
|
_dockButtons.TryGetValue(dock, out var dockButton);
|
|
|
|
// Rate limit
|
|
if (dockButton != null && dock.GridDockedWith != null)
|
|
{
|
|
dockButton.Disabled = !canDockChange;
|
|
}
|
|
|
|
// If the dock is in range then also do highlighting
|
|
if (viewedDockPos != null && dock.Coordinates.NetEntity != gridNent)
|
|
{
|
|
collisionCenter /= 4;
|
|
var range = viewedDockPos.Value - collisionCenter;
|
|
|
|
var maxRange = SharedDockingSystem.DockingHiglightRange * MinimapScale;
|
|
var maxRangeSq = maxRange * maxRange;
|
|
if (range.LengthSquared() < maxRangeSq)
|
|
{
|
|
if (dock.GridDockedWith == null)
|
|
{
|
|
var coordsOne = EntManager.GetCoordinates(_viewedState!.Coordinates);
|
|
var coordsTwo = EntManager.GetCoordinates(dock.Coordinates);
|
|
var mapOne = _xformSystem.ToMapCoordinates(coordsOne);
|
|
var mapTwo = _xformSystem.ToMapCoordinates(coordsTwo);
|
|
|
|
var rotA = _xformSystem.GetWorldRotation(coordsOne.EntityId) + _viewedState!.Angle;
|
|
var rotB = _xformSystem.GetWorldRotation(coordsTwo.EntityId) + dock.Angle;
|
|
|
|
var distanceSq = (mapOne.Position - mapTwo.Position).LengthSquared();
|
|
|
|
var inAlignment = _dockSystem.InAlignment(mapOne, rotA, mapTwo, rotB);
|
|
var maxDockDistSq = SharedDockingSystem.DockRange * SharedDockingSystem.DockRange;
|
|
var canDock = distanceSq < maxDockDistSq && inAlignment;
|
|
|
|
if (dockButton != null)
|
|
dockButton.Disabled = !canDock || !canDockChange;
|
|
|
|
var lineColor = inAlignment ? Color.Lime : Color.Red;
|
|
handle.DrawDottedLine(viewedDockPos.Value, collisionCenter, lineColor, offset: lineOffset);
|
|
}
|
|
|
|
canDraw = true;
|
|
}
|
|
else
|
|
{
|
|
if (dockButton != null)
|
|
dockButton.Disabled = true;
|
|
}
|
|
}
|
|
|
|
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, otherDockColor.WithAlpha(0.2f));
|
|
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts, otherDockColor);
|
|
|
|
// Position the dock control above it
|
|
var container = _dockContainers[dock];
|
|
container.Visible = canDraw;
|
|
|
|
if (canDraw)
|
|
{
|
|
// Because it's being layed out top-down we have to arrange for first frame.
|
|
container.Arrange(PixelRect);
|
|
var dockPositionInView = Vector2.Transform(dock.Coordinates.Position, curGridToView);
|
|
var containerPos = dockPositionInView / UIScale - container.DesiredSize / 2 - new Vector2(0f, 0.75f) * MinimapScale;
|
|
SetPosition(container, containerPos);
|
|
}
|
|
|
|
_drawnDocks.Add(dock);
|
|
}
|
|
}
|
|
|
|
// Draw the dock's collision
|
|
var invertedPosition = Vector2.Zero;
|
|
invertedPosition.Y = -invertedPosition.Y;
|
|
var rotation = Matrix3Helpers.CreateRotation(-_angle.Value + MathF.PI);
|
|
var ourDockConnection = new UIBox2(
|
|
ScalePosition(Vector2.Transform(new Vector2(-0.2f, -0.7f), rotation)),
|
|
ScalePosition(Vector2.Transform(new Vector2(0.2f, -0.5f), rotation)));
|
|
|
|
var ourDock = new UIBox2(
|
|
ScalePosition(Vector2.Transform(new Vector2(-0.5f, 0.5f), rotation)),
|
|
ScalePosition(Vector2.Transform(new Vector2(0.5f, -0.5f), rotation)));
|
|
|
|
var dockColor = Color.Magenta;
|
|
var connectionColor = Color.Pink;
|
|
|
|
handle.DrawRect(ourDockConnection, connectionColor.WithAlpha(0.2f));
|
|
handle.DrawRect(ourDockConnection, connectionColor, filled: false);
|
|
|
|
// Draw the dock itself
|
|
handle.DrawRect(ourDock, dockColor.WithAlpha(0.2f));
|
|
handle.DrawRect(ourDock, dockColor, filled: false);
|
|
}
|
|
|
|
private void HideDocks()
|
|
{
|
|
foreach (var (dock, control) in _dockContainers)
|
|
{
|
|
if (_drawnDocks.Contains(dock))
|
|
continue;
|
|
|
|
control.Visible = false;
|
|
}
|
|
}
|
|
|
|
public void BuildDocks(EntityUid? shuttle)
|
|
{
|
|
var viewedEnt = ViewedDock;
|
|
_viewedState = null;
|
|
|
|
foreach (var btn in _dockButtons.Values)
|
|
{
|
|
btn.Dispose();
|
|
}
|
|
|
|
foreach (var container in _dockContainers.Values)
|
|
{
|
|
container.Dispose();
|
|
}
|
|
|
|
_dockButtons.Clear();
|
|
_dockContainers.Clear();
|
|
|
|
if (DockState == null)
|
|
return;
|
|
|
|
var gridNent = EntManager.GetNetEntity(GridEntity);
|
|
|
|
foreach (var (otherShuttle, docks) in DockState.Docks)
|
|
{
|
|
// If it's our shuttle we add a view button
|
|
|
|
foreach (var dock in docks)
|
|
{
|
|
if (dock.Entity == viewedEnt)
|
|
{
|
|
_viewedState = dock;
|
|
}
|
|
|
|
var container = new BoxContainer()
|
|
{
|
|
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
|
Margin = new Thickness(3),
|
|
};
|
|
|
|
var panel = new PanelContainer()
|
|
{
|
|
HorizontalAlignment = HAlignment.Center,
|
|
VerticalAlignment = VAlignment.Center,
|
|
PanelOverride = new StyleBoxFlat(new Color(30, 30, 34, 200)),
|
|
Children =
|
|
{
|
|
container,
|
|
}
|
|
};
|
|
|
|
Button button;
|
|
|
|
if (otherShuttle == gridNent)
|
|
{
|
|
button = new Button()
|
|
{
|
|
Text = Loc.GetString("shuttle-console-view"),
|
|
};
|
|
|
|
button.OnPressed += args =>
|
|
{
|
|
SetViewedDock(dock);
|
|
};
|
|
}
|
|
else
|
|
{
|
|
if (dock.Connected)
|
|
{
|
|
button = new Button()
|
|
{
|
|
Text = Loc.GetString("shuttle-console-undock"),
|
|
};
|
|
|
|
button.OnPressed += args =>
|
|
{
|
|
_nextDockChange = _timing.CurTime + DockChangeCooldown;
|
|
UndockRequest?.Invoke(dock.Entity);
|
|
};
|
|
}
|
|
else
|
|
{
|
|
button = new Button()
|
|
{
|
|
Text = Loc.GetString("shuttle-console-dock"),
|
|
Disabled = true,
|
|
};
|
|
|
|
button.OnPressed += args =>
|
|
{
|
|
if (ViewedDock == null)
|
|
return;
|
|
|
|
_nextDockChange = _timing.CurTime + DockChangeCooldown;
|
|
DockRequest?.Invoke(ViewedDock.Value, dock.Entity);
|
|
};
|
|
}
|
|
|
|
_dockButtons.Add(dock, button);
|
|
}
|
|
|
|
container.AddChild(new Label()
|
|
{
|
|
Text = dock.Name,
|
|
HorizontalAlignment = HAlignment.Center,
|
|
});
|
|
|
|
button.HorizontalAlignment = HAlignment.Center;
|
|
container.AddChild(button);
|
|
|
|
AddChild(panel);
|
|
panel.Measure(Vector2Helpers.Infinity);
|
|
_dockContainers[dock] = panel;
|
|
}
|
|
}
|
|
}
|
|
}
|