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