* 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
289 lines
11 KiB
C#
289 lines
11 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Used to transform all of the radar objects. Typically is a shuttle console parented to a grid.
|
|
/// </summary>
|
|
private EntityCoordinates? _coordinates;
|
|
|
|
private Angle? _rotation;
|
|
|
|
private Dictionary<NetEntity, List<DockingPortState>> _docks = new();
|
|
|
|
public bool ShowIFF { get; set; } = true;
|
|
public bool ShowDocks { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Raised if the user left-clicks on the radar control with the relevant entitycoordinates.
|
|
/// </summary>
|
|
public Action<EntityCoordinates>? OnRadarClick;
|
|
|
|
private List<Entity<MapGridComponent>> _grids = new();
|
|
|
|
public ShuttleNavControl() : base(64f, 256f, 256f)
|
|
{
|
|
RobustXamlLoader.Load(this);
|
|
_shuttles = EntManager.System<SharedShuttleSystem>();
|
|
_transform = EntManager.System<SharedTransformSystem>();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the entitycoordinates of where the mouseposition is, relative to the control.
|
|
/// </summary>
|
|
[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<TransformComponent>();
|
|
var fixturesQuery = EntManager.GetEntityQuery<FixturesComponent>();
|
|
var bodyQuery = EntManager.GetEntityQuery<PhysicsComponent>();
|
|
|
|
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<MapGridComponent>(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<IFFComponent>(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;
|
|
}
|
|
}
|