Merge remote-tracking branch 'upstream/stable' into ed-29-10-2024-upstream

# Conflicts:
#	Content.Server/Chat/Managers/ChatSanitizationManager.cs
#	Content.Server/Temperature/Systems/TemperatureSystem.cs
#	Content.Shared/Localizations/ContentLocalizationManager.cs
This commit is contained in:
Ed
2024-10-29 11:16:56 +03:00
388 changed files with 34535 additions and 18822 deletions

View File

@@ -23,6 +23,7 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
{
private readonly IEntityManager _entManager;
private readonly SpriteSystem _spriteSystem;
private readonly SharedNavMapSystem _navMapSystem;
private EntityUid? _owner;
private NetEntity? _trackedEntity;
@@ -42,19 +43,32 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
private const float SilencingDuration = 2.5f;
// Colors
private Color _wallColor = new Color(64, 64, 64);
private Color _tileColor = new Color(28, 28, 28);
private Color _monitorBlipColor = Color.Cyan;
private Color _untrackedEntColor = Color.DimGray;
private Color _regionBaseColor = new Color(154, 154, 154);
private Color _inactiveColor = StyleNano.DisabledFore;
private Color _statusTextColor = StyleNano.GoodGreenFore;
private Color _goodColor = Color.LimeGreen;
private Color _warningColor = new Color(255, 182, 72);
private Color _dangerColor = new Color(255, 67, 67);
public AtmosAlertsComputerWindow(AtmosAlertsComputerBoundUserInterface userInterface, EntityUid? owner)
{
RobustXamlLoader.Load(this);
_entManager = IoCManager.Resolve<IEntityManager>();
_spriteSystem = _entManager.System<SpriteSystem>();
_navMapSystem = _entManager.System<SharedNavMapSystem>();
// Pass the owner to nav map
_owner = owner;
NavMap.Owner = _owner;
// Set nav map colors
NavMap.WallColor = new Color(64, 64, 64);
NavMap.TileColor = Color.DimGray * NavMap.WallColor;
NavMap.WallColor = _wallColor;
NavMap.TileColor = _tileColor;
// Set nav map grid uid
var stationName = Loc.GetString("atmos-alerts-window-unknown-location");
@@ -179,6 +193,9 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
// Add tracked entities to the nav map
foreach (var device in console.AtmosDevices)
{
if (!device.NetEntity.Valid)
continue;
if (!NavMap.Visible)
continue;
@@ -209,7 +226,7 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
if (consoleCoords != null && consoleUid != null)
{
var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
var blip = new NavMapBlip(consoleCoords.Value, texture, Color.Cyan, true, false);
var blip = new NavMapBlip(consoleCoords.Value, texture, _monitorBlipColor, true, false);
NavMap.TrackedEntities[consoleUid.Value] = blip;
}
@@ -258,7 +275,7 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
VerticalAlignment = VAlignment.Center,
};
label.SetMarkup(Loc.GetString("atmos-alerts-window-no-active-alerts", ("color", StyleNano.GoodGreenFore.ToHexNoAlpha())));
label.SetMarkup(Loc.GetString("atmos-alerts-window-no-active-alerts", ("color", _statusTextColor.ToHexNoAlpha())));
AlertsTable.AddChild(label);
}
@@ -270,6 +287,34 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
else
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;
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
if (_autoScrollAwaitsUpdate)
{
@@ -290,7 +335,7 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
var coords = _entManager.GetCoordinates(metaData.NetCoordinates);
if (_trackedEntity != null && _trackedEntity != metaData.NetEntity)
color *= Color.DimGray;
color *= _untrackedEntColor;
var selectable = true;
var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color, _trackedEntity == metaData.NetEntity, selectable);
@@ -298,6 +343,24 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
NavMap.TrackedEntities[metaData.NetEntity] = blip;
}
private bool TryGetSensorRegionColor(NetEntity regionOwner, AtmosAlarmType alarmState, out Color color)
{
color = Color.White;
var blip = GetBlipTexture(alarmState);
if (blip == null)
return false;
// Color the region based on alarm state and entity tracking
color = blip.Value.Item2 * _regionBaseColor;
if (_trackedEntity != null && _trackedEntity != regionOwner)
color *= _untrackedEntColor;
return true;
}
private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null)
{
// Make new UI entry if required
@@ -534,13 +597,13 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
switch (alarmState)
{
case AtmosAlarmType.Invalid:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), StyleNano.DisabledFore); break;
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), _inactiveColor); break;
case AtmosAlarmType.Normal:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), Color.LimeGreen); break;
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), _goodColor); break;
case AtmosAlarmType.Warning:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), new Color(255, 182, 72)); break;
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), _warningColor); break;
case AtmosAlarmType.Danger:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), new Color(255, 67, 67)); break;
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), _dangerColor); break;
}
return output;

