Files
tbd-station-14/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
metalgearsloth c5486873db Shuttle console + FTL rework (#24430)
* Add shuttle interior drawing back

Just do it per-tile she'll be right, at least it's done with 1 draw call.

* Revamp shuttle console

* Bunch of cleanup work

* Lables sortito

* dok

* Pixel alignment and colours

* Fix a bunch of drawing bugs

* Shuttle map drawing

* Drawing fixes

* Map parallax working finally

* weh

* Commit all my stuff

* mic

* deez

* Update everything

* Xamlify everything

* uh

* Rudimentary blocker range

* My enemies have succeeded

* Bunch of changes to FTL

* Heaps of cleanup

* Fix FTL bugs

* FTL

* weewoo

* FTL fallback

* wew

* weh

* Basic FTL working

* FTL working

* FTL destination fixes

* a

* Exclusion zones

* Fix drawing / FTL

* Beacons working

* Coordinates drawing

* Fix unknown map names

* Dorks beginning

* State + docking cleanup start

* Basic dock drawing

* Bunch of drawing fixes

* Batching / color fixes

* Cleanup and beacons support

* weh

* weh

* Begin pings

* First draft at map objects

* Map fixup

* Faster drawing

* Fix perf + FTL

* Cached drawing

* Fix drawing

* Best I got

* strips

* Back to lists but with caching

* Final optimisation

* Fix dock bounds

* Docking work

* stinker

* kobolds

* Btns

* Docking vis working

* Fix docking pre-vis

* canasses

* Helldivers 2

* a

* Array life

* Fix

* Fix TODOs

* liltenhead feature club

* dorking

* Merge artifacts

* Last-minute touchup
2024-03-03 18:39:19 +11:00

459 lines
16 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 ourGridMatrix = _xformSystem.GetWorldMatrix(gridXform.Owner);
var dockMatrix = Matrix3.CreateTransform(_coordinates.Value.Position, Angle.Zero);
Matrix3.Multiply(dockMatrix, ourGridMatrix, out var offsetMatrix);
offsetMatrix = offsetMatrix.Invert();
// Draw nearby grids
var controlBounds = SizeBox.Scale(1.25f);
_grids.Clear();
_mapManager.FindGridsIntersecting(gridXform.MapID, new Box2(mapPos.Position - WorldRangeVector, mapPos.Position + WorldRangeVector), 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 gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
Matrix3.Multiply(in gridMatrix, in offsetMatrix, out var matty);
var color = _shuttles.GetIFFColor(grid.Owner, grid.Owner == GridEntity, component: iffComp);
DrawGrid(handle, matty, 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 position = matty.Transform(dock.Coordinates.Position);
var otherDockRotation = Matrix3.CreateRotation(dock.Angle);
var scaledPos = ScalePosition(position with {Y = -position.Y});
if (!controlBounds.Contains(scaledPos.Floored()))
continue;
// Draw the dock's collision
var collisionBL = matty.Transform(dock.Coordinates.Position +
otherDockRotation.Transform(new Vector2(-0.2f, -0.7f)));
var collisionBR = matty.Transform(dock.Coordinates.Position +
otherDockRotation.Transform(new Vector2(0.2f, -0.7f)));
var collisionTR = matty.Transform(dock.Coordinates.Position +
otherDockRotation.Transform(new Vector2(0.2f, -0.5f)));
var collisionTL = matty.Transform(dock.Coordinates.Position +
otherDockRotation.Transform(new Vector2(-0.2f, -0.5f)));
var verts = new[]
{
collisionBL,
collisionBR,
collisionBR,
collisionTR,
collisionTR,
collisionTL,
collisionTL,
collisionBL,
};
for (var i = 0; i < verts.Length; i++)
{
var vert = verts[i];
vert.Y = -vert.Y;
verts[i] = ScalePosition(vert);
}
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 = matty.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f));
var dockBR = matty.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f));
var dockTR = matty.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f));
var dockTL = matty.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f));
verts = new[]
{
dockBL,
dockBR,
dockBR,
dockTR,
dockTR,
dockTL,
dockTL,
dockBL
};
for (var i = 0; i < verts.Length; i++)
{
var vert = verts[i];
vert.Y = -vert.Y;
verts[i] = ScalePosition(vert);
}
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;
if (range.Length() < SharedDockingSystem.DockingHiglightRange * MinimapScale)
{
if (_viewedState?.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 distance = (mapOne.Position - mapTwo.Position).Length();
var inAlignment = _dockSystem.InAlignment(mapOne, rotA, mapTwo, rotB);
var canDock = distance < SharedDockingSystem.DockRange && 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 containerPos = scaledPos - 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 = Matrix3.CreateRotation(-_angle.Value + MathF.PI);
var ourDockConnection = new UIBox2(
ScalePosition(rotation.Transform(new Vector2(-0.2f, -0.7f))),
ScalePosition(rotation.Transform(new Vector2(0.2f, -0.5f))));
var ourDock = new UIBox2(
ScalePosition(rotation.Transform(new Vector2(-0.5f, 0.5f))),
ScalePosition(rotation.Transform(new Vector2(0.5f, -0.5f))));
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;
}
}
}
}