using Content.Server.Power.EntitySystems;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
+using Content.Shared.Administration.Logs;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
+using Content.Shared.Database;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Systems;
public sealed class AirAlarmSystem : EntitySystem
{
[Dependency] private readonly AccessReaderSystem _access = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
addr = netConn.Address;
}
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} mode to {args.Mode}");
SetMode(uid, addr, args.Mode, false);
}
else
private void OnUpdateAutoMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAutoModeMessage args)
{
component.AutoMode = args.Enabled;
+
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} auto mode to {args.Enabled}");
UpdateUI(uid, component);
}
private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmThresholdMessage args)
{
if (AccessCheck(uid, args.Actor, component))
+ {
+ if (args.Gas != null)
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Gas} {args.Type} threshold using {ToPrettyString(uid)}");
+ else
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Type} threshold using {ToPrettyString(uid)}");
+
SetThreshold(uid, args.Address, args.Type, args.Threshold, args.Gas);
+ }
else
+ {
UpdateUI(uid, component);
+ }
}
private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args)
if (AccessCheck(uid, args.Actor, component)
&& _deviceList.ExistsInDeviceList(uid, args.Address))
{
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} settings using {ToPrettyString(uid)}");
+
SetDeviceData(uid, args.Address, args.Data);
}
else
case GasVentPumpData ventData:
foreach (string addr in component.VentData.Keys)
{
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to vent {addr}");
SetData(uid, addr, args.Data);
}
break;
case GasVentScrubberData scrubberData:
foreach (string addr in component.ScrubberData.Keys)
{
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to scrubber {addr}");
SetData(uid, addr, args.Data);
}
break;
if (!_access.IsAllowed(user.Value, uid, reader))
{
_popup.PopupEntity(Loc.GetString("air-alarm-ui-access-denied"), user.Value, user.Value);
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Low, $"{ToPrettyString(user)} attempted to access {ToPrettyString(uid)} without access");
return false;
}
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
+using Content.Shared.Administration.Logs;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
+using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.Power;
using Content.Shared.Tag;
// a danger), and atmos (which triggers based on set thresholds).
public sealed class AtmosMonitorSystem : EntitySystem
{
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
if (!Resolve(uid, ref monitor))
return;
+ // Used for logging after the switch statement
+ string logPrefix = "";
+ string logValueSuffix = "";
+ AtmosAlarmThreshold? logPreviousThreshold = null;
+
switch (type)
{
case AtmosMonitorThresholdType.Pressure:
+ logPrefix = "pressure";
+ logValueSuffix = "kPa";
+ logPreviousThreshold = monitor.PressureThreshold;
+
monitor.PressureThreshold = threshold;
break;
case AtmosMonitorThresholdType.Temperature:
+ logPrefix = "temperature";
+ logValueSuffix = "K";
+ logPreviousThreshold = monitor.TemperatureThreshold;
+
monitor.TemperatureThreshold = threshold;
break;
case AtmosMonitorThresholdType.Gas:
if (gas == null || monitor.GasThresholds == null)
return;
+
+ logPrefix = ((Gas) gas).ToString();
+ logValueSuffix = "kPa";
+ monitor.GasThresholds.TryGetValue((Gas) gas, out logPreviousThreshold);
+
monitor.GasThresholds[(Gas) gas] = threshold;
break;
}
+ // Admin log each change separately rather than logging the whole state
+ if (logPreviousThreshold != null)
+ {
+ if (threshold.Ignore != logPreviousThreshold.Ignore)
+ {
+ string enabled = threshold.Ignore ? "disabled" : "enabled";
+ _adminLogger.Add(
+ LogType.AtmosDeviceSetting,
+ LogImpact.Medium,
+ $"{ToPrettyString(uid)} {logPrefix} thresholds {enabled}"
+ );
+ }
+
+ foreach (var change in threshold.GetChanges(logPreviousThreshold))
+ {
+ if (change.Current.Enabled != change.Previous?.Enabled)
+ {
+ string enabled = change.Current.Enabled ? "enabled" : "disabled";
+ _adminLogger.Add(
+ LogType.AtmosDeviceSetting,
+ LogImpact.Medium,
+ $"{ToPrettyString(uid)} {logPrefix} {change.Type} {enabled}"
+ );
+ }
+
+ if (change.Current.Value != change.Previous?.Value)
+ {
+ _adminLogger.Add(
+ LogType.AtmosDeviceSetting,
+ LogImpact.Medium,
+ $"{ToPrettyString(uid)} {logPrefix} {change.Type} changed from {change.Previous?.Value} {logValueSuffix} to {change.Current.Value} {logValueSuffix}"
+ );
+ }
+ }
+ }
}
/// <summary>
using Content.Server.DeviceNetwork.Systems;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
+using Content.Shared.Administration.Logs;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Atmos.Visuals;
using Content.Shared.Audio;
+using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
[UsedImplicitly]
public sealed class GasVentPumpSystem : EntitySystem
{
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentPumpData? setData))
break;
+ var previous = component.ToAirAlarmData();
+
+ if (previous.Enabled != setData.Enabled)
+ {
+ string enabled = setData.Enabled ? "enabled" : "disabled" ;
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {enabled}");
+ }
+
+ if (previous.PumpDirection != setData.PumpDirection)
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} direction changed to {setData.PumpDirection}");
+
+ if (previous.PressureChecks != setData.PressureChecks)
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} pressure check changed to {setData.PressureChecks}");
+
+ if (previous.ExternalPressureBound != setData.ExternalPressureBound)
+ {
+ _adminLogger.Add(
+ LogType.AtmosDeviceSetting,
+ LogImpact.Medium,
+ $"{ToPrettyString(uid)} external pressure bound changed from {previous.ExternalPressureBound} kPa to {setData.ExternalPressureBound} kPa"
+ );
+ }
+
+ if (previous.InternalPressureBound != setData.InternalPressureBound)
+ {
+ _adminLogger.Add(
+ LogType.AtmosDeviceSetting,
+ LogImpact.Medium,
+ $"{ToPrettyString(uid)} internal pressure bound changed from {previous.InternalPressureBound} kPa to {setData.InternalPressureBound} kPa"
+ );
+ }
+
+ if (previous.PressureLockoutOverride != setData.PressureLockoutOverride)
+ {
+ string enabled = setData.PressureLockoutOverride ? "enabled" : "disabled" ;
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} pressure lockout override {enabled}");
+ }
+
component.FromAirAlarmData(setData);
UpdateState(uid, component);
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
+using Content.Shared.Administration.Logs;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Unary.Visuals;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Audio;
+using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.Power;
using Content.Shared.Tools.Systems;
[UsedImplicitly]
public sealed class GasVentScrubberSystem : EntitySystem
{
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentScrubberData? setData))
break;
+ var previous = component.ToAirAlarmData();
+
+ if (previous.Enabled != setData.Enabled)
+ {
+ string enabled = setData.Enabled ? "enabled" : "disabled" ;
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {enabled}");
+ }
+
+ // TODO: IgnoreAlarms?
+
+ if (previous.PumpDirection != setData.PumpDirection)
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} direction changed to {setData.PumpDirection}");
+
+ // TODO: This is iterating through both sets, it could probably be faster but they're both really small sets anyways
+ foreach (Gas gas in previous.FilterGases)
+ if (!setData.FilterGases.Contains(gas))
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {gas} filtering disabled");
+
+ foreach (Gas gas in setData.FilterGases)
+ if (!previous.FilterGases.Contains(gas))
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {gas} filtering enabled");
+
+ if (previous.VolumeRate != setData.VolumeRate)
+ {
+ _adminLogger.Add(
+ LogType.AtmosDeviceSetting,
+ LogImpact.Medium,
+ $"{ToPrettyString(uid)} volume rate changed from {previous.VolumeRate} L to {setData.VolumeRate} L"
+ );
+ }
+
+ if (previous.WideNet != setData.WideNet)
+ {
+ string enabled = setData.WideNet ? "enabled" : "disabled" ;
+ _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} WideNet {enabled}");
+ }
+
component.FromAirAlarmData(setData);
UpdateState(uid, component);
/// A player interacted with a PDA or its cartridge component
/// </summary>
PdaInteract = 96,
+
+ /// <summary>
+ /// An atmos networked device (such as a vent or pump) has had its settings changed, usually through an air alarm
+ /// </summary>
+ AtmosDeviceSetting = 97,
}
break;
}
}
+
+ /// <summary>
+ /// Iterates through the changes that these threshold settings would make from a
+ /// previous instance. Basically, diffs the two settings.
+ /// </summary>
+ public IEnumerable<AtmosAlarmThresholdChange> GetChanges(AtmosAlarmThreshold previous)
+ {
+ if (LowerBound != previous.LowerBound)
+ yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.LowerDanger, previous.LowerBound, LowerBound);
+
+ if (LowerWarningBound != previous.LowerWarningBound)
+ yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.LowerWarning, previous.LowerWarningBound, LowerWarningBound);
+
+ if (UpperBound != previous.UpperBound)
+ yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.UpperDanger, previous.UpperBound, UpperBound);
+
+ if (UpperWarningBound != previous.UpperWarningBound)
+ yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.UpperWarning, previous.UpperWarningBound, UpperWarningBound);
+ }
+}
+
+/// <summary>
+/// A change of a single value between two AtmosAlarmThreshold, for a given AtmosMonitorLimitType
+/// </summary>
+public readonly struct AtmosAlarmThresholdChange
+{
+ /// <summary>
+ /// The type of change between the two threshold sets
+ /// </summary>
+ public readonly AtmosMonitorLimitType Type;
+
+ /// <summary>
+ /// The value in the old threshold set
+ /// </summary>
+ public readonly AlarmThresholdSetting? Previous;
+
+ /// <summary>
+ /// The value in the new threshold set
+ /// </summary>
+ public readonly AlarmThresholdSetting Current;
+
+ public AtmosAlarmThresholdChange(AtmosMonitorLimitType type, AlarmThresholdSetting? previous, AlarmThresholdSetting current)
+ {
+ Type = type;
+ Previous = previous;
+ Current = current;
+ }
}
[DataDefinition, Serializable]
-public readonly partial struct AlarmThresholdSetting
+public readonly partial struct AlarmThresholdSetting: IEquatable<AlarmThresholdSetting>
{
[DataField("enabled")]
public bool Enabled { get; init; } = true;
{
return this with {Enabled = enabled};
}
+
+ public bool Equals(AlarmThresholdSetting other)
+ {
+ if (Enabled != other.Enabled)
+ return false;
+
+ if (Value != other.Value)
+ return false;
+
+ return true;
+ }
+
+ public static bool operator ==(AlarmThresholdSetting lhs, AlarmThresholdSetting rhs)
+ {
+ return lhs.Equals(rhs);
+ }
+
+ public static bool operator !=(AlarmThresholdSetting lhs, AlarmThresholdSetting rhs)
+ {
+ return !lhs.Equals(rhs);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Enabled, Value);
+ }
}
public enum AtmosMonitorThresholdBound