View File

@@ -4,6 +4,7 @@ using Content.Client.Chat.Managers;
using Content.Client.DebugMon;
using Content.Client.Eui;
using Content.Client.Fullscreen;
using Content.Client.GameTicking.Managers;
using Content.Client.GhostKick;
using Content.Client.Guidebook;
using Content.Client.Input;
@@ -71,6 +72,7 @@ namespace Content.Client.Entry
[Dependency] private readonly IReplayLoadManager _replayLoad = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
public override void Init()
{
@@ -140,6 +142,12 @@ namespace Content.Client.Entry
_configManager.SetCVar("interface.resolutionAutoScaleMinimum", 0.5f);
}
public override void Shutdown()
{
base.Shutdown();
_titleWindowManager.Shutdown();
}
public override void PostInit()
{
base.PostInit();
@@ -160,6 +168,7 @@ namespace Content.Client.Entry
_userInterfaceManager.SetDefaultTheme(_configManager.GetCVar(CCVars.UIDefaultInterfaceTheme));
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
_documentParsingManager.Initialize();
_titleWindowManager.Initialize();
_baseClient.RunLevelChanged += (_, args) =>
{

View File

@@ -0,0 +1,62 @@
using Content.Shared.CCVar;
using Robust.Client;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Configuration;
namespace Content.Client.GameTicking.Managers;
public sealed class TitleWindowManager
{
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameController _gameController = default!;
public void Initialize()
{
_cfg.OnValueChanged(CVars.GameHostName, OnHostnameChange, true);
_cfg.OnValueChanged(CCVars.GameHostnameInTitlebar, OnHostnameTitleChange, true);
_client.RunLevelChanged += OnRunLevelChangedChange;
}
public void Shutdown()
{
_cfg.UnsubValueChanged(CVars.GameHostName, OnHostnameChange);
_cfg.UnsubValueChanged(CCVars.GameHostnameInTitlebar, OnHostnameTitleChange);
}
private void OnHostnameChange(string hostname)
{
var defaultWindowTitle = _gameController.GameTitle();
// Since the game assumes the server name is MyServer and that GameHostnameInTitlebar CCVar is true by default
// Lets just... not show anything. This also is used to revert back to just the game title on disconnect.
if (_client.RunLevel == ClientRunLevel.Initialize)
{
_clyde.SetWindowTitle(defaultWindowTitle);
return;
}
if (_cfg.GetCVar(CCVars.GameHostnameInTitlebar))
// If you really dislike the dash I guess change it here
_clyde.SetWindowTitle(hostname + " - " + defaultWindowTitle);
else
_clyde.SetWindowTitle(defaultWindowTitle);
}
// Clients by default assume game.hostname_in_titlebar is true
// but we need to clear it as soon as we join and actually receive the servers preference on this.
// This will ensure we rerun OnHostnameChange and set the correct title bar name.
private void OnHostnameTitleChange(bool colonthree)
{
OnHostnameChange(_cfg.GetCVar(CVars.GameHostName));
}
// This is just used we can rerun the hostname change function when we disconnect to revert back to just the games title.
private void OnRunLevelChangedChange(object? sender, RunLevelChangedEventArgs runLevelChangedEventArgs)
{
OnHostnameChange(_cfg.GetCVar(CVars.GameHostName));
}
}

View File

@@ -130,9 +130,9 @@ namespace Content.Client.Hands.Systems
OnPlayerHandsAdded?.Invoke(hands);
}
public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null)
public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null, bool log = true)
{
base.DoDrop(uid, hand, doDropInteraction, hands);
base.DoDrop(uid, hand, doDropInteraction, hands, log);
if (TryComp(hand.HeldEntity, out SpriteComponent? sprite))
sprite.RenderOrder = EntityManager.CurrentTick.Value;

View File

@@ -1,3 +1,4 @@
using Content.Shared.Localizations;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
@@ -16,19 +17,10 @@ public sealed partial class PlaytimeStatsEntry : ContainerButton
RoleLabel.Text = role;
Playtime = playtime; // store the TimeSpan value directly
PlaytimeLabel.Text = ConvertTimeSpanToHoursMinutes(playtime); // convert to string for display
PlaytimeLabel.Text = ContentLocalizationManager.FormatPlaytime(playtime); // convert to string for display
BackgroundColorPanel.PanelOverride = styleBox;
}
private static string ConvertTimeSpanToHoursMinutes(TimeSpan timeSpan)
{
var hours = (int)timeSpan.TotalHours;
var minutes = timeSpan.Minutes;
var formattedTimeLoc = Loc.GetString("ui-playtime-time-format", ("hours", hours), ("minutes", minutes));
return formattedTimeLoc;
}
public void UpdateShading(StyleBoxFlat styleBox)
{
BackgroundColorPanel.PanelOverride = styleBox;

View File

@@ -104,8 +104,7 @@ public sealed partial class PlaytimeStatsWindow : FancyWindow
{
var overallPlaytime = _jobRequirementsManager.FetchOverallPlaytime();
var formattedPlaytime = ConvertTimeSpanToHoursMinutes(overallPlaytime);
OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", formattedPlaytime));
OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", overallPlaytime));
var rolePlaytimes = _jobRequirementsManager.FetchPlaytimeByRoles();
@@ -134,13 +133,4 @@ public sealed partial class PlaytimeStatsWindow : FancyWindow
_sawmill.Error($"The provided playtime string '{playtimeString}' is not in the correct format.");
}
}
private static string ConvertTimeSpanToHoursMinutes(TimeSpan timeSpan)
{
var hours = (int) timeSpan.TotalHours;
var minutes = timeSpan.Minutes;
var formattedTimeLoc = Loc.GetString("ui-playtime-time-format", ("hours", hours), ("minutes", minutes));
return formattedTimeLoc;
}
}

