Replace NavMap dictionaries with int[] (#27602)
* Replace NavMap dictionaries with int[] * Remove badly named const * Remove unnecessary offset * Prioritize airlocks
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
using Content.Shared.Atmos;
|
|
||||||
using Content.Shared.Pinpointer;
|
using Content.Shared.Pinpointer;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
@@ -25,12 +24,6 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
if (!state.AllChunks!.Contains(index))
|
if (!state.AllChunks!.Contains(index))
|
||||||
component.Chunks.Remove(index);
|
component.Chunks.Remove(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var beacon in component.Beacons)
|
|
||||||
{
|
|
||||||
if (!state.AllBeacons!.Contains(beacon))
|
|
||||||
component.Beacons.Remove(beacon);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -39,34 +32,19 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
if (!state.Chunks.ContainsKey(index))
|
if (!state.Chunks.ContainsKey(index))
|
||||||
component.Chunks.Remove(index);
|
component.Chunks.Remove(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var beacon in component.Beacons)
|
|
||||||
{
|
|
||||||
if (!state.Beacons.Contains(beacon))
|
|
||||||
component.Beacons.Remove(beacon);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (origin, chunk) in state.Chunks)
|
foreach (var (origin, chunk) in state.Chunks)
|
||||||
{
|
{
|
||||||
var newChunk = new NavMapChunk(origin);
|
var newChunk = new NavMapChunk(origin);
|
||||||
|
Array.Copy(chunk, newChunk.TileData, chunk.Length);
|
||||||
for (var i = 0; i < NavMapComponent.Categories; i++)
|
|
||||||
{
|
|
||||||
var newData = chunk[i];
|
|
||||||
|
|
||||||
if (newData == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
newChunk.TileData[i] = new(newData);
|
|
||||||
}
|
|
||||||
|
|
||||||
component.Chunks[origin] = newChunk;
|
component.Chunks[origin] = newChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var beacon in state.Beacons)
|
component.Beacons.Clear();
|
||||||
|
foreach (var (nuid, beacon) in state.Beacons)
|
||||||
{
|
{
|
||||||
component.Beacons.Add(beacon);
|
component.Beacons[nuid] = beacon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ using System.Numerics;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.Pinpointer.UI;
|
namespace Content.Client.Pinpointer.UI;
|
||||||
|
|
||||||
@@ -71,10 +72,10 @@ public partial class NavMapControl : MapGridControl
|
|||||||
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();
|
private Dictionary<Vector2i, Vector2i> _horizLines = new();
|
||||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookupReversed = new();
|
private Dictionary<Vector2i, Vector2i> _horizLinesReversed = new();
|
||||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookup = new();
|
private Dictionary<Vector2i, Vector2i> _vertLines = new();
|
||||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookupReversed = new();
|
private Dictionary<Vector2i, Vector2i> _vertLinesReversed = new();
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
private NavMapComponent? _navMap;
|
private NavMapComponent? _navMap;
|
||||||
@@ -376,7 +377,7 @@ public partial class NavMapControl : MapGridControl
|
|||||||
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.Values)
|
||||||
{
|
{
|
||||||
var position = beacon.Position - offset;
|
var position = beacon.Position - offset;
|
||||||
position = ScalePosition(position with { Y = -position.Y });
|
position = ScalePosition(position with { Y = -position.Y });
|
||||||
@@ -485,147 +486,105 @@ public partial class NavMapControl : MapGridControl
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// We'll use the following dictionaries to combine collinear wall lines
|
// We'll use the following dictionaries to combine collinear wall lines
|
||||||
HorizLinesLookup.Clear();
|
_horizLines.Clear();
|
||||||
HorizLinesLookupReversed.Clear();
|
_horizLinesReversed.Clear();
|
||||||
VertLinesLookup.Clear();
|
_vertLines.Clear();
|
||||||
VertLinesLookupReversed.Clear();
|
_vertLinesReversed.Clear();
|
||||||
|
|
||||||
|
const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall;
|
||||||
|
const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall;
|
||||||
|
const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall;
|
||||||
|
const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall;
|
||||||
|
|
||||||
foreach (var (chunkOrigin, chunk) in _navMap.Chunks)
|
foreach (var (chunkOrigin, chunk) in _navMap.Chunks)
|
||||||
{
|
{
|
||||||
for (var j = 0; j < NavMapComponent.Categories; j++)
|
for (var i = 0; i < SharedNavMapSystem.ArraySize; i++)
|
||||||
{
|
{
|
||||||
var category = (NavMapChunkType) j;
|
var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask;
|
||||||
|
if (tileData == 0)
|
||||||
if (category != NavMapChunkType.Wall)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var data = chunk.TileData[j];
|
tileData >>= (int) NavMapChunkType.Wall;
|
||||||
|
|
||||||
if (data == null)
|
var relativeTile = SharedNavMapSystem.GetTileFromIndex(i);
|
||||||
continue;
|
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
|
||||||
|
|
||||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
if (tileData != SharedNavMapSystem.AllDirMask)
|
||||||
{
|
{
|
||||||
var value = (ushort) Math.Pow(2, i);
|
AddRectForThinWall(tileData, tile);
|
||||||
var mask = _navMapSystem.GetCombinedEdgesForChunk(data) & value;
|
continue;
|
||||||
|
|
||||||
if (mask == 0x0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
|
||||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
|
|
||||||
|
|
||||||
if (!_navMapSystem.AllTileEdgesAreOccupied(data, relativeTile))
|
|
||||||
{
|
|
||||||
AddRectForThinWall(data, tile);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
tile = tile with { Y = -tile.Y };
|
|
||||||
|
|
||||||
NavMapChunk? neighborChunk;
|
|
||||||
bool neighbor;
|
|
||||||
|
|
||||||
// North edge
|
|
||||||
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
|
||||||
{
|
|
||||||
_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk);
|
|
||||||
var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall];
|
|
||||||
|
|
||||||
neighbor = neighborData != null &&
|
|
||||||
(neighborData[AtmosDirection.South] & SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + Vector2i.Up);
|
|
||||||
neighbor = (data[AtmosDirection.South] & flag) != 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!neighbor)
|
|
||||||
{
|
|
||||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize),
|
|
||||||
tile + new Vector2i(_grid.TileSize, -_grid.TileSize), HorizLinesLookup,
|
|
||||||
HorizLinesLookupReversed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// East edge
|
|
||||||
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
|
|
||||||
{
|
|
||||||
_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk);
|
|
||||||
var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall];
|
|
||||||
|
|
||||||
neighbor = neighborData != null &&
|
|
||||||
(neighborData[AtmosDirection.West] & SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + Vector2i.Right);
|
|
||||||
neighbor = (data[AtmosDirection.West] & flag) != 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!neighbor)
|
|
||||||
{
|
|
||||||
AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize),
|
|
||||||
tile + new Vector2i(_grid.TileSize, 0), VertLinesLookup, VertLinesLookupReversed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// South edge
|
|
||||||
if (relativeTile.Y == 0)
|
|
||||||
{
|
|
||||||
_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk);
|
|
||||||
var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall];
|
|
||||||
|
|
||||||
neighbor = neighborData != null &&
|
|
||||||
(neighborData[AtmosDirection.North] & SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + Vector2i.Down);
|
|
||||||
neighbor = (data[AtmosDirection.North] & flag) != 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!neighbor)
|
|
||||||
{
|
|
||||||
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup,
|
|
||||||
HorizLinesLookupReversed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// West edge
|
|
||||||
if (relativeTile.X == 0)
|
|
||||||
{
|
|
||||||
_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk);
|
|
||||||
var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall];
|
|
||||||
|
|
||||||
neighbor = neighborData != null &&
|
|
||||||
(neighborData[AtmosDirection.East] & SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + Vector2i.Left);
|
|
||||||
neighbor = (data[AtmosDirection.East] & flag) != 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tile = tile with { Y = -tile.Y };
|
||||||
|
NavMapChunk? neighborChunk;
|
||||||
|
|
||||||
|
// North edge
|
||||||
|
var neighborData = 0;
|
||||||
|
if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1)
|
||||||
|
neighborData = chunk.TileData[i+1];
|
||||||
|
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk))
|
||||||
|
neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize];
|
||||||
|
|
||||||
|
if ((neighborData & southMask) == 0)
|
||||||
|
{
|
||||||
|
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize),
|
||||||
|
tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines,
|
||||||
|
_horizLinesReversed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// East edge
|
||||||
|
neighborData = 0;
|
||||||
|
if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1)
|
||||||
|
neighborData = chunk.TileData[i+SharedNavMapSystem.ChunkSize];
|
||||||
|
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk))
|
||||||
|
neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize];
|
||||||
|
|
||||||
|
if ((neighborData & westMask) == 0)
|
||||||
|
{
|
||||||
|
AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize),
|
||||||
|
tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// South edge
|
||||||
|
neighborData = 0;
|
||||||
|
if (relativeTile.Y != 0)
|
||||||
|
neighborData = chunk.TileData[i-1];
|
||||||
|
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk))
|
||||||
|
neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize];
|
||||||
|
|
||||||
|
if ((neighborData & northMask) == 0)
|
||||||
|
{
|
||||||
|
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines,
|
||||||
|
_horizLinesReversed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// West edge
|
||||||
|
neighborData = 0;
|
||||||
|
if (relativeTile.X != 0)
|
||||||
|
neighborData = chunk.TileData[i-SharedNavMapSystem.ChunkSize];
|
||||||
|
else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk))
|
||||||
|
neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize];
|
||||||
|
|
||||||
|
if ((neighborData & eastMask) == 0)
|
||||||
|
{
|
||||||
|
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, _vertLines,
|
||||||
|
_vertLinesReversed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Record the combined lines
|
||||||
foreach (var (origin, terminal) in HorizLinesLookup)
|
foreach (var (origin, terminal) in _horizLines)
|
||||||
{
|
{
|
||||||
TileLines.Add((origin.Item2, terminal.Item2));
|
TileLines.Add((origin, terminal));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (origin, terminal) in VertLinesLookup)
|
foreach (var (origin, terminal) in _vertLines)
|
||||||
{
|
{
|
||||||
TileLines.Add((origin.Item2, terminal.Item2));
|
TileLines.Add((origin, terminal));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,28 +593,23 @@ public partial class NavMapControl : MapGridControl
|
|||||||
if (_navMap == null || _grid == null)
|
if (_navMap == null || _grid == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var (chunkOrigin, chunk) in _navMap.Chunks)
|
foreach (var chunk in _navMap.Chunks.Values)
|
||||||
{
|
{
|
||||||
var data = chunk.TileData[(int) NavMapChunkType.Airlock];
|
for (var i = 0; i < SharedNavMapSystem.ArraySize; i++)
|
||||||
|
|
||||||
if (data == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
|
||||||
{
|
{
|
||||||
var value = (int) Math.Pow(2, i);
|
var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask;
|
||||||
var mask = _navMapSystem.GetCombinedEdgesForChunk(data) & value;
|
if (tileData == 0)
|
||||||
|
|
||||||
if (mask == 0x0)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var relative = SharedNavMapSystem.GetTile(mask);
|
tileData >>= (int) NavMapChunkType.Airlock;
|
||||||
|
|
||||||
|
var relative = SharedNavMapSystem.GetTileFromIndex(i);
|
||||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize;
|
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 the edges of an airlock tile are not all occupied, draw a thin airlock for each edge
|
||||||
if (!_navMapSystem.AllTileEdgesAreOccupied(data, relative))
|
if (tileData != SharedNavMapSystem.AllDirMask)
|
||||||
{
|
{
|
||||||
AddRectForThinAirlock(data, tile);
|
AddRectForThinAirlock(tileData, tile);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,108 +623,90 @@ public partial class NavMapControl : MapGridControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddRectForThinWall(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
private void AddRectForThinWall(int tileData, Vector2i tile)
|
||||||
{
|
{
|
||||||
if (_navMapSystem == null || _grid == null)
|
var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness);
|
||||||
return;
|
var rightBottom = new Vector2(0.5f, 0.5f);
|
||||||
|
|
||||||
var leftTop = new Vector2(-0.5f, -0.5f + ThinWallThickness);
|
for (var i = 0; i < SharedNavMapSystem.Directions; i++)
|
||||||
var rightBottom = new Vector2(0.5f, -0.5f);
|
|
||||||
|
|
||||||
foreach (var (direction, mask) in tileData)
|
|
||||||
{
|
{
|
||||||
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize);
|
var dirMask = 1 << i;
|
||||||
var flag = (ushort) SharedNavMapSystem.GetFlag(relative);
|
if ((tileData & dirMask) == 0)
|
||||||
|
|
||||||
if ((mask & flag) == 0)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// TODO NAVMAP
|
||||||
|
// Consider using faster rotation operations, given that these are always 90 degree increments
|
||||||
|
var angle = -((AtmosDirection) dirMask).ToAngle();
|
||||||
TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
|
TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddRectForThinAirlock(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
private void AddRectForThinAirlock(int tileData, Vector2i tile)
|
||||||
{
|
{
|
||||||
if (_navMapSystem == null || _grid == null)
|
var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness);
|
||||||
return;
|
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);
|
||||||
|
|
||||||
var leftTop = new Vector2(-0.5f + FullWallInstep, -0.5f + FullWallInstep + ThinDoorThickness);
|
for (var i = 0; i < SharedNavMapSystem.Directions; i++)
|
||||||
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 dirMask = 1 << i;
|
||||||
var flag = (ushort) SharedNavMapSystem.GetFlag(relative);
|
if ((tileData & dirMask) == 0)
|
||||||
|
|
||||||
if ((mask & flag) == 0)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
|
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
|
||||||
var angle = new Angle(0);
|
var angle = -((AtmosDirection) dirMask).ToAngle();
|
||||||
|
|
||||||
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));
|
TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
|
||||||
TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition));
|
TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void AddOrUpdateNavMapLine
|
protected void AddOrUpdateNavMapLine(
|
||||||
(Vector2i origin,
|
Vector2i origin,
|
||||||
Vector2i terminus,
|
Vector2i terminus,
|
||||||
Dictionary<(int, Vector2i), (int, Vector2i)> lookup,
|
Dictionary<Vector2i, Vector2i> lookup,
|
||||||
Dictionary<(int, Vector2i), (int, Vector2i)> lookupReversed,
|
Dictionary<Vector2i, Vector2i> lookupReversed)
|
||||||
int index = 0)
|
|
||||||
{
|
{
|
||||||
(int, Vector2i) foundTermiusTuple;
|
Vector2i foundTermius;
|
||||||
(int, Vector2i) foundOriginTuple;
|
Vector2i foundOrigin;
|
||||||
|
|
||||||
if (lookup.TryGetValue((index, terminus), out foundTermiusTuple) &&
|
// Does our new line end at the beginning of an existing line?
|
||||||
lookupReversed.TryGetValue((index, origin), out foundOriginTuple))
|
if (lookup.Remove(terminus, out foundTermius))
|
||||||
{
|
{
|
||||||
lookup[foundOriginTuple] = foundTermiusTuple;
|
DebugTools.Assert(lookupReversed[foundTermius] == terminus);
|
||||||
lookupReversed[foundTermiusTuple] = foundOriginTuple;
|
|
||||||
|
|
||||||
lookup.Remove((index, terminus));
|
// Does our new line start at the end of an existing line?
|
||||||
lookupReversed.Remove((index, origin));
|
if (lookupReversed.Remove(origin, out foundOrigin))
|
||||||
|
{
|
||||||
|
// Our new line just connects two existing lines
|
||||||
|
DebugTools.Assert(lookup[foundOrigin] == origin);
|
||||||
|
lookup[foundOrigin] = foundTermius;
|
||||||
|
lookupReversed[foundTermius] = foundOrigin;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Our new line precedes an existing line, extending it further to the left
|
||||||
|
lookup[origin] = foundTermius;
|
||||||
|
lookupReversed[foundTermius] = origin;
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (lookup.TryGetValue((index, terminus), out foundTermiusTuple))
|
// Does our new line start at the end of an existing line?
|
||||||
|
if (lookupReversed.Remove(origin, out foundOrigin))
|
||||||
{
|
{
|
||||||
lookup[(index, origin)] = foundTermiusTuple;
|
// Our new line just extends an existing line further to the right
|
||||||
lookup.Remove((index, terminus));
|
DebugTools.Assert(lookup[foundOrigin] == origin);
|
||||||
lookupReversed[foundTermiusTuple] = (index, origin);
|
lookup[foundOrigin] = terminus;
|
||||||
|
lookupReversed[terminus] = foundOrigin;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (lookupReversed.TryGetValue((index, origin), out foundOriginTuple))
|
// Completely disconnected line segment.
|
||||||
{
|
lookup.Add(origin, terminus);
|
||||||
lookupReversed[(index, terminus)] = foundOriginTuple;
|
lookupReversed.Add(terminus, origin);
|
||||||
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()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Robust.Client.Graphics;
|
|||||||
using Robust.Shared.Collections;
|
using Robust.Shared.Collections;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using static Content.Shared.Power.SharedPowerMonitoringConsoleSystem;
|
||||||
|
|
||||||
namespace Content.Client.Power;
|
namespace Content.Client.Power;
|
||||||
|
|
||||||
@@ -26,6 +27,11 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
public List<PowerMonitoringConsoleLine> PowerCableNetwork = new();
|
public List<PowerMonitoringConsoleLine> PowerCableNetwork = new();
|
||||||
public List<PowerMonitoringConsoleLine> FocusCableNetwork = new();
|
public List<PowerMonitoringConsoleLine> FocusCableNetwork = new();
|
||||||
|
|
||||||
|
private Dictionary<Vector2i, Vector2i>[] _horizLines = [new(), new(), new()];
|
||||||
|
private Dictionary<Vector2i, Vector2i>[] _horizLinesReversed = [new(), new(), new()];
|
||||||
|
private Dictionary<Vector2i, Vector2i>[] _vertLines = [new(), new(), new()];
|
||||||
|
private Dictionary<Vector2i, Vector2i>[] _vertLinesReversed = [new(), new(), new()];
|
||||||
|
|
||||||
private MapGridComponent? _grid;
|
private MapGridComponent? _grid;
|
||||||
|
|
||||||
public PowerMonitoringConsoleNavMapControl() : base()
|
public PowerMonitoringConsoleNavMapControl() : base()
|
||||||
@@ -182,28 +188,32 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
if (chunks == null)
|
if (chunks == null)
|
||||||
return decodedOutput;
|
return decodedOutput;
|
||||||
|
|
||||||
// We'll use the following dictionaries to combine collinear power cable lines
|
Array.ForEach(_horizLines, x=> x.Clear());
|
||||||
HorizLinesLookup.Clear();
|
Array.ForEach(_horizLinesReversed, x=> x.Clear());
|
||||||
HorizLinesLookupReversed.Clear();
|
Array.ForEach(_vertLines, x=> x.Clear());
|
||||||
VertLinesLookup.Clear();
|
Array.ForEach(_vertLinesReversed, x=> x.Clear());
|
||||||
VertLinesLookupReversed.Clear();
|
|
||||||
|
|
||||||
foreach ((var chunkOrigin, var chunk) in chunks)
|
foreach (var (chunkOrigin, chunk) in chunks)
|
||||||
{
|
{
|
||||||
for (int cableIdx = 0; cableIdx < chunk.PowerCableData.Length; cableIdx++)
|
for (var cableIdx = 0; cableIdx < 3; cableIdx++)
|
||||||
{
|
{
|
||||||
|
var horizLines = _horizLines[cableIdx];
|
||||||
|
var horizLinesReversed = _horizLinesReversed[cableIdx];
|
||||||
|
var vertLines = _vertLines[cableIdx];
|
||||||
|
var vertLinesReversed = _vertLinesReversed[cableIdx];
|
||||||
|
|
||||||
var chunkMask = chunk.PowerCableData[cableIdx];
|
var chunkMask = chunk.PowerCableData[cableIdx];
|
||||||
|
|
||||||
for (var chunkIdx = 0; chunkIdx < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; chunkIdx++)
|
for (var chunkIdx = 0; chunkIdx < ChunkSize * ChunkSize; chunkIdx++)
|
||||||
{
|
{
|
||||||
var value = (int) Math.Pow(2, chunkIdx);
|
var value = 1 << chunkIdx;
|
||||||
var mask = chunkMask & value;
|
var mask = chunkMask & value;
|
||||||
|
|
||||||
if (mask == 0x0)
|
if (mask == 0x0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
var relativeTile = GetTileFromIndex(chunkIdx);
|
||||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
|
var tile = (chunk.Origin * ChunkSize + relativeTile) * _grid.TileSize;
|
||||||
tile = tile with { Y = -tile.Y };
|
tile = tile with { Y = -tile.Y };
|
||||||
|
|
||||||
PowerCableChunk neighborChunk;
|
PowerCableChunk neighborChunk;
|
||||||
@@ -212,39 +222,39 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
// Note: we only check the north and east neighbors
|
// Note: we only check the north and east neighbors
|
||||||
|
|
||||||
// East
|
// East
|
||||||
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
|
if (relativeTile.X == ChunkSize - 1)
|
||||||
{
|
{
|
||||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
|
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
|
||||||
(neighborChunk.PowerCableData[cableIdx] & SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
(neighborChunk.PowerCableData[cableIdx] & GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
|
var flag = GetFlag(relativeTile + new Vector2i(1, 0));
|
||||||
neighbor = (chunkMask & flag) != 0x0;
|
neighbor = (chunkMask & flag) != 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (neighbor)
|
if (neighbor)
|
||||||
{
|
{
|
||||||
// Add points
|
// Add points
|
||||||
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup, HorizLinesLookupReversed, cableIdx);
|
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), horizLines, horizLinesReversed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// North
|
// North
|
||||||
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
if (relativeTile.Y == ChunkSize - 1)
|
||||||
{
|
{
|
||||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
|
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
|
||||||
(neighborChunk.PowerCableData[cableIdx] & SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
(neighborChunk.PowerCableData[cableIdx] & GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
|
var flag = GetFlag(relativeTile + new Vector2i(0, 1));
|
||||||
neighbor = (chunkMask & flag) != 0x0;
|
neighbor = (chunkMask & flag) != 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (neighbor)
|
if (neighbor)
|
||||||
{
|
{
|
||||||
// Add points
|
// Add points
|
||||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup, VertLinesLookupReversed, cableIdx);
|
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, vertLines, vertLinesReversed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,11 +263,25 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
|||||||
|
|
||||||
var gridOffset = new Vector2(_grid.TileSize * 0.5f, -_grid.TileSize * 0.5f);
|
var gridOffset = new Vector2(_grid.TileSize * 0.5f, -_grid.TileSize * 0.5f);
|
||||||
|
|
||||||
foreach (var (origin, terminal) in HorizLinesLookup)
|
for (var index = 0; index < _horizLines.Length; index++)
|
||||||
decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1));
|
{
|
||||||
|
var horizLines = _horizLines[index];
|
||||||
|
foreach (var (origin, terminal) in horizLines)
|
||||||
|
{
|
||||||
|
decodedOutput.Add(new PowerMonitoringConsoleLine(origin + gridOffset, terminal + gridOffset,
|
||||||
|
(PowerMonitoringConsoleLineGroup) index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var (origin, terminal) in VertLinesLookup)
|
for (var index = 0; index < _vertLines.Length; index++)
|
||||||
decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1));
|
{
|
||||||
|
var vertLines = _vertLines[index];
|
||||||
|
foreach (var (origin, terminal) in vertLines)
|
||||||
|
{
|
||||||
|
decodedOutput.Add(new PowerMonitoringConsoleLine(origin + gridOffset, terminal + gridOffset,
|
||||||
|
(PowerMonitoringConsoleLineGroup) index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return decodedOutput;
|
return decodedOutput;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,7 @@ using JetBrains.Annotations;
|
|||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Content.Shared.Atmos;
|
|
||||||
using Content.Shared.Doors.Components;
|
|
||||||
|
|
||||||
namespace Content.Server.Pinpointer;
|
namespace Content.Server.Pinpointer;
|
||||||
|
|
||||||
@@ -44,6 +40,10 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
|
var categories = Enum.GetNames(typeof(NavMapChunkType)).Length - 1; // -1 due to "Invalid" entry.
|
||||||
|
if (Categories != categories)
|
||||||
|
throw new Exception($"{nameof(Categories)} must be equal to the number of chunk types");
|
||||||
|
|
||||||
_airtightQuery = GetEntityQuery<AirtightComponent>();
|
_airtightQuery = GetEntityQuery<AirtightComponent>();
|
||||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||||
_navQuery = GetEntityQuery<NavMapComponent>();
|
_navQuery = GetEntityQuery<NavMapComponent>();
|
||||||
@@ -65,15 +65,12 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region: Grid change event handling
|
#region: Grid change event handling
|
||||||
|
|
||||||
private void OnNavMapSplit(ref GridSplitEvent args)
|
private void OnNavMapSplit(ref GridSplitEvent args)
|
||||||
@@ -112,16 +109,30 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
var chunk = EnsureChunk(navMap, chunkOrigin);
|
var chunk = EnsureChunk(navMap, chunkOrigin);
|
||||||
|
|
||||||
// This could be easily replaced in the future to accommodate diagonal tiles
|
// This could be easily replaced in the future to accommodate diagonal tiles
|
||||||
if (ev.NewTile.IsSpace(_tileDefManager))
|
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||||
UnsetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
|
ref var tileData = ref chunk.TileData[GetTileIndex(relative)];
|
||||||
else
|
|
||||||
SetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
|
|
||||||
|
|
||||||
if (!PruneEmpty((ev.NewTile.GridUid, navMap), chunk))
|
if (ev.NewTile.IsSpace(_tileDefManager))
|
||||||
{
|
{
|
||||||
chunk.LastUpdate = _gameTiming.CurTick;
|
tileData = 0;
|
||||||
Dirty(ev.NewTile.GridUid, navMap);
|
if (PruneEmpty((ev.NewTile.GridUid, navMap), chunk))
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tileData = FloorMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirtyChunk((ev.NewTile.GridUid, navMap), chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DirtyChunk(Entity<NavMapComponent> entity, NavMapChunk chunk)
|
||||||
|
{
|
||||||
|
if (chunk.LastUpdate == _gameTiming.CurTick)
|
||||||
|
return;
|
||||||
|
|
||||||
|
chunk.LastUpdate = _gameTiming.CurTick;
|
||||||
|
Dirty(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAirtightChange(ref AirtightChanged args)
|
private void OnAirtightChange(ref AirtightChanged args)
|
||||||
@@ -137,15 +148,13 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh the affected tile
|
|
||||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(args.Position.Tile, ChunkSize);
|
var chunkOrigin = SharedMapSystem.GetChunkIndices(args.Position.Tile, ChunkSize);
|
||||||
|
var (newValue, chunk) = RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, args.Position.Tile, setFloor: false);
|
||||||
|
|
||||||
var chunk = RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, args.Position.Tile);
|
if (newValue == 0 && PruneEmpty((gridUid, navMap), chunk))
|
||||||
if (!PruneEmpty((gridUid, navMap), chunk))
|
return;
|
||||||
{
|
|
||||||
chunk.LastUpdate = _gameTiming.CurTick;
|
DirtyChunk((gridUid, navMap), chunk);
|
||||||
Dirty(gridUid, navMap);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -238,87 +247,63 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
|
|
||||||
var chunk = EnsureChunk(component, chunkOrigin);
|
var chunk = EnsureChunk(component, chunkOrigin);
|
||||||
chunk.LastUpdate = _gameTiming.CurTick;
|
chunk.LastUpdate = _gameTiming.CurTick;
|
||||||
|
RefreshTileEntityContents(uid, component, mapGrid, chunkOrigin, tile, setFloor: true);
|
||||||
// Refresh the floor tile
|
|
||||||
SetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
|
|
||||||
|
|
||||||
// Refresh the contents of the tile
|
|
||||||
RefreshTileEntityContents(uid, component, mapGrid, chunkOrigin, tile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Dirty(uid, component);
|
Dirty(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private NavMapChunk RefreshTileEntityContents(EntityUid uid, NavMapComponent component, MapGridComponent mapGrid, Vector2i chunkOrigin, Vector2i tile)
|
private (int NewVal, NavMapChunk Chunk) RefreshTileEntityContents(EntityUid uid,
|
||||||
|
NavMapComponent component,
|
||||||
|
MapGridComponent mapGrid,
|
||||||
|
Vector2i chunkOrigin,
|
||||||
|
Vector2i tile,
|
||||||
|
bool setFloor)
|
||||||
{
|
{
|
||||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||||
var flag = (ushort) GetFlag(relative);
|
|
||||||
var invFlag = (ushort) ~flag;
|
|
||||||
var chunk = EnsureChunk(component, chunkOrigin);
|
var chunk = EnsureChunk(component, chunkOrigin);
|
||||||
|
ref var tileData = ref chunk.TileData[GetTileIndex(relative)];
|
||||||
|
|
||||||
// Clear stale data from the tile across all entity associated chunks
|
// Clear all data except for floor bits
|
||||||
foreach (var category in EntityChunkTypes)
|
if (setFloor)
|
||||||
{
|
tileData = FloorMask;
|
||||||
var data = chunk.EnsureType(category);
|
else
|
||||||
|
tileData &= FloorMask;
|
||||||
|
|
||||||
foreach (var direction in data.Keys)
|
|
||||||
{
|
|
||||||
data[direction] &= invFlag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the tile data based on what entities are still anchored to the tile
|
|
||||||
var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(uid, mapGrid, tile);
|
var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(uid, mapGrid, tile);
|
||||||
|
|
||||||
while (enumerator.MoveNext(out var ent))
|
while (enumerator.MoveNext(out var ent))
|
||||||
{
|
{
|
||||||
if (!_airtightQuery.TryComp(ent, out var airtight))
|
if (!_airtightQuery.TryComp(ent, out var airtight))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var category = GetAssociatedEntityChunkType(ent.Value);
|
var category = GetEntityType(ent.Value);
|
||||||
var data = chunk.EnsureType(category);
|
if (category == NavMapChunkType.Invalid)
|
||||||
|
continue;
|
||||||
|
|
||||||
foreach (var direction in data.Keys)
|
var directions = (int)airtight.AirBlockedDirection;
|
||||||
{
|
tileData |= directions << (int) category;
|
||||||
if ((direction & airtight.AirBlockedDirection) > 0)
|
|
||||||
{
|
|
||||||
data[direction] |= flag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove walls that intersect with doors (unless they can both physically fit on the same tile)
|
// Remove walls that intersect with doors (unless they can both physically fit on the same tile)
|
||||||
var wallData = chunk.TileData[(int) NavMapChunkType.Wall];
|
// TODO NAVMAP why can this even happen?
|
||||||
var airlockData = chunk.TileData[(int) NavMapChunkType.Airlock];
|
// Is this for blast-doors or something?
|
||||||
|
|
||||||
if (wallData != null && airlockData != null)
|
// Shift airlock bits over to the wall bits
|
||||||
{
|
var shiftedAirlockBits = (tileData & AirlockMask) >> ((int) NavMapChunkType.Airlock - (int) NavMapChunkType.Wall);
|
||||||
foreach (var direction in wallData.Keys)
|
|
||||||
{
|
|
||||||
var airlockInvFlag = (ushort) ~airlockData[direction];
|
|
||||||
wallData[direction] &= airlockInvFlag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunk;
|
// And then mask door bits
|
||||||
|
tileData &= ~shiftedAirlockBits;
|
||||||
|
|
||||||
|
return (tileData, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool PruneEmpty(Entity<NavMapComponent> entity, NavMapChunk chunk)
|
private bool PruneEmpty(Entity<NavMapComponent> entity, NavMapChunk chunk)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < NavMapComponent.Categories; i++)
|
foreach (var val in chunk.TileData)
|
||||||
{
|
{
|
||||||
var data = chunk.TileData[i];
|
// TODO NAVMAP SIMD
|
||||||
|
if (val != 0)
|
||||||
if (data == null)
|
return false;
|
||||||
continue;
|
|
||||||
|
|
||||||
foreach (var value in data.Values)
|
|
||||||
{
|
|
||||||
if (value != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.Comp.Chunks.Remove(chunk.Origin);
|
entity.Comp.Chunks.Remove(chunk.Origin);
|
||||||
@@ -341,19 +326,12 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
if (!_navQuery.TryComp(xform.GridUid, out var navMap))
|
if (!_navQuery.TryComp(xform.GridUid, out var navMap))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var netEnt = GetNetEntity(uid);
|
var meta = MetaData(uid);
|
||||||
var oldBeacon = navMap.Beacons.FirstOrNull(x => x.NetEnt == netEnt);
|
var changed = navMap.Beacons.Remove(meta.NetEntity);
|
||||||
var changed = false;
|
|
||||||
|
|
||||||
if (oldBeacon != null)
|
if (TryCreateNavMapBeaconData(uid, component, xform, meta, out var beaconData))
|
||||||
{
|
{
|
||||||
navMap.Beacons.Remove(oldBeacon.Value);
|
navMap.Beacons.Add(meta.NetEntity, beaconData.Value);
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryCreateNavMapBeaconData(uid, component, xform, out var beaconData))
|
|
||||||
{
|
|
||||||
navMap.Beacons.Add(beaconData.Value);
|
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using Content.Server.GameTicking.Rules.Components;
|
|
||||||
using Content.Server.NodeContainer;
|
using Content.Server.NodeContainer;
|
||||||
using Content.Server.NodeContainer.EntitySystems;
|
using Content.Server.NodeContainer.EntitySystems;
|
||||||
using Content.Server.NodeContainer.NodeGroups;
|
|
||||||
using Content.Server.NodeContainer.Nodes;
|
using Content.Server.NodeContainer.Nodes;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.Power.Nodes;
|
using Content.Server.Power.Nodes;
|
||||||
@@ -13,10 +11,8 @@ using Content.Shared.Power;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Content.Server.GameTicking.Components;
|
using Content.Server.GameTicking.Components;
|
||||||
|
|
||||||
namespace Content.Server.Power.EntitySystems;
|
namespace Content.Server.Power.EntitySystems;
|
||||||
@@ -163,7 +159,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||||||
allChunks = new();
|
allChunks = new();
|
||||||
|
|
||||||
var tile = _sharedMapSystem.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates);
|
var tile = _sharedMapSystem.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates);
|
||||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, SharedNavMapSystem.ChunkSize);
|
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||||
|
|
||||||
if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
|
if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
|
||||||
{
|
{
|
||||||
@@ -171,8 +167,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||||||
allChunks[chunkOrigin] = chunk;
|
allChunks[chunkOrigin] = chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize);
|
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||||
var flag = SharedNavMapSystem.GetFlag(relative);
|
var flag = GetFlag(relative);
|
||||||
|
|
||||||
if (args.Anchored)
|
if (args.Anchored)
|
||||||
chunk.PowerCableData[(int) component.CableType] |= flag;
|
chunk.PowerCableData[(int) component.CableType] |= flag;
|
||||||
@@ -884,7 +880,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var tile = _sharedMapSystem.GetTileRef(gridUid, grid, entXform.Coordinates);
|
var tile = _sharedMapSystem.GetTileRef(gridUid, grid, entXform.Coordinates);
|
||||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, SharedNavMapSystem.ChunkSize);
|
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, ChunkSize);
|
||||||
|
|
||||||
if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
|
if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
|
||||||
{
|
{
|
||||||
@@ -892,8 +888,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||||||
allChunks[chunkOrigin] = chunk;
|
allChunks[chunkOrigin] = chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, SharedNavMapSystem.ChunkSize);
|
var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, ChunkSize);
|
||||||
var flag = SharedNavMapSystem.GetFlag(relative);
|
var flag = GetFlag(relative);
|
||||||
|
|
||||||
chunk.PowerCableData[(int) cable.CableType] |= flag;
|
chunk.PowerCableData[(int) cable.CableType] |= flag;
|
||||||
}
|
}
|
||||||
@@ -910,7 +906,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||||||
var xform = Transform(ent);
|
var xform = Transform(ent);
|
||||||
var tile = _sharedMapSystem.GetTileRef(gridUid, grid, xform.Coordinates);
|
var tile = _sharedMapSystem.GetTileRef(gridUid, grid, xform.Coordinates);
|
||||||
var gridIndices = tile.GridIndices;
|
var gridIndices = tile.GridIndices;
|
||||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(gridIndices, SharedNavMapSystem.ChunkSize);
|
var chunkOrigin = SharedMapSystem.GetChunkIndices(gridIndices, ChunkSize);
|
||||||
|
|
||||||
if (!component.FocusChunks.TryGetValue(chunkOrigin, out var chunk))
|
if (!component.FocusChunks.TryGetValue(chunkOrigin, out var chunk))
|
||||||
{
|
{
|
||||||
@@ -918,8 +914,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
|
|||||||
component.FocusChunks[chunkOrigin] = chunk;
|
component.FocusChunks[chunkOrigin] = chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
var relative = SharedMapSystem.GetChunkRelative(gridIndices, SharedNavMapSystem.ChunkSize);
|
var relative = SharedMapSystem.GetChunkRelative(gridIndices, ChunkSize);
|
||||||
var flag = SharedNavMapSystem.GetFlag(relative);
|
var flag = GetFlag(relative);
|
||||||
|
|
||||||
if (TryComp<CableComponent>(ent, out var cable))
|
if (TryComp<CableComponent>(ent, out var cable))
|
||||||
chunk.PowerCableData[(int) cable.CableType] |= flag;
|
chunk.PowerCableData[(int) cable.CableType] |= flag;
|
||||||
|
|||||||
@@ -104,15 +104,14 @@ namespace Content.Shared.Atmos
|
|||||||
{
|
{
|
||||||
return direction switch
|
return direction switch
|
||||||
{
|
{
|
||||||
AtmosDirection.East => Angle.FromDegrees(90),
|
AtmosDirection.South => Angle.Zero,
|
||||||
AtmosDirection.North => Angle.FromDegrees(180),
|
AtmosDirection.East => new Angle(MathHelper.PiOver2),
|
||||||
AtmosDirection.West => Angle.FromDegrees(270),
|
AtmosDirection.North => new Angle(Math.PI),
|
||||||
AtmosDirection.South => Angle.FromDegrees(0),
|
AtmosDirection.West => new Angle(-MathHelper.PiOver2),
|
||||||
|
AtmosDirection.NorthEast => new Angle(Math.PI*3/4),
|
||||||
AtmosDirection.NorthEast => Angle.FromDegrees(135),
|
AtmosDirection.NorthWest => new Angle(-Math.PI*3/4),
|
||||||
AtmosDirection.NorthWest => Angle.FromDegrees(205),
|
AtmosDirection.SouthWest => new Angle(-MathHelper.PiOver4),
|
||||||
AtmosDirection.SouthWest => Angle.FromDegrees(315),
|
AtmosDirection.SouthEast => new Angle(MathHelper.PiOver4),
|
||||||
AtmosDirection.SouthEast => Angle.FromDegrees(45),
|
|
||||||
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(direction), $"It was {direction}."),
|
_ => throw new ArgumentOutOfRangeException(nameof(direction), $"It was {direction}."),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ namespace Content.Shared.Pinpointer;
|
|||||||
[RegisterComponent, NetworkedComponent]
|
[RegisterComponent, NetworkedComponent]
|
||||||
public sealed partial class NavMapComponent : Component
|
public sealed partial class NavMapComponent : Component
|
||||||
{
|
{
|
||||||
public const int Categories = 4;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Don't need DataFields as this can be reconstructed
|
* Don't need DataFields as this can be reconstructed
|
||||||
*/
|
*/
|
||||||
@@ -28,62 +26,37 @@ public sealed partial class NavMapComponent : Component
|
|||||||
/// List of station beacons.
|
/// List of station beacons.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public HashSet<SharedNavMapSystem.NavMapBeacon> Beacons = new();
|
public Dictionary<NetEntity, SharedNavMapSystem.NavMapBeacon> Beacons = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class NavMapChunk
|
public sealed class NavMapChunk(Vector2i origin)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The chunk origin
|
/// The chunk origin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Vector2i Origin;
|
[ViewVariables]
|
||||||
|
public readonly Vector2i Origin = origin;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Array with each entry corresponding to a <see cref="NavMapChunkType"/>.
|
/// Array containing the chunk's data. The
|
||||||
/// Uses a 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 Dictionary<AtmosDirection, ushort>?[] TileData;
|
[ViewVariables]
|
||||||
|
public int[] TileData = new int[SharedNavMapSystem.ArraySize];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The last game tick that the chunk was updated
|
/// The last game tick that the chunk was updated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
public GameTick LastUpdate;
|
public GameTick LastUpdate;
|
||||||
|
|
||||||
public NavMapChunk(Vector2i origin)
|
|
||||||
{
|
|
||||||
Origin = origin;
|
|
||||||
TileData = new Dictionary<AtmosDirection, ushort>?[NavMapComponent.Categories];
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<AtmosDirection, ushort> EnsureType(NavMapChunkType chunkType)
|
|
||||||
{
|
|
||||||
var data = TileData[(int) chunkType];
|
|
||||||
|
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
data = new Dictionary<AtmosDirection, ushort>()
|
|
||||||
{
|
|
||||||
[AtmosDirection.North] = 0,
|
|
||||||
[AtmosDirection.East] = 0,
|
|
||||||
[AtmosDirection.South] = 0,
|
|
||||||
[AtmosDirection.West] = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
TileData[(int) chunkType] = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum NavMapChunkType : byte
|
public enum NavMapChunkType : byte
|
||||||
{
|
{
|
||||||
Invalid,
|
// Values represent bit shift offsets when retrieving data in the tile array.
|
||||||
Floor,
|
Invalid = byte.MaxValue,
|
||||||
Wall,
|
Floor = 0, // I believe floors have directional information for diagonal tiles?
|
||||||
Airlock,
|
Wall = SharedNavMapSystem.Directions,
|
||||||
// Update the categories const if you update this.
|
Airlock = 2 * SharedNavMapSystem.Directions,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Shared.Atmos;
|
using System.Runtime.CompilerServices;
|
||||||
using Content.Shared.Tag;
|
using Content.Shared.Tag;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
@@ -11,19 +11,22 @@ namespace Content.Shared.Pinpointer;
|
|||||||
|
|
||||||
public abstract class SharedNavMapSystem : EntitySystem
|
public abstract class SharedNavMapSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
public const int Categories = 3;
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
public const int Directions = 4; // Not directly tied to number of atmos directions
|
||||||
|
|
||||||
public const byte ChunkSize = 4;
|
public const int ChunkSize = 8;
|
||||||
|
public const int ArraySize = ChunkSize* ChunkSize;
|
||||||
|
|
||||||
public readonly NavMapChunkType[] EntityChunkTypes =
|
public const int AllDirMask = (1 << Directions) - 1;
|
||||||
{
|
public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock;
|
||||||
NavMapChunkType.Invalid,
|
public const int WallMask = AllDirMask << (int) NavMapChunkType.Wall;
|
||||||
NavMapChunkType.Wall,
|
public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor;
|
||||||
NavMapChunkType.Airlock,
|
|
||||||
};
|
[Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!;
|
||||||
|
[Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
private readonly string[] _wallTags = ["Wall", "Window"];
|
private readonly string[] _wallTags = ["Wall", "Window"];
|
||||||
|
private EntityQuery<NavMapDoorComponent> _doorQuery;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -31,112 +34,49 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
|
|
||||||
// Data handling events
|
// Data handling events
|
||||||
SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
|
SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
|
||||||
|
_doorQuery = GetEntityQuery<NavMapDoorComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int GetTileIndex(Vector2i relativeTile)
|
||||||
|
{
|
||||||
|
return relativeTile.X * ChunkSize + relativeTile.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts the chunk's tile into a bitflag for the slot.
|
/// Inverse of <see cref="GetTileIndex"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static int GetFlag(Vector2i relativeTile)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Vector2i GetTileFromIndex(int index)
|
||||||
{
|
{
|
||||||
return 1 << (relativeTile.X * ChunkSize + relativeTile.Y);
|
var x = index / ChunkSize;
|
||||||
}
|
var y = index % ChunkSize;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts the chunk's tile into a bitflag for the slot.
|
|
||||||
/// </summary>
|
|
||||||
public static Vector2i GetTile(int flag)
|
|
||||||
{
|
|
||||||
var value = Math.Log2(flag);
|
|
||||||
var x = (int) value / ChunkSize;
|
|
||||||
var y = (int) value % ChunkSize;
|
|
||||||
var result = new Vector2i(x, y);
|
|
||||||
|
|
||||||
DebugTools.Assert(GetFlag(result) == flag);
|
|
||||||
|
|
||||||
return new Vector2i(x, y);
|
return new Vector2i(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile, NavMapChunkType chunkType)
|
public NavMapChunkType GetEntityType(EntityUid uid)
|
||||||
{
|
{
|
||||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
if (_doorQuery.HasComp(uid))
|
||||||
var flag = (ushort) GetFlag(relative);
|
return NavMapChunkType.Airlock;
|
||||||
var data = chunk.EnsureType(chunkType);
|
|
||||||
|
|
||||||
foreach (var direction in data.Keys)
|
if (_tagSystem.HasAnyTag(uid, _wallTags))
|
||||||
{
|
return NavMapChunkType.Wall;
|
||||||
data[direction] |= flag;
|
|
||||||
}
|
return NavMapChunkType.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnsetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile, NavMapChunkType chunkType)
|
protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent xform, MetaDataComponent meta, [NotNullWhen(true)] out NavMapBeacon? beaconData)
|
||||||
{
|
|
||||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
|
||||||
var flag = (ushort) GetFlag(relative);
|
|
||||||
var invFlag = (ushort) ~flag;
|
|
||||||
|
|
||||||
var data = chunk.EnsureType(chunkType);
|
|
||||||
|
|
||||||
foreach (var direction in data.Keys)
|
|
||||||
{
|
|
||||||
data[direction] &= invFlag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ushort GetCombinedEdgesForChunk(Dictionary<AtmosDirection, ushort> tile)
|
|
||||||
{
|
|
||||||
ushort combined = 0;
|
|
||||||
|
|
||||||
foreach (var value in tile.Values)
|
|
||||||
{
|
|
||||||
combined |= value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AllTileEdgesAreOccupied(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
|
||||||
{
|
|
||||||
var flag = (ushort) GetFlag(tile);
|
|
||||||
|
|
||||||
foreach (var value in tileData.Values)
|
|
||||||
{
|
|
||||||
if ((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;
|
beaconData = null;
|
||||||
|
|
||||||
if (!component.Enabled || xform.GridUid == null || !xform.Anchored)
|
if (!component.Enabled || xform.GridUid == null || !xform.Anchored)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
string? name = component.Text;
|
var name = component.Text;
|
||||||
var meta = MetaData(uid);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(name))
|
if (string.IsNullOrEmpty(name))
|
||||||
name = meta.EntityName;
|
name = meta.EntityName;
|
||||||
|
|
||||||
beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition)
|
beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition);
|
||||||
{
|
|
||||||
LastUpdate = _gameTiming.CurTick
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -145,91 +85,36 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
|
|
||||||
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
|
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
|
||||||
{
|
{
|
||||||
var chunks = new Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]>();
|
Dictionary<Vector2i, int[]> chunks;
|
||||||
var beacons = new HashSet<NavMapBeacon>();
|
|
||||||
|
|
||||||
// Should this be a full component state or a delta-state?
|
// Should this be a full component state or a delta-state?
|
||||||
if (args.FromTick <= component.CreationTick)
|
if (args.FromTick <= component.CreationTick)
|
||||||
{
|
{
|
||||||
|
// Full state
|
||||||
|
chunks = new(component.Chunks.Count);
|
||||||
foreach (var (origin, chunk) in component.Chunks)
|
foreach (var (origin, chunk) in component.Chunks)
|
||||||
{
|
{
|
||||||
var sentChunk = new Dictionary<AtmosDirection, ushort>[NavMapComponent.Categories];
|
chunks.Add(origin, chunk.TileData);
|
||||||
chunks.Add(origin, sentChunk);
|
|
||||||
|
|
||||||
foreach (var value in Enum.GetValues<NavMapChunkType>())
|
|
||||||
{
|
|
||||||
ref var data = ref chunk.TileData[(int) value];
|
|
||||||
|
|
||||||
if (data == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var chunkDatum = new Dictionary<AtmosDirection, ushort>(data.Count);
|
|
||||||
|
|
||||||
foreach (var (direction, tileData) in data)
|
|
||||||
{
|
|
||||||
chunkDatum[direction] = tileData;
|
|
||||||
}
|
|
||||||
|
|
||||||
sentChunk[(int) value] = chunkDatum;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var beaconQuery = AllEntityQuery<NavMapBeaconComponent, TransformComponent>();
|
args.State = new NavMapComponentState(chunks, component.Beacons);
|
||||||
|
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chunks = new();
|
||||||
foreach (var (origin, chunk) in component.Chunks)
|
foreach (var (origin, chunk) in component.Chunks)
|
||||||
{
|
{
|
||||||
if (chunk.LastUpdate < args.FromTick)
|
if (chunk.LastUpdate < args.FromTick)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var sentChunk = new Dictionary<AtmosDirection, ushort>[NavMapComponent.Categories];
|
chunks.Add(origin, chunk.TileData);
|
||||||
chunks.Add(origin, sentChunk);
|
|
||||||
|
|
||||||
foreach (var value in Enum.GetValues<NavMapChunkType>())
|
|
||||||
{
|
|
||||||
ref var data = ref chunk.TileData[(int) value];
|
|
||||||
|
|
||||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
|
||||||
if (data == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var chunkDatum = new Dictionary<AtmosDirection, ushort>(data.Count);
|
|
||||||
|
|
||||||
foreach (var (direction, tileData) in data)
|
|
||||||
{
|
|
||||||
chunkDatum[direction] = tileData;
|
|
||||||
}
|
|
||||||
|
|
||||||
sentChunk[(int) value] = chunkDatum;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var beacon in component.Beacons)
|
args.State = new NavMapComponentState(chunks, component.Beacons)
|
||||||
{
|
|
||||||
if (beacon.LastUpdate < args.FromTick)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
beacons.Add(beacon);
|
|
||||||
}
|
|
||||||
|
|
||||||
args.State = new NavMapComponentState(chunks, beacons)
|
|
||||||
{
|
{
|
||||||
|
// TODO NAVMAP cache a single AllChunks hashset in the component.
|
||||||
|
// Or maybe just only send them if a chunk gets removed.
|
||||||
AllChunks = new(component.Chunks.Keys),
|
AllChunks = new(component.Chunks.Keys),
|
||||||
AllBeacons = new(component.Beacons)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,22 +123,18 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
#region: System messages
|
#region: System messages
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
protected sealed class NavMapComponentState : ComponentState, IComponentDeltaState
|
protected sealed class NavMapComponentState(
|
||||||
|
Dictionary<Vector2i, int[]> chunks,
|
||||||
|
Dictionary<NetEntity, NavMapBeacon> beacons)
|
||||||
|
: ComponentState, IComponentDeltaState
|
||||||
{
|
{
|
||||||
public Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]> Chunks = new();
|
public Dictionary<Vector2i, int[]> Chunks = chunks;
|
||||||
public HashSet<NavMapBeacon> Beacons = new();
|
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
|
||||||
|
|
||||||
// Required to infer deleted/missing chunks for delta states
|
// Required to infer deleted/missing chunks for delta states
|
||||||
public HashSet<Vector2i>? AllChunks;
|
public HashSet<Vector2i>? AllChunks;
|
||||||
public HashSet<NavMapBeacon>? AllBeacons;
|
|
||||||
|
|
||||||
public NavMapComponentState(Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]> chunks, HashSet<NavMapBeacon> beacons)
|
public bool FullState => AllChunks == null;
|
||||||
{
|
|
||||||
Chunks = chunks;
|
|
||||||
Beacons = beacons;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FullState => (AllChunks == null || AllBeacons == null);
|
|
||||||
|
|
||||||
public void ApplyToFullState(IComponentState fullState)
|
public void ApplyToFullState(IComponentState fullState)
|
||||||
{
|
{
|
||||||
@@ -261,32 +142,24 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
var state = (NavMapComponentState) fullState;
|
var state = (NavMapComponentState) fullState;
|
||||||
DebugTools.Assert(state.FullState);
|
DebugTools.Assert(state.FullState);
|
||||||
|
|
||||||
// Update chunks
|
|
||||||
foreach (var key in state.Chunks.Keys)
|
foreach (var key in state.Chunks.Keys)
|
||||||
{
|
{
|
||||||
if (!AllChunks!.Contains(key))
|
if (!AllChunks!.Contains(key))
|
||||||
state.Chunks.Remove(key);
|
state.Chunks.Remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (chunk, data) in Chunks)
|
foreach (var (index, data) in Chunks)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < NavMapComponent.Categories; i++)
|
if (!state.Chunks.TryGetValue(index, out var stateValue))
|
||||||
{
|
state.Chunks[index] = stateValue = new int[data.Length];
|
||||||
var chunkData = data[i];
|
|
||||||
state.Chunks[chunk][i] = chunkData == null ? chunkData : new(chunkData);
|
Array.Copy(data, stateValue, data.Length);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update beacons
|
state.Beacons.Clear();
|
||||||
foreach (var beacon in state.Beacons)
|
foreach (var (nuid, beacon) in Beacons)
|
||||||
{
|
{
|
||||||
if (!AllBeacons!.Contains(beacon))
|
state.Beacons.Add(nuid, beacon);
|
||||||
state.Beacons.Remove(beacon);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var beacon in Beacons)
|
|
||||||
{
|
|
||||||
state.Beacons.Add(beacon);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,56 +169,26 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
var state = (NavMapComponentState) fullState;
|
var state = (NavMapComponentState) fullState;
|
||||||
DebugTools.Assert(state.FullState);
|
DebugTools.Assert(state.FullState);
|
||||||
|
|
||||||
var chunks = new Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]>();
|
var chunks = new Dictionary<Vector2i, int[]>(state.Chunks.Count);
|
||||||
var beacons = new HashSet<NavMapBeacon>();
|
foreach (var (index, data) in state.Chunks)
|
||||||
|
|
||||||
foreach (var (chunk, data) in Chunks)
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < NavMapComponent.Categories; i++)
|
if (!AllChunks!.Contains(index))
|
||||||
{
|
continue;
|
||||||
var chunkData = data[i];
|
|
||||||
state.Chunks[chunk][i] = chunkData == null ? chunkData : new(chunkData);
|
var newData = chunks[index] = new int[ArraySize];
|
||||||
}
|
|
||||||
|
if (Chunks.TryGetValue(index, out var updatedData))
|
||||||
|
Array.Copy(newData, updatedData, ArraySize);
|
||||||
|
else
|
||||||
|
Array.Copy(newData, data, ArraySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (chunk, data) in state.Chunks)
|
return new NavMapComponentState(chunks, new(Beacons));
|
||||||
{
|
|
||||||
if (AllChunks!.Contains(chunk))
|
|
||||||
{
|
|
||||||
var copied = new Dictionary<AtmosDirection, ushort>?[NavMapComponent.Categories];
|
|
||||||
|
|
||||||
for (var i = 0; i < NavMapComponent.Categories; i++)
|
|
||||||
{
|
|
||||||
var chunkData = data[i];
|
|
||||||
copied[i] = chunkData == null ? chunkData : new(chunkData);
|
|
||||||
}
|
|
||||||
|
|
||||||
chunks.TryAdd(chunk, copied);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position)
|
public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position);
|
||||||
{
|
|
||||||
public GameTick LastUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Content.Shared.Power;
|
namespace Content.Shared.Power;
|
||||||
@@ -5,4 +6,23 @@ namespace Content.Shared.Power;
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public abstract class SharedPowerMonitoringConsoleSystem : EntitySystem
|
public abstract class SharedPowerMonitoringConsoleSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
// Chunk size is limited as we require ChunkSize^2 <= 32 (number of bits in an int)
|
||||||
|
public const int ChunkSize = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the chunk's tile into a bitflag for the slot.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int GetFlag(Vector2i relativeTile)
|
||||||
|
{
|
||||||
|
return 1 << (relativeTile.X * ChunkSize + relativeTile.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Vector2i GetTileFromIndex(int index)
|
||||||
|
{
|
||||||
|
var x = index / ChunkSize;
|
||||||
|
var y = index % ChunkSize;
|
||||||
|
return new Vector2i(x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -541,7 +541,15 @@ public sealed class TagSystem : EntitySystem
|
|||||||
/// </exception>
|
/// </exception>
|
||||||
public bool HasAnyTag(TagComponent component, params string[] ids)
|
public bool HasAnyTag(TagComponent component, params string[] ids)
|
||||||
{
|
{
|
||||||
return HasAnyTag(component, ids.AsEnumerable());
|
foreach (var id in ids)
|
||||||
|
{
|
||||||
|
AssertValidTag(id);
|
||||||
|
|
||||||
|
if (component.Tags.Contains(id))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user