1
0

ShuttleDockControl.xaml.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. using System.Numerics;
  2. using Content.Client.Shuttles.Systems;
  3. using Content.Shared.Shuttles.BUIStates;
  4. using Content.Shared.Shuttles.Components;
  5. using Content.Shared.Shuttles.Systems;
  6. using Robust.Client.AutoGenerated;
  7. using Robust.Client.Graphics;
  8. using Robust.Client.UserInterface;
  9. using Robust.Client.UserInterface.Controls;
  10. using Robust.Client.UserInterface.XAML;
  11. using Robust.Shared.Map;
  12. using Robust.Shared.Map.Components;
  13. using Robust.Shared.Timing;
  14. namespace Content.Client.Shuttles.UI;
  15. [GenerateTypedNameReferences]
  16. public sealed partial class ShuttleDockControl : BaseShuttleControl
  17. {
  18. [Dependency] private readonly IGameTiming _timing = default!;
  19. [Dependency] private readonly IMapManager _mapManager = default!;
  20. private readonly DockingSystem _dockSystem;
  21. private readonly SharedShuttleSystem _shuttles;
  22. private readonly SharedTransformSystem _xformSystem;
  23. public NetEntity? HighlightedDock;
  24. public NetEntity? ViewedDock => _viewedState?.Entity;
  25. private DockingPortState? _viewedState;
  26. public EntityUid? GridEntity;
  27. private EntityCoordinates? _coordinates;
  28. private Angle? _angle;
  29. public DockingInterfaceState? DockState = null;
  30. private List<Entity<MapGridComponent>> _grids = new();
  31. private readonly HashSet<DockingPortState> _drawnDocks = new();
  32. private readonly Dictionary<DockingPortState, Button> _dockButtons = new();
  33. /// <summary>
  34. /// Store buttons for every other dock
  35. /// </summary>
  36. private readonly Dictionary<DockingPortState, Control> _dockContainers = new();
  37. private static readonly TimeSpan DockChangeCooldown = TimeSpan.FromSeconds(0.5);
  38. /// <summary>
  39. /// Rate-limiting for docking changes
  40. /// </summary>
  41. private TimeSpan _nextDockChange;
  42. public event Action<NetEntity>? OnViewDock;
  43. public event Action<NetEntity, NetEntity>? DockRequest;
  44. public event Action<NetEntity>? UndockRequest;
  45. public ShuttleDockControl() : base(2f, 32f, 8f)
  46. {
  47. RobustXamlLoader.Load(this);
  48. _dockSystem = EntManager.System<DockingSystem>();
  49. _shuttles = EntManager.System<SharedShuttleSystem>();
  50. _xformSystem = EntManager.System<SharedTransformSystem>();
  51. MinSize = new Vector2(SizeFull, SizeFull);
  52. }
  53. public void SetViewedDock(DockingPortState? dockState)
  54. {
  55. _viewedState = dockState;
  56. if (dockState != null)
  57. {
  58. _coordinates = EntManager.GetCoordinates(dockState.Coordinates);
  59. _angle = dockState.Angle;
  60. OnViewDock?.Invoke(dockState.Entity);
  61. }
  62. else
  63. {
  64. _coordinates = null;
  65. _angle = null;
  66. }
  67. }
  68. protected override void FrameUpdate(FrameEventArgs args)
  69. {
  70. base.FrameUpdate(args);
  71. HideDocks();
  72. _drawnDocks.Clear();
  73. }
  74. protected override void Draw(DrawingHandleScreen handle)
  75. {
  76. base.Draw(handle);
  77. DrawBacking(handle);
  78. if (_coordinates == null ||
  79. _angle == null ||
  80. DockState == null ||
  81. !EntManager.TryGetComponent<TransformComponent>(GridEntity, out var gridXform))
  82. {
  83. DrawNoSignal(handle);
  84. return;
  85. }
  86. DrawCircles(handle);
  87. var gridNent = EntManager.GetNetEntity(GridEntity);
  88. var mapPos = _xformSystem.ToMapCoordinates(_coordinates.Value);
  89. var ourGridToWorld = _xformSystem.GetWorldMatrix(GridEntity.Value);
  90. var selectedDockToOurGrid = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, Angle.Zero);
  91. var selectedDockToWorld = Matrix3x2.Multiply(selectedDockToOurGrid, ourGridToWorld);
  92. Box2 viewBoundsWorld = Matrix3Helpers.TransformBox(selectedDockToWorld, new Box2(-WorldRangeVector, WorldRangeVector));
  93. Matrix3x2.Invert(selectedDockToWorld, out var worldToSelectedDock);
  94. var selectedDockToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
  95. // Draw nearby grids
  96. var controlBounds = PixelSizeBox;
  97. _grids.Clear();
  98. _mapManager.FindGridsIntersecting(gridXform.MapID, viewBoundsWorld, ref _grids);
  99. // offset the dotted-line position to the bounds.
  100. Vector2? viewedDockPos = _viewedState != null ? MidPointVector : null;
  101. if (viewedDockPos != null)
  102. {
  103. viewedDockPos = viewedDockPos.Value + _angle.Value.RotateVec(new Vector2(0f,-0.6f) * MinimapScale);
  104. }
  105. var canDockChange = _timing.CurTime > _nextDockChange;
  106. var lineOffset = (float) _timing.RealTime.TotalSeconds * 30f;
  107. foreach (var grid in _grids)
  108. {
  109. EntManager.TryGetComponent(grid.Owner, out IFFComponent? iffComp);
  110. if (grid.Owner != GridEntity && !_shuttles.CanDraw(grid.Owner, iffComp: iffComp))
  111. continue;
  112. var curGridToWorld = _xformSystem.GetWorldMatrix(grid.Owner);
  113. var curGridToView = curGridToWorld * worldToSelectedDock * selectedDockToView;
  114. var color = _shuttles.GetIFFColor(grid.Owner, grid.Owner == GridEntity, component: iffComp);
  115. DrawGrid(handle, curGridToView, grid, color);
  116. // Draw any docks on that grid
  117. if (!DockState.Docks.TryGetValue(EntManager.GetNetEntity(grid), out var gridDocks))
  118. continue;
  119. foreach (var dock in gridDocks)
  120. {
  121. if (ViewedDock == dock.Entity)
  122. continue;
  123. var otherDockRotation = Matrix3Helpers.CreateRotation(dock.Angle);
  124. // This box is the AABB of all the vertices we draw below.
  125. var dockRenderBoundsLocal = new Box2(-0.5f, -0.7f, 0.5f, 0.5f);
  126. var currentDockToCurGrid = Matrix3Helpers.CreateTransform(dock.Coordinates.Position, dock.Angle);
  127. var currentDockToWorld = Matrix3x2.Multiply(currentDockToCurGrid, curGridToWorld);
  128. var dockRenderBoundsWorld = Matrix3Helpers.TransformBox(currentDockToWorld, dockRenderBoundsLocal);
  129. if (!viewBoundsWorld.Intersects(dockRenderBoundsWorld))
  130. continue;
  131. var collisionBL = Vector2.Transform(dock.Coordinates.Position +
  132. Vector2.Transform(new Vector2(-0.2f, -0.7f), otherDockRotation), curGridToView);
  133. var collisionBR = Vector2.Transform(dock.Coordinates.Position +
  134. Vector2.Transform(new Vector2(0.2f, -0.7f), otherDockRotation), curGridToView);
  135. var collisionTR = Vector2.Transform(dock.Coordinates.Position +
  136. Vector2.Transform(new Vector2(0.2f, -0.5f), otherDockRotation), curGridToView);
  137. var collisionTL = Vector2.Transform(dock.Coordinates.Position +
  138. Vector2.Transform(new Vector2(-0.2f, -0.5f), otherDockRotation), curGridToView);
  139. var verts = new[]
  140. {
  141. collisionBL,
  142. collisionBR,
  143. collisionBR,
  144. collisionTR,
  145. collisionTR,
  146. collisionTL,
  147. collisionTL,
  148. collisionBL,
  149. };
  150. var collisionCenter = verts[0] + verts[1] + verts[3] + verts[5];
  151. var otherDockConnection = Color.ToSrgb(Color.Pink);
  152. handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, otherDockConnection.WithAlpha(0.2f));
  153. handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts, otherDockConnection);
  154. // Draw the dock itself
  155. var dockBL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f), curGridToView);
  156. var dockBR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f), curGridToView);
  157. var dockTR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f), curGridToView);
  158. var dockTL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f), curGridToView);
  159. verts = new[]
  160. {
  161. dockBL,
  162. dockBR,
  163. dockBR,
  164. dockTR,
  165. dockTR,
  166. dockTL,
  167. dockTL,
  168. dockBL
  169. };
  170. Color otherDockColor;
  171. if (HighlightedDock == dock.Entity)
  172. {
  173. otherDockColor = Color.ToSrgb(Color.Magenta);
  174. }
  175. else
  176. {
  177. otherDockColor = Color.ToSrgb(Color.Purple);
  178. }
  179. /*
  180. * Can draw in these conditions:
  181. * 1. Same grid
  182. * 2. It's in range
  183. *
  184. * We don't want to draw stuff far away that's docked because it will just overlap our buttons
  185. */
  186. var canDraw = grid.Owner == GridEntity;
  187. _dockButtons.TryGetValue(dock, out var dockButton);
  188. // Rate limit
  189. if (dockButton != null && dock.GridDockedWith != null)
  190. {
  191. dockButton.Disabled = !canDockChange;
  192. }
  193. // If the dock is in range then also do highlighting
  194. if (viewedDockPos != null && dock.Coordinates.NetEntity != gridNent)
  195. {
  196. collisionCenter /= 4;
  197. var range = viewedDockPos.Value - collisionCenter;
  198. var maxRange = SharedDockingSystem.DockingHiglightRange * MinimapScale;
  199. var maxRangeSq = maxRange * maxRange;
  200. if (range.LengthSquared() < maxRangeSq)
  201. {
  202. if (dock.GridDockedWith == null)
  203. {
  204. var coordsOne = EntManager.GetCoordinates(_viewedState!.Coordinates);
  205. var coordsTwo = EntManager.GetCoordinates(dock.Coordinates);
  206. var mapOne = _xformSystem.ToMapCoordinates(coordsOne);
  207. var mapTwo = _xformSystem.ToMapCoordinates(coordsTwo);
  208. var rotA = _xformSystem.GetWorldRotation(coordsOne.EntityId) + _viewedState!.Angle;
  209. var rotB = _xformSystem.GetWorldRotation(coordsTwo.EntityId) + dock.Angle;
  210. var distanceSq = (mapOne.Position - mapTwo.Position).LengthSquared();
  211. var inAlignment = _dockSystem.InAlignment(mapOne, rotA, mapTwo, rotB);
  212. var maxDockDistSq = SharedDockingSystem.DockRange * SharedDockingSystem.DockRange;
  213. var canDock = distanceSq < maxDockDistSq && inAlignment;
  214. if (dockButton != null)
  215. dockButton.Disabled = !canDock || !canDockChange;
  216. var lineColor = inAlignment ? Color.Lime : Color.Red;
  217. handle.DrawDottedLine(viewedDockPos.Value, collisionCenter, lineColor, offset: lineOffset);
  218. }
  219. canDraw = true;
  220. }
  221. else
  222. {
  223. if (dockButton != null)
  224. dockButton.Disabled = true;
  225. }
  226. }
  227. handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, otherDockColor.WithAlpha(0.2f));
  228. handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts, otherDockColor);
  229. // Position the dock control above it
  230. var container = _dockContainers[dock];
  231. container.Visible = canDraw;
  232. if (canDraw)
  233. {
  234. // Because it's being layed out top-down we have to arrange for first frame.
  235. container.Arrange(PixelRect);
  236. var dockPositionInView = Vector2.Transform(dock.Coordinates.Position, curGridToView);
  237. var containerPos = dockPositionInView / UIScale - container.DesiredSize / 2 - new Vector2(0f, 0.75f) * MinimapScale;
  238. SetPosition(container, containerPos);
  239. }
  240. _drawnDocks.Add(dock);
  241. }
  242. }
  243. // Draw the dock's collision
  244. var invertedPosition = Vector2.Zero;
  245. invertedPosition.Y = -invertedPosition.Y;
  246. var rotation = Matrix3Helpers.CreateRotation(-_angle.Value + MathF.PI);
  247. var ourDockConnection = new UIBox2(
  248. ScalePosition(Vector2.Transform(new Vector2(-0.2f, -0.7f), rotation)),
  249. ScalePosition(Vector2.Transform(new Vector2(0.2f, -0.5f), rotation)));
  250. var ourDock = new UIBox2(
  251. ScalePosition(Vector2.Transform(new Vector2(-0.5f, 0.5f), rotation)),
  252. ScalePosition(Vector2.Transform(new Vector2(0.5f, -0.5f), rotation)));
  253. var dockColor = Color.Magenta;
  254. var connectionColor = Color.Pink;
  255. handle.DrawRect(ourDockConnection, connectionColor.WithAlpha(0.2f));
  256. handle.DrawRect(ourDockConnection, connectionColor, filled: false);
  257. // Draw the dock itself
  258. handle.DrawRect(ourDock, dockColor.WithAlpha(0.2f));
  259. handle.DrawRect(ourDock, dockColor, filled: false);
  260. }
  261. private void HideDocks()
  262. {
  263. foreach (var (dock, control) in _dockContainers)
  264. {
  265. if (_drawnDocks.Contains(dock))
  266. continue;
  267. control.Visible = false;
  268. }
  269. }
  270. public void BuildDocks(EntityUid? shuttle)
  271. {
  272. var viewedEnt = ViewedDock;
  273. _viewedState = null;
  274. foreach (var btn in _dockButtons.Values)
  275. {
  276. btn.Dispose();
  277. }
  278. foreach (var container in _dockContainers.Values)
  279. {
  280. container.Dispose();
  281. }
  282. _dockButtons.Clear();
  283. _dockContainers.Clear();
  284. if (DockState == null)
  285. return;
  286. var gridNent = EntManager.GetNetEntity(GridEntity);
  287. foreach (var (otherShuttle, docks) in DockState.Docks)
  288. {
  289. // If it's our shuttle we add a view button
  290. foreach (var dock in docks)
  291. {
  292. if (dock.Entity == viewedEnt)
  293. {
  294. _viewedState = dock;
  295. }
  296. var container = new BoxContainer()
  297. {
  298. Orientation = BoxContainer.LayoutOrientation.Vertical,
  299. Margin = new Thickness(3),
  300. };
  301. var panel = new PanelContainer()
  302. {
  303. HorizontalAlignment = HAlignment.Center,
  304. VerticalAlignment = VAlignment.Center,
  305. PanelOverride = new StyleBoxFlat(new Color(30, 30, 34, 200)),
  306. Children =
  307. {
  308. container,
  309. }
  310. };
  311. Button button;
  312. if (otherShuttle == gridNent)
  313. {
  314. button = new Button()
  315. {
  316. Text = Loc.GetString("shuttle-console-view"),
  317. };
  318. button.OnPressed += args =>
  319. {
  320. SetViewedDock(dock);
  321. };
  322. }
  323. else
  324. {
  325. if (dock.Connected)
  326. {
  327. button = new Button()
  328. {
  329. Text = Loc.GetString("shuttle-console-undock"),
  330. };
  331. button.OnPressed += args =>
  332. {
  333. _nextDockChange = _timing.CurTime + DockChangeCooldown;
  334. UndockRequest?.Invoke(dock.Entity);
  335. };
  336. }
  337. else
  338. {
  339. button = new Button()
  340. {
  341. Text = Loc.GetString("shuttle-console-dock"),
  342. Disabled = true,
  343. };
  344. button.OnPressed += args =>
  345. {
  346. if (ViewedDock == null)
  347. return;
  348. _nextDockChange = _timing.CurTime + DockChangeCooldown;
  349. DockRequest?.Invoke(ViewedDock.Value, dock.Entity);
  350. };
  351. }
  352. _dockButtons.Add(dock, button);
  353. }
  354. container.AddChild(new Label()
  355. {
  356. Text = dock.Name,
  357. HorizontalAlignment = HAlignment.Center,
  358. });
  359. button.HorizontalAlignment = HAlignment.Center;
  360. container.AddChild(button);
  361. AddChild(panel);
  362. panel.Measure(Vector2Helpers.Infinity);
  363. _dockContainers[dock] = panel;
  364. }
  365. }
  366. }
  367. }