View File

@@ -5,6 +5,7 @@ using Content.Client.Clickable;
using Content.Client.DebugMon;
using Content.Client.Eui;
using Content.Client.Fullscreen;
using Content.Client.GameTicking.Managers;
using Content.Client.GhostKick;
using Content.Client.Guidebook;
using Content.Client.Launcher;
@@ -57,6 +58,7 @@ namespace Content.Client.IoC
collection.Register<DebugMonitorManager>();
collection.Register<PlayerRateLimitManager>();
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
collection.Register<TitleWindowManager>();
}
}
}

View 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;
}
}

View File

@@ -1,3 +1,4 @@
using System.Linq;
using Content.Shared.Pinpointer;
using Robust.Shared.GameStates;
@@ -16,6 +17,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
{
Dictionary<Vector2i, int[]> modifiedChunks;
Dictionary<NetEntity, NavMapBeacon> beacons;
Dictionary<NetEntity, NavMapRegionProperties> regions;
switch (args.Current)
{
@@ -23,6 +25,8 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
{
modifiedChunks = delta.ModifiedChunks;
beacons = delta.Beacons;
regions = delta.Regions;
foreach (var index in component.Chunks.Keys)
{
if (!delta.AllChunks!.Contains(index))
@@ -35,6 +39,8 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
{
modifiedChunks = state.Chunks;
beacons = state.Beacons;
regions = state.Regions;
foreach (var index in component.Chunks.Keys)
{
if (!state.Chunks.ContainsKey(index))
@@ -47,13 +53,54 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
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)
{
var newChunk = new NavMapChunk(origin);
Array.Copy(chunk, newChunk.TileData, chunk.Length);
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();
foreach (var (nuid, beacon) in beacons)
{

View File

@@ -48,6 +48,7 @@ public partial class NavMapControl : MapGridControl
public List<(Vector2, Vector2)> TileLines = new();
public List<(Vector2, Vector2)> TileRects = new();
public List<(Vector2[], Color)> TilePolygons = new();
public List<NavMapRegionOverlay> RegionOverlays = new();
// Default colors
public Color WallColor = new(102, 217, 102);
@@ -228,7 +229,7 @@ public partial class NavMapControl : MapGridControl
{
if (!blip.Selectable)
continue;
var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length();
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
if (TileLines.Any())
{

View File

@@ -51,6 +51,8 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
{
// Reset on disconnect, just in case.
_roles.Clear();
_jobWhitelists.Clear();
_roleBans.Clear();
}
}
@@ -58,9 +60,6 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
{
_sawmill.Debug($"Received roleban info containing {message.Bans.Count} entries.");
if (_roleBans.Equals(message.Bans))
return;
_roleBans.Clear();
_roleBans.AddRange(message.Bans);
Updated?.Invoke();

View File

@@ -1,8 +1,9 @@
using Content.Client.Power.APC.UI;
using Content.Client.Power.APC.UI;
using Content.Shared.Access.Systems;
using Content.Shared.APC;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Shared.Player;
namespace Content.Client.Power.APC
{
@@ -22,6 +23,14 @@ namespace Content.Client.Power.APC
_menu = this.CreateWindow<ApcMenu>();
_menu.SetEntity(Owner);
_menu.OnBreaker += BreakerPressed;
var hasAccess = false;
if (PlayerManager.LocalEntity != null)
{
var accessReader = EntMan.System<AccessReaderSystem>();
hasAccess = accessReader.IsAllowed((EntityUid)PlayerManager.LocalEntity, Owner);
}
_menu?.SetAccessEnabled(hasAccess);
}
protected override void UpdateState(BoundUserInterfaceState state)

View File

@@ -1,4 +1,4 @@
using Robust.Client.AutoGenerated;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Client.GameObjects;
using Robust.Shared.IoC;
@@ -36,19 +36,9 @@ namespace Content.Client.Power.APC.UI
{
var castState = (ApcBoundInterfaceState) state;
if (BreakerButton != null)
if (!BreakerButton.Disabled)
{
if(castState.HasAccess == false)
{
BreakerButton.Disabled = true;
BreakerButton.ToolTip = Loc.GetString("apc-component-insufficient-access");
}
else
{
BreakerButton.Disabled = false;
BreakerButton.ToolTip = null;
BreakerButton.Pressed = castState.MainBreaker;
}
BreakerButton.Pressed = castState.MainBreaker;
}
if (PowerLabel != null)
@@ -86,6 +76,20 @@ namespace Content.Client.Power.APC.UI
}
}
public void SetAccessEnabled(bool hasAccess)
{
if(hasAccess)
{
BreakerButton.Disabled = false;
BreakerButton.ToolTip = null;
}
else
{
BreakerButton.Disabled = true;
BreakerButton.ToolTip = Loc.GetString("apc-component-insufficient-access");
}
}
private void UpdateChargeBarColor(float charge)
{
if (ChargeBar == null)

View File

@@ -2,7 +2,8 @@
xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"
MinHeight="210">
<BoxContainer Name="MainContainer" Orientation="Vertical">
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'vending-machine-component-search-filter'}" HorizontalExpand="True" Margin ="4 4"/>
<co:SearchListContainer Name="VendingContents" VerticalExpand="True" Margin="4 4"/>

View File

@@ -28,6 +28,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
[Dependency] private readonly MapSystem _map = default!;
private EntityQuery<TransformComponent> _xformQuery;
@@ -109,11 +110,11 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _))
{
coordinates = EntityCoordinates.FromMap(gridUid, mousePos, TransformSystem, EntityManager);
coordinates = TransformSystem.ToCoordinates(gridUid, mousePos);
}
else
{
coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, TransformSystem, EntityManager);
coordinates = TransformSystem.ToCoordinates(_map.GetMap(mousePos.MapId), mousePos);
}
// Heavy attack.