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
This commit is contained in:
458
Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
Normal file
458
Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
Normal file
@@ -0,0 +1,458 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user