Visualized regions for NavMapControl (#31910)
* Atmospheric alerts computer * Moved components, restricted access to them * Minor tweaks * The screen will now turn off when the computer is not powered * Bug fix * Adjusted label * Updated to latest master version * Initial commit * Tidy up * Add firelocks to the nav map * Add nav map regions to atmos alerts computer * Added support for multiple region overlay sets per grid * Fixed issue where console values were not updating correctly * Fixing merge conflict * Fixing merge conflicts * Finished all major features * Removed station map regions (to be re-added in a separate PR) * Improved clarity * Adjusted the color saturation of the regions displayed on the atmos alerts computer
This commit is contained in:
@@ -23,6 +23,7 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
|
|||||||
{
|
{
|
||||||
private readonly IEntityManager _entManager;
|
private readonly IEntityManager _entManager;
|
||||||
private readonly SpriteSystem _spriteSystem;
|
private readonly SpriteSystem _spriteSystem;
|
||||||
|
private readonly SharedNavMapSystem _navMapSystem;
|
||||||
|
|
||||||
private EntityUid? _owner;
|
private EntityUid? _owner;
|
||||||
private NetEntity? _trackedEntity;
|
private NetEntity? _trackedEntity;
|
||||||
@@ -47,6 +48,7 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
|
|||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||||
|
_navMapSystem = _entManager.System<SharedNavMapSystem>();
|
||||||
|
|
||||||
// Pass the owner to nav map
|
// Pass the owner to nav map
|
||||||
_owner = owner;
|
_owner = owner;
|
||||||
@@ -179,6 +181,9 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
|
|||||||
// Add tracked entities to the nav map
|
// Add tracked entities to the nav map
|
||||||
foreach (var device in console.AtmosDevices)
|
foreach (var device in console.AtmosDevices)
|
||||||
{
|
{
|
||||||
|
if (!device.NetEntity.Valid)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!NavMap.Visible)
|
if (!NavMap.Visible)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -270,6 +275,34 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
|
|||||||
else
|
else
|
||||||
MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount)));
|
MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount)));
|
||||||
|
|
||||||
|
// Update sensor regions
|
||||||
|
NavMap.RegionOverlays.Clear();
|
||||||
|
var prioritizedRegionOverlays = new Dictionary<NavMapRegionOverlay, int>();
|
||||||
|
|
||||||
|
if (_owner != null &&
|
||||||
|
_entManager.TryGetComponent<TransformComponent>(_owner, out var xform) &&
|
||||||
|
_entManager.TryGetComponent<NavMapComponent>(xform.GridUid, out var navMap))
|
||||||
|
{
|
||||||
|
var regionOverlays = _navMapSystem.GetNavMapRegionOverlays(_owner.Value, navMap, AtmosAlertsComputerUiKey.Key);
|
||||||
|
|
||||||
|
foreach (var (regionOwner, regionOverlay) in regionOverlays)
|
||||||
|
{
|
||||||
|
var alarmState = GetAlarmState(regionOwner);
|
||||||
|
|
||||||
|
if (!TryGetSensorRegionColor(regionOwner, alarmState, out var regionColor))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
regionOverlay.Color = regionColor.Value;
|
||||||
|
|
||||||
|
var priority = (_trackedEntity == regionOwner) ? 999 : (int)alarmState;
|
||||||
|
prioritizedRegionOverlays.Add(regionOverlay, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort overlays according to their priority
|
||||||
|
var sortedOverlays = prioritizedRegionOverlays.OrderBy(x => x.Value).Select(x => x.Key).ToList();
|
||||||
|
NavMap.RegionOverlays = sortedOverlays;
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-scroll re-enable
|
// Auto-scroll re-enable
|
||||||
if (_autoScrollAwaitsUpdate)
|
if (_autoScrollAwaitsUpdate)
|
||||||
{
|
{
|
||||||
@@ -298,6 +331,24 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
|
|||||||
NavMap.TrackedEntities[metaData.NetEntity] = blip;
|
NavMap.TrackedEntities[metaData.NetEntity] = blip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryGetSensorRegionColor(NetEntity regionOwner, AtmosAlarmType alarmState, [NotNullWhen(true)] out Color? color)
|
||||||
|
{
|
||||||
|
color = null;
|
||||||
|
|
||||||
|
var blip = GetBlipTexture(alarmState);
|
||||||
|
|
||||||
|
if (blip == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Color the region based on alarm state and entity tracking
|
||||||
|
color = blip.Value.Item2 * new Color(154, 154, 154);
|
||||||
|
|
||||||
|
if (_trackedEntity != null && _trackedEntity != regionOwner)
|
||||||
|
color *= Color.DimGray;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null)
|
private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null)
|
||||||
{
|
{
|
||||||
// Make new UI entry if required
|
// Make new UI entry if required
|
||||||
|
|||||||
303
Content.Client/Pinpointer/NavMapSystem.Regions.cs
Normal file
303
Content.Client/Pinpointer/NavMapSystem.Regions.cs
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
using Content.Shared.Atmos;
|
||||||
|
using Content.Shared.Pinpointer;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Content.Client.Pinpointer;
|
||||||
|
|
||||||
|
public sealed partial class NavMapSystem
|
||||||
|
{
|
||||||
|
private (AtmosDirection, Vector2i, AtmosDirection)[] _regionPropagationTable =
|
||||||
|
{
|
||||||
|
(AtmosDirection.East, new Vector2i(1, 0), AtmosDirection.West),
|
||||||
|
(AtmosDirection.West, new Vector2i(-1, 0), AtmosDirection.East),
|
||||||
|
(AtmosDirection.North, new Vector2i(0, 1), AtmosDirection.South),
|
||||||
|
(AtmosDirection.South, new Vector2i(0, -1), AtmosDirection.North),
|
||||||
|
};
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
// To prevent compute spikes, only one region is flood filled per frame
|
||||||
|
var query = AllEntityQuery<NavMapComponent>();
|
||||||
|
|
||||||
|
while (query.MoveNext(out var ent, out var entNavMapRegions))
|
||||||
|
FloodFillNextEnqueuedRegion(ent, entNavMapRegions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FloodFillNextEnqueuedRegion(EntityUid uid, NavMapComponent component)
|
||||||
|
{
|
||||||
|
if (!component.QueuedRegionsToFlood.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var regionOwner = component.QueuedRegionsToFlood.Dequeue();
|
||||||
|
|
||||||
|
// If the region is no longer valid, flood the next one in the queue
|
||||||
|
if (!component.RegionProperties.TryGetValue(regionOwner, out var regionProperties) ||
|
||||||
|
!regionProperties.Seeds.Any())
|
||||||
|
{
|
||||||
|
FloodFillNextEnqueuedRegion(uid, component);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flood fill the region, using the region seeds as starting points
|
||||||
|
var (floodedTiles, floodedChunks) = FloodFillRegion(uid, component, regionProperties);
|
||||||
|
|
||||||
|
// Combine the flooded tiles into larger rectangles
|
||||||
|
var gridCoords = GetMergedRegionTiles(floodedTiles);
|
||||||
|
|
||||||
|
// Create and assign the new region overlay
|
||||||
|
var regionOverlay = new NavMapRegionOverlay(regionProperties.UiKey, gridCoords)
|
||||||
|
{
|
||||||
|
Color = regionProperties.Color
|
||||||
|
};
|
||||||
|
|
||||||
|
component.RegionOverlays[regionOwner] = regionOverlay;
|
||||||
|
|
||||||
|
// To reduce unnecessary future flood fills, we will track which chunks have been flooded by a region owner
|
||||||
|
|
||||||
|
// First remove an old assignments
|
||||||
|
if (component.RegionOwnerToChunkTable.TryGetValue(regionOwner, out var oldChunks))
|
||||||
|
{
|
||||||
|
foreach (var chunk in oldChunks)
|
||||||
|
{
|
||||||
|
if (component.ChunkToRegionOwnerTable.TryGetValue(chunk, out var oldOwners))
|
||||||
|
{
|
||||||
|
oldOwners.Remove(regionOwner);
|
||||||
|
component.ChunkToRegionOwnerTable[chunk] = oldOwners;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now update with the new assignments
|
||||||
|
component.RegionOwnerToChunkTable[regionOwner] = floodedChunks;
|
||||||
|
|
||||||
|
foreach (var chunk in floodedChunks)
|
||||||
|
{
|
||||||
|
if (!component.ChunkToRegionOwnerTable.TryGetValue(chunk, out var owners))
|
||||||
|
owners = new();
|
||||||
|
|
||||||
|
owners.Add(regionOwner);
|
||||||
|
component.ChunkToRegionOwnerTable[chunk] = owners;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (HashSet<Vector2i>, HashSet<Vector2i>) FloodFillRegion(EntityUid uid, NavMapComponent component, NavMapRegionProperties regionProperties)
|
||||||
|
{
|
||||||
|
if (!regionProperties.Seeds.Any())
|
||||||
|
return (new(), new());
|
||||||
|
|
||||||
|
var visitedChunks = new HashSet<Vector2i>();
|
||||||
|
var visitedTiles = new HashSet<Vector2i>();
|
||||||
|
var tilesToVisit = new Stack<Vector2i>();
|
||||||
|
|
||||||
|
foreach (var regionSeed in regionProperties.Seeds)
|
||||||
|
{
|
||||||
|
tilesToVisit.Push(regionSeed);
|
||||||
|
|
||||||
|
while (tilesToVisit.Count > 0)
|
||||||
|
{
|
||||||
|
// If the max region area is hit, exit
|
||||||
|
if (visitedTiles.Count > regionProperties.MaxArea)
|
||||||
|
return (new(), new());
|
||||||
|
|
||||||
|
// Pop the top tile from the stack
|
||||||
|
var current = tilesToVisit.Pop();
|
||||||
|
|
||||||
|
// If the current tile position has already been visited,
|
||||||
|
// or is too far away from the seed, continue
|
||||||
|
if ((regionSeed - current).Length > regionProperties.MaxRadius)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (visitedTiles.Contains(current))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Determine the tile's chunk index
|
||||||
|
var chunkOrigin = SharedMapSystem.GetChunkIndices(current, ChunkSize);
|
||||||
|
var relative = SharedMapSystem.GetChunkRelative(current, ChunkSize);
|
||||||
|
var idx = GetTileIndex(relative);
|
||||||
|
|
||||||
|
// Extract the tile data
|
||||||
|
if (!component.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var flag = chunk.TileData[idx];
|
||||||
|
|
||||||
|
// If the current tile is entirely occupied, continue
|
||||||
|
if ((FloorMask & flag) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((WallMask & flag) == WallMask)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((AirlockMask & flag) == AirlockMask)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Otherwise the tile can be added to this region
|
||||||
|
visitedTiles.Add(current);
|
||||||
|
visitedChunks.Add(chunkOrigin);
|
||||||
|
|
||||||
|
// Determine if we can propagate the region into its cardinally adjacent neighbors
|
||||||
|
// To propagate to a neighbor, movement into the neighbors closest edge must not be
|
||||||
|
// blocked, and vice versa
|
||||||
|
|
||||||
|
foreach (var (direction, tileOffset, reverseDirection) in _regionPropagationTable)
|
||||||
|
{
|
||||||
|
if (!RegionCanPropagateInDirection(chunk, current, direction))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var neighbor = current + tileOffset;
|
||||||
|
var neighborOrigin = SharedMapSystem.GetChunkIndices(neighbor, ChunkSize);
|
||||||
|
|
||||||
|
if (!component.Chunks.TryGetValue(neighborOrigin, out var neighborChunk))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
visitedChunks.Add(neighborOrigin);
|
||||||
|
|
||||||
|
if (!RegionCanPropagateInDirection(neighborChunk, neighbor, reverseDirection))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tilesToVisit.Push(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (visitedTiles, visitedChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RegionCanPropagateInDirection(NavMapChunk chunk, Vector2i tile, AtmosDirection direction)
|
||||||
|
{
|
||||||
|
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||||
|
var idx = GetTileIndex(relative);
|
||||||
|
var flag = chunk.TileData[idx];
|
||||||
|
|
||||||
|
if ((FloorMask & flag) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var directionMask = 1 << (int)direction;
|
||||||
|
var wallMask = (int)direction << (int)NavMapChunkType.Wall;
|
||||||
|
var airlockMask = (int)direction << (int)NavMapChunkType.Airlock;
|
||||||
|
|
||||||
|
if ((wallMask & flag) > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((airlockMask & flag) > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<(Vector2i, Vector2i)> GetMergedRegionTiles(HashSet<Vector2i> tiles)
|
||||||
|
{
|
||||||
|
if (!tiles.Any())
|
||||||
|
return new();
|
||||||
|
|
||||||
|
var x = tiles.Select(t => t.X);
|
||||||
|
var minX = x.Min();
|
||||||
|
var maxX = x.Max();
|
||||||
|
|
||||||
|
var y = tiles.Select(t => t.Y);
|
||||||
|
var minY = y.Min();
|
||||||
|
var maxY = y.Max();
|
||||||
|
|
||||||
|
var matrix = new int[maxX - minX + 1, maxY - minY + 1];
|
||||||
|
|
||||||
|
foreach (var tile in tiles)
|
||||||
|
{
|
||||||
|
var a = tile.X - minX;
|
||||||
|
var b = tile.Y - minY;
|
||||||
|
|
||||||
|
matrix[a, b] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetMergedRegionTiles(matrix, new Vector2i(minX, minY));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<(Vector2i, Vector2i)> GetMergedRegionTiles(int[,] matrix, Vector2i offset)
|
||||||
|
{
|
||||||
|
var output = new List<(Vector2i, Vector2i)>();
|
||||||
|
|
||||||
|
var rows = matrix.GetLength(0);
|
||||||
|
var cols = matrix.GetLength(1);
|
||||||
|
|
||||||
|
var dp = new int[rows, cols];
|
||||||
|
var coords = (new Vector2i(), new Vector2i());
|
||||||
|
var maxArea = 0;
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
while (!IsArrayEmpty(matrix))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count > rows * cols)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Clear old values
|
||||||
|
dp = new int[rows, cols];
|
||||||
|
coords = (new Vector2i(), new Vector2i());
|
||||||
|
maxArea = 0;
|
||||||
|
|
||||||
|
// Initialize the first row of dp
|
||||||
|
for (int j = 0; j < cols; j++)
|
||||||
|
{
|
||||||
|
dp[0, j] = matrix[0, j];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate dp values for remaining rows
|
||||||
|
for (int i = 1; i < rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < cols; j++)
|
||||||
|
dp[i, j] = matrix[i, j] == 1 ? dp[i - 1, j] + 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the largest rectangular area seeded for each position in the matrix
|
||||||
|
for (int i = 0; i < rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < cols; j++)
|
||||||
|
{
|
||||||
|
int minWidth = dp[i, j];
|
||||||
|
|
||||||
|
for (int k = j; k >= 0; k--)
|
||||||
|
{
|
||||||
|
if (dp[i, k] <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
minWidth = Math.Min(minWidth, dp[i, k]);
|
||||||
|
var currArea = Math.Max(maxArea, minWidth * (j - k + 1));
|
||||||
|
|
||||||
|
if (currArea > maxArea)
|
||||||
|
{
|
||||||
|
maxArea = currArea;
|
||||||
|
coords = (new Vector2i(i - minWidth + 1, k), new Vector2i(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the recorded rectangle vertices
|
||||||
|
output.Add((coords.Item1 + offset, coords.Item2 + offset));
|
||||||
|
|
||||||
|
// Removed the tiles covered by the rectangle from matrix
|
||||||
|
for (int i = coords.Item1.X; i <= coords.Item2.X; i++)
|
||||||
|
{
|
||||||
|
for (int j = coords.Item1.Y; j <= coords.Item2.Y; j++)
|
||||||
|
matrix[i, j] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsArrayEmpty(int[,] matrix)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < matrix.GetLength(0); i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix.GetLength(1); j++)
|
||||||
|
{
|
||||||
|
if (matrix[i, j] == 1)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Linq;
|
||||||
using Content.Shared.Pinpointer;
|
using Content.Shared.Pinpointer;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
{
|
{
|
||||||
Dictionary<Vector2i, int[]> modifiedChunks;
|
Dictionary<Vector2i, int[]> modifiedChunks;
|
||||||
Dictionary<NetEntity, NavMapBeacon> beacons;
|
Dictionary<NetEntity, NavMapBeacon> beacons;
|
||||||
|
Dictionary<NetEntity, NavMapRegionProperties> regions;
|
||||||
|
|
||||||
switch (args.Current)
|
switch (args.Current)
|
||||||
{
|
{
|
||||||
@@ -23,6 +25,8 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
{
|
{
|
||||||
modifiedChunks = delta.ModifiedChunks;
|
modifiedChunks = delta.ModifiedChunks;
|
||||||
beacons = delta.Beacons;
|
beacons = delta.Beacons;
|
||||||
|
regions = delta.Regions;
|
||||||
|
|
||||||
foreach (var index in component.Chunks.Keys)
|
foreach (var index in component.Chunks.Keys)
|
||||||
{
|
{
|
||||||
if (!delta.AllChunks!.Contains(index))
|
if (!delta.AllChunks!.Contains(index))
|
||||||
@@ -35,6 +39,8 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
{
|
{
|
||||||
modifiedChunks = state.Chunks;
|
modifiedChunks = state.Chunks;
|
||||||
beacons = state.Beacons;
|
beacons = state.Beacons;
|
||||||
|
regions = state.Regions;
|
||||||
|
|
||||||
foreach (var index in component.Chunks.Keys)
|
foreach (var index in component.Chunks.Keys)
|
||||||
{
|
{
|
||||||
if (!state.Chunks.ContainsKey(index))
|
if (!state.Chunks.ContainsKey(index))
|
||||||
@@ -47,13 +53,54 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update region data and queue new regions for flooding
|
||||||
|
var prevRegionOwners = component.RegionProperties.Keys.ToList();
|
||||||
|
var validRegionOwners = new List<NetEntity>();
|
||||||
|
|
||||||
|
component.RegionProperties.Clear();
|
||||||
|
|
||||||
|
foreach (var (regionOwner, regionData) in regions)
|
||||||
|
{
|
||||||
|
if (!regionData.Seeds.Any())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
component.RegionProperties[regionOwner] = regionData;
|
||||||
|
validRegionOwners.Add(regionOwner);
|
||||||
|
|
||||||
|
if (component.RegionOverlays.ContainsKey(regionOwner))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (component.QueuedRegionsToFlood.Contains(regionOwner))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
component.QueuedRegionsToFlood.Enqueue(regionOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stale region owners
|
||||||
|
var regionOwnersToRemove = prevRegionOwners.Except(validRegionOwners);
|
||||||
|
|
||||||
|
foreach (var regionOwnerRemoved in regionOwnersToRemove)
|
||||||
|
RemoveNavMapRegion(uid, component, regionOwnerRemoved);
|
||||||
|
|
||||||
|
// Modify chunks
|
||||||
foreach (var (origin, chunk) in modifiedChunks)
|
foreach (var (origin, chunk) in modifiedChunks)
|
||||||
{
|
{
|
||||||
var newChunk = new NavMapChunk(origin);
|
var newChunk = new NavMapChunk(origin);
|
||||||
Array.Copy(chunk, newChunk.TileData, chunk.Length);
|
Array.Copy(chunk, newChunk.TileData, chunk.Length);
|
||||||
component.Chunks[origin] = newChunk;
|
component.Chunks[origin] = newChunk;
|
||||||
|
|
||||||
|
// If the affected chunk intersects one or more regions, re-flood them
|
||||||
|
if (!component.ChunkToRegionOwnerTable.TryGetValue(origin, out var affectedOwners))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var affectedOwner in affectedOwners)
|
||||||
|
{
|
||||||
|
if (!component.QueuedRegionsToFlood.Contains(affectedOwner))
|
||||||
|
component.QueuedRegionsToFlood.Enqueue(affectedOwner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh beacons
|
||||||
component.Beacons.Clear();
|
component.Beacons.Clear();
|
||||||
foreach (var (nuid, beacon) in beacons)
|
foreach (var (nuid, beacon) in beacons)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public partial class NavMapControl : MapGridControl
|
|||||||
public List<(Vector2, Vector2)> TileLines = new();
|
public List<(Vector2, Vector2)> TileLines = new();
|
||||||
public List<(Vector2, Vector2)> TileRects = new();
|
public List<(Vector2, Vector2)> TileRects = new();
|
||||||
public List<(Vector2[], Color)> TilePolygons = new();
|
public List<(Vector2[], Color)> TilePolygons = new();
|
||||||
|
public List<NavMapRegionOverlay> RegionOverlays = new();
|
||||||
|
|
||||||
// Default colors
|
// Default colors
|
||||||
public Color WallColor = new(102, 217, 102);
|
public Color WallColor = new(102, 217, 102);
|
||||||
@@ -228,7 +229,7 @@ public partial class NavMapControl : MapGridControl
|
|||||||
{
|
{
|
||||||
if (!blip.Selectable)
|
if (!blip.Selectable)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length();
|
var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length();
|
||||||
|
|
||||||
if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance)
|
if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance)
|
||||||
@@ -319,6 +320,22 @@ public partial class NavMapControl : MapGridControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw region overlays
|
||||||
|
if (_grid != null)
|
||||||
|
{
|
||||||
|
foreach (var regionOverlay in RegionOverlays)
|
||||||
|
{
|
||||||
|
foreach (var gridCoords in regionOverlay.GridCoords)
|
||||||
|
{
|
||||||
|
var positionTopLeft = ScalePosition(new Vector2(gridCoords.Item1.X, -gridCoords.Item1.Y) - new Vector2(offset.X, -offset.Y));
|
||||||
|
var positionBottomRight = ScalePosition(new Vector2(gridCoords.Item2.X + _grid.TileSize, -gridCoords.Item2.Y - _grid.TileSize) - new Vector2(offset.X, -offset.Y));
|
||||||
|
|
||||||
|
var box = new UIBox2(positionTopLeft, positionBottomRight);
|
||||||
|
handle.DrawRect(box, regionOverlay.Color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw map lines
|
// Draw map lines
|
||||||
if (TileLines.Any())
|
if (TileLines.Any())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
using Content.Server.Atmos.Monitor.Components;
|
using Content.Server.Atmos.Monitor.Components;
|
||||||
using Content.Server.DeviceNetwork.Components;
|
using Content.Server.DeviceNetwork.Components;
|
||||||
|
using Content.Server.DeviceNetwork.Systems;
|
||||||
|
using Content.Server.Pinpointer;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Atmos.Components;
|
using Content.Shared.Atmos.Components;
|
||||||
using Content.Shared.Atmos.Consoles;
|
using Content.Shared.Atmos.Consoles;
|
||||||
using Content.Shared.Atmos.Monitor;
|
using Content.Shared.Atmos.Monitor;
|
||||||
using Content.Shared.Atmos.Monitor.Components;
|
using Content.Shared.Atmos.Monitor.Components;
|
||||||
|
using Content.Shared.DeviceNetwork.Components;
|
||||||
using Content.Shared.Pinpointer;
|
using Content.Shared.Pinpointer;
|
||||||
|
using Content.Shared.Tag;
|
||||||
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.Timing;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -21,6 +25,12 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
|||||||
[Dependency] private readonly AirAlarmSystem _airAlarmSystem = default!;
|
[Dependency] private readonly AirAlarmSystem _airAlarmSystem = default!;
|
||||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||||
|
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||||
|
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||||
|
[Dependency] private readonly NavMapSystem _navMapSystem = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly DeviceListSystem _deviceListSystem = default!;
|
||||||
|
|
||||||
private const float UpdateTime = 1.0f;
|
private const float UpdateTime = 1.0f;
|
||||||
|
|
||||||
@@ -38,6 +48,9 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
|||||||
|
|
||||||
// Grid events
|
// Grid events
|
||||||
SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
|
SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
|
||||||
|
|
||||||
|
// Alarm events
|
||||||
|
SubscribeLocalEvent<AtmosAlertsDeviceComponent, EntityTerminatingEvent>(OnDeviceTerminatingEvent);
|
||||||
SubscribeLocalEvent<AtmosAlertsDeviceComponent, AnchorStateChangedEvent>(OnDeviceAnchorChanged);
|
SubscribeLocalEvent<AtmosAlertsDeviceComponent, AnchorStateChangedEvent>(OnDeviceAnchorChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +94,16 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void OnDeviceAnchorChanged(EntityUid uid, AtmosAlertsDeviceComponent component, AnchorStateChangedEvent args)
|
private void OnDeviceAnchorChanged(EntityUid uid, AtmosAlertsDeviceComponent component, AnchorStateChangedEvent args)
|
||||||
|
{
|
||||||
|
OnDeviceAdditionOrRemoval(uid, component, args.Anchored);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDeviceTerminatingEvent(EntityUid uid, AtmosAlertsDeviceComponent component, ref EntityTerminatingEvent args)
|
||||||
|
{
|
||||||
|
OnDeviceAdditionOrRemoval(uid, component, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDeviceAdditionOrRemoval(EntityUid uid, AtmosAlertsDeviceComponent component, bool isAdding)
|
||||||
{
|
{
|
||||||
var xform = Transform(uid);
|
var xform = Transform(uid);
|
||||||
var gridUid = xform.GridUid;
|
var gridUid = xform.GridUid;
|
||||||
@@ -88,10 +111,13 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
|||||||
if (gridUid == null)
|
if (gridUid == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!TryGetAtmosDeviceNavMapData(uid, component, xform, gridUid.Value, out var data))
|
if (!TryComp<NavMapComponent>(xform.GridUid, out var navMap))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var netEntity = EntityManager.GetNetEntity(uid);
|
if (!TryGetAtmosDeviceNavMapData(uid, component, xform, out var data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var netEntity = GetNetEntity(uid);
|
||||||
|
|
||||||
var query = AllEntityQuery<AtmosAlertsComputerComponent, TransformComponent>();
|
var query = AllEntityQuery<AtmosAlertsComputerComponent, TransformComponent>();
|
||||||
while (query.MoveNext(out var ent, out var entConsole, out var entXform))
|
while (query.MoveNext(out var ent, out var entConsole, out var entXform))
|
||||||
@@ -99,11 +125,18 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
|||||||
if (gridUid != entXform.GridUid)
|
if (gridUid != entXform.GridUid)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (args.Anchored)
|
if (isAdding)
|
||||||
|
{
|
||||||
entConsole.AtmosDevices.Add(data.Value);
|
entConsole.AtmosDevices.Add(data.Value);
|
||||||
|
}
|
||||||
|
|
||||||
else if (!args.Anchored)
|
else
|
||||||
|
{
|
||||||
entConsole.AtmosDevices.RemoveWhere(x => x.NetEntity == netEntity);
|
entConsole.AtmosDevices.RemoveWhere(x => x.NetEntity == netEntity);
|
||||||
|
_navMapSystem.RemoveNavMapRegion(gridUid.Value, navMap, netEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dirty(ent, entConsole);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +242,12 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
|||||||
if (entDevice.Group != group)
|
if (entDevice.Group != group)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (!TryComp<MapGridComponent>(entXform.GridUid, out var mapGrid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!TryComp<NavMapComponent>(entXform.GridUid, out var navMap))
|
||||||
|
continue;
|
||||||
|
|
||||||
// If emagged, change the alarm type to normal
|
// If emagged, change the alarm type to normal
|
||||||
var alarmState = (entAtmosAlarmable.LastAlarmState == AtmosAlarmType.Emagged) ? AtmosAlarmType.Normal : entAtmosAlarmable.LastAlarmState;
|
var alarmState = (entAtmosAlarmable.LastAlarmState == AtmosAlarmType.Emagged) ? AtmosAlarmType.Normal : entAtmosAlarmable.LastAlarmState;
|
||||||
|
|
||||||
@@ -216,14 +255,45 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
|||||||
if (TryComp<ApcPowerReceiverComponent>(ent, out var entAPCPower) && !entAPCPower.Powered)
|
if (TryComp<ApcPowerReceiverComponent>(ent, out var entAPCPower) && !entAPCPower.Powered)
|
||||||
alarmState = AtmosAlarmType.Invalid;
|
alarmState = AtmosAlarmType.Invalid;
|
||||||
|
|
||||||
|
// Create entry
|
||||||
|
var netEnt = GetNetEntity(ent);
|
||||||
|
|
||||||
var entry = new AtmosAlertsComputerEntry
|
var entry = new AtmosAlertsComputerEntry
|
||||||
(GetNetEntity(ent),
|
(netEnt,
|
||||||
GetNetCoordinates(entXform.Coordinates),
|
GetNetCoordinates(entXform.Coordinates),
|
||||||
entDevice.Group,
|
entDevice.Group,
|
||||||
alarmState,
|
alarmState,
|
||||||
MetaData(ent).EntityName,
|
MetaData(ent).EntityName,
|
||||||
entDeviceNetwork.Address);
|
entDeviceNetwork.Address);
|
||||||
|
|
||||||
|
// Get the list of sensors attached to the alarm
|
||||||
|
var sensorList = TryComp<DeviceListComponent>(ent, out var entDeviceList) ? _deviceListSystem.GetDeviceList(ent, entDeviceList) : null;
|
||||||
|
|
||||||
|
if (sensorList?.Any() == true)
|
||||||
|
{
|
||||||
|
var alarmRegionSeeds = new HashSet<Vector2i>();
|
||||||
|
|
||||||
|
// If valid and anchored, use the position of sensors as seeds for the region
|
||||||
|
foreach (var (address, sensorEnt) in sensorList)
|
||||||
|
{
|
||||||
|
if (!sensorEnt.IsValid() || !HasComp<AtmosMonitorComponent>(sensorEnt))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var sensorXform = Transform(sensorEnt);
|
||||||
|
|
||||||
|
if (sensorXform.Anchored && sensorXform.GridUid == entXform.GridUid)
|
||||||
|
alarmRegionSeeds.Add(_mapSystem.CoordinatesToTile(entXform.GridUid.Value, mapGrid, _transformSystem.GetMapCoordinates(sensorEnt, sensorXform)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var regionProperties = new SharedNavMapSystem.NavMapRegionProperties(netEnt, AtmosAlertsComputerUiKey.Key, alarmRegionSeeds);
|
||||||
|
_navMapSystem.AddOrUpdateNavMapRegion(gridUid, navMap, netEnt, regionProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_navMapSystem.RemoveNavMapRegion(entXform.GridUid.Value, navMap, netEnt);
|
||||||
|
}
|
||||||
|
|
||||||
alarmStateData.Add(entry);
|
alarmStateData.Add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +376,10 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
|||||||
var query = AllEntityQuery<AtmosAlertsDeviceComponent, TransformComponent>();
|
var query = AllEntityQuery<AtmosAlertsDeviceComponent, TransformComponent>();
|
||||||
while (query.MoveNext(out var ent, out var entComponent, out var entXform))
|
while (query.MoveNext(out var ent, out var entComponent, out var entXform))
|
||||||
{
|
{
|
||||||
if (TryGetAtmosDeviceNavMapData(ent, entComponent, entXform, gridUid, out var data))
|
if (entXform.GridUid != gridUid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (TryGetAtmosDeviceNavMapData(ent, entComponent, entXform, out var data))
|
||||||
atmosDeviceNavMapData.Add(data.Value);
|
atmosDeviceNavMapData.Add(data.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,14 +390,10 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
|||||||
(EntityUid uid,
|
(EntityUid uid,
|
||||||
AtmosAlertsDeviceComponent component,
|
AtmosAlertsDeviceComponent component,
|
||||||
TransformComponent xform,
|
TransformComponent xform,
|
||||||
EntityUid gridUid,
|
|
||||||
[NotNullWhen(true)] out AtmosAlertsDeviceNavMapData? output)
|
[NotNullWhen(true)] out AtmosAlertsDeviceNavMapData? output)
|
||||||
{
|
{
|
||||||
output = null;
|
output = null;
|
||||||
|
|
||||||
if (xform.GridUid != gridUid)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!xform.Anchored)
|
if (!xform.Anchored)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,50 @@ public sealed partial class NavMapComponent : Component
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public Dictionary<NetEntity, SharedNavMapSystem.NavMapBeacon> Beacons = new();
|
public Dictionary<NetEntity, SharedNavMapSystem.NavMapBeacon> Beacons = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes the properties of a region on the station.
|
||||||
|
/// It is indexed by the entity assigned as the region owner.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public Dictionary<NetEntity, SharedNavMapSystem.NavMapRegionProperties> RegionProperties = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All flood filled regions, ready for display on a NavMapControl.
|
||||||
|
/// It is indexed by the entity assigned as the region owner.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For client use only
|
||||||
|
/// </remarks>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public Dictionary<NetEntity, NavMapRegionOverlay> RegionOverlays = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A queue of all region owners that are waiting their associated regions to be floodfilled.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For client use only
|
||||||
|
/// </remarks>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public Queue<NetEntity> QueuedRegionsToFlood = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A look up table to get a list of region owners associated with a flood filled chunk.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For client use only
|
||||||
|
/// </remarks>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public Dictionary<Vector2i, HashSet<NetEntity>> ChunkToRegionOwnerTable = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A look up table to find flood filled chunks associated with a given region owner.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For client use only
|
||||||
|
/// </remarks>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public Dictionary<NetEntity, HashSet<Vector2i>> RegionOwnerToChunkTable = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
@@ -51,10 +95,30 @@ public sealed class NavMapChunk(Vector2i origin)
|
|||||||
public GameTick LastUpdate;
|
public GameTick LastUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class NavMapRegionOverlay(Enum uiKey, List<(Vector2i, Vector2i)> gridCoords)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The key to the UI that will be displaying this region on its navmap
|
||||||
|
/// </summary>
|
||||||
|
public Enum UiKey = uiKey;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The local grid coordinates of the rectangles that make up the region
|
||||||
|
/// Item1 is the top left corner, Item2 is the bottom right corner
|
||||||
|
/// </summary>
|
||||||
|
public List<(Vector2i, Vector2i)> GridCoords = gridCoords;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Color of the region
|
||||||
|
/// </summary>
|
||||||
|
public Color Color = Color.White;
|
||||||
|
}
|
||||||
|
|
||||||
public enum NavMapChunkType : byte
|
public enum NavMapChunkType : byte
|
||||||
{
|
{
|
||||||
// Values represent bit shift offsets when retrieving data in the tile array.
|
// Values represent bit shift offsets when retrieving data in the tile array.
|
||||||
Invalid = byte.MaxValue,
|
Invalid = byte.MaxValue,
|
||||||
Floor = 0, // I believe floors have directional information for diagonal tiles?
|
Floor = 0, // I believe floors have directional information for diagonal tiles?
|
||||||
Wall = SharedNavMapSystem.Directions,
|
Wall = SharedNavMapSystem.Directions,
|
||||||
Airlock = 2 * SharedNavMapSystem.Directions,
|
Airlock = 2 * SharedNavMapSystem.Directions,
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ using System.Numerics;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Content.Shared.Tag;
|
using Content.Shared.Tag;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Timing;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
|
|
||||||
namespace Content.Shared.Pinpointer;
|
namespace Content.Shared.Pinpointer;
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
public const int Directions = 4; // Not directly tied to number of atmos directions
|
public const int Directions = 4; // Not directly tied to number of atmos directions
|
||||||
|
|
||||||
public const int ChunkSize = 8;
|
public const int ChunkSize = 8;
|
||||||
public const int ArraySize = ChunkSize* ChunkSize;
|
public const int ArraySize = ChunkSize * ChunkSize;
|
||||||
|
|
||||||
public const int AllDirMask = (1 << Directions) - 1;
|
public const int AllDirMask = (1 << Directions) - 1;
|
||||||
public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock;
|
public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock;
|
||||||
@@ -24,6 +23,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor;
|
public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor;
|
||||||
|
|
||||||
[Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!;
|
[Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!;
|
||||||
|
[Robust.Shared.IoC.Dependency] private readonly INetManager _net = default!;
|
||||||
|
|
||||||
private static readonly ProtoId<TagPrototype>[] WallTags = {"Wall", "Window"};
|
private static readonly ProtoId<TagPrototype>[] WallTags = {"Wall", "Window"};
|
||||||
private EntityQuery<NavMapDoorComponent> _doorQuery;
|
private EntityQuery<NavMapDoorComponent> _doorQuery;
|
||||||
@@ -57,7 +57,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
public NavMapChunkType GetEntityType(EntityUid uid)
|
public NavMapChunkType GetEntityType(EntityUid uid)
|
||||||
{
|
{
|
||||||
if (_doorQuery.HasComp(uid))
|
if (_doorQuery.HasComp(uid))
|
||||||
return NavMapChunkType.Airlock;
|
return NavMapChunkType.Airlock;
|
||||||
|
|
||||||
if (_tagSystem.HasAnyTag(uid, WallTags))
|
if (_tagSystem.HasAnyTag(uid, WallTags))
|
||||||
return NavMapChunkType.Wall;
|
return NavMapChunkType.Wall;
|
||||||
@@ -81,6 +81,57 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdateNavMapRegion(EntityUid uid, NavMapComponent component, NetEntity regionOwner, NavMapRegionProperties regionProperties)
|
||||||
|
{
|
||||||
|
// Check if a new region has been added or an existing one has been altered
|
||||||
|
var isDirty = !component.RegionProperties.TryGetValue(regionOwner, out var oldProperties) || oldProperties != regionProperties;
|
||||||
|
|
||||||
|
if (isDirty)
|
||||||
|
{
|
||||||
|
component.RegionProperties[regionOwner] = regionProperties;
|
||||||
|
|
||||||
|
if (_net.IsServer)
|
||||||
|
Dirty(uid, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveNavMapRegion(EntityUid uid, NavMapComponent component, NetEntity regionOwner)
|
||||||
|
{
|
||||||
|
bool regionOwnerRemoved = component.RegionProperties.Remove(regionOwner) | component.RegionOverlays.Remove(regionOwner);
|
||||||
|
|
||||||
|
if (regionOwnerRemoved)
|
||||||
|
{
|
||||||
|
if (component.RegionOwnerToChunkTable.TryGetValue(regionOwner, out var affectedChunks))
|
||||||
|
{
|
||||||
|
foreach (var affectedChunk in affectedChunks)
|
||||||
|
{
|
||||||
|
if (component.ChunkToRegionOwnerTable.TryGetValue(affectedChunk, out var regionOwners))
|
||||||
|
regionOwners.Remove(regionOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
component.RegionOwnerToChunkTable.Remove(regionOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_net.IsServer)
|
||||||
|
Dirty(uid, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<NetEntity, NavMapRegionOverlay> GetNavMapRegionOverlays(EntityUid uid, NavMapComponent component, Enum uiKey)
|
||||||
|
{
|
||||||
|
var regionOverlays = new Dictionary<NetEntity, NavMapRegionOverlay>();
|
||||||
|
|
||||||
|
foreach (var (regionOwner, regionOverlay) in component.RegionOverlays)
|
||||||
|
{
|
||||||
|
if (!regionOverlay.UiKey.Equals(uiKey))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
regionOverlays.Add(regionOwner, regionOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return regionOverlays;
|
||||||
|
}
|
||||||
|
|
||||||
#region: Event handling
|
#region: Event handling
|
||||||
|
|
||||||
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
|
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
|
||||||
@@ -97,7 +148,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
chunks.Add(origin, chunk.TileData);
|
chunks.Add(origin, chunk.TileData);
|
||||||
}
|
}
|
||||||
|
|
||||||
args.State = new NavMapState(chunks, component.Beacons);
|
args.State = new NavMapState(chunks, component.Beacons, component.RegionProperties);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +161,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
chunks.Add(origin, chunk.TileData);
|
chunks.Add(origin, chunk.TileData);
|
||||||
}
|
}
|
||||||
|
|
||||||
args.State = new NavMapDeltaState(chunks, component.Beacons, new(component.Chunks.Keys));
|
args.State = new NavMapDeltaState(chunks, component.Beacons, component.RegionProperties, new(component.Chunks.Keys));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -120,22 +171,26 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
protected sealed class NavMapState(
|
protected sealed class NavMapState(
|
||||||
Dictionary<Vector2i, int[]> chunks,
|
Dictionary<Vector2i, int[]> chunks,
|
||||||
Dictionary<NetEntity, NavMapBeacon> beacons)
|
Dictionary<NetEntity, NavMapBeacon> beacons,
|
||||||
|
Dictionary<NetEntity, NavMapRegionProperties> regions)
|
||||||
: ComponentState
|
: ComponentState
|
||||||
{
|
{
|
||||||
public Dictionary<Vector2i, int[]> Chunks = chunks;
|
public Dictionary<Vector2i, int[]> Chunks = chunks;
|
||||||
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
|
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
|
||||||
|
public Dictionary<NetEntity, NavMapRegionProperties> Regions = regions;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
protected sealed class NavMapDeltaState(
|
protected sealed class NavMapDeltaState(
|
||||||
Dictionary<Vector2i, int[]> modifiedChunks,
|
Dictionary<Vector2i, int[]> modifiedChunks,
|
||||||
Dictionary<NetEntity, NavMapBeacon> beacons,
|
Dictionary<NetEntity, NavMapBeacon> beacons,
|
||||||
|
Dictionary<NetEntity, NavMapRegionProperties> regions,
|
||||||
HashSet<Vector2i> allChunks)
|
HashSet<Vector2i> allChunks)
|
||||||
: ComponentState, IComponentDeltaState<NavMapState>
|
: ComponentState, IComponentDeltaState<NavMapState>
|
||||||
{
|
{
|
||||||
public Dictionary<Vector2i, int[]> ModifiedChunks = modifiedChunks;
|
public Dictionary<Vector2i, int[]> ModifiedChunks = modifiedChunks;
|
||||||
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
|
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
|
||||||
|
public Dictionary<NetEntity, NavMapRegionProperties> Regions = regions;
|
||||||
public HashSet<Vector2i> AllChunks = allChunks;
|
public HashSet<Vector2i> AllChunks = allChunks;
|
||||||
|
|
||||||
public void ApplyToFullState(NavMapState state)
|
public void ApplyToFullState(NavMapState state)
|
||||||
@@ -159,11 +214,18 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
state.Beacons.Add(nuid, beacon);
|
state.Beacons.Add(nuid, beacon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.Regions.Clear();
|
||||||
|
foreach (var (nuid, region) in Regions)
|
||||||
|
{
|
||||||
|
state.Regions.Add(nuid, region);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public NavMapState CreateNewFullState(NavMapState state)
|
public NavMapState CreateNewFullState(NavMapState state)
|
||||||
{
|
{
|
||||||
var chunks = new Dictionary<Vector2i, int[]>(state.Chunks.Count);
|
var chunks = new Dictionary<Vector2i, int[]>(state.Chunks.Count);
|
||||||
|
|
||||||
foreach (var (index, data) in state.Chunks)
|
foreach (var (index, data) in state.Chunks)
|
||||||
{
|
{
|
||||||
if (!AllChunks!.Contains(index))
|
if (!AllChunks!.Contains(index))
|
||||||
@@ -177,12 +239,25 @@ public abstract class SharedNavMapSystem : EntitySystem
|
|||||||
Array.Copy(newData, data, ArraySize);
|
Array.Copy(newData, data, ArraySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new NavMapState(chunks, new(Beacons));
|
return new NavMapState(chunks, new(Beacons), new(Regions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[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);
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public record struct NavMapRegionProperties(NetEntity Owner, Enum UiKey, HashSet<Vector2i> Seeds)
|
||||||
|
{
|
||||||
|
// Server defined color for the region
|
||||||
|
public Color Color = Color.White;
|
||||||
|
|
||||||
|
// The maximum number of tiles that can be assigned to this region
|
||||||
|
public int MaxArea = 625;
|
||||||
|
|
||||||
|
// The maximum distance this region can propagate from its seeds
|
||||||
|
public int MaxRadius = 25;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ atmos-alerts-window-warning-state = Warning
|
|||||||
atmos-alerts-window-danger-state = Danger!
|
atmos-alerts-window-danger-state = Danger!
|
||||||
atmos-alerts-window-invalid-state = Inactive
|
atmos-alerts-window-invalid-state = Inactive
|
||||||
|
|
||||||
atmos-alerts-window-no-active-alerts = [font size=16][color=white]No active alerts -[/color] [color={$color}]situation normal[/color][/font]
|
atmos-alerts-window-no-active-alerts = [font size=16][color=white]No active alerts -[/color] [color={$color}]Situation normal[/color][/font]
|
||||||
atmos-alerts-window-no-data-available = No data available
|
atmos-alerts-window-no-data-available = No data available
|
||||||
atmos-alerts-window-alerts-being-silenced = Silencing alerts...
|
atmos-alerts-window-alerts-being-silenced = Silencing alerts...
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,7 @@
|
|||||||
color: Red
|
color: Red
|
||||||
enabled: false
|
enabled: false
|
||||||
castShadows: false
|
castShadows: false
|
||||||
|
- type: NavMapDoor
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: Firelock
|
id: Firelock
|
||||||
|
|||||||
Reference in New Issue
Block a user