NavMapControl.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. using Content.Client.Stylesheets;
  2. using Content.Client.UserInterface.Controls;
  3. using Content.Shared.Input;
  4. using Content.Shared.Pinpointer;
  5. using Robust.Client.Graphics;
  6. using Robust.Client.ResourceManagement;
  7. using Robust.Client.UserInterface;
  8. using Robust.Client.UserInterface.Controls;
  9. using Robust.Shared.Collections;
  10. using Robust.Shared.Input;
  11. using Robust.Shared.Map;
  12. using Robust.Shared.Map.Components;
  13. using Robust.Shared.Physics;
  14. using Robust.Shared.Physics.Collision.Shapes;
  15. using Robust.Shared.Physics.Components;
  16. using Robust.Shared.Timing;
  17. using System.Numerics;
  18. using JetBrains.Annotations;
  19. using Content.Shared.Atmos;
  20. using System.Linq;
  21. using Robust.Shared.Utility;
  22. namespace Content.Client.Pinpointer.UI;
  23. /// <summary>
  24. /// Displays the nav map data of the specified grid.
  25. /// </summary>
  26. [UsedImplicitly, Virtual]
  27. public partial class NavMapControl : MapGridControl
  28. {
  29. [Dependency] private IResourceCache _cache = default!;
  30. private readonly SharedTransformSystem _transformSystem;
  31. private readonly SharedNavMapSystem _navMapSystem;
  32. public EntityUid? Owner;
  33. public EntityUid? MapUid;
  34. protected override bool Draggable => true;
  35. // Actions
  36. public event Action<NetEntity?>? TrackedEntitySelectedAction;
  37. public event Action<DrawingHandleScreen>? PostWallDrawingAction;
  38. // Tracked data
  39. public Dictionary<EntityCoordinates, (bool Visible, Color Color)> TrackedCoordinates = new();
  40. public Dictionary<NetEntity, NavMapBlip> TrackedEntities = new();
  41. public List<(Vector2, Vector2)> TileLines = new();
  42. public List<(Vector2, Vector2)> TileRects = new();
  43. public List<(Vector2[], Color)> TilePolygons = new();
  44. public List<NavMapRegionOverlay> RegionOverlays = new();
  45. // Default colors
  46. public Color WallColor = new(102, 217, 102);
  47. public Color TileColor = new(30, 67, 30);
  48. // Constants
  49. protected float UpdateTime = 1.0f;
  50. protected float MaxSelectableDistance = 10f;
  51. protected float MinDragDistance = 5f;
  52. protected static float MinDisplayedRange = 8f;
  53. protected static float MaxDisplayedRange = 128f;
  54. protected static float DefaultDisplayedRange = 48f;
  55. protected float MinmapScaleModifier = 0.075f;
  56. protected float FullWallInstep = 0.165f;
  57. protected float ThinWallThickness = 0.165f;
  58. protected float ThinDoorThickness = 0.30f;
  59. // Local variables
  60. private float _updateTimer = 1.0f;
  61. private Dictionary<Color, Color> _sRGBLookUp = new();
  62. protected Color BackgroundColor;
  63. protected float BackgroundOpacity = 0.9f;
  64. private int _targetFontsize = 8;
  65. private Dictionary<Vector2i, Vector2i> _horizLines = new();
  66. private Dictionary<Vector2i, Vector2i> _horizLinesReversed = new();
  67. private Dictionary<Vector2i, Vector2i> _vertLines = new();
  68. private Dictionary<Vector2i, Vector2i> _vertLinesReversed = new();
  69. // Components
  70. private NavMapComponent? _navMap;
  71. private MapGridComponent? _grid;
  72. private TransformComponent? _xform;
  73. private PhysicsComponent? _physics;
  74. private FixturesComponent? _fixtures;
  75. // TODO: https://github.com/space-wizards/RobustToolbox/issues/3818
  76. private readonly Label _zoom = new()
  77. {
  78. VerticalAlignment = VAlignment.Top,
  79. HorizontalExpand = true,
  80. Margin = new Thickness(8f, 8f),
  81. };
  82. private readonly Button _recenter = new()
  83. {
  84. Text = Loc.GetString("navmap-recenter"),
  85. VerticalAlignment = VAlignment.Top,
  86. HorizontalAlignment = HAlignment.Right,
  87. HorizontalExpand = true,
  88. Margin = new Thickness(8f, 4f),
  89. Disabled = true,
  90. };
  91. private readonly CheckBox _beacons = new()
  92. {
  93. Text = Loc.GetString("navmap-toggle-beacons"),
  94. VerticalAlignment = VAlignment.Center,
  95. HorizontalAlignment = HAlignment.Center,
  96. HorizontalExpand = true,
  97. Margin = new Thickness(4f, 0f),
  98. Pressed = true,
  99. };
  100. public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDisplayedRange)
  101. {
  102. IoCManager.InjectDependencies(this);
  103. _transformSystem = EntManager.System<SharedTransformSystem>();
  104. _navMapSystem = EntManager.System<SharedNavMapSystem>();
  105. BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity));
  106. RectClipContent = true;
  107. HorizontalExpand = true;
  108. VerticalExpand = true;
  109. var topPanel = new PanelContainer()
  110. {
  111. PanelOverride = new StyleBoxFlat()
  112. {
  113. BackgroundColor = StyleNano.ButtonColorContext.WithAlpha(1f),
  114. BorderColor = StyleNano.PanelDark
  115. },
  116. VerticalExpand = false,
  117. HorizontalExpand = true,
  118. SetWidth = 650f,
  119. Children =
  120. {
  121. new BoxContainer()
  122. {
  123. Orientation = BoxContainer.LayoutOrientation.Horizontal,
  124. Children =
  125. {
  126. _zoom,
  127. _beacons,
  128. _recenter
  129. }
  130. }
  131. }
  132. };
  133. var topContainer = new BoxContainer()
  134. {
  135. Orientation = BoxContainer.LayoutOrientation.Vertical,
  136. HorizontalExpand = true,
  137. Children =
  138. {
  139. topPanel,
  140. new Control()
  141. {
  142. Name = "DrawingControl",
  143. VerticalExpand = true,
  144. Margin = new Thickness(5f, 5f)
  145. }
  146. }
  147. };
  148. AddChild(topContainer);
  149. topPanel.Measure(Vector2Helpers.Infinity);
  150. _recenter.OnPressed += args =>
  151. {
  152. Recentering = true;
  153. };
  154. ForceNavMapUpdate();
  155. }
  156. public void ForceNavMapUpdate()
  157. {
  158. EntManager.TryGetComponent(MapUid, out _navMap);
  159. EntManager.TryGetComponent(MapUid, out _grid);
  160. EntManager.TryGetComponent(MapUid, out _xform);
  161. EntManager.TryGetComponent(MapUid, out _physics);
  162. EntManager.TryGetComponent(MapUid, out _fixtures);
  163. UpdateNavMap();
  164. }
  165. public void CenterToCoordinates(EntityCoordinates coordinates)
  166. {
  167. if (_physics != null)
  168. Offset = new Vector2(coordinates.X, coordinates.Y) - _physics.LocalCenter;
  169. _recenter.Disabled = false;
  170. }
  171. protected override void KeyBindUp(GUIBoundKeyEventArgs args)
  172. {
  173. base.KeyBindUp(args);
  174. if (args.Function == EngineKeyFunctions.UIClick)
  175. {
  176. if (TrackedEntitySelectedAction == null)
  177. return;
  178. if (_xform == null || _physics == null || TrackedEntities.Count == 0)
  179. return;
  180. // If the cursor has moved a significant distance, exit
  181. if ((StartDragPosition - args.PointerLocation.Position).Length() > MinDragDistance)
  182. return;
  183. // Get the clicked position
  184. var offset = Offset + _physics.LocalCenter;
  185. var localPosition = args.PointerLocation.Position - GlobalPixelPosition;
  186. // Convert to a world position
  187. var unscaledPosition = (localPosition - MidPointVector) / MinimapScale;
  188. var worldPosition = Vector2.Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset, _transformSystem.GetWorldMatrix(_xform));
  189. // Find closest tracked entity in range
  190. var closestEntity = NetEntity.Invalid;
  191. var closestDistance = float.PositiveInfinity;
  192. foreach ((var currentEntity, var blip) in TrackedEntities)
  193. {
  194. if (!blip.Selectable)
  195. continue;
  196. var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length();
  197. if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance)
  198. continue;
  199. closestEntity = currentEntity;
  200. closestDistance = currentDistance;
  201. }
  202. if (closestDistance > MaxSelectableDistance || !closestEntity.IsValid())
  203. return;
  204. TrackedEntitySelectedAction.Invoke(closestEntity);
  205. }
  206. else if (args.Function == EngineKeyFunctions.UIRightClick)
  207. {
  208. // Clear current selection with right click
  209. TrackedEntitySelectedAction?.Invoke(null);
  210. }
  211. else if (args.Function == ContentKeyFunctions.ExamineEntity)
  212. {
  213. // Toggle beacon labels
  214. _beacons.Pressed = !_beacons.Pressed;
  215. }
  216. }
  217. protected override void MouseMove(GUIMouseMoveEventArgs args)
  218. {
  219. base.MouseMove(args);
  220. if (Offset != Vector2.Zero)
  221. _recenter.Disabled = false;
  222. else
  223. _recenter.Disabled = true;
  224. }
  225. protected override void Draw(DrawingHandleScreen handle)
  226. {
  227. base.Draw(handle);
  228. // Get the components necessary for drawing the navmap
  229. EntManager.TryGetComponent(MapUid, out _navMap);
  230. EntManager.TryGetComponent(MapUid, out _grid);
  231. EntManager.TryGetComponent(MapUid, out _xform);
  232. EntManager.TryGetComponent(MapUid, out _physics);
  233. EntManager.TryGetComponent(MapUid, out _fixtures);
  234. if (_navMap == null || _grid == null || _xform == null)
  235. return;
  236. // Map re-centering
  237. _recenter.Disabled = DrawRecenter();
  238. // Update zoom text
  239. _zoom.Text = Loc.GetString("navmap-zoom", ("value", $"{(DefaultDisplayedRange / WorldRange):0.0}"));
  240. // Update offset with physics local center
  241. var offset = Offset;
  242. if (_physics != null)
  243. offset += _physics.LocalCenter;
  244. var offsetVec = new Vector2(offset.X, -offset.Y);
  245. // Wall sRGB
  246. if (!_sRGBLookUp.TryGetValue(WallColor, out var wallsRGB))
  247. {
  248. wallsRGB = Color.ToSrgb(WallColor);
  249. _sRGBLookUp[WallColor] = wallsRGB;
  250. }
  251. // Draw floor tiles
  252. if (TilePolygons.Any())
  253. {
  254. Span<Vector2> verts = new Vector2[8];
  255. foreach (var (polygonVerts, polygonColor) in TilePolygons)
  256. {
  257. for (var i = 0; i < polygonVerts.Length; i++)
  258. {
  259. var vert = polygonVerts[i] - offset;
  260. verts[i] = ScalePosition(new Vector2(vert.X, -vert.Y));
  261. }
  262. handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..polygonVerts.Length], polygonColor);
  263. }
  264. }
  265. // Draw region overlays
  266. if (_grid != null)
  267. {
  268. foreach (var regionOverlay in RegionOverlays)
  269. {
  270. foreach (var gridCoords in regionOverlay.GridCoords)
  271. {
  272. var positionTopLeft = ScalePosition(new Vector2(gridCoords.Item1.X, -gridCoords.Item1.Y) - new Vector2(offset.X, -offset.Y));
  273. var positionBottomRight = ScalePosition(new Vector2(gridCoords.Item2.X + _grid.TileSize, -gridCoords.Item2.Y - _grid.TileSize) - new Vector2(offset.X, -offset.Y));
  274. var box = new UIBox2(positionTopLeft, positionBottomRight);
  275. handle.DrawRect(box, regionOverlay.Color);
  276. }
  277. }
  278. }
  279. // Draw map lines
  280. if (TileLines.Any())
  281. {
  282. var lines = new ValueList<Vector2>(TileLines.Count * 2);
  283. foreach (var (o, t) in TileLines)
  284. {
  285. var origin = ScalePosition(o - offsetVec);
  286. var terminus = ScalePosition(t - offsetVec);
  287. lines.Add(origin);
  288. lines.Add(terminus);
  289. }
  290. if (lines.Count > 0)
  291. handle.DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, wallsRGB);
  292. }
  293. // Draw map rects
  294. if (TileRects.Any())
  295. {
  296. var rects = new ValueList<Vector2>(TileRects.Count * 8);
  297. foreach (var (lt, rb) in TileRects)
  298. {
  299. var leftTop = ScalePosition(lt - offsetVec);
  300. var rightBottom = ScalePosition(rb - offsetVec);
  301. var rightTop = new Vector2(rightBottom.X, leftTop.Y);
  302. var leftBottom = new Vector2(leftTop.X, rightBottom.Y);
  303. rects.Add(leftTop);
  304. rects.Add(rightTop);
  305. rects.Add(rightTop);
  306. rects.Add(rightBottom);
  307. rects.Add(rightBottom);
  308. rects.Add(leftBottom);
  309. rects.Add(leftBottom);
  310. rects.Add(leftTop);
  311. }
  312. if (rects.Count > 0)
  313. handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, wallsRGB);
  314. }
  315. // Invoke post wall drawing action
  316. if (PostWallDrawingAction != null)
  317. PostWallDrawingAction.Invoke(handle);
  318. var curTime = Timing.RealTime;
  319. var blinkFrequency = 1f / 1f;
  320. var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f;
  321. // Tracked coordinates (simple dot, legacy)
  322. foreach (var (coord, value) in TrackedCoordinates)
  323. {
  324. if (lit && value.Visible)
  325. {
  326. var mapPos = _transformSystem.ToMapCoordinates(coord);
  327. if (mapPos.MapId != MapId.Nullspace)
  328. {
  329. var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset;
  330. position = ScalePosition(new Vector2(position.X, -position.Y));
  331. handle.DrawCircle(position, float.Sqrt(MinimapScale) * 2f, value.Color);
  332. }
  333. }
  334. }
  335. // Tracked entities (can use a supplied sprite as a marker instead; should probably just replace TrackedCoordinates with this eventually)
  336. foreach (var blip in TrackedEntities.Values)
  337. {
  338. if (blip.Blinks && !lit)
  339. continue;
  340. if (blip.Texture == null)
  341. continue;
  342. var mapPos = _transformSystem.ToMapCoordinates(blip.Coordinates);
  343. if (mapPos.MapId != MapId.Nullspace)
  344. {
  345. var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset;
  346. position = ScalePosition(new Vector2(position.X, -position.Y));
  347. var scalingCoefficient = MinmapScaleModifier * float.Sqrt(MinimapScale);
  348. var positionOffset = new Vector2(scalingCoefficient * blip.Scale * blip.Texture.Width, scalingCoefficient * blip.Scale * blip.Texture.Height);
  349. handle.DrawTextureRect(blip.Texture, new UIBox2(position - positionOffset, position + positionOffset), blip.Color);
  350. }
  351. }
  352. // Beacons
  353. if (_beacons.Pressed)
  354. {
  355. var rectBuffer = new Vector2(5f, 3f);
  356. // Calculate font size for current zoom level
  357. var fontSize = (int)Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0);
  358. var font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize);
  359. foreach (var beacon in _navMap.Beacons.Values)
  360. {
  361. var position = beacon.Position - offset;
  362. position = ScalePosition(position with { Y = -position.Y });
  363. var textDimensions = handle.GetDimensions(font, beacon.Text, 1f);
  364. handle.DrawRect(new UIBox2(position - textDimensions / 2 - rectBuffer, position + textDimensions / 2 + rectBuffer), BackgroundColor);
  365. handle.DrawString(font, position - textDimensions / 2, beacon.Text, beacon.Color);
  366. }
  367. }
  368. }
  369. protected override void FrameUpdate(FrameEventArgs args)
  370. {
  371. // Update the timer
  372. _updateTimer += args.DeltaSeconds;
  373. if (_updateTimer >= UpdateTime)
  374. {
  375. _updateTimer -= UpdateTime;
  376. UpdateNavMap();
  377. }
  378. }
  379. protected virtual void UpdateNavMap()
  380. {
  381. // Clear stale values
  382. TilePolygons.Clear();
  383. TileLines.Clear();
  384. TileRects.Clear();
  385. UpdateNavMapFloorTiles();
  386. UpdateNavMapWallLines();
  387. UpdateNavMapAirlocks();
  388. }
  389. private void UpdateNavMapFloorTiles()
  390. {
  391. if (_fixtures == null)
  392. return;
  393. var verts = new Vector2[8];
  394. foreach (var fixture in _fixtures.Fixtures.Values)
  395. {
  396. if (fixture.Shape is not PolygonShape poly)
  397. continue;
  398. for (var i = 0; i < poly.VertexCount; i++)
  399. {
  400. var vert = poly.Vertices[i];
  401. verts[i] = new Vector2(MathF.Round(vert.X), MathF.Round(vert.Y));
  402. }
  403. TilePolygons.Add((verts[..poly.VertexCount], TileColor));
  404. }
  405. }
  406. private void UpdateNavMapWallLines()
  407. {
  408. if (_navMap == null || _grid == null)
  409. return;
  410. // We'll use the following dictionaries to combine collinear wall lines
  411. _horizLines.Clear();
  412. _horizLinesReversed.Clear();
  413. _vertLines.Clear();
  414. _vertLinesReversed.Clear();
  415. const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall;
  416. const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall;
  417. const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall;
  418. const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall;
  419. foreach (var (chunkOrigin, chunk) in _navMap.Chunks)
  420. {
  421. for (var i = 0; i < SharedNavMapSystem.ArraySize; i++)
  422. {
  423. var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask;
  424. if (tileData == 0)
  425. continue;
  426. tileData >>= (int) NavMapChunkType.Wall;
  427. var relativeTile = SharedNavMapSystem.GetTileFromIndex(i);
  428. var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
  429. if (tileData != SharedNavMapSystem.AllDirMask)
  430. {
  431. AddRectForThinWall(tileData, tile);
  432. continue;
  433. }
  434. tile = tile with { Y = -tile.Y };
  435. NavMapChunk? neighborChunk;
  436. // North edge
  437. var neighborData = 0;
  438. if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1)
  439. neighborData = chunk.TileData[i+1];
  440. else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk))
  441. neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize];
  442. if ((neighborData & southMask) == 0)
  443. {
  444. AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize),
  445. tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines,
  446. _horizLinesReversed);
  447. }
  448. // East edge
  449. neighborData = 0;
  450. if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1)
  451. neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize];
  452. else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk))
  453. neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize];
  454. if ((neighborData & westMask) == 0)
  455. {
  456. AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize),
  457. tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed);
  458. }
  459. // South edge
  460. neighborData = 0;
  461. if (relativeTile.Y != 0)
  462. neighborData = chunk.TileData[i - 1];
  463. else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk))
  464. neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize];
  465. if ((neighborData & northMask) == 0)
  466. {
  467. AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines,
  468. _horizLinesReversed);
  469. }
  470. // West edge
  471. neighborData = 0;
  472. if (relativeTile.X != 0)
  473. neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize];
  474. else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk))
  475. neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize];
  476. if ((neighborData & eastMask) == 0)
  477. {
  478. AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, _vertLines,
  479. _vertLinesReversed);
  480. }
  481. // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these
  482. TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0)));
  483. }
  484. }
  485. // Record the combined lines
  486. foreach (var (origin, terminal) in _horizLines)
  487. {
  488. TileLines.Add((origin, terminal));
  489. }
  490. foreach (var (origin, terminal) in _vertLines)
  491. {
  492. TileLines.Add((origin, terminal));
  493. }
  494. }
  495. private void UpdateNavMapAirlocks()
  496. {
  497. if (_navMap == null || _grid == null)
  498. return;
  499. foreach (var chunk in _navMap.Chunks.Values)
  500. {
  501. for (var i = 0; i < SharedNavMapSystem.ArraySize; i++)
  502. {
  503. var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask;
  504. if (tileData == 0)
  505. continue;
  506. tileData >>= (int) NavMapChunkType.Airlock;
  507. var relative = SharedNavMapSystem.GetTileFromIndex(i);
  508. var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize;
  509. // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge
  510. if (tileData != SharedNavMapSystem.AllDirMask)
  511. {
  512. AddRectForThinAirlock(tileData, tile);
  513. continue;
  514. }
  515. // Otherwise add a single full tile airlock
  516. TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep),
  517. new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1)));
  518. TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep),
  519. new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1)));
  520. }
  521. }
  522. }
  523. private void AddRectForThinWall(int tileData, Vector2i tile)
  524. {
  525. var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness);
  526. var rightBottom = new Vector2(0.5f, 0.5f);
  527. for (var i = 0; i < SharedNavMapSystem.Directions; i++)
  528. {
  529. var dirMask = 1 << i;
  530. if ((tileData & dirMask) == 0)
  531. continue;
  532. var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
  533. // TODO NAVMAP
  534. // Consider using faster rotation operations, given that these are always 90 degree increments
  535. var angle = -((AtmosDirection) dirMask).ToAngle();
  536. TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
  537. }
  538. }
  539. private void AddRectForThinAirlock(int tileData, Vector2i tile)
  540. {
  541. var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness);
  542. var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep);
  543. var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness);
  544. var centreBottom = new Vector2(0f, 0.5f - FullWallInstep);
  545. for (var i = 0; i < SharedNavMapSystem.Directions; i++)
  546. {
  547. var dirMask = 1 << i;
  548. if ((tileData & dirMask) == 0)
  549. continue;
  550. var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
  551. var angle = -((AtmosDirection) dirMask).ToAngle();
  552. TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
  553. TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition));
  554. }
  555. }
  556. protected void AddOrUpdateNavMapLine(
  557. Vector2i origin,
  558. Vector2i terminus,
  559. Dictionary<Vector2i, Vector2i> lookup,
  560. Dictionary<Vector2i, Vector2i> lookupReversed)
  561. {
  562. Vector2i foundTermius;
  563. Vector2i foundOrigin;
  564. if (origin == terminus)
  565. return;
  566. // Does our new line end at the beginning of an existing line?
  567. if (lookup.Remove(terminus, out foundTermius))
  568. {
  569. DebugTools.Assert(lookupReversed[foundTermius] == terminus);
  570. // Does our new line start at the end of an existing line?
  571. if (lookupReversed.Remove(origin, out foundOrigin))
  572. {
  573. // Our new line just connects two existing lines
  574. DebugTools.Assert(lookup[foundOrigin] == origin);
  575. lookup[foundOrigin] = foundTermius;
  576. lookupReversed[foundTermius] = foundOrigin;
  577. }
  578. else
  579. {
  580. // Our new line precedes an existing line, extending it further to the left
  581. lookup[origin] = foundTermius;
  582. lookupReversed[foundTermius] = origin;
  583. }
  584. return;
  585. }
  586. // Does our new line start at the end of an existing line?
  587. if (lookupReversed.Remove(origin, out foundOrigin))
  588. {
  589. // Our new line just extends an existing line further to the right
  590. DebugTools.Assert(lookup[foundOrigin] == origin);
  591. lookup[foundOrigin] = terminus;
  592. lookupReversed[terminus] = foundOrigin;
  593. return;
  594. }
  595. // Completely disconnected line segment.
  596. lookup.Add(origin, terminus);
  597. lookupReversed.Add(terminus, origin);
  598. }
  599. protected Vector2 GetOffset()
  600. {
  601. return Offset + (_physics?.LocalCenter ?? new Vector2());
  602. }
  603. }
  604. public struct NavMapBlip
  605. {
  606. public EntityCoordinates Coordinates;
  607. public Texture Texture;
  608. public Color Color;
  609. public bool Blinks;
  610. public bool Selectable;
  611. public float Scale;
  612. public NavMapBlip(EntityCoordinates coordinates, Texture texture, Color color, bool blinks, bool selectable = true, float scale = 1f)
  613. {
  614. Coordinates = coordinates;
  615. Texture = texture;
  616. Color = color;
  617. Blinks = blinks;
  618. Selectable = selectable;
  619. Scale = scale;
  620. }
  621. }