Navmap rework (#26713)
* Optimized the drawing of lines and tracked entities * Optimized nav map updating and added thin wall support * Added support for thin doors * Removed floor tile seams, more line drawing optimizations * Fixed split grids not updating correctly * Cleaned up NavMapControl code * Fix nav map header * Converted nav map updates from system network messages to delta-states * Addressed review comments * Fixed timing issue where NavMapSystem would update before AirtightSystem did
This commit is contained in:
@@ -1,18 +1,14 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Content.Shared.Pinpointer;
|
using Content.Shared.Pinpointer;
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Shared.Enums;
|
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Map.Components;
|
|
||||||
|
|
||||||
namespace Content.Client.Pinpointer;
|
namespace Content.Client.Pinpointer;
|
||||||
|
|
||||||
public sealed class NavMapSystem : SharedNavMapSystem
|
public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||||
{
|
{
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<NavMapComponent, ComponentHandleState>(OnHandleState);
|
SubscribeLocalEvent<NavMapComponent, ComponentHandleState>(OnHandleState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,89 +17,47 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
|||||||
if (args.Current is not NavMapComponentState state)
|
if (args.Current is not NavMapComponentState state)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
component.Chunks.Clear();
|
if (!state.FullState)
|
||||||
|
|
||||||
foreach (var (origin, data) in state.TileData)
|
|
||||||
{
|
{
|
||||||
component.Chunks.Add(origin, new NavMapChunk(origin)
|
foreach (var index in component.Chunks.Keys)
|
||||||
{
|
{
|
||||||
TileData = data,
|
if (!state.AllChunks!.Contains(index))
|
||||||
});
|
component.Chunks.Remove(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
component.Beacons.Clear();
|
foreach (var beacon in component.Beacons)
|
||||||
component.Beacons.AddRange(state.Beacons);
|
{
|
||||||
|
if (!state.AllBeacons!.Contains(beacon))
|
||||||
|
component.Beacons.Remove(beacon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
component.Airlocks.Clear();
|
else
|
||||||
component.Airlocks.AddRange(state.Airlocks);
|
{
|
||||||
}
|
foreach (var index in component.Chunks.Keys)
|
||||||
}
|
{
|
||||||
|
if (!state.Chunks.ContainsKey(index))
|
||||||
public sealed class NavMapOverlay : Overlay
|
component.Chunks.Remove(index);
|
||||||
{
|
}
|
||||||
private readonly IEntityManager _entManager;
|
|
||||||
private readonly IMapManager _mapManager;
|
foreach (var beacon in component.Beacons)
|
||||||
|
{
|
||||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
if (!state.Beacons.Contains(beacon))
|
||||||
|
component.Beacons.Remove(beacon);
|
||||||
private List<Entity<MapGridComponent>> _grids = new();
|
}
|
||||||
|
}
|
||||||
public NavMapOverlay(IEntityManager entManager, IMapManager mapManager)
|
|
||||||
{
|
foreach (var ((category, origin), chunk) in state.Chunks)
|
||||||
_entManager = entManager;
|
{
|
||||||
_mapManager = mapManager;
|
var newChunk = new NavMapChunk(origin);
|
||||||
}
|
|
||||||
|
foreach (var (atmosDirection, value) in chunk)
|
||||||
protected override void Draw(in OverlayDrawArgs args)
|
newChunk.TileData[atmosDirection] = value;
|
||||||
{
|
|
||||||
var query = _entManager.GetEntityQuery<NavMapComponent>();
|
component.Chunks[(category, origin)] = newChunk;
|
||||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
}
|
||||||
var scale = Matrix3.CreateScale(new Vector2(1f, 1f));
|
|
||||||
|
foreach (var beacon in state.Beacons)
|
||||||
_grids.Clear();
|
component.Beacons.Add(beacon);
|
||||||
_mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref _grids);
|
|
||||||
|
|
||||||
foreach (var grid in _grids)
|
|
||||||
{
|
|
||||||
if (!query.TryGetComponent(grid, out var navMap) || !xformQuery.TryGetComponent(grid.Owner, out var xform))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// TODO: Faster helper method
|
|
||||||
var (_, _, matrix, invMatrix) = xform.GetWorldPositionRotationMatrixWithInv();
|
|
||||||
|
|
||||||
var localAABB = invMatrix.TransformBox(args.WorldBounds);
|
|
||||||
Matrix3.Multiply(in scale, in matrix, out var matty);
|
|
||||||
|
|
||||||
args.WorldHandle.SetTransform(matty);
|
|
||||||
|
|
||||||
for (var x = Math.Floor(localAABB.Left); x <= Math.Ceiling(localAABB.Right); x += SharedNavMapSystem.ChunkSize * grid.Comp.TileSize)
|
|
||||||
{
|
|
||||||
for (var y = Math.Floor(localAABB.Bottom); y <= Math.Ceiling(localAABB.Top); y += SharedNavMapSystem.ChunkSize * grid.Comp.TileSize)
|
|
||||||
{
|
|
||||||
var floored = new Vector2i((int) x, (int) y);
|
|
||||||
|
|
||||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(floored, SharedNavMapSystem.ChunkSize);
|
|
||||||
|
|
||||||
if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// TODO: Okay maybe I should just use ushorts lmao...
|
|
||||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
|
||||||
{
|
|
||||||
var value = (int) Math.Pow(2, i);
|
|
||||||
|
|
||||||
var mask = chunk.TileData & value;
|
|
||||||
|
|
||||||
if (mask == 0x0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var tile = chunk.Origin * SharedNavMapSystem.ChunkSize + SharedNavMapSystem.GetTile(mask);
|
|
||||||
args.WorldHandle.DrawRect(new Box2(tile * grid.Comp.TileSize, (tile + 1) * grid.Comp.TileSize), Color.Aqua, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args.WorldHandle.SetTransform(Matrix3.Identity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ using Robust.Shared.Physics.Components;
|
|||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Content.Client.Pinpointer.UI;
|
namespace Content.Client.Pinpointer.UI;
|
||||||
|
|
||||||
@@ -27,6 +29,7 @@ public partial class NavMapControl : MapGridControl
|
|||||||
{
|
{
|
||||||
[Dependency] private IResourceCache _cache = default!;
|
[Dependency] private IResourceCache _cache = default!;
|
||||||
private readonly SharedTransformSystem _transformSystem;
|
private readonly SharedTransformSystem _transformSystem;
|
||||||
|
private readonly SharedNavMapSystem _navMapSystem;
|
||||||
|
|
||||||
public EntityUid? Owner;
|
public EntityUid? Owner;
|
||||||
public EntityUid? MapUid;
|
public EntityUid? MapUid;
|
||||||
@@ -40,7 +43,10 @@ public partial class NavMapControl : MapGridControl
|
|||||||
// Tracked data
|
// Tracked data
|
||||||
public Dictionary<EntityCoordinates, (bool Visible, Color Color)> TrackedCoordinates = new();
|
public Dictionary<EntityCoordinates, (bool Visible, Color Color)> TrackedCoordinates = new();
|
||||||
public Dictionary<NetEntity, NavMapBlip> TrackedEntities = new();
|
public Dictionary<NetEntity, NavMapBlip> TrackedEntities = new();
|
||||||
public Dictionary<Vector2i, List<NavMapLine>>? TileGrid = default!;
|
|
||||||
|
public List<(Vector2, Vector2)> TileLines = new();
|
||||||
|
public List<(Vector2, Vector2)> TileRects = new();
|
||||||
|
public List<(Vector2[], Color)> TilePolygons = new();
|
||||||
|
|
||||||
// Default colors
|
// Default colors
|
||||||
public Color WallColor = new(102, 217, 102);
|
public Color WallColor = new(102, 217, 102);
|
||||||
@@ -53,14 +59,23 @@ public partial class NavMapControl : MapGridControl
|
|||||||
protected static float MinDisplayedRange = 8f;
|
protected static float MinDisplayedRange = 8f;
|
||||||
protected static float MaxDisplayedRange = 128f;
|
protected static float MaxDisplayedRange = 128f;
|
||||||
protected static float DefaultDisplayedRange = 48f;
|
protected static float DefaultDisplayedRange = 48f;
|
||||||
|
protected float MinmapScaleModifier = 0.075f;
|
||||||
|
protected float FullWallInstep = 0.165f;
|
||||||
|
protected float ThinWallThickness = 0.165f;
|
||||||
|
protected float ThinDoorThickness = 0.30f;
|
||||||
|
|
||||||
// Local variables
|
// Local variables
|
||||||
private float _updateTimer = 0.25f;
|
private float _updateTimer = 1.0f;
|
||||||
private Dictionary<Color, Color> _sRGBLookUp = new();
|
private Dictionary<Color, Color> _sRGBLookUp = new();
|
||||||
protected Color BackgroundColor;
|
protected Color BackgroundColor;
|
||||||
protected float BackgroundOpacity = 0.9f;
|
protected float BackgroundOpacity = 0.9f;
|
||||||
private int _targetFontsize = 8;
|
private int _targetFontsize = 8;
|
||||||
|
|
||||||
|
protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookup = new();
|
||||||
|
protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookupReversed = new();
|
||||||
|
protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookup = new();
|
||||||
|
protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookupReversed = new();
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
private NavMapComponent? _navMap;
|
private NavMapComponent? _navMap;
|
||||||
private MapGridComponent? _grid;
|
private MapGridComponent? _grid;
|
||||||
@@ -72,6 +87,7 @@ public partial class NavMapControl : MapGridControl
|
|||||||
private readonly Label _zoom = new()
|
private readonly Label _zoom = new()
|
||||||
{
|
{
|
||||||
VerticalAlignment = VAlignment.Top,
|
VerticalAlignment = VAlignment.Top,
|
||||||
|
HorizontalExpand = true,
|
||||||
Margin = new Thickness(8f, 8f),
|
Margin = new Thickness(8f, 8f),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,6 +96,7 @@ public partial class NavMapControl : MapGridControl
|
|||||||
Text = Loc.GetString("navmap-recenter"),
|
Text = Loc.GetString("navmap-recenter"),
|
||||||
VerticalAlignment = VAlignment.Top,
|
VerticalAlignment = VAlignment.Top,
|
||||||
HorizontalAlignment = HAlignment.Right,
|
HorizontalAlignment = HAlignment.Right,
|
||||||
|
HorizontalExpand = true,
|
||||||
Margin = new Thickness(8f, 4f),
|
Margin = new Thickness(8f, 4f),
|
||||||
Disabled = true,
|
Disabled = true,
|
||||||
};
|
};
|
||||||
@@ -87,9 +104,10 @@ public partial class NavMapControl : MapGridControl
|
|||||||
private readonly CheckBox _beacons = new()
|
private readonly CheckBox _beacons = new()
|
||||||
{
|
{
|
||||||
Text = Loc.GetString("navmap-toggle-beacons"),
|
Text = Loc.GetString("navmap-toggle-beacons"),
|
||||||
Margin = new Thickness(4f, 0f),
|
|
||||||
VerticalAlignment = VAlignment.Center,
|
VerticalAlignment = VAlignment.Center,
|
||||||
HorizontalAlignment = HAlignment.Center,
|
HorizontalAlignment = HAlignment.Center,
|
||||||
|
HorizontalExpand = true,
|
||||||
|
Margin = new Thickness(4f, 0f),
|
||||||
Pressed = true,
|
Pressed = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,6 +116,8 @@ public partial class NavMapControl : MapGridControl
|
|||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
_transformSystem = EntManager.System<SharedTransformSystem>();
|
_transformSystem = EntManager.System<SharedTransformSystem>();
|
||||||
|
_navMapSystem = EntManager.System<SharedNavMapSystem>();
|
||||||
|
|
||||||
BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity));
|
BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity));
|
||||||
|
|
||||||
RectClipContent = true;
|
RectClipContent = true;
|
||||||
@@ -112,6 +132,8 @@ public partial class NavMapControl : MapGridControl
|
|||||||
BorderColor = StyleNano.PanelDark
|
BorderColor = StyleNano.PanelDark
|
||||||
},
|
},
|
||||||
VerticalExpand = false,
|
VerticalExpand = false,
|
||||||
|
HorizontalExpand = true,
|
||||||
|
SetWidth = 650f,
|
||||||
Children =
|
Children =
|
||||||
{
|
{
|
||||||
new BoxContainer()
|
new BoxContainer()
|
||||||
@@ -130,6 +152,7 @@ public partial class NavMapControl : MapGridControl
|
|||||||
var topContainer = new BoxContainer()
|
var topContainer = new BoxContainer()
|
||||||
{
|
{
|
||||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||||
|
HorizontalExpand = true,
|
||||||
Children =
|
Children =
|
||||||
{
|
{
|
||||||
topPanel,
|
topPanel,
|
||||||
@@ -157,6 +180,9 @@ public partial class NavMapControl : MapGridControl
|
|||||||
{
|
{
|
||||||
EntManager.TryGetComponent(MapUid, out _navMap);
|
EntManager.TryGetComponent(MapUid, out _navMap);
|
||||||
EntManager.TryGetComponent(MapUid, out _grid);
|
EntManager.TryGetComponent(MapUid, out _grid);
|
||||||
|
EntManager.TryGetComponent(MapUid, out _xform);
|
||||||
|
EntManager.TryGetComponent(MapUid, out _physics);
|
||||||
|
EntManager.TryGetComponent(MapUid, out _fixtures);
|
||||||
|
|
||||||
UpdateNavMap();
|
UpdateNavMap();
|
||||||
}
|
}
|
||||||
@@ -251,119 +277,93 @@ public partial class NavMapControl : MapGridControl
|
|||||||
EntManager.TryGetComponent(MapUid, out _physics);
|
EntManager.TryGetComponent(MapUid, out _physics);
|
||||||
EntManager.TryGetComponent(MapUid, out _fixtures);
|
EntManager.TryGetComponent(MapUid, out _fixtures);
|
||||||
|
|
||||||
|
if (_navMap == null || _grid == null || _xform == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// Map re-centering
|
// Map re-centering
|
||||||
_recenter.Disabled = DrawRecenter();
|
_recenter.Disabled = DrawRecenter();
|
||||||
|
|
||||||
_zoom.Text = Loc.GetString("navmap-zoom", ("value", $"{(DefaultDisplayedRange / WorldRange ):0.0}"));
|
// Update zoom text
|
||||||
|
_zoom.Text = Loc.GetString("navmap-zoom", ("value", $"{(DefaultDisplayedRange / WorldRange):0.0}"));
|
||||||
if (_navMap == null || _xform == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
// Update offset with physics local center
|
||||||
var offset = Offset;
|
var offset = Offset;
|
||||||
|
|
||||||
if (_physics != null)
|
if (_physics != null)
|
||||||
offset += _physics.LocalCenter;
|
offset += _physics.LocalCenter;
|
||||||
|
|
||||||
// Draw tiles
|
var offsetVec = new Vector2(offset.X, -offset.Y);
|
||||||
if (_fixtures != null)
|
|
||||||
|
// Wall sRGB
|
||||||
|
if (!_sRGBLookUp.TryGetValue(WallColor, out var wallsRGB))
|
||||||
|
{
|
||||||
|
wallsRGB = Color.ToSrgb(WallColor);
|
||||||
|
_sRGBLookUp[WallColor] = wallsRGB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw floor tiles
|
||||||
|
if (TilePolygons.Any())
|
||||||
{
|
{
|
||||||
Span<Vector2> verts = new Vector2[8];
|
Span<Vector2> verts = new Vector2[8];
|
||||||
|
|
||||||
foreach (var fixture in _fixtures.Fixtures.Values)
|
foreach (var (polygonVerts, polygonColor) in TilePolygons)
|
||||||
{
|
{
|
||||||
if (fixture.Shape is not PolygonShape poly)
|
for (var i = 0; i < polygonVerts.Length; i++)
|
||||||
continue;
|
|
||||||
|
|
||||||
for (var i = 0; i < poly.VertexCount; i++)
|
|
||||||
{
|
{
|
||||||
var vert = poly.Vertices[i] - offset;
|
var vert = polygonVerts[i] - offset;
|
||||||
|
|
||||||
verts[i] = ScalePosition(new Vector2(vert.X, -vert.Y));
|
verts[i] = ScalePosition(new Vector2(vert.X, -vert.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..poly.VertexCount], TileColor);
|
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..polygonVerts.Length], polygonColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset);
|
// Draw map lines
|
||||||
|
if (TileLines.Any())
|
||||||
// Drawing lines can be rather expensive due to the number of neighbors that need to be checked in order
|
|
||||||
// to figure out where they should be drawn. However, we don't *need* to do check these every frame.
|
|
||||||
// Instead, lets periodically update where to draw each line and then store these points in a list.
|
|
||||||
// Then we can just run through the list each frame and draw the lines without any extra computation.
|
|
||||||
|
|
||||||
// Draw walls
|
|
||||||
if (TileGrid != null && TileGrid.Count > 0)
|
|
||||||
{
|
{
|
||||||
var walls = new ValueList<Vector2>();
|
var lines = new ValueList<Vector2>(TileLines.Count * 2);
|
||||||
|
|
||||||
foreach ((var chunk, var chunkedLines) in TileGrid)
|
foreach (var (o, t) in TileLines)
|
||||||
{
|
{
|
||||||
var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
|
var origin = ScalePosition(o - offsetVec);
|
||||||
|
var terminus = ScalePosition(t - offsetVec);
|
||||||
|
|
||||||
if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
|
lines.Add(origin);
|
||||||
continue;
|
lines.Add(terminus);
|
||||||
|
}
|
||||||
|
|
||||||
if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
|
if (lines.Count > 0)
|
||||||
continue;
|
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, wallsRGB);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var chunkedLine in chunkedLines)
|
// Draw map rects
|
||||||
|
if (TileRects.Any())
|
||||||
{
|
{
|
||||||
var start = ScalePosition(chunkedLine.Origin - new Vector2(offset.X, -offset.Y));
|
var rects = new ValueList<Vector2>(TileRects.Count * 8);
|
||||||
var end = ScalePosition(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y));
|
|
||||||
|
|
||||||
walls.Add(start);
|
foreach (var (lt, rb) in TileRects)
|
||||||
walls.Add(end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (walls.Count > 0)
|
|
||||||
{
|
{
|
||||||
if (!_sRGBLookUp.TryGetValue(WallColor, out var sRGB))
|
var leftTop = ScalePosition(lt - offsetVec);
|
||||||
{
|
var rightBottom = ScalePosition(rb - offsetVec);
|
||||||
sRGB = Color.ToSrgb(WallColor);
|
|
||||||
_sRGBLookUp[WallColor] = sRGB;
|
var rightTop = new Vector2(rightBottom.X, leftTop.Y);
|
||||||
|
var leftBottom = new Vector2(leftTop.X, rightBottom.Y);
|
||||||
|
|
||||||
|
rects.Add(leftTop);
|
||||||
|
rects.Add(rightTop);
|
||||||
|
rects.Add(rightTop);
|
||||||
|
rects.Add(rightBottom);
|
||||||
|
rects.Add(rightBottom);
|
||||||
|
rects.Add(leftBottom);
|
||||||
|
rects.Add(leftBottom);
|
||||||
|
rects.Add(leftTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, walls.Span, sRGB);
|
if (rects.Count > 0)
|
||||||
}
|
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, wallsRGB);
|
||||||
}
|
|
||||||
|
|
||||||
var airlockBuffer = Vector2.One * (MinimapScale / 2.25f) * 0.75f;
|
|
||||||
var airlockLines = new ValueList<Vector2>();
|
|
||||||
var foobarVec = new Vector2(1, -1);
|
|
||||||
|
|
||||||
foreach (var airlock in _navMap.Airlocks)
|
|
||||||
{
|
|
||||||
var position = airlock.Position - offset;
|
|
||||||
position = ScalePosition(position with { Y = -position.Y });
|
|
||||||
airlockLines.Add(position + airlockBuffer);
|
|
||||||
airlockLines.Add(position - airlockBuffer * foobarVec);
|
|
||||||
|
|
||||||
airlockLines.Add(position + airlockBuffer);
|
|
||||||
airlockLines.Add(position + airlockBuffer * foobarVec);
|
|
||||||
|
|
||||||
airlockLines.Add(position - airlockBuffer);
|
|
||||||
airlockLines.Add(position + airlockBuffer * foobarVec);
|
|
||||||
|
|
||||||
airlockLines.Add(position - airlockBuffer);
|
|
||||||
airlockLines.Add(position - airlockBuffer * foobarVec);
|
|
||||||
|
|
||||||
airlockLines.Add(position + airlockBuffer * -Vector2.UnitY);
|
|
||||||
airlockLines.Add(position - airlockBuffer * -Vector2.UnitY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (airlockLines.Count > 0)
|
|
||||||
{
|
|
||||||
if (!_sRGBLookUp.TryGetValue(WallColor, out var sRGB))
|
|
||||||
{
|
|
||||||
sRGB = Color.ToSrgb(WallColor);
|
|
||||||
_sRGBLookUp[WallColor] = sRGB;
|
|
||||||
}
|
|
||||||
|
|
||||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, airlockLines.Span, sRGB);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoke post wall drawing action
|
||||||
if (PostWallDrawingAction != null)
|
if (PostWallDrawingAction != null)
|
||||||
PostWallDrawingAction.Invoke(handle);
|
PostWallDrawingAction.Invoke(handle);
|
||||||
|
|
||||||
@@ -373,7 +373,7 @@ public partial class NavMapControl : MapGridControl
|
|||||||
var rectBuffer = new Vector2(5f, 3f);
|
var rectBuffer = new Vector2(5f, 3f);
|
||||||
|
|
||||||
// Calculate font size for current zoom level
|
// Calculate font size for current zoom level
|
||||||
var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize , 0);
|
var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0);
|
||||||
var font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize);
|
var font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize);
|
||||||
|
|
||||||
foreach (var beacon in _navMap.Beacons)
|
foreach (var beacon in _navMap.Beacons)
|
||||||
@@ -409,8 +409,6 @@ public partial class NavMapControl : MapGridControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tracked entities (can use a supplied sprite as a marker instead; should probably just replace TrackedCoordinates with this eventually)
|
// Tracked entities (can use a supplied sprite as a marker instead; should probably just replace TrackedCoordinates with this eventually)
|
||||||
var iconVertexUVs = new Dictionary<(Texture, Color), ValueList<DrawVertexUV2D>>();
|
|
||||||
|
|
||||||
foreach (var blip in TrackedEntities.Values)
|
foreach (var blip in TrackedEntities.Values)
|
||||||
{
|
{
|
||||||
if (blip.Blinks && !lit)
|
if (blip.Blinks && !lit)
|
||||||
@@ -419,9 +417,6 @@ public partial class NavMapControl : MapGridControl
|
|||||||
if (blip.Texture == null)
|
if (blip.Texture == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!iconVertexUVs.TryGetValue((blip.Texture, blip.Color), out var vertexUVs))
|
|
||||||
vertexUVs = new();
|
|
||||||
|
|
||||||
var mapPos = blip.Coordinates.ToMap(EntManager, _transformSystem);
|
var mapPos = blip.Coordinates.ToMap(EntManager, _transformSystem);
|
||||||
|
|
||||||
if (mapPos.MapId != MapId.Nullspace)
|
if (mapPos.MapId != MapId.Nullspace)
|
||||||
@@ -429,29 +424,11 @@ public partial class NavMapControl : MapGridControl
|
|||||||
var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset;
|
var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset;
|
||||||
position = ScalePosition(new Vector2(position.X, -position.Y));
|
position = ScalePosition(new Vector2(position.X, -position.Y));
|
||||||
|
|
||||||
var scalingCoefficient = 2.5f;
|
var scalingCoefficient = MinmapScaleModifier * float.Sqrt(MinimapScale);
|
||||||
var positionOffset = scalingCoefficient * float.Sqrt(MinimapScale);
|
var positionOffset = new Vector2(scalingCoefficient * blip.Texture.Width, scalingCoefficient * blip.Texture.Height);
|
||||||
|
|
||||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y - positionOffset), new Vector2(1f, 1f)));
|
handle.DrawTextureRect(blip.Texture, new UIBox2(position - positionOffset, position + positionOffset), blip.Color);
|
||||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y + positionOffset), new Vector2(1f, 0f)));
|
|
||||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y - positionOffset), new Vector2(0f, 1f)));
|
|
||||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y + positionOffset), new Vector2(1f, 0f)));
|
|
||||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y - positionOffset), new Vector2(0f, 1f)));
|
|
||||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y + positionOffset), new Vector2(0f, 0f)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iconVertexUVs[(blip.Texture, blip.Color)] = vertexUVs;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ((var (texture, color), var vertexUVs) in iconVertexUVs)
|
|
||||||
{
|
|
||||||
if (!_sRGBLookUp.TryGetValue(color, out var sRGB))
|
|
||||||
{
|
|
||||||
sRGB = Color.ToSrgb(color);
|
|
||||||
_sRGBLookUp[color] = sRGB;
|
|
||||||
}
|
|
||||||
|
|
||||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, texture, vertexUVs.Span, sRGB);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,124 +446,294 @@ public partial class NavMapControl : MapGridControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void UpdateNavMap()
|
protected virtual void UpdateNavMap()
|
||||||
|
{
|
||||||
|
// Clear stale values
|
||||||
|
TilePolygons.Clear();
|
||||||
|
TileLines.Clear();
|
||||||
|
TileRects.Clear();
|
||||||
|
|
||||||
|
UpdateNavMapFloorTiles();
|
||||||
|
UpdateNavMapWallLines();
|
||||||
|
UpdateNavMapAirlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateNavMapFloorTiles()
|
||||||
|
{
|
||||||
|
if (_fixtures == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var verts = new Vector2[8];
|
||||||
|
|
||||||
|
foreach (var fixture in _fixtures.Fixtures.Values)
|
||||||
|
{
|
||||||
|
if (fixture.Shape is not PolygonShape poly)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (var i = 0; i < poly.VertexCount; i++)
|
||||||
|
{
|
||||||
|
var vert = poly.Vertices[i];
|
||||||
|
verts[i] = new Vector2(MathF.Round(vert.X), MathF.Round(vert.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
TilePolygons.Add((verts[..poly.VertexCount], TileColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateNavMapWallLines()
|
||||||
{
|
{
|
||||||
if (_navMap == null || _grid == null)
|
if (_navMap == null || _grid == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TileGrid = GetDecodedWallChunks(_navMap.Chunks, _grid);
|
// We'll use the following dictionaries to combine collinear wall lines
|
||||||
}
|
HorizLinesLookup.Clear();
|
||||||
|
HorizLinesLookupReversed.Clear();
|
||||||
|
VertLinesLookup.Clear();
|
||||||
|
VertLinesLookupReversed.Clear();
|
||||||
|
|
||||||
public Dictionary<Vector2i, List<NavMapLine>> GetDecodedWallChunks
|
foreach ((var (category, chunkOrigin), var chunk) in _navMap.Chunks)
|
||||||
(Dictionary<Vector2i, NavMapChunk> chunks,
|
|
||||||
MapGridComponent grid)
|
|
||||||
{
|
{
|
||||||
var decodedOutput = new Dictionary<Vector2i, List<NavMapLine>>();
|
if (category != NavMapChunkType.Wall)
|
||||||
|
continue;
|
||||||
|
|
||||||
foreach ((var chunkOrigin, var chunk) in chunks)
|
|
||||||
{
|
|
||||||
var list = new List<NavMapLine>();
|
|
||||||
|
|
||||||
// TODO: Okay maybe I should just use ushorts lmao...
|
|
||||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
||||||
{
|
{
|
||||||
var value = (int) Math.Pow(2, i);
|
var value = (ushort) Math.Pow(2, i);
|
||||||
|
var mask = _navMapSystem.GetCombinedEdgesForChunk(chunk.TileData) & value;
|
||||||
var mask = chunk.TileData & value;
|
|
||||||
|
|
||||||
if (mask == 0x0)
|
if (mask == 0x0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Alright now we'll work out our edges
|
|
||||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
||||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize;
|
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
|
||||||
var position = new Vector2(tile.X, -tile.Y);
|
|
||||||
|
if (!_navMapSystem.AllTileEdgesAreOccupied(chunk.TileData, relativeTile))
|
||||||
|
{
|
||||||
|
AddRectForThinWall(chunk.TileData, tile);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile = tile with { Y = -tile.Y };
|
||||||
|
|
||||||
NavMapChunk? neighborChunk;
|
NavMapChunk? neighborChunk;
|
||||||
bool neighbor;
|
bool neighbor;
|
||||||
|
|
||||||
// North edge
|
// North edge
|
||||||
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
||||||
{
|
{
|
||||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
|
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(0, 1)), out neighborChunk) &&
|
||||||
(neighborChunk.TileData &
|
(neighborChunk.TileData[AtmosDirection.South] &
|
||||||
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
|
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
|
||||||
neighbor = (chunk.TileData & flag) != 0x0;
|
neighbor = (chunk.TileData[AtmosDirection.South] & flag) != 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!neighbor)
|
if (!neighbor)
|
||||||
{
|
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile + new Vector2i(_grid.TileSize, -_grid.TileSize), HorizLinesLookup, HorizLinesLookupReversed);
|
||||||
// Add points
|
|
||||||
list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, -grid.TileSize)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// East edge
|
// East edge
|
||||||
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
|
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
|
||||||
{
|
{
|
||||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
|
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(1, 0)), out neighborChunk) &&
|
||||||
(neighborChunk.TileData &
|
(neighborChunk.TileData[AtmosDirection.West] &
|
||||||
SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
|
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
|
||||||
neighbor = (chunk.TileData & flag) != 0x0;
|
neighbor = (chunk.TileData[AtmosDirection.West] & flag) != 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!neighbor)
|
if (!neighbor)
|
||||||
{
|
AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize), tile + new Vector2i(_grid.TileSize, 0), VertLinesLookup, VertLinesLookupReversed);
|
||||||
// Add points
|
|
||||||
list.Add(new NavMapLine(position + new Vector2(grid.TileSize, -grid.TileSize), position + new Vector2(grid.TileSize, 0f)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// South edge
|
// South edge
|
||||||
if (relativeTile.Y == 0)
|
if (relativeTile.Y == 0)
|
||||||
{
|
{
|
||||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, -1), out neighborChunk) &&
|
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(0, -1)), out neighborChunk) &&
|
||||||
(neighborChunk.TileData &
|
(neighborChunk.TileData[AtmosDirection.North] &
|
||||||
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
|
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, -1));
|
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, -1));
|
||||||
neighbor = (chunk.TileData & flag) != 0x0;
|
neighbor = (chunk.TileData[AtmosDirection.North] & flag) != 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!neighbor)
|
if (!neighbor)
|
||||||
{
|
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup, HorizLinesLookupReversed);
|
||||||
// Add points
|
|
||||||
list.Add(new NavMapLine(position + new Vector2(grid.TileSize, 0f), position));
|
|
||||||
}
|
|
||||||
|
|
||||||
// West edge
|
// West edge
|
||||||
if (relativeTile.X == 0)
|
if (relativeTile.X == 0)
|
||||||
{
|
{
|
||||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(-1, 0), out neighborChunk) &&
|
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(-1, 0)), out neighborChunk) &&
|
||||||
(neighborChunk.TileData &
|
(neighborChunk.TileData[AtmosDirection.East] &
|
||||||
SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
|
SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(-1, 0));
|
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(-1, 0));
|
||||||
neighbor = (chunk.TileData & flag) != 0x0;
|
neighbor = (chunk.TileData[AtmosDirection.East] & flag) != 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!neighbor)
|
if (!neighbor)
|
||||||
|
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup, VertLinesLookupReversed);
|
||||||
|
|
||||||
|
// Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these
|
||||||
|
TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the combined lines
|
||||||
|
foreach (var (origin, terminal) in HorizLinesLookup)
|
||||||
|
TileLines.Add((origin.Item2, terminal.Item2));
|
||||||
|
|
||||||
|
foreach (var (origin, terminal) in VertLinesLookup)
|
||||||
|
TileLines.Add((origin.Item2, terminal.Item2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateNavMapAirlocks()
|
||||||
{
|
{
|
||||||
// Add point
|
if (_navMap == null || _grid == null)
|
||||||
list.Add(new NavMapLine(position, position + new Vector2(0f, -grid.TileSize)));
|
return;
|
||||||
|
|
||||||
|
foreach (var ((category, _), chunk) in _navMap.Chunks)
|
||||||
|
{
|
||||||
|
if (category != NavMapChunkType.Airlock)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
||||||
|
{
|
||||||
|
var value = (int) Math.Pow(2, i);
|
||||||
|
var mask = _navMapSystem.GetCombinedEdgesForChunk(chunk.TileData) & value;
|
||||||
|
|
||||||
|
if (mask == 0x0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var relative = SharedNavMapSystem.GetTile(mask);
|
||||||
|
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize;
|
||||||
|
|
||||||
|
// If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge
|
||||||
|
if (!_navMapSystem.AllTileEdgesAreOccupied(chunk.TileData, relative))
|
||||||
|
{
|
||||||
|
AddRectForThinAirlock(chunk.TileData, tile);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw a diagonal line for interiors.
|
// Otherwise add a single full tile airlock
|
||||||
list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, 0f)));
|
TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep),
|
||||||
|
new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1)));
|
||||||
|
|
||||||
|
TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep),
|
||||||
|
new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decodedOutput.Add(chunkOrigin, list);
|
private void AddRectForThinWall(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
||||||
|
{
|
||||||
|
if (_navMapSystem == null || _grid == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var leftTop = new Vector2(-0.5f, -0.5f + ThinWallThickness);
|
||||||
|
var rightBottom = new Vector2(0.5f, -0.5f);
|
||||||
|
|
||||||
|
foreach (var (direction, mask) in tileData)
|
||||||
|
{
|
||||||
|
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize);
|
||||||
|
var flag = (ushort) SharedNavMapSystem.GetFlag(relative);
|
||||||
|
|
||||||
|
if ((mask & flag) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
|
||||||
|
var angle = new Angle(0);
|
||||||
|
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case AtmosDirection.East: angle = new Angle(MathF.PI * 0.5f); break;
|
||||||
|
case AtmosDirection.South: angle = new Angle(MathF.PI); break;
|
||||||
|
case AtmosDirection.West: angle = new Angle(MathF.PI * -0.5f); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return decodedOutput;
|
TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddRectForThinAirlock(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
||||||
|
{
|
||||||
|
if (_navMapSystem == null || _grid == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var leftTop = new Vector2(-0.5f + FullWallInstep, -0.5f + FullWallInstep + ThinDoorThickness);
|
||||||
|
var rightBottom = new Vector2(0.5f - FullWallInstep, -0.5f + FullWallInstep);
|
||||||
|
var centreTop = new Vector2(0f, -0.5f + FullWallInstep + ThinDoorThickness);
|
||||||
|
var centreBottom = new Vector2(0f, -0.5f + FullWallInstep);
|
||||||
|
|
||||||
|
foreach (var (direction, mask) in tileData)
|
||||||
|
{
|
||||||
|
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize);
|
||||||
|
var flag = (ushort) SharedNavMapSystem.GetFlag(relative);
|
||||||
|
|
||||||
|
if ((mask & flag) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
|
||||||
|
var angle = new Angle(0);
|
||||||
|
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case AtmosDirection.East: angle = new Angle(MathF.PI * 0.5f);break;
|
||||||
|
case AtmosDirection.South: angle = new Angle(MathF.PI); break;
|
||||||
|
case AtmosDirection.West: angle = new Angle(MathF.PI * -0.5f); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
|
||||||
|
TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AddOrUpdateNavMapLine
|
||||||
|
(Vector2i origin,
|
||||||
|
Vector2i terminus,
|
||||||
|
Dictionary<(int, Vector2i), (int, Vector2i)> lookup,
|
||||||
|
Dictionary<(int, Vector2i), (int, Vector2i)> lookupReversed,
|
||||||
|
int index = 0)
|
||||||
|
{
|
||||||
|
(int, Vector2i) foundTermiusTuple;
|
||||||
|
(int, Vector2i) foundOriginTuple;
|
||||||
|
|
||||||
|
if (lookup.TryGetValue((index, terminus), out foundTermiusTuple) &&
|
||||||
|
lookupReversed.TryGetValue((index, origin), out foundOriginTuple))
|
||||||
|
{
|
||||||
|
lookup[foundOriginTuple] = foundTermiusTuple;
|
||||||
|
lookupReversed[foundTermiusTuple] = foundOriginTuple;
|
||||||
|
|
||||||
|
lookup.Remove((index, terminus));
|
||||||
|
lookupReversed.Remove((index, origin));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (lookup.TryGetValue((index, terminus), out foundTermiusTuple))
|
||||||
|
{
|
||||||
|
lookup[(index, origin)] = foundTermiusTuple;
|
||||||
|
lookup.Remove((index, terminus));
|
||||||
|
lookupReversed[foundTermiusTuple] = (index, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (lookupReversed.TryGetValue((index, origin), out foundOriginTuple))
|
||||||
|
{
|
||||||
|
lookupReversed[(index, terminus)] = foundOriginTuple;
|
||||||
|
lookupReversed.Remove(foundOriginTuple);
|
||||||
|
lookup[foundOriginTuple] = (index, terminus);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lookup.Add((index, origin), (index, terminus));
|
||||||
|
lookupReversed.Add((index, terminus), (index, origin));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Vector2 GetOffset()
|
protected Vector2 GetOffset()
|
||||||
@@ -612,15 +759,3 @@ public struct NavMapBlip
|
|||||||
Selectable = selectable;
|
Selectable = selectable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct NavMapLine
|
|
||||||
{
|
|
||||||
public readonly Vector2 Origin;
|
|
||||||
public readonly Vector2 Terminus;
|
|
||||||
|
|
||||||
public NavMapLine(Vector2 origin, Vector2 terminus)
|
|
||||||
{
|
|
||||||
Origin = origin;
|
|
||||||
Terminus = terminus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
|
|
||||||
public PowerMonitoringCableNetworksComponent? PowerMonitoringCableNetworks;
|
public PowerMonitoringCableNetworksComponent? PowerMonitoringCableNetworks;
|
||||||
public List<PowerMonitoringConsoleLineGroup> HiddenLineGroups = new();
|
public List<PowerMonitoringConsoleLineGroup> HiddenLineGroups = new();
|
||||||
public Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>? PowerCableNetwork;
|
public List<PowerMonitoringConsoleLine> PowerCableNetwork = new();
|
||||||
public Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>? FocusCableNetwork;
|
public List<PowerMonitoringConsoleLine> FocusCableNetwork = new();
|
||||||
|
|
||||||
private MapGridComponent? _grid;
|
private MapGridComponent? _grid;
|
||||||
|
|
||||||
@@ -48,15 +48,15 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
if (!_entManager.TryGetComponent<PowerMonitoringCableNetworksComponent>(Owner, out var cableNetworks))
|
if (!_entManager.TryGetComponent<PowerMonitoringCableNetworksComponent>(Owner, out var cableNetworks))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_entManager.TryGetComponent(MapUid, out _grid))
|
PowerCableNetwork = GetDecodedPowerCableChunks(cableNetworks.AllChunks);
|
||||||
return;
|
FocusCableNetwork = GetDecodedPowerCableChunks(cableNetworks.FocusChunks);
|
||||||
|
|
||||||
PowerCableNetwork = GetDecodedPowerCableChunks(cableNetworks.AllChunks, _grid);
|
|
||||||
FocusCableNetwork = GetDecodedPowerCableChunks(cableNetworks.FocusChunks, _grid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawAllCableNetworks(DrawingHandleScreen handle)
|
public void DrawAllCableNetworks(DrawingHandleScreen handle)
|
||||||
{
|
{
|
||||||
|
if (!_entManager.TryGetComponent(MapUid, out _grid))
|
||||||
|
return;
|
||||||
|
|
||||||
// Draw full cable network
|
// Draw full cable network
|
||||||
if (PowerCableNetwork != null && PowerCableNetwork.Count > 0)
|
if (PowerCableNetwork != null && PowerCableNetwork.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -69,36 +69,29 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
DrawCableNetwork(handle, FocusCableNetwork, Color.White);
|
DrawCableNetwork(handle, FocusCableNetwork, Color.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary<Vector2i, List<PowerMonitoringConsoleLine>> fullCableNetwork, Color modulator)
|
public void DrawCableNetwork(DrawingHandleScreen handle, List<PowerMonitoringConsoleLine> fullCableNetwork, Color modulator)
|
||||||
{
|
{
|
||||||
|
if (!_entManager.TryGetComponent(MapUid, out _grid))
|
||||||
|
return;
|
||||||
|
|
||||||
var offset = GetOffset();
|
var offset = GetOffset();
|
||||||
var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset);
|
offset = offset with { Y = -offset.Y };
|
||||||
|
|
||||||
if (WorldRange / WorldMaxRange > 0.5f)
|
if (WorldRange / WorldMaxRange > 0.5f)
|
||||||
{
|
{
|
||||||
var cableNetworks = new ValueList<Vector2>[3];
|
var cableNetworks = new ValueList<Vector2>[3];
|
||||||
|
|
||||||
foreach ((var chunk, var chunkedLines) in fullCableNetwork)
|
foreach (var line in fullCableNetwork)
|
||||||
{
|
{
|
||||||
var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
|
if (HiddenLineGroups.Contains(line.Group))
|
||||||
|
|
||||||
if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
|
var cableOffset = _powerCableOffsets[(int) line.Group];
|
||||||
continue;
|
var start = ScalePosition(line.Origin + cableOffset - offset);
|
||||||
|
var end = ScalePosition(line.Terminus + cableOffset - offset);
|
||||||
|
|
||||||
foreach (var chunkedLine in chunkedLines)
|
cableNetworks[(int) line.Group].Add(start);
|
||||||
{
|
cableNetworks[(int) line.Group].Add(end);
|
||||||
if (HiddenLineGroups.Contains(chunkedLine.Group))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var start = ScalePosition(chunkedLine.Origin - new Vector2(offset.X, -offset.Y));
|
|
||||||
var end = ScalePosition(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y));
|
|
||||||
|
|
||||||
cableNetworks[(int) chunkedLine.Group].Add(start);
|
|
||||||
cableNetworks[(int) chunkedLine.Group].Add(end);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int cableNetworkIdx = 0; cableNetworkIdx < cableNetworks.Length; cableNetworkIdx++)
|
for (int cableNetworkIdx = 0; cableNetworkIdx < cableNetworks.Length; cableNetworkIdx++)
|
||||||
@@ -124,48 +117,39 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
{
|
{
|
||||||
var cableVertexUVs = new ValueList<Vector2>[3];
|
var cableVertexUVs = new ValueList<Vector2>[3];
|
||||||
|
|
||||||
foreach ((var chunk, var chunkedLines) in fullCableNetwork)
|
foreach (var line in fullCableNetwork)
|
||||||
{
|
{
|
||||||
var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
|
if (HiddenLineGroups.Contains(line.Group))
|
||||||
|
|
||||||
if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
|
var cableOffset = _powerCableOffsets[(int) line.Group];
|
||||||
continue;
|
|
||||||
|
|
||||||
foreach (var chunkedLine in chunkedLines)
|
|
||||||
{
|
|
||||||
if (HiddenLineGroups.Contains(chunkedLine.Group))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var leftTop = ScalePosition(new Vector2
|
var leftTop = ScalePosition(new Vector2
|
||||||
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
|
(Math.Min(line.Origin.X, line.Terminus.X) - 0.1f,
|
||||||
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
|
Math.Min(line.Origin.Y, line.Terminus.Y) - 0.1f)
|
||||||
- new Vector2(offset.X, -offset.Y));
|
+ cableOffset - offset);
|
||||||
|
|
||||||
var rightTop = ScalePosition(new Vector2
|
var rightTop = ScalePosition(new Vector2
|
||||||
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
|
(Math.Max(line.Origin.X, line.Terminus.X) + 0.1f,
|
||||||
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
|
Math.Min(line.Origin.Y, line.Terminus.Y) - 0.1f)
|
||||||
- new Vector2(offset.X, -offset.Y));
|
+ cableOffset - offset);
|
||||||
|
|
||||||
var leftBottom = ScalePosition(new Vector2
|
var leftBottom = ScalePosition(new Vector2
|
||||||
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
|
(Math.Min(line.Origin.X, line.Terminus.X) - 0.1f,
|
||||||
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
|
Math.Max(line.Origin.Y, line.Terminus.Y) + 0.1f)
|
||||||
- new Vector2(offset.X, -offset.Y));
|
+ cableOffset - offset);
|
||||||
|
|
||||||
var rightBottom = ScalePosition(new Vector2
|
var rightBottom = ScalePosition(new Vector2
|
||||||
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
|
(Math.Max(line.Origin.X, line.Terminus.X) + 0.1f,
|
||||||
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
|
Math.Max(line.Origin.Y, line.Terminus.Y) + 0.1f)
|
||||||
- new Vector2(offset.X, -offset.Y));
|
+ cableOffset - offset);
|
||||||
|
|
||||||
cableVertexUVs[(int) chunkedLine.Group].Add(leftBottom);
|
cableVertexUVs[(int) line.Group].Add(leftBottom);
|
||||||
cableVertexUVs[(int) chunkedLine.Group].Add(leftTop);
|
cableVertexUVs[(int) line.Group].Add(leftTop);
|
||||||
cableVertexUVs[(int) chunkedLine.Group].Add(rightBottom);
|
cableVertexUVs[(int) line.Group].Add(rightBottom);
|
||||||
cableVertexUVs[(int) chunkedLine.Group].Add(leftTop);
|
cableVertexUVs[(int) line.Group].Add(leftTop);
|
||||||
cableVertexUVs[(int) chunkedLine.Group].Add(rightBottom);
|
cableVertexUVs[(int) line.Group].Add(rightBottom);
|
||||||
cableVertexUVs[(int) chunkedLine.Group].Add(rightTop);
|
cableVertexUVs[(int) line.Group].Add(rightTop);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int cableNetworkIdx = 0; cableNetworkIdx < cableVertexUVs.Length; cableNetworkIdx++)
|
for (int cableNetworkIdx = 0; cableNetworkIdx < cableVertexUVs.Length; cableNetworkIdx++)
|
||||||
@@ -188,23 +172,28 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>? GetDecodedPowerCableChunks(Dictionary<Vector2i, PowerCableChunk>? chunks, MapGridComponent? grid)
|
public List<PowerMonitoringConsoleLine> GetDecodedPowerCableChunks(Dictionary<Vector2i, PowerCableChunk>? chunks)
|
||||||
{
|
{
|
||||||
if (chunks == null || grid == null)
|
var decodedOutput = new List<PowerMonitoringConsoleLine>();
|
||||||
return null;
|
|
||||||
|
|
||||||
var decodedOutput = new Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>();
|
if (!_entManager.TryGetComponent(MapUid, out _grid))
|
||||||
|
return decodedOutput;
|
||||||
|
|
||||||
|
if (chunks == null)
|
||||||
|
return decodedOutput;
|
||||||
|
|
||||||
|
// We'll use the following dictionaries to combine collinear power cable lines
|
||||||
|
HorizLinesLookup.Clear();
|
||||||
|
HorizLinesLookupReversed.Clear();
|
||||||
|
VertLinesLookup.Clear();
|
||||||
|
VertLinesLookupReversed.Clear();
|
||||||
|
|
||||||
foreach ((var chunkOrigin, var chunk) in chunks)
|
foreach ((var chunkOrigin, var chunk) in chunks)
|
||||||
{
|
{
|
||||||
var list = new List<PowerMonitoringConsoleLine>();
|
|
||||||
|
|
||||||
for (int cableIdx = 0; cableIdx < chunk.PowerCableData.Length; cableIdx++)
|
for (int cableIdx = 0; cableIdx < chunk.PowerCableData.Length; cableIdx++)
|
||||||
{
|
{
|
||||||
var chunkMask = chunk.PowerCableData[cableIdx];
|
var chunkMask = chunk.PowerCableData[cableIdx];
|
||||||
|
|
||||||
Vector2 offset = _powerCableOffsets[cableIdx];
|
|
||||||
|
|
||||||
for (var chunkIdx = 0; chunkIdx < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; chunkIdx++)
|
for (var chunkIdx = 0; chunkIdx < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; chunkIdx++)
|
||||||
{
|
{
|
||||||
var value = (int) Math.Pow(2, chunkIdx);
|
var value = (int) Math.Pow(2, chunkIdx);
|
||||||
@@ -214,8 +203,8 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
||||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize;
|
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
|
||||||
var position = new Vector2(tile.X, -tile.Y);
|
tile = tile with { Y = -tile.Y };
|
||||||
|
|
||||||
PowerCableChunk neighborChunk;
|
PowerCableChunk neighborChunk;
|
||||||
bool neighbor;
|
bool neighbor;
|
||||||
@@ -237,12 +226,7 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
if (neighbor)
|
if (neighbor)
|
||||||
{
|
{
|
||||||
// Add points
|
// Add points
|
||||||
var line = new PowerMonitoringConsoleLine
|
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup, HorizLinesLookupReversed, cableIdx);
|
||||||
(position + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
|
||||||
position + new Vector2(1f, 0f) + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
|
||||||
(PowerMonitoringConsoleLineGroup) cableIdx);
|
|
||||||
|
|
||||||
list.Add(line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// North
|
// North
|
||||||
@@ -260,21 +244,21 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
if (neighbor)
|
if (neighbor)
|
||||||
{
|
{
|
||||||
// Add points
|
// Add points
|
||||||
var line = new PowerMonitoringConsoleLine
|
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup, VertLinesLookupReversed, cableIdx);
|
||||||
(position + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
|
||||||
position + new Vector2(0f, -1f) + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
|
||||||
(PowerMonitoringConsoleLineGroup) cableIdx);
|
|
||||||
|
|
||||||
list.Add(line);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.Count > 0)
|
|
||||||
decodedOutput.Add(chunkOrigin, list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gridOffset = new Vector2(_grid.TileSize * 0.5f, -_grid.TileSize * 0.5f);
|
||||||
|
|
||||||
|
foreach (var (origin, terminal) in HorizLinesLookup)
|
||||||
|
decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1));
|
||||||
|
|
||||||
|
foreach (var (origin, terminal) in VertLinesLookup)
|
||||||
|
decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1));
|
||||||
|
|
||||||
return decodedOutput;
|
return decodedOutput;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,9 +170,6 @@ public sealed partial class PowerMonitoringWindow : FancyWindow
|
|||||||
NavMap.TrackedEntities[mon.Value] = blip;
|
NavMap.TrackedEntities[mon.Value] = blip;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update nav map
|
|
||||||
NavMap.ForceNavMapUpdate();
|
|
||||||
|
|
||||||
// If the entry group doesn't match the current tab, the data is out dated, do not use it
|
// If the entry group doesn't match the current tab, the data is out dated, do not use it
|
||||||
if (allEntries.Length > 0 && allEntries[0].Group != GetCurrentPowerMonitoringConsoleGroup())
|
if (allEntries.Length > 0 && allEntries[0].Group != GetCurrentPowerMonitoringConsoleGroup())
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||||
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
||||||
|
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -59,12 +60,14 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
var gridId = xform.GridUid;
|
var gridId = xform.GridUid;
|
||||||
var coords = xform.Coordinates;
|
var coords = xform.Coordinates;
|
||||||
|
var tilePos = _mapSystem.TileIndicesFor(gridId.Value, grid, coords);
|
||||||
var tilePos = grid.TileIndicesFor(coords);
|
|
||||||
|
|
||||||
// Update and invalidate new position.
|
// Update and invalidate new position.
|
||||||
airtight.LastPosition = (gridId.Value, tilePos);
|
airtight.LastPosition = (gridId.Value, tilePos);
|
||||||
InvalidatePosition(gridId.Value, tilePos);
|
InvalidatePosition(gridId.Value, tilePos);
|
||||||
|
|
||||||
|
var airtightEv = new AirtightChanged(uid, airtight, (gridId.Value, tilePos));
|
||||||
|
RaiseLocalEvent(uid, ref airtightEv, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAirtightReAnchor(EntityUid uid, AirtightComponent airtight, ref ReAnchorEvent args)
|
private void OnAirtightReAnchor(EntityUid uid, AirtightComponent airtight, ref ReAnchorEvent args)
|
||||||
@@ -74,6 +77,9 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
// Update and invalidate new position.
|
// Update and invalidate new position.
|
||||||
airtight.LastPosition = (gridId, args.TilePos);
|
airtight.LastPosition = (gridId, args.TilePos);
|
||||||
InvalidatePosition(gridId, args.TilePos);
|
InvalidatePosition(gridId, args.TilePos);
|
||||||
|
|
||||||
|
var airtightEv = new AirtightChanged(uid, airtight, (gridId, args.TilePos));
|
||||||
|
RaiseLocalEvent(uid, ref airtightEv, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +159,5 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
}
|
}
|
||||||
|
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight,
|
public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight, (EntityUid Grid, Vector2i Tile) Position);
|
||||||
(EntityUid Grid, Vector2i Tile) Position);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,33 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
|
using Content.Server.Atmos.Components;
|
||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
using Content.Server.Warps;
|
using Content.Server.Warps;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.Localizations;
|
using Content.Shared.Localizations;
|
||||||
|
using Content.Shared.Maps;
|
||||||
using Content.Shared.Pinpointer;
|
using Content.Shared.Pinpointer;
|
||||||
using Content.Shared.Tag;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.GameStates;
|
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Utility;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Content.Server.Pinpointer;
|
namespace Content.Server.Pinpointer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles data to be used for in-grid map displays.
|
/// Handles data to be used for in-grid map displays.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class NavMapSystem : SharedNavMapSystem
|
public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
[Dependency] private readonly TagSystem _tags = default!;
|
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||||
[Dependency] private readonly MapSystem _map = default!;
|
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly TransformSystem _transform = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
|
||||||
private EntityQuery<TagComponent> _tagQuery;
|
|
||||||
|
|
||||||
public const float CloseDistance = 15f;
|
public const float CloseDistance = 15f;
|
||||||
public const float FarDistance = 30f;
|
public const float FarDistance = 30f;
|
||||||
@@ -39,153 +36,34 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
// Initialization events
|
||||||
_tagQuery = GetEntityQuery<TagComponent>();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<AnchorStateChangedEvent>(OnAnchorChange);
|
|
||||||
SubscribeLocalEvent<ReAnchorEvent>(OnReAnchor);
|
|
||||||
SubscribeLocalEvent<StationGridAddedEvent>(OnStationInit);
|
SubscribeLocalEvent<StationGridAddedEvent>(OnStationInit);
|
||||||
SubscribeLocalEvent<NavMapComponent, ComponentStartup>(OnNavMapStartup);
|
|
||||||
SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
|
// Grid change events
|
||||||
SubscribeLocalEvent<GridSplitEvent>(OnNavMapSplit);
|
SubscribeLocalEvent<GridSplitEvent>(OnNavMapSplit);
|
||||||
|
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
|
||||||
|
|
||||||
|
// Airtight structure change event
|
||||||
|
SubscribeLocalEvent<AirtightChanged>(OnAirtightChanged);
|
||||||
|
|
||||||
|
// Beacon events
|
||||||
SubscribeLocalEvent<NavMapBeaconComponent, MapInitEvent>(OnNavMapBeaconMapInit);
|
SubscribeLocalEvent<NavMapBeaconComponent, MapInitEvent>(OnNavMapBeaconMapInit);
|
||||||
SubscribeLocalEvent<NavMapBeaconComponent, ComponentStartup>(OnNavMapBeaconStartup);
|
|
||||||
SubscribeLocalEvent<NavMapBeaconComponent, AnchorStateChangedEvent>(OnNavMapBeaconAnchor);
|
SubscribeLocalEvent<NavMapBeaconComponent, AnchorStateChangedEvent>(OnNavMapBeaconAnchor);
|
||||||
|
|
||||||
SubscribeLocalEvent<NavMapDoorComponent, ComponentStartup>(OnNavMapDoorStartup);
|
|
||||||
SubscribeLocalEvent<NavMapDoorComponent, AnchorStateChangedEvent>(OnNavMapDoorAnchor);
|
|
||||||
|
|
||||||
SubscribeLocalEvent<ConfigurableNavMapBeaconComponent, NavMapBeaconConfigureBuiMessage>(OnConfigureMessage);
|
SubscribeLocalEvent<ConfigurableNavMapBeaconComponent, NavMapBeaconConfigureBuiMessage>(OnConfigureMessage);
|
||||||
SubscribeLocalEvent<ConfigurableNavMapBeaconComponent, MapInitEvent>(OnConfigurableMapInit);
|
SubscribeLocalEvent<ConfigurableNavMapBeaconComponent, MapInitEvent>(OnConfigurableMapInit);
|
||||||
SubscribeLocalEvent<ConfigurableNavMapBeaconComponent, ExaminedEvent>(OnConfigurableExamined);
|
SubscribeLocalEvent<ConfigurableNavMapBeaconComponent, ExaminedEvent>(OnConfigurableExamined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region: Initialization event handling
|
||||||
private void OnStationInit(StationGridAddedEvent ev)
|
private void OnStationInit(StationGridAddedEvent ev)
|
||||||
{
|
{
|
||||||
var comp = EnsureComp<NavMapComponent>(ev.GridId);
|
var comp = EnsureComp<NavMapComponent>(ev.GridId);
|
||||||
RefreshGrid(ev.GridId, comp, Comp<MapGridComponent>(ev.GridId));
|
RefreshGrid(ev.GridId, comp, Comp<MapGridComponent>(ev.GridId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnNavMapBeaconMapInit(EntityUid uid, NavMapBeaconComponent component, MapInitEvent args)
|
#endregion
|
||||||
{
|
|
||||||
if (component.DefaultText == null || component.Text != null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
component.Text = Loc.GetString(component.DefaultText);
|
#region: Grid change event handling
|
||||||
Dirty(uid, component);
|
|
||||||
RefreshNavGrid(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNavMapBeaconStartup(EntityUid uid, NavMapBeaconComponent component, ComponentStartup args)
|
|
||||||
{
|
|
||||||
RefreshNavGrid(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNavMapBeaconAnchor(EntityUid uid, NavMapBeaconComponent component, ref AnchorStateChangedEvent args)
|
|
||||||
{
|
|
||||||
UpdateBeaconEnabledVisuals((uid, component));
|
|
||||||
RefreshNavGrid(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNavMapDoorStartup(Entity<NavMapDoorComponent> ent, ref ComponentStartup args)
|
|
||||||
{
|
|
||||||
RefreshNavGrid(ent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNavMapDoorAnchor(Entity<NavMapDoorComponent> ent, ref AnchorStateChangedEvent args)
|
|
||||||
{
|
|
||||||
RefreshNavGrid(ent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConfigureMessage(Entity<ConfigurableNavMapBeaconComponent> ent, ref NavMapBeaconConfigureBuiMessage args)
|
|
||||||
{
|
|
||||||
if (args.Session.AttachedEntity is not { } user)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!TryComp<NavMapBeaconComponent>(ent, out var navMap))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (navMap.Text == args.Text &&
|
|
||||||
navMap.Color == args.Color &&
|
|
||||||
navMap.Enabled == args.Enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
|
||||||
$"{ToPrettyString(user):player} configured NavMapBeacon \'{ToPrettyString(ent):entity}\' with text \'{args.Text}\', color {args.Color.ToHexNoAlpha()}, and {(args.Enabled ? "enabled" : "disabled")} it.");
|
|
||||||
|
|
||||||
if (TryComp<WarpPointComponent>(ent, out var warpPoint))
|
|
||||||
{
|
|
||||||
warpPoint.Location = args.Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
navMap.Text = args.Text;
|
|
||||||
navMap.Color = args.Color;
|
|
||||||
navMap.Enabled = args.Enabled;
|
|
||||||
UpdateBeaconEnabledVisuals((ent, navMap));
|
|
||||||
Dirty(ent, navMap);
|
|
||||||
RefreshNavGrid(ent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConfigurableMapInit(Entity<ConfigurableNavMapBeaconComponent> ent, ref MapInitEvent args)
|
|
||||||
{
|
|
||||||
if (!TryComp<NavMapBeaconComponent>(ent, out var navMap))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// We set this on mapinit just in case the text was edited via VV or something.
|
|
||||||
if (TryComp<WarpPointComponent>(ent, out var warpPoint))
|
|
||||||
{
|
|
||||||
warpPoint.Location = navMap.Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateBeaconEnabledVisuals((ent, navMap));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConfigurableExamined(Entity<ConfigurableNavMapBeaconComponent> ent, ref ExaminedEvent args)
|
|
||||||
{
|
|
||||||
if (!args.IsInDetailsRange || !TryComp<NavMapBeaconComponent>(ent, out var navMap))
|
|
||||||
return;
|
|
||||||
|
|
||||||
args.PushMarkup(Loc.GetString("nav-beacon-examine-text",
|
|
||||||
("enabled", navMap.Enabled),
|
|
||||||
("color", navMap.Color.ToHexNoAlpha()),
|
|
||||||
("label", navMap.Text ?? string.Empty)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBeaconEnabledVisuals(Entity<NavMapBeaconComponent> ent)
|
|
||||||
{
|
|
||||||
_appearance.SetData(ent, NavMapBeaconVisuals.Enabled, ent.Comp.Enabled && Transform(ent).Anchored);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Refreshes the grid for the corresponding beacon.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uid"></param>
|
|
||||||
private void RefreshNavGrid(EntityUid uid)
|
|
||||||
{
|
|
||||||
var xform = Transform(uid);
|
|
||||||
|
|
||||||
if (!TryComp<NavMapComponent>(xform.GridUid, out var navMap))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Dirty(xform.GridUid.Value, navMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CanBeacon(EntityUid uid, TransformComponent? xform = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref xform))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return xform.GridUid != null && xform.Anchored;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNavMapStartup(EntityUid uid, NavMapComponent component, ComponentStartup args)
|
|
||||||
{
|
|
||||||
if (!TryComp<MapGridComponent>(uid, out var grid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
RefreshGrid(uid, component, grid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNavMapSplit(ref GridSplitEvent args)
|
private void OnNavMapSplit(ref GridSplitEvent args)
|
||||||
{
|
{
|
||||||
@@ -203,180 +81,258 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
|||||||
RefreshGrid(args.Grid, comp, gridQuery.GetComponent(args.Grid));
|
RefreshGrid(args.Grid, comp, gridQuery.GetComponent(args.Grid));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshGrid(EntityUid uid, NavMapComponent component, MapGridComponent grid)
|
private void OnTileChanged(ref TileChangedEvent ev)
|
||||||
{
|
{
|
||||||
component.Chunks.Clear();
|
if (!TryComp<NavMapComponent>(ev.NewTile.GridUid, out var navMap))
|
||||||
|
|
||||||
var tiles = grid.GetAllTilesEnumerator();
|
|
||||||
|
|
||||||
while (tiles.MoveNext(out var tile))
|
|
||||||
{
|
|
||||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.Value.GridIndices, ChunkSize);
|
|
||||||
|
|
||||||
if (!component.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
|
||||||
{
|
|
||||||
chunk = new NavMapChunk(chunkOrigin);
|
|
||||||
component.Chunks[chunkOrigin] = chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
RefreshTile(uid, grid, component, chunk, tile.Value.GridIndices);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
|
|
||||||
{
|
|
||||||
if (!TryComp<MapGridComponent>(uid, out var mapGrid))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var data = new Dictionary<Vector2i, int>(component.Chunks.Count);
|
var tile = ev.NewTile.GridIndices;
|
||||||
foreach (var (index, chunk) in component.Chunks)
|
|
||||||
{
|
|
||||||
data.Add(index, chunk.TileData);
|
|
||||||
}
|
|
||||||
|
|
||||||
var beaconQuery = AllEntityQuery<NavMapBeaconComponent, TransformComponent>();
|
|
||||||
var beacons = new List<NavMapBeacon>();
|
|
||||||
|
|
||||||
while (beaconQuery.MoveNext(out var beaconUid, out var beacon, out var xform))
|
|
||||||
{
|
|
||||||
if (!beacon.Enabled || xform.GridUid != uid || !CanBeacon(beaconUid, xform))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// TODO: Make warp points use metadata name instead.
|
|
||||||
string? name = beacon.Text;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
{
|
|
||||||
if (TryComp<WarpPointComponent>(beaconUid, out var warpPoint) && warpPoint.Location != null)
|
|
||||||
{
|
|
||||||
name = warpPoint.Location;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
name = MetaData(beaconUid).EntityName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beacons.Add(new NavMapBeacon(beacon.Color, name, xform.LocalPosition));
|
|
||||||
}
|
|
||||||
|
|
||||||
var airlockQuery = EntityQueryEnumerator<NavMapDoorComponent, TransformComponent>();
|
|
||||||
var airlocks = new List<NavMapAirlock>();
|
|
||||||
while (airlockQuery.MoveNext(out _, out _, out var xform))
|
|
||||||
{
|
|
||||||
if (xform.GridUid != uid || !xform.Anchored)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var pos = _map.TileIndicesFor(uid, mapGrid, xform.Coordinates);
|
|
||||||
var enumerator = _map.GetAnchoredEntitiesEnumerator(uid, mapGrid, pos);
|
|
||||||
|
|
||||||
var wallPresent = false;
|
|
||||||
while (enumerator.MoveNext(out var ent))
|
|
||||||
{
|
|
||||||
if (!_physicsQuery.TryGetComponent(ent, out var body) ||
|
|
||||||
!body.CanCollide ||
|
|
||||||
!body.Hard ||
|
|
||||||
body.BodyType != BodyType.Static ||
|
|
||||||
!_tags.HasTag(ent.Value, "Wall", _tagQuery) &&
|
|
||||||
!_tags.HasTag(ent.Value, "Window", _tagQuery))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
wallPresent = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wallPresent)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
airlocks.Add(new NavMapAirlock(xform.LocalPosition));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Diffs
|
|
||||||
args.State = new NavMapComponentState()
|
|
||||||
{
|
|
||||||
TileData = data,
|
|
||||||
Beacons = beacons,
|
|
||||||
Airlocks = airlocks
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnReAnchor(ref ReAnchorEvent ev)
|
|
||||||
{
|
|
||||||
if (TryComp<MapGridComponent>(ev.OldGrid, out var oldGrid) &&
|
|
||||||
TryComp<NavMapComponent>(ev.OldGrid, out var navMap))
|
|
||||||
{
|
|
||||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(ev.TilePos, ChunkSize);
|
|
||||||
|
|
||||||
if (navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
|
||||||
{
|
|
||||||
RefreshTile(ev.OldGrid, oldGrid, navMap, chunk, ev.TilePos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleAnchor(ev.Xform);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAnchorChange(ref AnchorStateChangedEvent ev)
|
|
||||||
{
|
|
||||||
HandleAnchor(ev.Transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleAnchor(TransformComponent xform)
|
|
||||||
{
|
|
||||||
if (!TryComp<NavMapComponent>(xform.GridUid, out var navMap) ||
|
|
||||||
!TryComp<MapGridComponent>(xform.GridUid, out var grid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var tile = grid.LocalToTile(xform.Coordinates);
|
|
||||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||||
|
|
||||||
if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
if (!navMap.Chunks.TryGetValue((NavMapChunkType.Floor, chunkOrigin), out var chunk))
|
||||||
|
chunk = new(chunkOrigin);
|
||||||
|
|
||||||
|
// This could be easily replaced in the future to accommodate diagonal tiles
|
||||||
|
if (ev.NewTile.IsSpace())
|
||||||
|
chunk = UnsetAllEdgesForChunkTile(chunk, tile);
|
||||||
|
|
||||||
|
else
|
||||||
|
chunk = SetAllEdgesForChunkTile(chunk, tile);
|
||||||
|
|
||||||
|
chunk.LastUpdate = _gameTiming.CurTick;
|
||||||
|
navMap.Chunks[(NavMapChunkType.Floor, chunkOrigin)] = chunk;
|
||||||
|
|
||||||
|
Dirty(ev.NewTile.GridUid, navMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAirtightChanged(ref AirtightChanged ev)
|
||||||
{
|
{
|
||||||
chunk = new NavMapChunk(chunkOrigin);
|
var gridUid = ev.Position.Grid;
|
||||||
navMap.Chunks[chunkOrigin] = chunk;
|
|
||||||
|
if (!TryComp<NavMapComponent>(gridUid, out var navMap) ||
|
||||||
|
!TryComp<MapGridComponent>(gridUid, out var mapGrid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Refresh the affected tile
|
||||||
|
var tile = ev.Position.Tile;
|
||||||
|
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||||
|
|
||||||
|
RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, tile);
|
||||||
|
|
||||||
|
// Update potentially affected chunks
|
||||||
|
foreach (var category in EntityChunkTypes)
|
||||||
|
{
|
||||||
|
if (!navMap.Chunks.TryGetValue((category, chunkOrigin), out var chunk))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
chunk.LastUpdate = _gameTiming.CurTick;
|
||||||
|
navMap.Chunks[(category, chunkOrigin)] = chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
RefreshTile(xform.GridUid.Value, grid, navMap, chunk, tile);
|
Dirty(gridUid, navMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshTile(EntityUid uid, MapGridComponent grid, NavMapComponent component, NavMapChunk chunk, Vector2i tile)
|
#endregion
|
||||||
|
|
||||||
|
#region: Beacon event handling
|
||||||
|
|
||||||
|
private void OnNavMapBeaconMapInit(EntityUid uid, NavMapBeaconComponent component, MapInitEvent args)
|
||||||
|
{
|
||||||
|
if (component.DefaultText == null || component.Text != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.Text = Loc.GetString(component.DefaultText);
|
||||||
|
Dirty(uid, component);
|
||||||
|
|
||||||
|
UpdateNavMapBeaconData(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNavMapBeaconAnchor(EntityUid uid, NavMapBeaconComponent component, ref AnchorStateChangedEvent args)
|
||||||
|
{
|
||||||
|
UpdateBeaconEnabledVisuals((uid, component));
|
||||||
|
UpdateNavMapBeaconData(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigureMessage(Entity<ConfigurableNavMapBeaconComponent> ent, ref NavMapBeaconConfigureBuiMessage args)
|
||||||
|
{
|
||||||
|
if (args.Session.AttachedEntity is not { } user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<NavMapBeaconComponent>(ent, out var beacon))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (beacon.Text == args.Text &&
|
||||||
|
beacon.Color == args.Color &&
|
||||||
|
beacon.Enabled == args.Enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||||
|
$"{ToPrettyString(user):player} configured NavMapBeacon \'{ToPrettyString(ent):entity}\' with text \'{args.Text}\', color {args.Color.ToHexNoAlpha()}, and {(args.Enabled ? "enabled" : "disabled")} it.");
|
||||||
|
|
||||||
|
if (TryComp<WarpPointComponent>(ent, out var warpPoint))
|
||||||
|
{
|
||||||
|
warpPoint.Location = args.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
beacon.Text = args.Text;
|
||||||
|
beacon.Color = args.Color;
|
||||||
|
beacon.Enabled = args.Enabled;
|
||||||
|
|
||||||
|
UpdateBeaconEnabledVisuals((ent, beacon));
|
||||||
|
UpdateNavMapBeaconData(ent, beacon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigurableMapInit(Entity<ConfigurableNavMapBeaconComponent> ent, ref MapInitEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<NavMapBeaconComponent>(ent, out var navMap))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// We set this on mapinit just in case the text was edited via VV or something.
|
||||||
|
if (TryComp<WarpPointComponent>(ent, out var warpPoint))
|
||||||
|
warpPoint.Location = navMap.Text;
|
||||||
|
|
||||||
|
UpdateBeaconEnabledVisuals((ent, navMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigurableExamined(Entity<ConfigurableNavMapBeaconComponent> ent, ref ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (!args.IsInDetailsRange || !TryComp<NavMapBeaconComponent>(ent, out var navMap))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.PushMarkup(Loc.GetString("nav-beacon-examine-text",
|
||||||
|
("enabled", navMap.Enabled),
|
||||||
|
("color", navMap.Color.ToHexNoAlpha()),
|
||||||
|
("label", navMap.Text ?? string.Empty)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region: Grid functions
|
||||||
|
|
||||||
|
private void RefreshGrid(EntityUid uid, NavMapComponent component, MapGridComponent mapGrid)
|
||||||
|
{
|
||||||
|
// Clear stale data
|
||||||
|
component.Chunks.Clear();
|
||||||
|
component.Beacons.Clear();
|
||||||
|
|
||||||
|
// Loop over all tiles
|
||||||
|
var tileRefs = _mapSystem.GetAllTiles(uid, mapGrid);
|
||||||
|
|
||||||
|
foreach (var tileRef in tileRefs)
|
||||||
|
{
|
||||||
|
var tile = tileRef.GridIndices;
|
||||||
|
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||||
|
|
||||||
|
if (!component.Chunks.TryGetValue((NavMapChunkType.Floor, chunkOrigin), out var chunk))
|
||||||
|
chunk = new(chunkOrigin);
|
||||||
|
|
||||||
|
chunk.LastUpdate = _gameTiming.CurTick;
|
||||||
|
|
||||||
|
// Refresh the floor tile
|
||||||
|
component.Chunks[(NavMapChunkType.Floor, chunkOrigin)] = SetAllEdgesForChunkTile(chunk, tile);
|
||||||
|
|
||||||
|
// Refresh the contents of the tile
|
||||||
|
RefreshTileEntityContents(uid, component, mapGrid, chunkOrigin, tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dirty(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshTileEntityContents(EntityUid uid, NavMapComponent component, MapGridComponent mapGrid, Vector2i chunkOrigin, Vector2i tile)
|
||||||
{
|
{
|
||||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||||
var existing = chunk.TileData;
|
var flag = (ushort) GetFlag(relative);
|
||||||
var flag = GetFlag(relative);
|
var invFlag = (ushort) ~flag;
|
||||||
|
|
||||||
chunk.TileData &= ~flag;
|
// Clear stale data from the tile across all entity associated chunks
|
||||||
|
foreach (var category in EntityChunkTypes)
|
||||||
|
{
|
||||||
|
if (!component.Chunks.TryGetValue((category, chunkOrigin), out var chunk))
|
||||||
|
chunk = new(chunkOrigin);
|
||||||
|
|
||||||
var enumerator = grid.GetAnchoredEntitiesEnumerator(tile);
|
foreach (var (direction, _) in chunk.TileData)
|
||||||
// TODO: Use something to get convex poly.
|
chunk.TileData[direction] &= invFlag;
|
||||||
|
|
||||||
|
chunk.LastUpdate = _gameTiming.CurTick;
|
||||||
|
component.Chunks[(category, chunkOrigin)] = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the tile data based on what entities are still anchored to the tile
|
||||||
|
var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(uid, mapGrid, tile);
|
||||||
|
|
||||||
while (enumerator.MoveNext(out var ent))
|
while (enumerator.MoveNext(out var ent))
|
||||||
{
|
{
|
||||||
if (!_physicsQuery.TryGetComponent(ent, out var body) ||
|
if (!TryComp<AirtightComponent>(ent, out var entAirtight))
|
||||||
!body.CanCollide ||
|
|
||||||
!body.Hard ||
|
|
||||||
body.BodyType != BodyType.Static ||
|
|
||||||
!_tags.HasTag(ent.Value, "Wall", _tagQuery) &&
|
|
||||||
!_tags.HasTag(ent.Value, "Window", _tagQuery))
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
chunk.TileData |= flag;
|
var category = GetAssociatedEntityChunkType(ent.Value);
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunk.TileData == 0)
|
if (!component.Chunks.TryGetValue((category, chunkOrigin), out var chunk))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var (direction, _) in chunk.TileData)
|
||||||
{
|
{
|
||||||
component.Chunks.Remove(chunk.Origin);
|
if ((direction & entAirtight.AirBlockedDirection) > 0)
|
||||||
|
chunk.TileData[direction] |= flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing == chunk.TileData)
|
chunk.LastUpdate = _gameTiming.CurTick;
|
||||||
|
component.Chunks[(category, chunkOrigin)] = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove walls that intersect with doors (unless they can both physically fit on the same tile)
|
||||||
|
if (component.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin), out var wallChunk) &&
|
||||||
|
component.Chunks.TryGetValue((NavMapChunkType.Airlock, chunkOrigin), out var airlockChunk))
|
||||||
|
{
|
||||||
|
foreach (var (direction, _) in wallChunk.TileData)
|
||||||
|
{
|
||||||
|
var airlockInvFlag = (ushort) ~airlockChunk.TileData[direction];
|
||||||
|
wallChunk.TileData[direction] &= airlockInvFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
wallChunk.LastUpdate = _gameTiming.CurTick;
|
||||||
|
component.Chunks[(NavMapChunkType.Wall, chunkOrigin)] = wallChunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region: Beacon functions
|
||||||
|
|
||||||
|
private void UpdateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent? xform = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref xform))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Dirty(uid, component);
|
if (xform.GridUid == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<NavMapComponent>(xform.GridUid, out var navMap))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var netEnt = GetNetEntity(uid);
|
||||||
|
var oldBeacon = navMap.Beacons.FirstOrNull(x => x.NetEnt == netEnt);
|
||||||
|
var changed = false;
|
||||||
|
|
||||||
|
if (oldBeacon != null)
|
||||||
|
{
|
||||||
|
navMap.Beacons.Remove(oldBeacon.Value);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryCreateNavMapBeaconData(uid, component, xform, out var beaconData))
|
||||||
|
{
|
||||||
|
navMap.Beacons.Add(beaconData.Value);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
Dirty(xform.GridUid.Value, navMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBeaconEnabledVisuals(Entity<NavMapBeaconComponent> ent)
|
||||||
|
{
|
||||||
|
_appearance.SetData(ent, NavMapBeaconVisuals.Enabled, ent.Comp.Enabled && Transform(ent).Anchored);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -389,9 +345,6 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
|||||||
|
|
||||||
comp.Enabled = enabled;
|
comp.Enabled = enabled;
|
||||||
UpdateBeaconEnabledVisuals((uid, comp));
|
UpdateBeaconEnabledVisuals((uid, comp));
|
||||||
Dirty(uid, comp);
|
|
||||||
|
|
||||||
RefreshNavGrid(uid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -419,7 +372,7 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
|||||||
if (!Resolve(ent, ref ent.Comp))
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return TryGetNearestBeacon(_transform.GetMapCoordinates(ent, ent.Comp), out beacon, out beaconCoords);
|
return TryGetNearestBeacon(_transformSystem.GetMapCoordinates(ent, ent.Comp), out beacon, out beaconCoords);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -446,7 +399,7 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
|||||||
if (coordinates.MapId != xform.MapID)
|
if (coordinates.MapId != xform.MapID)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var coords = _transform.GetWorldPosition(xform);
|
var coords = _transformSystem.GetWorldPosition(xform);
|
||||||
var distanceSquared = (coordinates.Position - coords).LengthSquared();
|
var distanceSquared = (coordinates.Position - coords).LengthSquared();
|
||||||
if (!float.IsInfinity(minDistance) && distanceSquared >= minDistance)
|
if (!float.IsInfinity(minDistance) && distanceSquared >= minDistance)
|
||||||
continue;
|
continue;
|
||||||
@@ -465,7 +418,7 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
|||||||
if (!Resolve(ent, ref ent.Comp))
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
return Loc.GetString("nav-beacon-pos-no-beacons");
|
return Loc.GetString("nav-beacon-pos-no-beacons");
|
||||||
|
|
||||||
return GetNearestBeaconString(_transform.GetMapCoordinates(ent, ent.Comp));
|
return GetNearestBeaconString(_transformSystem.GetMapCoordinates(ent, ent.Comp));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetNearestBeaconString(MapCoordinates coordinates)
|
public string GetNearestBeaconString(MapCoordinates coordinates)
|
||||||
@@ -494,11 +447,13 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
|||||||
? Loc.GetString("nav-beacon-pos-format-direction-mod-far")
|
? Loc.GetString("nav-beacon-pos-format-direction-mod-far")
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
|
|
||||||
// we can null suppress the text being null because TRyGetNearestVisibleStationBeacon always gives us a beacon with not-null text.
|
// we can null suppress the text being null because TryGetNearestVisibleStationBeacon always gives us a beacon with not-null text.
|
||||||
return Loc.GetString("nav-beacon-pos-format-direction",
|
return Loc.GetString("nav-beacon-pos-format-direction",
|
||||||
("modifier", modifier),
|
("modifier", modifier),
|
||||||
("direction", ContentLocalizationManager.FormatDirection(adjustedDir).ToLowerInvariant()),
|
("direction", ContentLocalizationManager.FormatDirection(adjustedDir).ToLowerInvariant()),
|
||||||
("color", beacon.Value.Comp.Color),
|
("color", beacon.Value.Comp.Color),
|
||||||
("marker", beacon.Value.Comp.Text!));
|
("marker", beacon.Value.Comp.Text!));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
using Content.Shared.Atmos;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Shared.Pinpointer;
|
namespace Content.Shared.Pinpointer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to store grid poly data to be used for UIs.
|
/// Used to store grid data to be used for UIs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent, NetworkedComponent]
|
[RegisterComponent, NetworkedComponent]
|
||||||
public sealed partial class NavMapComponent : Component
|
public sealed partial class NavMapComponent : Component
|
||||||
@@ -12,25 +15,57 @@ public sealed partial class NavMapComponent : Component
|
|||||||
* Don't need DataFields as this can be reconstructed
|
* Don't need DataFields as this can be reconstructed
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bitmasks that represent chunked tiles.
|
||||||
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public readonly Dictionary<Vector2i, NavMapChunk> Chunks = new();
|
public Dictionary<(NavMapChunkType, Vector2i), NavMapChunk> Chunks = new();
|
||||||
|
|
||||||
[ViewVariables] public readonly List<SharedNavMapSystem.NavMapBeacon> Beacons = new();
|
/// <summary>
|
||||||
|
/// List of station beacons.
|
||||||
[ViewVariables] public readonly List<SharedNavMapSystem.NavMapAirlock> Airlocks = new();
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public HashSet<SharedNavMapSystem.NavMapBeacon> Beacons = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
public sealed class NavMapChunk
|
public sealed class NavMapChunk
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The chunk origin
|
||||||
|
/// </summary>
|
||||||
public readonly Vector2i Origin;
|
public readonly Vector2i Origin;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bitmask for tiles, 1 for occupied and 0 for empty.
|
/// Bitmask for tiles, 1 for occupied and 0 for empty. There is a bitmask for each cardinal direction,
|
||||||
|
/// representing each edge of the tile, in case the entities inside it do not entirely fill it
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int TileData;
|
public Dictionary<AtmosDirection, ushort> TileData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last game tick that the chunk was updated
|
||||||
|
/// </summary>
|
||||||
|
[NonSerialized]
|
||||||
|
public GameTick LastUpdate;
|
||||||
|
|
||||||
public NavMapChunk(Vector2i origin)
|
public NavMapChunk(Vector2i origin)
|
||||||
{
|
{
|
||||||
Origin = origin;
|
Origin = origin;
|
||||||
|
|
||||||
|
TileData = new()
|
||||||
|
{
|
||||||
|
[AtmosDirection.North] = 0,
|
||||||
|
[AtmosDirection.East] = 0,
|
||||||
|
[AtmosDirection.South] = 0,
|
||||||
|
[AtmosDirection.West] = 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum NavMapChunkType : byte
|
||||||
|
{
|
||||||
|
Invalid,
|
||||||
|
Floor,
|
||||||
|
Wall,
|
||||||
|
Airlock,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,38 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
using Content.Shared.Tag;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Pinpointer;
|
namespace Content.Shared.Pinpointer;
|
||||||
|
|
||||||
public abstract class SharedNavMapSystem : EntitySystem
|
public abstract class SharedNavMapSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
public const byte ChunkSize = 4;
|
public const byte ChunkSize = 4;
|
||||||
|
|
||||||
|
public readonly NavMapChunkType[] EntityChunkTypes =
|
||||||
|
{
|
||||||
|
NavMapChunkType.Invalid,
|
||||||
|
NavMapChunkType.Wall,
|
||||||
|
NavMapChunkType.Airlock,
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly string[] _wallTags = ["Wall", "Window"];
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
// Data handling events
|
||||||
|
SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts the chunk's tile into a bitflag for the slot.
|
/// Converts the chunk's tile into a bitflag for the slot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -31,19 +56,236 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
return new Vector2i(x, y);
|
return new Vector2i(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
public NavMapChunk SetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile)
|
||||||
protected sealed class NavMapComponentState : ComponentState
|
|
||||||
{
|
{
|
||||||
public Dictionary<Vector2i, int> TileData = new();
|
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||||
|
var flag = (ushort) GetFlag(relative);
|
||||||
|
|
||||||
public List<NavMapBeacon> Beacons = new();
|
foreach (var (direction, _) in chunk.TileData)
|
||||||
|
chunk.TileData[direction] |= flag;
|
||||||
|
|
||||||
public List<NavMapAirlock> Airlocks = new();
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NavMapChunk UnsetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile)
|
||||||
|
{
|
||||||
|
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||||
|
var flag = (ushort) GetFlag(relative);
|
||||||
|
var invFlag = (ushort) ~flag;
|
||||||
|
|
||||||
|
foreach (var (direction, _) in chunk.TileData)
|
||||||
|
chunk.TileData[direction] &= invFlag;
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort GetCombinedEdgesForChunk(Dictionary<AtmosDirection, ushort> tile)
|
||||||
|
{
|
||||||
|
ushort combined = 0;
|
||||||
|
|
||||||
|
foreach (var kvp in tile)
|
||||||
|
combined |= kvp.Value;
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllTileEdgesAreOccupied(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
||||||
|
{
|
||||||
|
var flag = (ushort) GetFlag(tile);
|
||||||
|
|
||||||
|
foreach (var kvp in tileData)
|
||||||
|
{
|
||||||
|
if ((kvp.Value & flag) == 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NavMapChunkType GetAssociatedEntityChunkType(EntityUid uid)
|
||||||
|
{
|
||||||
|
var category = NavMapChunkType.Invalid;
|
||||||
|
|
||||||
|
if (HasComp<NavMapDoorComponent>(uid))
|
||||||
|
category = NavMapChunkType.Airlock;
|
||||||
|
|
||||||
|
else if (_tagSystem.HasAnyTag(uid, _wallTags))
|
||||||
|
category = NavMapChunkType.Wall;
|
||||||
|
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent xform, [NotNullWhen(true)] out NavMapBeacon? beaconData)
|
||||||
|
{
|
||||||
|
beaconData = null;
|
||||||
|
|
||||||
|
if (!component.Enabled || xform.GridUid == null || !xform.Anchored)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string? name = component.Text;
|
||||||
|
var meta = MetaData(uid);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
name = meta.EntityName;
|
||||||
|
|
||||||
|
beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition)
|
||||||
|
{
|
||||||
|
LastUpdate = _gameTiming.CurTick
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region: Event handling
|
||||||
|
|
||||||
|
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
var chunks = new Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>>();
|
||||||
|
var beacons = new HashSet<NavMapBeacon>();
|
||||||
|
|
||||||
|
// Should this be a full component state or a delta-state?
|
||||||
|
if (args.FromTick <= component.CreationTick)
|
||||||
|
{
|
||||||
|
foreach (var ((category, origin), chunk) in component.Chunks)
|
||||||
|
{
|
||||||
|
var chunkDatum = new Dictionary<AtmosDirection, ushort>(chunk.TileData.Count);
|
||||||
|
|
||||||
|
foreach (var (direction, tileData) in chunk.TileData)
|
||||||
|
chunkDatum[direction] = tileData;
|
||||||
|
|
||||||
|
chunks.Add((category, origin), chunkDatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
var beaconQuery = AllEntityQuery<NavMapBeaconComponent, TransformComponent>();
|
||||||
|
|
||||||
|
while (beaconQuery.MoveNext(out var beaconUid, out var beacon, out var xform))
|
||||||
|
{
|
||||||
|
if (xform.GridUid != uid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!TryCreateNavMapBeaconData(beaconUid, beacon, xform, out var beaconData))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
beacons.Add(beaconData.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.State = new NavMapComponentState(chunks, beacons);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ((category, origin), chunk) in component.Chunks)
|
||||||
|
{
|
||||||
|
if (chunk.LastUpdate < args.FromTick)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var chunkDatum = new Dictionary<AtmosDirection, ushort>(chunk.TileData.Count);
|
||||||
|
|
||||||
|
foreach (var (direction, tileData) in chunk.TileData)
|
||||||
|
chunkDatum[direction] = tileData;
|
||||||
|
|
||||||
|
chunks.Add((category, origin), chunkDatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var beacon in component.Beacons)
|
||||||
|
{
|
||||||
|
if (beacon.LastUpdate < args.FromTick)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
beacons.Add(beacon);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.State = new NavMapComponentState(chunks, beacons)
|
||||||
|
{
|
||||||
|
AllChunks = new(component.Chunks.Keys),
|
||||||
|
AllBeacons = new(component.Beacons)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region: System messages
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
protected sealed class NavMapComponentState : ComponentState, IComponentDeltaState
|
||||||
|
{
|
||||||
|
public Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>> Chunks = new();
|
||||||
|
public HashSet<NavMapBeacon> Beacons = new();
|
||||||
|
|
||||||
|
// Required to infer deleted/missing chunks for delta states
|
||||||
|
public HashSet<(NavMapChunkType, Vector2i)>? AllChunks;
|
||||||
|
public HashSet<NavMapBeacon>? AllBeacons;
|
||||||
|
|
||||||
|
public NavMapComponentState(Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>> chunks, HashSet<NavMapBeacon> beacons)
|
||||||
|
{
|
||||||
|
Chunks = chunks;
|
||||||
|
Beacons = beacons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FullState => (AllChunks == null || AllBeacons == null);
|
||||||
|
|
||||||
|
public void ApplyToFullState(IComponentState fullState)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(!FullState);
|
||||||
|
var state = (NavMapComponentState) fullState;
|
||||||
|
DebugTools.Assert(state.FullState);
|
||||||
|
|
||||||
|
// Update chunks
|
||||||
|
foreach (var key in state.Chunks.Keys)
|
||||||
|
{
|
||||||
|
if (!AllChunks!.Contains(key))
|
||||||
|
state.Chunks.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (chunk, data) in Chunks)
|
||||||
|
state.Chunks[chunk] = new(data);
|
||||||
|
|
||||||
|
// Update beacons
|
||||||
|
foreach (var beacon in state.Beacons)
|
||||||
|
{
|
||||||
|
if (!AllBeacons!.Contains(beacon))
|
||||||
|
state.Beacons.Remove(beacon);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var beacon in Beacons)
|
||||||
|
state.Beacons.Add(beacon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IComponentState CreateNewFullState(IComponentState fullState)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(!FullState);
|
||||||
|
var state = (NavMapComponentState) fullState;
|
||||||
|
DebugTools.Assert(state.FullState);
|
||||||
|
|
||||||
|
var chunks = new Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>>();
|
||||||
|
var beacons = new HashSet<NavMapBeacon>();
|
||||||
|
|
||||||
|
foreach (var (chunk, data) in Chunks)
|
||||||
|
chunks[chunk] = new(data);
|
||||||
|
|
||||||
|
foreach (var (chunk, data) in state.Chunks)
|
||||||
|
{
|
||||||
|
if (AllChunks!.Contains(chunk))
|
||||||
|
chunks.TryAdd(chunk, new(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var beacon in Beacons)
|
||||||
|
beacons.Add(new NavMapBeacon(beacon.NetEnt, beacon.Color, beacon.Text, beacon.Position));
|
||||||
|
|
||||||
|
foreach (var beacon in state.Beacons)
|
||||||
|
{
|
||||||
|
if (AllBeacons!.Contains(beacon))
|
||||||
|
beacons.Add(new NavMapBeacon(beacon.NetEnt, beacon.Color, beacon.Text, beacon.Position));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NavMapComponentState(chunks, beacons);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public readonly record struct NavMapBeacon(Color Color, string Text, Vector2 Position);
|
public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position)
|
||||||
|
{
|
||||||
|
public GameTick LastUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
#endregion
|
||||||
public readonly record struct NavMapAirlock(Vector2 Position);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user