ShuttleMapControl.xaml.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. using System.Buffers;
  2. using System.Numerics;
  3. using Content.Client.Shuttles.Systems;
  4. using Content.Shared.Shuttles.Components;
  5. using Content.Shared.Shuttles.UI.MapObjects;
  6. using Robust.Client.AutoGenerated;
  7. using Robust.Client.Graphics;
  8. using Robust.Client.Input;
  9. using Robust.Client.ResourceManagement;
  10. using Robust.Client.UserInterface;
  11. using Robust.Client.UserInterface.XAML;
  12. using Robust.Shared.Collections;
  13. using Robust.Shared.Input;
  14. using Robust.Shared.Map;
  15. using Robust.Shared.Map.Components;
  16. using Robust.Shared.Physics.Components;
  17. using Robust.Shared.Timing;
  18. using Robust.Shared.Utility;
  19. namespace Content.Client.Shuttles.UI;
  20. [GenerateTypedNameReferences]
  21. public sealed partial class ShuttleMapControl : BaseShuttleControl
  22. {
  23. [Dependency] private readonly IGameTiming _timing = default!;
  24. [Dependency] private readonly IInputManager _inputs = default!;
  25. [Dependency] private readonly IMapManager _mapManager = default!;
  26. private readonly ShuttleSystem _shuttles;
  27. private readonly SharedTransformSystem _xformSystem;
  28. protected override bool Draggable => true;
  29. public bool ShowBeacons = true;
  30. public MapId ViewingMap = MapId.Nullspace;
  31. private EntityUid? _shuttleEntity;
  32. private readonly Font _font;
  33. private readonly EntityQuery<PhysicsComponent> _physicsQuery;
  34. /// <summary>
  35. /// Toggles FTL mode on. This shows a pre-vis for FTLing a grid.
  36. /// </summary>
  37. public bool FtlMode;
  38. private Angle _ftlAngle;
  39. /// <summary>
  40. /// Are we currently in FTL.
  41. /// </summary>
  42. public bool InFtl;
  43. /// <summary>
  44. /// Raised when a request to FTL to a particular spot is raised.
  45. /// </summary>
  46. public event Action<MapCoordinates, Angle>? RequestFTL;
  47. public event Action<NetEntity, Angle>? RequestBeaconFTL;
  48. /// <summary>
  49. /// Set every draw to determine the beacons that are clickable for mouse events
  50. /// </summary>
  51. private List<IMapObject> _beacons = new();
  52. // Per frame data to avoid re-allocating
  53. private readonly List<IMapObject> _mapObjects = new();
  54. private readonly Dictionary<Color, List<Vector2>> _verts = new();
  55. private readonly Dictionary<Color, List<Vector2>> _edges = new();
  56. private readonly Dictionary<Color, List<(Vector2, string)>> _strings = new();
  57. private readonly List<ShuttleExclusionObject> _viewportExclusions = new();
  58. public ShuttleMapControl() : base(256f, 512f, 512f)
  59. {
  60. RobustXamlLoader.Load(this);
  61. _shuttles = EntManager.System<ShuttleSystem>();
  62. _xformSystem = EntManager.System<SharedTransformSystem>();
  63. var cache = IoCManager.Resolve<IResourceCache>();
  64. _physicsQuery = EntManager.GetEntityQuery<PhysicsComponent>();
  65. _font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
  66. }
  67. public void SetMap(MapId mapId, Vector2 offset, bool recentering = false)
  68. {
  69. ViewingMap = mapId;
  70. TargetOffset = offset;
  71. Recentering = recentering;
  72. }
  73. public void SetShuttle(EntityUid? entity)
  74. {
  75. _shuttleEntity = entity;
  76. }
  77. protected override void MouseMove(GUIMouseMoveEventArgs args)
  78. {
  79. // No move for you.
  80. if (FtlMode)
  81. return;
  82. base.MouseMove(args);
  83. }
  84. protected override void KeyBindUp(GUIBoundKeyEventArgs args)
  85. {
  86. if (FtlMode && ViewingMap != MapId.Nullspace)
  87. {
  88. if (args.Function == EngineKeyFunctions.UIClick)
  89. {
  90. var mapUid = _mapManager.GetMapEntityId(ViewingMap);
  91. var beaconsOnly = EntManager.TryGetComponent(mapUid, out FTLDestinationComponent? destComp) &&
  92. destComp.BeaconsOnly;
  93. var mapTransform = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero);
  94. if (beaconsOnly && TryGetBeacon(_beacons, mapTransform, args.RelativePixelPosition, PixelRect, out var foundBeacon, out _))
  95. {
  96. RequestBeaconFTL?.Invoke(foundBeacon.Entity, _ftlAngle);
  97. }
  98. else
  99. {
  100. // We'll send the "adjusted" position and server will adjust it back when relevant.
  101. var mapCoords = new MapCoordinates(InverseMapPosition(args.RelativePosition), ViewingMap);
  102. RequestFTL?.Invoke(mapCoords, _ftlAngle);
  103. }
  104. }
  105. }
  106. base.KeyBindUp(args);
  107. }
  108. protected override void MouseWheel(GUIMouseWheelEventArgs args)
  109. {
  110. // Scroll handles FTL rotation if you're in FTL mode.
  111. if (FtlMode)
  112. {
  113. _ftlAngle += Angle.FromDegrees(15f) * args.Delta.Y;
  114. _ftlAngle = _ftlAngle.Reduced();
  115. return;
  116. }
  117. base.MouseWheel(args);
  118. }
  119. private void DrawParallax(DrawingHandleScreen handle)
  120. {
  121. if (!EntManager.TryGetComponent(_shuttleEntity, out TransformComponent? shuttleXform) || shuttleXform.MapUid == null)
  122. return;
  123. // TODO: Figure out how the fuck to make this common between the 3 slightly different parallax methods and move to parallaxsystem.
  124. // Draw background texture
  125. var tex = _shuttles.GetTexture(shuttleXform.MapUid.Value);
  126. // Size of the texture in world units.
  127. var size = tex.Size * MinimapScale * 1f;
  128. var position = ScalePosition(new Vector2(-Offset.X, Offset.Y));
  129. var slowness = 1f;
  130. // The "home" position is the effective origin of this layer.
  131. // Parallax shifting is relative to the home, and shifts away from the home and towards the Eye centre.
  132. // The effects of this are such that a slowness of 1 anchors the layer to the centre of the screen, while a slowness of 0 anchors the layer to the world.
  133. // (For values 0.0 to 1.0 this is in effect a lerp, but it's deliberately unclamped.)
  134. // The ParallaxAnchor adapts the parallax for station positioning and possibly map-specific tweaks.
  135. var home = Vector2.Zero;
  136. var scrolled = Vector2.Zero;
  137. // Origin - start with the parallax shift itself.
  138. var originBL = (position - home) * slowness + scrolled;
  139. // Place at the home.
  140. originBL += home;
  141. // Centre the image.
  142. originBL -= size / 2;
  143. // Remove offset so we can floor.
  144. var botLeft = new Vector2(0f, 0f);
  145. var topRight = botLeft + Size;
  146. var flooredBL = botLeft - originBL;
  147. // Floor to background size.
  148. flooredBL = (flooredBL / size).Floored() * size;
  149. // Re-offset.
  150. flooredBL += originBL;
  151. for (var x = flooredBL.X; x < topRight.X; x += size.X)
  152. {
  153. for (var y = flooredBL.Y; y < topRight.Y; y += size.Y)
  154. {
  155. handle.DrawTextureRect(tex, new UIBox2(x, y, x + size.X, y + size.Y));
  156. }
  157. }
  158. }
  159. /// <summary>
  160. /// Gets the map objects that intersect the viewport.
  161. /// </summary>
  162. /// <param name="mapObjects"></param>
  163. /// <returns></returns>
  164. private List<IMapObject> GetViewportMapObjects(Matrix3x2 matty, List<IMapObject> mapObjects)
  165. {
  166. var results = new List<IMapObject>();
  167. var enlargement = new Vector2i((int) (16 * UIScale), (int) (16 * UIScale));
  168. var viewBox = new UIBox2i(Vector2i.Zero - enlargement, PixelSize + enlargement);
  169. foreach (var mapObj in mapObjects)
  170. {
  171. // If it's a grid-map skip it.
  172. if (mapObj is GridMapObject gridObj && EntManager.HasComponent<MapComponent>(gridObj.Entity))
  173. continue;
  174. var mapCoords = _shuttles.GetMapCoordinates(mapObj);
  175. var relativePos = Vector2.Transform(mapCoords.Position, matty);
  176. relativePos = relativePos with { Y = -relativePos.Y };
  177. var uiPosition = ScalePosition(relativePos);
  178. if (!viewBox.Contains(uiPosition.Floored()))
  179. continue;
  180. results.Add(mapObj);
  181. }
  182. return results;
  183. }
  184. protected override void Draw(DrawingHandleScreen handle)
  185. {
  186. base.Draw(handle);
  187. if (ViewingMap == MapId.Nullspace)
  188. return;
  189. var mapObjects = _mapObjects;
  190. DrawRecenter();
  191. if (InFtl || mapObjects.Count == 0)
  192. {
  193. DrawBacking(handle);
  194. DrawNoSignal(handle);
  195. return;
  196. }
  197. DrawParallax(handle);
  198. var viewedMapUid = _mapManager.GetMapEntityId(ViewingMap);
  199. var matty = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero);
  200. var realTime = _timing.RealTime;
  201. var viewBox = new Box2(Offset - WorldRangeVector, Offset + WorldRangeVector);
  202. var viewportObjects = GetViewportMapObjects(matty, mapObjects);
  203. _viewportExclusions.Clear();
  204. // Draw our FTL range + no FTL zones
  205. // Do it up here because we want this layered below most things.
  206. if (FtlMode)
  207. {
  208. if (EntManager.TryGetComponent<TransformComponent>(_shuttleEntity, out var shuttleXform))
  209. {
  210. var gridUid = _shuttleEntity.Value;
  211. var gridPhysics = _physicsQuery.GetComponent(gridUid);
  212. var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(shuttleXform);
  213. gridPos = Maps.GetGridPosition((gridUid, gridPhysics), gridPos, gridRot);
  214. var gridRelativePos = Vector2.Transform(gridPos, matty);
  215. gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y };
  216. var gridUiPos = ScalePosition(gridRelativePos);
  217. var range = _shuttles.GetFTLRange(gridUid);
  218. range *= MinimapScale;
  219. handle.DrawCircle(gridUiPos, range, Color.Gold, filled: false);
  220. }
  221. }
  222. var exclusionColor = Color.Red;
  223. // Exclusions need a bumped range so we check all the ones on the map.
  224. foreach (var mapObj in mapObjects)
  225. {
  226. if (mapObj is not ShuttleExclusionObject exclusion)
  227. continue;
  228. // Check if it even intersects the viewport.
  229. var coords = EntManager.GetCoordinates(exclusion.Coordinates);
  230. var mapCoords = _xformSystem.ToMapCoordinates(coords);
  231. var enlargedBounds = viewBox.Enlarged(exclusion.Range);
  232. if (mapCoords.MapId != ViewingMap ||
  233. !enlargedBounds.Contains(mapCoords.Position))
  234. {
  235. continue;
  236. }
  237. var adjustedPos = Vector2.Transform(mapCoords.Position, matty);
  238. var localPos = ScalePosition(adjustedPos with { Y = -adjustedPos.Y});
  239. handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor.WithAlpha(0.05f));
  240. handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor, filled: false);
  241. _viewportExclusions.Add(exclusion);
  242. }
  243. _verts.Clear();
  244. _edges.Clear();
  245. _strings.Clear();
  246. // Add beacons if relevant.
  247. var beaconsOnly = _shuttles.IsBeaconMap(viewedMapUid);
  248. var controlLocalBounds = PixelRect;
  249. _beacons.Clear();
  250. if (ShowBeacons)
  251. {
  252. var beaconColor = Color.AliceBlue;
  253. foreach (var (beaconName, coords, mapO) in GetBeacons(viewportObjects, matty, controlLocalBounds))
  254. {
  255. var localPos = Vector2.Transform(coords.Position, matty);
  256. localPos = localPos with { Y = -localPos.Y };
  257. var beaconUiPos = ScalePosition(localPos);
  258. var mapObject = GetMapObject(localPos, Angle.Zero, scale: 0.75f, scalePosition: true);
  259. var existingVerts = _verts.GetOrNew(beaconColor);
  260. var existingEdges = _edges.GetOrNew(beaconColor);
  261. AddMapObject(existingEdges, existingVerts, mapObject);
  262. _beacons.Add(mapO);
  263. var existingStrings = _strings.GetOrNew(beaconColor);
  264. existingStrings.Add((beaconUiPos, beaconName));
  265. }
  266. }
  267. foreach (var mapObj in viewportObjects)
  268. {
  269. if (mapObj is not GridMapObject gridObj || !EntManager.TryGetComponent(gridObj.Entity, out MapGridComponent? mapGrid))
  270. continue;
  271. Entity<MapGridComponent> grid = (gridObj.Entity, mapGrid);
  272. IFFComponent? iffComp = null;
  273. // Rudimentary IFF for now, if IFF hiding on then we don't show on the map at all
  274. if (grid.Owner != _shuttleEntity &&
  275. EntManager.TryGetComponent(grid, out iffComp) &&
  276. (iffComp.Flags & IFFFlags.Hide) != 0x0)
  277. {
  278. continue;
  279. }
  280. var gridColor = _shuttles.GetIFFColor(grid, self: _shuttleEntity == grid.Owner, component: iffComp);
  281. var existingVerts = _verts.GetOrNew(gridColor);
  282. var existingEdges = _edges.GetOrNew(gridColor);
  283. var gridPhysics = _physicsQuery.GetComponent(grid.Owner);
  284. var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(grid.Owner);
  285. gridPos = Maps.GetGridPosition((grid, gridPhysics), gridPos, gridRot);
  286. var gridRelativePos = Vector2.Transform(gridPos, matty);
  287. gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y };
  288. var gridUiPos = ScalePosition(gridRelativePos);
  289. var mapObject = GetMapObject(gridRelativePos, Angle.Zero, scalePosition: true);
  290. AddMapObject(existingEdges, existingVerts, mapObject);
  291. // Text
  292. if (iffComp != null && (iffComp.Flags & IFFFlags.HideLabel) != 0x0)
  293. continue;
  294. // Force drawing it at this point.
  295. var iffText = _shuttles.GetIFFLabel(grid, self: true, component: iffComp);
  296. if (string.IsNullOrEmpty(iffText))
  297. continue;
  298. var existingStrings = _strings.GetOrNew(gridColor);
  299. existingStrings.Add((gridUiPos, iffText));
  300. }
  301. // Batch the colors whoopie
  302. // really only affects forks with lots of grids.
  303. foreach (var (color, sendVerts) in _verts)
  304. {
  305. handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, sendVerts, color.WithAlpha(0.05f));
  306. }
  307. foreach (var (color, sendEdges) in _edges)
  308. {
  309. handle.DrawPrimitives(DrawPrimitiveTopology.LineList, sendEdges, color);
  310. }
  311. foreach (var (color, sendStrings) in _strings)
  312. {
  313. var adjustedColor = Color.FromSrgb(color);
  314. foreach (var (gridUiPos, iffText) in sendStrings)
  315. {
  316. var textWidth = handle.GetDimensions(_font, iffText, 1f);
  317. handle.DrawString(_font, gridUiPos + textWidth with { X = -textWidth.X / 2f, Y = textWidth.Y * UIScale }, iffText, adjustedColor);
  318. }
  319. }
  320. var mousePos = _inputs.MouseScreenPosition;
  321. var mouseLocalPos = GetLocalPosition(mousePos);
  322. // Draw dotted line from our own shuttle entity to mouse.
  323. if (FtlMode)
  324. {
  325. if (mousePos.Window != WindowId.Invalid)
  326. {
  327. // If mouse inbounds then draw it.
  328. if (_shuttleEntity != null && controlLocalBounds.Contains(mouseLocalPos.Floored()) &&
  329. EntManager.TryGetComponent(_shuttleEntity, out TransformComponent? shuttleXform) &&
  330. shuttleXform.MapID != MapId.Nullspace)
  331. {
  332. // If it's a beacon only map then snap the mouse to a nearby spot.
  333. ShuttleBeaconObject foundBeacon = default;
  334. // Check for beacons around mouse and snap to that.
  335. if (beaconsOnly && TryGetBeacon(viewportObjects, matty, mouseLocalPos, controlLocalBounds, out foundBeacon, out var foundLocalPos))
  336. {
  337. mouseLocalPos = foundLocalPos;
  338. }
  339. var grid = EntManager.GetComponent<MapGridComponent>(_shuttleEntity.Value);
  340. var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(shuttleXform);
  341. gridPos = Maps.GetGridPosition(_shuttleEntity.Value, gridPos, gridRot);
  342. // do NOT apply LocalCenter operation here because it will be adjusted in FTLFree.
  343. var mouseMapPos = InverseMapPosition(mouseLocalPos);
  344. var ftlFree = (!beaconsOnly || foundBeacon != default) &&
  345. _shuttles.FTLFree(_shuttleEntity.Value, new EntityCoordinates(viewedMapUid, mouseMapPos), _ftlAngle, _viewportExclusions);
  346. var color = ftlFree ? Color.LimeGreen : Color.Magenta;
  347. var gridRelativePos = Vector2.Transform(gridPos, matty);
  348. gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y };
  349. var gridUiPos = ScalePosition(gridRelativePos);
  350. // Draw FTL buffer around the mouse.
  351. var ourFTLBuffer = _shuttles.GetFTLBufferRange(_shuttleEntity.Value, grid);
  352. ourFTLBuffer *= MinimapScale;
  353. handle.DrawCircle(mouseLocalPos, ourFTLBuffer, Color.Magenta.WithAlpha(0.01f));
  354. handle.DrawCircle(mouseLocalPos, ourFTLBuffer, Color.Magenta, filled: false);
  355. // Draw line from our shuttle to target
  356. // Might need to clip the line if it's too far? But my brain wasn't working so F.
  357. handle.DrawDottedLine(gridUiPos, mouseLocalPos, color, (float) realTime.TotalSeconds * 30f);
  358. // Draw shuttle pre-vis
  359. var mouseVerts = GetMapObject(mouseLocalPos, _ftlAngle, scale: MinimapScale);
  360. handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, mouseVerts.Span, color.WithAlpha(0.05f));
  361. handle.DrawPrimitives(DrawPrimitiveTopology.LineLoop, mouseVerts.Span, color);
  362. // Draw a notch indicating direction.
  363. var ftlLength = GetMapObjectRadius() + 16f;
  364. var ftlEnd = mouseLocalPos + _ftlAngle.RotateVec(new Vector2(0f, -ftlLength));
  365. handle.DrawLine(mouseLocalPos, ftlEnd, color);
  366. }
  367. }
  368. }
  369. // Draw the coordinates
  370. var mapOffset = MidPointVector;
  371. if (mousePos.Window != WindowId.Invalid &&
  372. controlLocalBounds.Contains(mouseLocalPos.Floored()))
  373. {
  374. mapOffset = mouseLocalPos;
  375. }
  376. mapOffset = InverseMapPosition(mapOffset);
  377. var coordsText = $"{mapOffset.X:0.0}, {mapOffset.Y:0.0}";
  378. DrawData(handle, coordsText);
  379. }
  380. private void AddMapObject(List<Vector2> edges, List<Vector2> verts, ValueList<Vector2> mapObject)
  381. {
  382. var bottom = mapObject[0];
  383. var right = mapObject[1];
  384. var top = mapObject[2];
  385. var left = mapObject[3];
  386. // Diamond interior
  387. verts.Add(bottom);
  388. verts.Add(right);
  389. verts.Add(top);
  390. verts.Add(bottom);
  391. verts.Add(top);
  392. verts.Add(left);
  393. // Diamond edges
  394. edges.Add(bottom);
  395. edges.Add(right);
  396. edges.Add(right);
  397. edges.Add(top);
  398. edges.Add(top);
  399. edges.Add(left);
  400. edges.Add(left);
  401. edges.Add(bottom);
  402. }
  403. /// <summary>
  404. /// Returns the beacons that intersect the viewport.
  405. /// </summary>
  406. private IEnumerable<(string Beacon, MapCoordinates Coordinates, IMapObject MapObject)> GetBeacons(List<IMapObject> mapObjs, Matrix3x2 mapTransform, UIBox2i area)
  407. {
  408. foreach (var mapO in mapObjs)
  409. {
  410. if (mapO is not ShuttleBeaconObject beacon)
  411. continue;
  412. var beaconCoords = _xformSystem.ToMapCoordinates(EntManager.GetCoordinates(beacon.Coordinates));
  413. var position = Vector2.Transform(beaconCoords.Position, mapTransform);
  414. var localPos = ScalePosition(position with {Y = -position.Y});
  415. // If beacon not on screen then ignore it.
  416. if (!area.Contains(localPos.Floored()))
  417. continue;
  418. yield return (beacon.Name, beaconCoords, mapO);
  419. }
  420. }
  421. private float GetMapObjectRadius(float scale = 1f) => WorldRange / 40f * scale;
  422. private ValueList<Vector2> GetMapObject(Vector2 localPos, Angle angle, float scale = 1f, bool scalePosition = false)
  423. {
  424. // Constant size diamonds
  425. var diamondRadius = GetMapObjectRadius();
  426. var mapObj = new ValueList<Vector2>(4)
  427. {
  428. localPos + angle.RotateVec(new Vector2(0f, -2f * diamondRadius)) * scale,
  429. localPos + angle.RotateVec(new Vector2(diamondRadius, 0f)) * scale,
  430. localPos + angle.RotateVec(new Vector2(0f, 2f * diamondRadius)) * scale,
  431. localPos + angle.RotateVec(new Vector2(-diamondRadius, 0f)) * scale,
  432. };
  433. if (scalePosition)
  434. {
  435. for (var i = 0; i < mapObj.Count; i++)
  436. {
  437. mapObj[i] = ScalePosition(mapObj[i]);
  438. }
  439. }
  440. return mapObj;
  441. }
  442. private bool TryGetBeacon(IEnumerable<IMapObject> mapObjects, Matrix3x2 mapTransform, Vector2 mousePos, UIBox2i area, out ShuttleBeaconObject foundBeacon, out Vector2 foundLocalPos)
  443. {
  444. // In pixels
  445. const float BeaconSnapRange = 32f;
  446. float nearestValue = float.MaxValue;
  447. foundLocalPos = Vector2.Zero;
  448. foundBeacon = default;
  449. foreach (var mapObj in mapObjects)
  450. {
  451. if (mapObj is not ShuttleBeaconObject beaconObj)
  452. continue;
  453. var beaconCoords = _xformSystem.ToMapCoordinates(EntManager.GetCoordinates(beaconObj.Coordinates));
  454. if (beaconCoords.MapId != ViewingMap)
  455. continue;
  456. // Invalid beacon?
  457. if (!_shuttles.CanFTLBeacon(beaconObj.Coordinates))
  458. continue;
  459. var position = Vector2.Transform(beaconCoords.Position, mapTransform);
  460. var localPos = ScalePosition(position with {Y = -position.Y});
  461. // If beacon not on screen then ignore it.
  462. if (!area.Contains(localPos.Floored()))
  463. continue;
  464. var distance = (localPos - mousePos).Length();
  465. if (distance > BeaconSnapRange * UIScale ||
  466. distance > nearestValue)
  467. {
  468. continue;
  469. }
  470. foundLocalPos = localPos;
  471. nearestValue = distance;
  472. foundBeacon = beaconObj;
  473. }
  474. return foundBeacon != default;
  475. }
  476. /// <summary>
  477. /// Sets the map objects for the next draw.
  478. /// </summary>
  479. public void SetMapObjects(Dictionary<MapId, List<IMapObject>> mapObjects)
  480. {
  481. _mapObjects.Clear();
  482. if (mapObjects.TryGetValue(ViewingMap, out var obbies))
  483. {
  484. _mapObjects.AddRange(obbies);
  485. }
  486. }
  487. }