diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs index 0638d945aa..9b878eac40 100644 --- a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs +++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs @@ -8,6 +8,7 @@ using Robust.Shared; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Prototypes; namespace Content.Benchmarks; @@ -18,9 +19,11 @@ namespace Content.Benchmarks; [Virtual, MemoryDiagnoser] public class SpawnEquipDeleteBenchmark { + private static readonly EntProtoId Mob = "MobHuman"; + private static readonly ProtoId CaptainStartingGear = "CaptainGear"; + private TestPair _pair = default!; private StationSpawningSystem _spawnSys = default!; - private const string Mob = "MobHuman"; private StartingGearPrototype _gear = default!; private EntityUid _entity; private EntityCoordinates _coords; @@ -39,7 +42,7 @@ public class SpawnEquipDeleteBenchmark var mapData = await _pair.CreateTestMap(); _coords = mapData.GridCoords; _spawnSys = server.System(); - _gear = server.ProtoMan.Index("CaptainGear"); + _gear = server.ProtoMan.Index(CaptainStartingGear); } [GlobalCleanup] diff --git a/Content.Client/Access/UI/GroupedAccessLevelChecklist.xaml.cs b/Content.Client/Access/UI/GroupedAccessLevelChecklist.xaml.cs index da68653ce5..4f07c31009 100644 --- a/Content.Client/Access/UI/GroupedAccessLevelChecklist.xaml.cs +++ b/Content.Client/Access/UI/GroupedAccessLevelChecklist.xaml.cs @@ -15,6 +15,8 @@ namespace Content.Client.Access.UI; [GenerateTypedNameReferences] public sealed partial class GroupedAccessLevelChecklist : BoxContainer { + private static readonly ProtoId GeneralAccessGroup = "General"; + [Dependency] private readonly IPrototypeManager _protoManager = default!; private bool _isMonotone; @@ -63,7 +65,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer // Ensure that the 'general' access group is added to handle // misc. access levels that aren't associated with any group - if (_protoManager.TryIndex("General", out var generalAccessProto)) + if (_protoManager.TryIndex(GeneralAccessGroup, out var generalAccessProto)) _groupedAccessLevels.TryAdd(generalAccessProto, new()); // Assign known access levels with their associated groups diff --git a/Content.Client/Administration/UI/Logs/AdminLogsEui.cs b/Content.Client/Administration/UI/Logs/AdminLogsEui.cs index 2c64b82d20..1544b827ae 100644 --- a/Content.Client/Administration/UI/Logs/AdminLogsEui.cs +++ b/Content.Client/Administration/UI/Logs/AdminLogsEui.cs @@ -20,6 +20,10 @@ public sealed class AdminLogsEui : BaseEui [Dependency] private readonly IFileDialogManager _dialogManager = default!; [Dependency] private readonly ILogManager _log = default!; + private const char CsvSeparator = ','; + private const string CsvQuote = "\""; + private const string CsvHeader = "Date,ID,PlayerID,Severity,Type,Message"; + private ISawmill _sawmill; private bool _currentlyExportingLogs = false; @@ -100,7 +104,9 @@ public sealed class AdminLogsEui : BaseEui try { - await using var writer = new StreamWriter(file.Value.fileStream); + // Buffer is set to 4KB for performance reasons. As the average export of 1000 logs is ~200KB + await using var writer = new StreamWriter(file.Value.fileStream, bufferSize: 4096); + await writer.WriteLineAsync(CsvHeader); foreach (var child in LogsControl.LogsContainer.Children) { if (child is not AdminLogLabel logLabel || !child.Visible) @@ -108,28 +114,31 @@ public sealed class AdminLogsEui : BaseEui var log = logLabel.Log; + // Date // I swear to god if someone adds ,s or "s to the other fields... await writer.WriteAsync(log.Date.ToString("s", System.Globalization.CultureInfo.InvariantCulture)); - await writer.WriteAsync(','); + await writer.WriteAsync(CsvSeparator); + // ID await writer.WriteAsync(log.Id.ToString()); - await writer.WriteAsync(','); - await writer.WriteAsync(log.Impact.ToString()); - await writer.WriteAsync(','); - // Message - await writer.WriteAsync('"'); - await writer.WriteAsync(log.Message.Replace("\"", "\"\"")); - await writer.WriteAsync('"'); - // End of message - await writer.WriteAsync(','); - + await writer.WriteAsync(CsvSeparator); + // PlayerID var players = log.Players; for (var i = 0; i < players.Length; i++) { await writer.WriteAsync(players[i] + (i == players.Length - 1 ? "" : " ")); } - - await writer.WriteAsync(','); + await writer.WriteAsync(CsvSeparator); + // Severity + await writer.WriteAsync(log.Impact.ToString()); + await writer.WriteAsync(CsvSeparator); + // Type await writer.WriteAsync(log.Type.ToString()); + await writer.WriteAsync(CsvSeparator); + // Message + await writer.WriteAsync(CsvQuote); + await writer.WriteAsync(log.Message.Replace(CsvQuote, CsvQuote + CsvQuote)); + await writer.WriteAsync(CsvQuote); + await writer.WriteLineAsync(); } } diff --git a/Content.Client/Atmos/EntitySystems/GasPressureRegulatorSystem.cs b/Content.Client/Atmos/EntitySystems/GasPressureRegulatorSystem.cs new file mode 100644 index 0000000000..6f12297ff0 --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/GasPressureRegulatorSystem.cs @@ -0,0 +1,31 @@ +using Content.Shared.Atmos.EntitySystems; +using Content.Shared.Atmos.Piping.Binary.Components; + +namespace Content.Client.Atmos.EntitySystems; + +/// +/// Represents the client system responsible for managing and updating the gas pressure regulator interface. +/// Inherits from the shared system . +/// +public sealed partial class GasPressureRegulatorSystem : SharedGasPressureRegulatorSystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnValveUpdate); + } + + private void OnValveUpdate(Entity ent, ref AfterAutoHandleStateEvent args) + { + UpdateUi(ent); + } + + protected override void UpdateUi(Entity ent) + { + if (UserInterfaceSystem.TryGetOpenUi(ent.Owner, GasPressureRegulatorUiKey.Key, out var bui)) + { + bui.Update(); + } + } +} diff --git a/Content.Client/Atmos/Overlays/GasTileOverlay.cs b/Content.Client/Atmos/Overlays/GasTileOverlay.cs index cdbcb5b3f6..d91cc29e4c 100644 --- a/Content.Client/Atmos/Overlays/GasTileOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileOverlay.cs @@ -19,6 +19,8 @@ namespace Content.Client.Atmos.Overlays { public sealed class GasTileOverlay : Overlay { + private static readonly ProtoId UnshadedShader = "unshaded"; + private readonly IEntityManager _entManager; private readonly IMapManager _mapManager; private readonly SharedMapSystem _mapSystem; @@ -54,7 +56,7 @@ namespace Content.Client.Atmos.Overlays _mapManager = IoCManager.Resolve(); _mapSystem = entManager.System(); _xformSys = xformSys; - _shader = protoMan.Index("unshaded").Instance(); + _shader = protoMan.Index(UnshadedShader).Instance(); ZIndex = GasOverlayZIndex; _gasCount = system.VisibleGasId.Length; diff --git a/Content.Client/Atmos/UI/GasPressureRegulatorBoundUserInterface.cs b/Content.Client/Atmos/UI/GasPressureRegulatorBoundUserInterface.cs new file mode 100644 index 0000000000..9d66a9985f --- /dev/null +++ b/Content.Client/Atmos/UI/GasPressureRegulatorBoundUserInterface.cs @@ -0,0 +1,58 @@ +using Content.Shared.Atmos.Piping.Binary.Components; +using Content.Shared.IdentityManagement; +using Content.Shared.Localizations; +using Robust.Client.UserInterface; + +namespace Content.Client.Atmos.UI; + +public sealed class GasPressureRegulatorBoundUserInterface(EntityUid owner, Enum uiKey) + : BoundUserInterface(owner, uiKey) +{ + private GasPressureRegulatorWindow? _window; + + protected override void Open() + { + base.Open(); + + _window = this.CreateWindow(); + + _window.SetEntity(Owner); + + _window.ThresholdPressureChanged += OnThresholdChanged; + + if (EntMan.TryGetComponent(Owner, out GasPressureRegulatorComponent? comp)) + _window.SetThresholdPressureInput(comp.Threshold); + + Update(); + } + + public override void Update() + { + if (_window == null) + return; + + _window.Title = Identity.Name(Owner, EntMan); + + if (!EntMan.TryGetComponent(Owner, out GasPressureRegulatorComponent? comp)) + return; + + _window.SetThresholdPressureLabel(comp.Threshold); + _window.UpdateInfo(comp.InletPressure, comp.OutletPressure, comp.FlowRate); + } + + private void OnThresholdChanged(string newThreshold) + { + var sentThreshold = 0f; + + if (UserInputParser.TryFloat(newThreshold, out var parsedNewThreshold) && parsedNewThreshold >= 0 && + !float.IsInfinity(parsedNewThreshold)) + { + sentThreshold = parsedNewThreshold; + } + + // Autofill to zero if the user inputs an invalid value. + _window?.SetThresholdPressureInput(sentThreshold); + + SendPredictedMessage(new GasPressureRegulatorChangeThresholdMessage(sentThreshold)); + } +} diff --git a/Content.Client/Atmos/UI/GasPressureRegulatorWindow.xaml b/Content.Client/Atmos/UI/GasPressureRegulatorWindow.xaml new file mode 100644 index 0000000000..b6a55f769e --- /dev/null +++ b/Content.Client/Atmos/UI/GasPressureRegulatorWindow.xaml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +