Device Network refactor part 1: API improvements (#44346)

* Refactor DeviceNetwork

* Singleton

* NetworkPayloads refactor

* Reorganize DeviceNetworkSystem

* Surveillance cameras

* Fix code

* Fix build

* Fix test fail by rewriting entire singleton logic

* Properly predict examine

* Update DeviceNetworkSystem.API.cs

* Update EntityTest.cs

* Update DeviceNetworkSystem.cs

* Alright I'll do this expensive check fine

* Fix disposals debug assert

* Implement device network static events support

* Use frozen dictionaries for performance

* Update SharedDeviceNetworkSystem.Payloads.cs

* Parallel payload handling support

* Fix some spagetti-code

* Optimize NetworkPayload cancellation

* Parallel payloads are now cancellable

* Fix screens

* Fix robotics console

* Allow new methods for shared

* Mail fix

* Turret fix

* Fix network system method usage + signal and borg fix

* finally fix atmos devices OH MY GOOOD

* Remove stupid parallelism from fax

* Fix surveiollance cameras

* Working on a single thing for 2 days straight award

* Fix the test?

* Update units.yml

* Add IDevice interface because why not

* Add more device network suffixes

* Fix camera router failing test

* yaml linter peak

* Improve docs for DeviceNetworkHandlers

* fix build

* Update CartridgeLoaderSystem.Relay.cs

* Remove parallel system and EntitySystem post init

* Add more docs and also events

* Remove unnecessary caching

* Update DevicePayloadComponentSystem.cs

* Back to entity events

* Implement DeviceData

* Remove serializable from payloads

* Update DeviceData.cs

* Add a prototype for a singleton

* Fix tests

* Fix air alarms

* Update DeviceNetworkSystem.cs

* Fix cameras

* Make camera router event-less

* Fix atmos sync payloads, add more docs

* Remove singleton + instant packets

* oops remove public access

* Fix tests?

* Why the hell overload effect makes puddle sparks

* Apparently it was redundant

* Update gates.yml

* Finish documentation

* Review

* Mark relay properties as obsolete

* Struct payloads support (#blazinglyfastmemorysafe)

* Update DeviceNetworkTest.cs

* Make device structs stuff better

* Move address prefix to DeviceNetworkComponent

* Locale fixes

* Fix tests

* Doesn't know boolean logic award

* Update AtmosAlarmableSystem.cs

* Fix address predicted examine

* Partial fix to air alarmables

* Fix deployable turrets UI

* Fix firelocks, maybe

* Apparently Activatable UI was enough lol

* Fix surveillance cameras

* Rename `QueuePacket` into `SendPacket`

* Simplify DeviceData as much as possible

* Update DeviceData.cs

* Update FaxMachineComponent.cs

* Remove DeviceData because its stupid

* Update ScreenSystem.cs

* Fix build (camera map broken still)

* Fix map

* fix usings

* Review changes

* Build fix
This commit is contained in:
Rouden 2026-08-31 19:42:56 +00:00 • committed by GitHub
parent 0b2245ff2c
commit cc2c14f8e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
149 changed files with 3573 additions and 3260 deletions

View file

@ -6,7 +6,6 @@ using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.IntegrationTests.Tests.DeviceNetwork;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.DeviceNetwork;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
@ -26,9 +25,6 @@ public class DeviceNetworkingBenchmark
private readonly List<EntityUid> _targetEntities = new();
private readonly List<EntityUid> _targetWirelessEntities = new();
private NetworkPayload _payload = default!;
[TestPrototypes]
private const string Prototypes = @"
- type: entity
@ -54,7 +50,7 @@ public class DeviceNetworkingBenchmark
//public static IEnumerable<int> EntityCountSource { get; set; }
//[ParamsSource(nameof(EntityCountSource))]
public int EntityCount = 500;
public int EntityCount = 1000;
[GlobalSetup]
public async Task SetupAsync()
@ -70,14 +66,6 @@ public class DeviceNetworkingBenchmark
_deviceNetworkSystem = entityManager.EntitySysManager.GetEntitySystem<DeviceNetworkSystem>();
_deviceNetTestSystem = entityManager.EntitySysManager.GetEntitySystem<DeviceNetworkTestSystem>();
var testValue = "test";
_payload = new NetworkPayload
{
["Test"] = testValue,
["testnumber"] = 1,
["testbool"] = true
};
_sourceEntity = entityManager.SpawnEntity("DummyNetworkDevicePrivate", MapCoordinates.Nullspace);
_sourceWirelessEntity = entityManager.SpawnEntity("DummyWirelessNetworkDevice", MapCoordinates.Nullspace);
@ -120,7 +108,13 @@ public class DeviceNetworkingBenchmark
_pair.Server.Post(() =>
{
_deviceNetworkSystem.QueuePacket(_sourceEntity, null, _payload, 100);
var payload = new TestPayload
{
TestString = "test",
TestNumber = 1,
TestBool = true,
};
_deviceNetworkSystem.SendPacket(_sourceEntity, null, ref payload, 100);
});
await server.WaitRunTicks(1);
@ -134,7 +128,53 @@ public class DeviceNetworkingBenchmark
_pair.Server.Post(() =>
{
_deviceNetworkSystem.QueuePacket(_sourceWirelessEntity, null, _payload, 100);
var payload = new TestPayload
{
TestString = "test",
TestNumber = 1,
TestBool = true,
};
_deviceNetworkSystem.SendPacket(_sourceWirelessEntity, null, ref payload, 100);
});
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
}
[Benchmark(Description = "Device Net Broadcast No Connection Checks (Class payload)")]
public async Task DeviceNetworkBroadcastNoConnectionChecksClass()
{
var server = _pair.Server;
_pair.Server.Post(() =>
{
var payload = new TestPayloadClass
{
TestString = "test",
TestNumber = 1,
TestBool = true,
};
_deviceNetworkSystem.SendPacket(_sourceEntity, null, ref payload, 100);
});
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
}
[Benchmark(Description = "Device Net Broadcast Wireless Connection Checks (Class payload)")]
public async Task DeviceNetworkBroadcastWirelessConnectionChecksClass()
{
var server = _pair.Server;
_pair.Server.Post(() =>
{
var payload = new TestPayloadClass
{
TestString = "test",
TestNumber = 1,
TestBool = true,
};
_deviceNetworkSystem.SendPacket(_sourceWirelessEntity, null, ref payload, 100);
});
await server.WaitRunTicks(1);

View file

@ -33,14 +33,14 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
SendMessage(new AirAlarmResyncAllDevicesMessage());
}
private void OnDeviceDataChanged(string address, IAtmosDeviceData data)
private void OnDeviceDataChanged(string address, IAtmosDeviceData dataPayload)
{
SendMessage(new AirAlarmUpdateDeviceDataMessage(address, data));
SendMessage(new AirAlarmUpdateDeviceDataMessage(address, dataPayload));
}
private void OnDeviceDataCopied(IAtmosDeviceData data)
private void OnDeviceDataCopied(IAtmosDeviceData dataPayload)
{
SendMessage(new AirAlarmCopyDeviceDataMessage(data));
SendMessage(new AirAlarmCopyDeviceDataMessage(dataPayload));
}
private void OnAirAlarmModeChanged(AirAlarmMode mode)

View file

@ -17,7 +17,7 @@ namespace Content.Client.Atmos.Monitor.UI;
public sealed partial class AirAlarmWindow : FancyWindow
{
public event Action<string, IAtmosDeviceData>? AtmosDeviceDataChanged;
public event Action<IAtmosDeviceData>? AtmosDeviceDataCopied;
public event Action<IAtmosDeviceData>? AtmosDeviceDataCopied;
public event Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? AtmosAlarmThresholdChanged;
public event Action<AirAlarmMode>? AirAlarmModeChanged;
public event Action<bool>? AutoModeChanged;
@ -158,7 +158,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
}
break;
case AtmosSensorData sensor:
case AtmosMonitorData sensor:
if (!_sensors.TryGetValue(addr, out var sensorControl))
{
var control = new SensorInfo(sensor, addr);

View file

@ -1,4 +1,4 @@
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Unary.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
@ -9,7 +9,7 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets;
[GenerateTypedNameReferences]
public sealed partial class PumpControl : BoxContainer
{
private GasVentPumpData _data;
private GasVentPumpData _dataPayload;
private string _address;
public event Action<string, IAtmosDeviceData>? PumpDataChanged;
@ -23,37 +23,37 @@ public sealed partial class PumpControl : BoxContainer
private FloatSpinBox _internalBound => CInternalBound;
private Button _copySettings => CCopySettings;
public PumpControl(GasVentPumpData data, string address)
public PumpControl(GasVentPumpData dataPayload, string address)
{
RobustXamlLoader.Load(this);
Name = address;
_data = data;
_dataPayload = dataPayload;
_address = address;
_addressLabel.Title = Loc.GetString("air-alarm-ui-atmos-net-device-label", ("address", $"{address}"));
_enabled.Pressed = data.Enabled;
_enabled.Pressed = dataPayload.Enabled;
_enabled.OnToggled += _ =>
{
_data.Enabled = _enabled.Pressed;
PumpDataChanged?.Invoke(_address, _data);
_dataPayload.Enabled = _enabled.Pressed;
PumpDataChanged?.Invoke(_address, _dataPayload);
};
_internalBound.Value = _data.InternalPressureBound;
_internalBound.Value = _dataPayload.InternalPressureBound;
_internalBound.OnValueChanged += _ =>
{
_data.InternalPressureBound = _internalBound.Value;
PumpDataChanged?.Invoke(_address, _data);
_dataPayload.InternalPressureBound = _internalBound.Value;
PumpDataChanged?.Invoke(_address, _dataPayload);
};
_internalBound.IsValid += value => value >= 0;
_externalBound.Value = _data.ExternalPressureBound;
_externalBound.Value = _dataPayload.ExternalPressureBound;
_externalBound.OnValueChanged += _ =>
{
_data.ExternalPressureBound = _externalBound.Value;
PumpDataChanged?.Invoke(_address, _data);
_dataPayload.ExternalPressureBound = _externalBound.Value;
PumpDataChanged?.Invoke(_address, _dataPayload);
};
_externalBound.IsValid += value => value >= 0;
@ -62,12 +62,12 @@ public sealed partial class PumpControl : BoxContainer
_pumpDirection.AddItem(Loc.GetString($"air-alarm-ui-pump-direction-{value.ToString().ToLower()}"), (int) value);
}
_pumpDirection.SelectId((int) _data.PumpDirection);
_pumpDirection.SelectId((int) _dataPayload.PumpDirection);
_pumpDirection.OnItemSelected += args =>
{
_pumpDirection.SelectId(args.Id);
_data.PumpDirection = (VentPumpDirection) args.Id;
PumpDataChanged?.Invoke(_address, _data);
_dataPayload.PumpDirection = (VentPumpDirection) args.Id;
PumpDataChanged?.Invoke(_address, _dataPayload);
};
foreach (var value in Enum.GetValues<VentPressureBound>())
@ -75,35 +75,35 @@ public sealed partial class PumpControl : BoxContainer
_pressureCheck.AddItem(Loc.GetString($"air-alarm-ui-pressure-bound-{value.ToString().ToLower()}"), (int) value);
}
_pressureCheck.SelectId((int) _data.PressureChecks);
_pressureCheck.SelectId((int) _dataPayload.PressureChecks);
_pressureCheck.OnItemSelected += args =>
{
_pressureCheck.SelectId(args.Id);
_data.PressureChecks = (VentPressureBound) args.Id;
PumpDataChanged?.Invoke(_address, _data);
_dataPayload.PressureChecks = (VentPressureBound) args.Id;
PumpDataChanged?.Invoke(_address, _dataPayload);
};
_copySettings.OnPressed += _ =>
{
PumpDataCopied?.Invoke(_data);
PumpDataCopied?.Invoke(_dataPayload);
};
}
public void ChangeData(GasVentPumpData data)
public void ChangeData(GasVentPumpData dataPayload)
{
_data.Enabled = data.Enabled;
_enabled.Pressed = _data.Enabled;
_dataPayload.Enabled = dataPayload.Enabled;
_enabled.Pressed = _dataPayload.Enabled;
_data.PumpDirection = data.PumpDirection;
_pumpDirection.SelectId((int) _data.PumpDirection);
_dataPayload.PumpDirection = dataPayload.PumpDirection;
_pumpDirection.SelectId((int) _dataPayload.PumpDirection);
_data.PressureChecks = data.PressureChecks;
_pressureCheck.SelectId((int) _data.PressureChecks);
_dataPayload.PressureChecks = dataPayload.PressureChecks;
_pressureCheck.SelectId((int) _dataPayload.PressureChecks);
_data.ExternalPressureBound = data.ExternalPressureBound;
_externalBound.Value = _data.ExternalPressureBound;
_dataPayload.ExternalPressureBound = dataPayload.ExternalPressureBound;
_externalBound.Value = _dataPayload.ExternalPressureBound;
_data.InternalPressureBound = data.InternalPressureBound;
_internalBound.Value = _data.InternalPressureBound;
_dataPayload.InternalPressureBound = dataPayload.InternalPressureBound;
_internalBound.Value = _dataPayload.InternalPressureBound;
}
}

View file

@ -1,6 +1,6 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Atmos.Prototypes;
using Robust.Client.AutoGenerated;
@ -16,7 +16,7 @@ public sealed partial class ScrubberControl : BoxContainer
[Dependency] private IPrototypeManager _prototypeManager = default!;
[Dependency] private IEntityManager _entMan = default!;
private GasVentScrubberData _data;
private GasVentScrubberData _dataPayload;
private string _address;
public event Action<string, IAtmosDeviceData>? ScrubberDataChanged;
@ -34,7 +34,7 @@ public sealed partial class ScrubberControl : BoxContainer
private GridContainer _gases => CGasContainer;
private Dictionary<Gas, Button> _gasControls = new();
public ScrubberControl(GasVentScrubberData data, string address)
public ScrubberControl(GasVentScrubberData dataPayload, string address)
{
IoCManager.InjectDependencies(this);
@ -44,30 +44,30 @@ public sealed partial class ScrubberControl : BoxContainer
Name = address;
_data = data;
_dataPayload = dataPayload;
_address = address;
_addressLabel.Title = Loc.GetString("air-alarm-ui-atmos-net-device-label", ("address", $"{address}"));
_enabled.Pressed = data.Enabled;
_enabled.Pressed = dataPayload.Enabled;
_enabled.OnToggled += _ =>
{
_data.Enabled = _enabled.Pressed;
ScrubberDataChanged?.Invoke(_address, _data);
_dataPayload.Enabled = _enabled.Pressed;
ScrubberDataChanged?.Invoke(_address, _dataPayload);
};
_wideNet.Pressed = data.WideNet;
_wideNet.Pressed = dataPayload.WideNet;
_wideNet.OnToggled += _ =>
{
_data.WideNet = _wideNet.Pressed;
ScrubberDataChanged?.Invoke(_address, _data);
_dataPayload.WideNet = _wideNet.Pressed;
ScrubberDataChanged?.Invoke(_address, _dataPayload);
};
_volumeRate.Value = _data.VolumeRate;
_volumeRate.Value = _dataPayload.VolumeRate;
_volumeRate.OnValueChanged += _ =>
{
_data.VolumeRate = _volumeRate.Value;
ScrubberDataChanged?.Invoke(_address, _data);
_dataPayload.VolumeRate = _volumeRate.Value;
ScrubberDataChanged?.Invoke(_address, _dataPayload);
};
_volumeRate.IsValid += value => value >= 0;
@ -76,31 +76,31 @@ public sealed partial class ScrubberControl : BoxContainer
_pumpDirection.AddItem(Loc.GetString($"air-alarm-ui-pump-direction-{value.ToString().ToLower()}"), (int) value);
}
_pumpDirection.SelectId((int) _data.PumpDirection);
_pumpDirection.SelectId((int) _dataPayload.PumpDirection);
_pumpDirection.OnItemSelected += args =>
{
_pumpDirection.SelectId(args.Id);
_data.PumpDirection = (ScrubberPumpDirection) args.Id;
ScrubberDataChanged?.Invoke(_address, _data);
_dataPayload.PumpDirection = (ScrubberPumpDirection) args.Id;
ScrubberDataChanged?.Invoke(_address, _dataPayload);
};
_pumpDirection.Disabled = data.AirAlarmPanicWireCut;
_pumpDirection.Disabled = dataPayload.AirAlarmPanicWireCut;
_copySettings.OnPressed += _ =>
{
ScrubberDataCopied?.Invoke(_data);
ScrubberDataCopied?.Invoke(_dataPayload);
};
var allGases = Enum.GetValues<Gas>();
_selectAll.OnPressed += _ =>
{
_data.FilterGases = new HashSet<Gas>(allGases);
ScrubberDataChanged?.Invoke(_address, _data);
_dataPayload.FilterGases = new HashSet<Gas>(allGases);
ScrubberDataChanged?.Invoke(_address, _dataPayload);
};
_deselectAll.OnPressed += _ =>
{
_data.FilterGases = [];
ScrubberDataChanged?.Invoke(_address, _data);
_dataPayload.FilterGases = [];
ScrubberDataChanged?.Invoke(_address, _dataPayload);
};
foreach (var value in allGases)
@ -114,16 +114,16 @@ public sealed partial class ScrubberControl : BoxContainer
Text = Loc.GetString(gasName),
ToggleMode = true,
HorizontalExpand = true,
Pressed = _data.FilterGases.Contains(value)
Pressed = _dataPayload.FilterGases.Contains(value)
};
gasButton.OnToggled += args =>
{
if (args.Pressed)
_data.FilterGases.Add(value);
_dataPayload.FilterGases.Add(value);
else
_data.FilterGases.Remove(value);
_dataPayload.FilterGases.Remove(value);
ScrubberDataChanged?.Invoke(_address, _data);
ScrubberDataChanged?.Invoke(_address, _dataPayload);
};
_gasControls.Add(value, gasButton);
_gases.AddChild(gasButton);
@ -131,25 +131,25 @@ public sealed partial class ScrubberControl : BoxContainer
}
public void ChangeData(GasVentScrubberData data)
public void ChangeData(GasVentScrubberData dataPayload)
{
_data.Enabled = data.Enabled;
_enabled.Pressed = _data.Enabled;
_dataPayload.Enabled = dataPayload.Enabled;
_enabled.Pressed = _dataPayload.Enabled;
_data.PumpDirection = data.PumpDirection;
_pumpDirection.Select((int) _data.PumpDirection);
_pumpDirection.Disabled = data.AirAlarmPanicWireCut;
_dataPayload.PumpDirection = dataPayload.PumpDirection;
_pumpDirection.Select((int) _dataPayload.PumpDirection);
_pumpDirection.Disabled = dataPayload.AirAlarmPanicWireCut;
_data.VolumeRate = data.VolumeRate;
_volumeRate.Value = _data.VolumeRate;
_dataPayload.VolumeRate = dataPayload.VolumeRate;
_volumeRate.Value = _dataPayload.VolumeRate;
_data.WideNet = data.WideNet;
_wideNet.Pressed = _data.WideNet;
_data.FilterGases = data.FilterGases;
_dataPayload.WideNet = dataPayload.WideNet;
_wideNet.Pressed = _dataPayload.WideNet;
_dataPayload.FilterGases = dataPayload.FilterGases;
foreach (var value in Enum.GetValues<Gas>())
{
_gasControls[value].Pressed = data.FilterGases.Contains(value);
_gasControls[value].Pressed = dataPayload.FilterGases.Contains(value);
}
}
}

View file

@ -18,7 +18,7 @@ public sealed partial class SensorInfo : BoxContainer
[Dependency] private IEntityManager _entMan = default!;
public Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? OnThresholdUpdate;
public event Action<AtmosSensorData>? SensorDataCopied;
public event Action<AtmosMonitorData>? SensorDataCopied;
private string _address;
private ThresholdControl _pressureThreshold;
@ -27,7 +27,7 @@ public sealed partial class SensorInfo : BoxContainer
private Dictionary<Gas, RichTextLabel> _gasLabels = new();
private Button _copySettings => CCopySettings;
public SensorInfo(AtmosSensorData data, string address)
public SensorInfo(AtmosMonitorData dataPayload, string address)
{
IoCManager.InjectDependencies(this);
var atmosphereSystem = _entMan.System<SharedAtmosphereSystem>();
@ -36,37 +36,37 @@ public sealed partial class SensorInfo : BoxContainer
_address = address;
SensorAddress.Title = Loc.GetString("air-alarm-ui-window-listing-title", ("address", _address), ("state", data.AlarmState));
SensorAddress.Title = Loc.GetString("air-alarm-ui-window-listing-title", ("address", _address), ("state", dataPayload.AlarmState));
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state-indicator",
("color", AirAlarmWindow.ColorForAlarm(data.AlarmState)),
("state", data.AlarmState)));
("color", AirAlarmWindow.ColorForAlarm(dataPayload.AlarmState)),
("state", dataPayload.AlarmState)));
PressureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure-indicator",
("color", AirAlarmWindow.ColorForThreshold(data.Pressure, data.PressureThreshold)),
("pressure", $"{data.Pressure:0.##}")));
("color", AirAlarmWindow.ColorForThreshold(dataPayload.Pressure, dataPayload.PressureThreshold)),
("pressure", $"{dataPayload.Pressure:0.##}")));
TemperatureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature-indicator",
("color", AirAlarmWindow.ColorForThreshold(data.Temperature, data.TemperatureThreshold)),
("tempC", $"{TemperatureHelpers.KelvinToCelsius(data.Temperature):0.#}"),
("temperature", $"{data.Temperature:0.##}")));
("color", AirAlarmWindow.ColorForThreshold(dataPayload.Temperature, dataPayload.TemperatureThreshold)),
("tempC", $"{TemperatureHelpers.KelvinToCelsius(dataPayload.Temperature):0.#}"),
("temperature", $"{dataPayload.Temperature:0.##}")));
foreach (var (gas, amount) in data.Gases)
foreach (var (gas, amount) in dataPayload.Gases)
{
var label = new RichTextLabel();
var fractionGas = amount / data.TotalMoles;
var fractionGas = amount / dataPayload.TotalMoles;
ProtoId<GasPrototype> gasProtoId = atmosphereSystem.GetGas(gas);
var gasName = _prototypeManager.Index(gasProtoId).Name;
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
("gas", Loc.GetString(gasName)),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, dataPayload.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * fractionGas):0.##}")));
GasContainer.AddChild(label);
_gasLabels.Add(gas, label);
var threshold = data.GasThresholds[gas];
var threshold = dataPayload.GasThresholds[gas];
var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title"), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
gasThresholdControl.ThresholdDataChanged += (type, alarmThreshold, arg3) =>
@ -78,10 +78,10 @@ public sealed partial class SensorInfo : BoxContainer
GasContainer.AddChild(gasThresholdControl);
}
_pressureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-pressure-title"), data.PressureThreshold, AtmosMonitorThresholdType.Pressure);
_pressureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-pressure-title"), dataPayload.PressureThreshold, AtmosMonitorThresholdType.Pressure);
PressureThresholdContainer.AddChild(_pressureThreshold);
_temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"),
data.TemperatureThreshold,
dataPayload.TemperatureThreshold,
AtmosMonitorThresholdType.Temperature);
TemperatureThresholdContainer.AddChild(_temperatureThreshold);
@ -97,58 +97,58 @@ public sealed partial class SensorInfo : BoxContainer
_copySettings.OnPressed += _ =>
{
SensorDataCopied?.Invoke(data);
SensorDataCopied?.Invoke(dataPayload);
};
}
public void ChangeData(AtmosSensorData data)
public void ChangeData(AtmosMonitorData dataPayload)
{
IoCManager.InjectDependencies(this);
var atmosphereSystem = _entMan.System<SharedAtmosphereSystem>();
SensorAddress.Title = Loc.GetString("air-alarm-ui-window-listing-title", ("address", _address), ("state", data.AlarmState));
SensorAddress.Title = Loc.GetString("air-alarm-ui-window-listing-title", ("address", _address), ("state", dataPayload.AlarmState));
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state-indicator",
("color", AirAlarmWindow.ColorForAlarm(data.AlarmState)),
("state", data.AlarmState)));
("color", AirAlarmWindow.ColorForAlarm(dataPayload.AlarmState)),
("state", dataPayload.AlarmState)));
PressureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure-indicator",
("color", AirAlarmWindow.ColorForThreshold(data.Pressure, data.PressureThreshold)),
("pressure", $"{data.Pressure:0.##}")));
("color", AirAlarmWindow.ColorForThreshold(dataPayload.Pressure, dataPayload.PressureThreshold)),
("pressure", $"{dataPayload.Pressure:0.##}")));
TemperatureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature-indicator",
("color", AirAlarmWindow.ColorForThreshold(data.Temperature, data.TemperatureThreshold)),
("tempC", $"{TemperatureHelpers.KelvinToCelsius(data.Temperature):0.#}"),
("temperature", $"{data.Temperature:0.##}")));
("color", AirAlarmWindow.ColorForThreshold(dataPayload.Temperature, dataPayload.TemperatureThreshold)),
("tempC", $"{TemperatureHelpers.KelvinToCelsius(dataPayload.Temperature):0.#}"),
("temperature", $"{dataPayload.Temperature:0.##}")));
foreach (var (gas, amount) in data.Gases)
foreach (var (gas, amount) in dataPayload.Gases)
{
if (!_gasLabels.TryGetValue(gas, out var label))
{
continue;
}
var fractionGas = amount / data.TotalMoles;
var fractionGas = amount / dataPayload.TotalMoles;
ProtoId<GasPrototype> gasProtoId = atmosphereSystem.GetGas(gas);
var gasName = _prototypeManager.Index(gasProtoId).Name;
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
("gas", Loc.GetString(gasName)),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, dataPayload.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * fractionGas):0.##}")));
}
_pressureThreshold.UpdateThresholdData(data.PressureThreshold, data.Pressure);
_temperatureThreshold.UpdateThresholdData(data.TemperatureThreshold, data.Temperature);
_pressureThreshold.UpdateThresholdData(dataPayload.PressureThreshold, dataPayload.Pressure);
_temperatureThreshold.UpdateThresholdData(dataPayload.TemperatureThreshold, dataPayload.Temperature);
foreach (var (gas, control) in _gasThresholds)
{
if (!data.GasThresholds.TryGetValue(gas, out var threshold))
if (!dataPayload.GasThresholds.TryGetValue(gas, out var threshold))
{
continue;
}
control.UpdateThresholdData(threshold, data.Gases[gas] / data.TotalMoles);
control.UpdateThresholdData(threshold, dataPayload.Gases[gas] / dataPayload.TotalMoles);
}
}

View file

@ -4,7 +4,7 @@ using System.Numerics;
using Content.Client.Pinpointer.UI;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Medical.SuitSensors;
using Content.Shared.StatusIcon;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
@ -98,8 +98,9 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
var uniqueSensors = uniqueSensorsMap.Values.ToList();
// Order sensor data
var orderedSensors = uniqueSensors.OrderBy(n => n.Name).OrderBy(j => j.Job);
var assignedSensors = new HashSet<SuitSensorStatus>();
// TODO too much evil LINQ going on here
var orderedSensors = uniqueSensors.OrderBy(n => n.Name).ThenBy(j => j.Job);
var assignedSensors = new List<SuitSensorStatus>();
var departments = uniqueSensors.SelectMany(d => d.JobDepartments).Distinct().OrderBy(n => n);
// Create department labels and populate lists

View file

@ -180,9 +180,10 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
private int AddSubnet(ProtoId<DeviceFrequencyPrototype> subnet)
{
var name = subnet;
if (_prototypeManager.TryIndex(subnet, out var frequency))
if (_prototypeManager.TryIndex(subnet, out var frequency)
&& frequency.Name != null)
{
name = Loc.GetString(frequency.Name ?? subnet);
name = Loc.GetString(frequency.Name);
}
SubnetSelector.AddItem(name);

View file

@ -70,7 +70,7 @@ public sealed class DeviceLinkingTest : GameTest
sinkComp);
// Send a signal to the port
Assert.DoesNotThrow(() => { deviceLinkSys.InvokePort(sourceEnt, "Output", null, sourceComp); },
Assert.DoesNotThrow(() => { deviceLinkSys.InvokePort((sourceEnt, sourceComp), "Output"); },
$"Exception thrown while triggering port {port.Id} of the sink device.");
mapSys.DeleteMap(mapId);

View file

@ -2,7 +2,6 @@ using System.Numerics;
using Content.IntegrationTests.Fixtures;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.DeviceNetwork;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Content.Shared.DeviceNetwork.Components;
@ -58,22 +57,20 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork
var deviceNetSystem = entityManager.EntitySysManager.GetEntitySystem<DeviceNetworkSystem>();
var deviceNetTestSystem = entityManager.EntitySysManager.GetEntitySystem<DeviceNetworkTestSystem>();
EntityUid device1 = default;
EntityUid device2 = default;
DeviceNetworkComponent networkComponent1 = null;
DeviceNetworkComponent networkComponent2 = null;
var testValue = "test";
var payload = new NetworkPayload
{
["Test"] = testValue,
["testnumber"] = 1,
["testbool"] = true
};
await server.WaitAssertion(() =>
{
var payload = new TestPayload
{
TestString = "test",
TestNumber = 1,
TestBool = true
};
device1 = entityManager.SpawnEntity("DummyNetworkDevice", MapCoordinates.Nullspace);
Assert.That(entityManager.TryGetComponent(device1, out networkComponent1), Is.True);
@ -94,15 +91,8 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork
Assert.That(networkComponent1.Address, Is.Not.EqualTo(networkComponent2.Address));
});
deviceNetSystem.QueuePacket(device1, networkComponent2.Address, payload, networkComponent2.ReceiveFrequency.Value);
});
await server.WaitRunTicks(2);
await server.WaitIdleAsync();
await server.WaitAssertion(() =>
{
Assert.That(payload, Is.EquivalentTo(deviceNetTestSystem.LastPayload));
deviceNetSystem.SendPacket(device1, networkComponent2.Address, ref payload, networkComponent2.ReceiveFrequency.Value);
Assert.That(payload, Is.EqualTo(deviceNetTestSystem.LastPayload));
});
}
@ -124,14 +114,6 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork
DeviceNetworkComponent networkComponent2 = null;
WirelessNetworkComponent wirelessNetworkComponent = null;
var testValue = "test";
var payload = new NetworkPayload
{
["Test"] = testValue,
["testnumber"] = 1,
["testbool"] = true
};
await server.WaitAssertion(() =>
{
device1 = entityManager.SpawnEntity("WirelessNetworkDeviceDummy", coordinates);
@ -158,33 +140,28 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork
Assert.That(networkComponent1.Address, Is.Not.EqualTo(networkComponent2.Address));
});
deviceNetSystem.QueuePacket(device1, networkComponent2.Address, payload, networkComponent2.ReceiveFrequency.Value);
});
await server.WaitRunTicks(2);
await server.WaitIdleAsync();
await server.WaitAssertion(() =>
{
Assert.That(payload, Is.EqualTo(deviceNetTestSystem.LastPayload).AsCollection);
payload = new NetworkPayload
var payload = new TestPayload
{
["Wirelesstest"] = 5
TestString = "test",
TestNumber = 1,
TestBool = true
};
deviceNetSystem.SendPacket(device1, networkComponent2.Address, ref payload, networkComponent2.ReceiveFrequency.Value);
Assert.That(payload, Is.EqualTo(deviceNetTestSystem.LastPayload));
wirelessNetworkComponent.Range = 0;
deviceNetSystem.QueuePacket(device1, networkComponent2.Address, payload, networkComponent2.ReceiveFrequency.Value);
});
var secondPayload = new SecondTestPayload
{
TestString = "test",
TestNumber = 1,
TestBool = true
};
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
await server.WaitAssertion(() =>
{
Assert.That(payload, Is.Not.EqualTo(deviceNetTestSystem.LastPayload).AsCollection);
deviceNetSystem.SendPacket(device1, networkComponent2.Address, ref secondPayload, networkComponent2.ReceiveFrequency.Value);
Assert.That(secondPayload, Is.Not.EqualTo(deviceNetTestSystem.LastPayloadSecond));
});
}
@ -205,15 +182,6 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork
DeviceNetworkComponent networkComponent1 = null;
DeviceNetworkComponent networkComponent2 = null;
WiredNetworkComponent wiredNetworkComponent = null;
var grid = testMap.Grid.Comp;
var testValue = "test";
var payload = new NetworkPayload
{
["Test"] = testValue,
["testnumber"] = 1,
["testbool"] = true
};
await server.WaitRunTicks(2);
await server.WaitIdleAsync();
@ -244,27 +212,20 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork
Assert.That(networkComponent1.Address, Is.Not.EqualTo(networkComponent2.Address));
});
deviceNetSystem.QueuePacket(device1, networkComponent2.Address, payload, networkComponent2.ReceiveFrequency.Value);
});
var payload = new TestPayload
{
TestString = "test",
TestNumber = 1,
TestBool = true
};
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
await server.WaitAssertion(() =>
{
//CollectionAssert.AreNotEqual(deviceNetTestSystem.LastPayload, payload);
deviceNetSystem.SendPacket(device1, networkComponent2.Address, ref payload, networkComponent2.ReceiveFrequency.Value);
entityManager.SpawnEntity("CableApcExtension", coordinates);
deviceNetSystem.QueuePacket(device1, networkComponent2.Address, payload, networkComponent2.ReceiveFrequency.Value);
});
deviceNetSystem.SendPacket(device1, networkComponent2.Address, ref payload, networkComponent2.ReceiveFrequency.Value);
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
await server.WaitAssertion(() =>
{
Assert.That(payload, Is.EqualTo(deviceNetTestSystem.LastPayload).AsCollection);
Assert.That(payload, Is.EqualTo(deviceNetTestSystem.LastPayload));
});
}
}

View file

@ -1,30 +1,71 @@
#nullable enable
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Robust.Shared.GameObjects;
using Robust.Shared.Reflection;
using Content.Shared.DeviceNetwork.Components;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.IntegrationTests.Tests.DeviceNetwork;
[Reflect(false)]
public sealed partial class DeviceNetworkTestSystem : EntitySystem
{
public NetworkPayload LastPayload = default;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DeviceNetworkComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<DeviceNetworkComponent, DeviceNetworkPacketData>(OnBaselinePacketReceived);
SubscribeLocalEvent<DeviceNetworkComponent, DeviceNetworkPacketEvent<TestPayload>>(OnTypedPacketReceived);
SubscribeLocalEvent<DeviceNetworkComponent, DeviceNetworkPacketEvent<SecondTestPayload>>(OnTypedPacketReceived);
}
public TestPayload LastPayload = default;
public SecondTestPayload LastPayloadSecond = default;
public TestPayloadClass LastPayloadClass = default!;
public void SendBaselineTestEvent(EntityUid uid)
{
RaiseLocalEvent(uid, new DeviceNetworkPacketEvent(0, "", 0, "", uid, new NetworkPayload()));
var ev = new DeviceNetworkPacketData(0, "", 0, "", uid, new TestPayloadClass());
RaiseLocalEvent(uid, ref ev);
}
private void OnPacketReceived(EntityUid uid, DeviceNetworkComponent component, DeviceNetworkPacketEvent args)
private void OnBaselinePacketReceived(Entity<DeviceNetworkComponent> ent, ref DeviceNetworkPacketData args)
{
LastPayloadClass = (TestPayloadClass) args.Data;
}
private void OnTypedPacketReceived(Entity<DeviceNetworkComponent> ent, ref DeviceNetworkPacketEvent<TestPayload> args)
{
LastPayload = args.Data;
}
private void OnTypedPacketReceived(Entity<DeviceNetworkComponent> ent, ref DeviceNetworkPacketEvent<SecondTestPayload> args)
{
LastPayloadSecond = args.Data;
}
}
[ByRefEvent]
public record struct DeviceNetworkPacketData(
int NetId,
string? Address,
uint Frequency,
string SenderAddress,
EntityUid Sender,
INetworkPayload Data);
public readonly partial record struct TestPayload(string TestString, int TestNumber, bool TestBool) : INetworkPayload;
public readonly partial record struct SecondTestPayload(string TestString, int TestNumber, bool TestBool) : INetworkPayload;
public sealed partial class TestPayloadClass : INetworkPayload
{
[DataField]
public string TestString;
[DataField]
public int TestNumber;
[DataField]
public bool TestBool;
}

View file

@ -128,6 +128,6 @@ public sealed partial class TechAnomalySystem : EntitySystem
private void OnPulse(Entity<TechAnomalyComponent> tech, ref AnomalyPulseEvent args)
{
_signal.InvokePort(tech, tech.Comp.PulsePort);
_signal.InvokePort(tech.Owner, tech.Comp.PulsePort);
}
}

View file

@ -253,8 +253,8 @@ public sealed partial class AtmosAlertsComputerSystem : SharedAtmosAlertsCompute
// Create entry
var netEnt = GetNetEntity(ent);
var entry = new AtmosAlertsComputerEntry
(netEnt,
var entry = new AtmosAlertsComputerEntry(
netEnt,
GetNetCoordinates(entXform.Coordinates),
entDevice.Group,
alarmState,

View file

@ -20,7 +20,7 @@ public sealed partial class AirAlarmComponent : Component
public readonly HashSet<string> KnownDevices = new();
public readonly Dictionary<string, GasVentPumpData> VentData = new();
public readonly Dictionary<string, GasVentScrubberData> ScrubberData = new();
public readonly Dictionary<string, AtmosSensorData> SensorData = new();
public readonly Dictionary<string, AtmosMonitorData> SensorData = new();
public bool CanSync = true;

View file

@ -0,0 +1,23 @@
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.DeviceNetwork;
namespace Content.Server.Atmos.Monitor.Payloads;
/// <summary>
/// A network payload sent to an air alarm to set its mode.
/// </summary>
public partial record struct AirAlarmSetModePayload : INetworkPayload
{
[DataField]
public AirAlarmMode Mode;
}
/// <summary>
/// A network payload sent from an atmos device to an air alarm to update its UI.
/// </summary>
public partial record struct AirAlarmSetDataPayload : INetworkPayload
{
[DataField]
public IAtmosDeviceData Payload;
}

View file

@ -0,0 +1,48 @@
using Content.Shared.Atmos.Monitor;
using Content.Shared.DeviceNetwork;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
namespace Content.Server.Atmos.Monitor.Payloads;
[ImplicitDataDefinitionForInheritors]
public partial interface IAtmosAlarmableSourcePayload : INetworkPayload
{
HashSet<ProtoId<TagPrototype>> Source { get; set; }
}
/// <summary>
/// Broadcasts an atmos alarm.
/// </summary>
public partial record struct AtmosAlarmPayload : IAtmosAlarmableSourcePayload
{
[DataField]
public AtmosAlarmType Type;
[DataField]
public AtmosMonitorThresholdTypeFlags? TrippedThresholds;
[DataField]
public HashSet<ProtoId<TagPrototype>> Source { get; set; } = new();
}
/// <summary>
/// Synchronizes the data of an atmos alarmable to all other alarmable devices.
/// </summary>
public partial record struct AtmosAlarmableSyncAlertsPayload : IAtmosAlarmableSourcePayload
{
[DataField]
public Dictionary<string, AtmosAlarmType> AlarmStates = new();
[DataField]
public HashSet<ProtoId<TagPrototype>> Source { get; set; } = new();
}
/// <summary>
/// Resets the state of an atmos alarmable to Normal.
/// </summary>
public partial record struct AtmosAlarmableResetAllPayload : IAtmosAlarmableSourcePayload
{
[DataField]
public HashSet<ProtoId<TagPrototype>> Source { get; set; } = new();
}

View file

@ -0,0 +1,46 @@
using Content.Server.Atmos.Monitor.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.DeviceNetwork;
namespace Content.Server.Atmos.Monitor.Payloads;
/// <summary>
/// Used for synchronizing the sender device and adding its address to all listeners.
/// </summary>
public partial record struct AtmosMonitorRegisterDevicePayload : INetworkPayload;
/// <summary>
/// Removes the sender device from all listeners of this payload.
/// </summary>
public partial record struct AtmosMonitorDeregisterDevicePayload : INetworkPayload;
/// <summary>
/// Sets specific threshold on the target atmos device.
/// </summary>
public partial record struct AtmosMonitorSetThresholdPayload : INetworkPayload
{
[DataField]
public AtmosMonitorThresholdType Type;
[DataField]
public AtmosAlarmThreshold Threshold;
[DataField]
public Gas? Gas;
}
/// <summary>
/// Sets thresholds on all connected devices.
/// </summary>
public partial record struct AtmosMonitorSetAllThresholdsPayload : INetworkPayload
{
[DataField]
public AtmosMonitorData Data;
}
/// <summary>
/// Sets the alarm state of a <see cref="AtmosMonitorComponent"/> to Normal
/// and broadcasts it to all listening <see cref="AtmosAlarmableComponent"/>.
/// </summary>
public partial record struct AtmosMonitorResetPayload : INetworkPayload;

View file

@ -0,0 +1,8 @@
using Content.Shared.DeviceNetwork;
namespace Content.Server.Atmos.Monitor.Payloads;
/// <summary>
/// A general payload that when sent to an atmos device forces it to respond with its data payload.
/// </summary>
public partial record struct AtmosSyncPayload : INetworkPayload;

View file

@ -1,7 +1,6 @@
using Content.Server.Atmos.Monitor.Components;
using Content.Server.Atmos.Monitor.Systems;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;

View file

@ -12,28 +12,22 @@ 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;
using Content.Shared.Interaction;
using Content.Shared.Power;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
using System.Linq;
using Content.Server.Atmos.Monitor.Payloads;
using Content.Shared.Atmos.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.UserInterface;
namespace Content.Server.Atmos.Monitor.Systems;
// AirAlarm system - specific for atmos devices, rather than
// atmos monitors.
//
// oh boy, message passing!
//
// Commands should always be sent into packet's Command
// data key. In response, a packet will be transmitted
// with the response type as its command, and the
// response data in its data key.
public sealed partial class AirAlarmSystem : EntitySystem
{
[Dependency] private AccessReaderSystem _access = default!;
@ -45,26 +39,31 @@ public sealed partial class AirAlarmSystem : EntitySystem
[Dependency] private DeviceListSystem _deviceList = default!;
[Dependency] private PopupSystem _popup = default!;
[Dependency] private UserInterfaceSystem _ui = default!;
[Dependency] private EntityQuery<DeviceNetworkComponent> _deviceNetworkQuery = default!;
#region Device Network API
/// <summary>
/// Command to set an air alarm's mode.
/// </summary>
public const string AirAlarmSetMode = "air_alarm_set_mode";
// -- API --
/// <summary>
/// Set the data for an air alarm managed device.
/// </summary>
/// <param name="address">The address of the device.</param>
/// <param name="data">The data to send to the device.</param>
public void SetData(EntityUid uid, string address, IAtmosDeviceData data)
public void SetData(EntityUid uid, string address, GasVentPumpData payload)
{
_atmosDevNet.SetDeviceState(uid, address, data);
_atmosDevNet.Sync(uid, address);
var sendPayload = new GasVentPumpSetDataPayload { Data = payload };
_deviceNet.SendPacket(uid, address, ref sendPayload);
SetDeviceDataInternal(uid, address, payload);
}
public void SetData(EntityUid uid, string address, GasVentScrubberData payload)
{
var sendPayload = new GasVentScrubberSetDataPayload { Data = payload };
_deviceNet.SendPacket(uid, address, ref sendPayload);
SetDeviceDataInternal(uid, address, payload);
}
private void SetDeviceDataInternal(EntityUid uid, string address, IAtmosDeviceData payload)
{
var setPayload = new AirAlarmSetDataPayload { Payload = payload };
_deviceNet.SendPacket(uid, address, ref setPayload);
}
/// <summary>
@ -116,32 +115,26 @@ public sealed partial class AirAlarmSystem : EntitySystem
private void SetThreshold(EntityUid uid, string address, AtmosMonitorThresholdType type,
AtmosAlarmThreshold threshold, Gas? gas = null)
{
var payload = new NetworkPayload
var payload = new AtmosMonitorSetThresholdPayload
{
[DeviceNetworkConstants.Command] = AtmosMonitorSystem.AtmosMonitorSetThresholdCmd,
[AtmosMonitorSystem.AtmosMonitorThresholdDataType] = type,
[AtmosMonitorSystem.AtmosMonitorThresholdData] = threshold,
Type = type,
Threshold = threshold,
Gas = gas,
};
if (gas != null)
{
payload.Add(AtmosMonitorSystem.AtmosMonitorThresholdGasType, gas);
}
_deviceNet.QueuePacket(uid, address, payload);
_deviceNet.SendPacket(uid, address, ref payload);
SyncDevice(uid, address);
}
private void SetAllThresholds(EntityUid uid, string address, AtmosSensorData data)
private void SetAllThresholds(EntityUid uid, string address, AtmosMonitorData dataPayload)
{
var payload = new NetworkPayload
var payload = new AtmosMonitorSetAllThresholdsPayload
{
[DeviceNetworkConstants.Command] = AtmosMonitorSystem.AtmosMonitorSetAllThresholdsCmd,
[AtmosMonitorSystem.AtmosMonitorAllThresholdData] = data
Data = dataPayload
};
_deviceNet.QueuePacket(uid, address, payload);
_deviceNet.SendPacket(uid, address, ref payload);
SyncDevice(uid, address);
}
@ -155,13 +148,12 @@ public sealed partial class AirAlarmSystem : EntitySystem
if (TryComp<AtmosMonitorComponent>(uid, out var monitor) && !monitor.NetEnabled)
return;
var payload = new NetworkPayload
var payload = new AirAlarmSetModePayload
{
[DeviceNetworkConstants.Command] = AirAlarmSetMode,
[AirAlarmSetMode] = mode
Mode = mode,
};
_deviceNet.QueuePacket(uid, null, payload);
_deviceNet.SendPacket(uid, null, ref payload);
}
#endregion
@ -170,7 +162,7 @@ public sealed partial class AirAlarmSystem : EntitySystem
public override void Initialize()
{
SubscribeLocalEvent<AirAlarmComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
base.Initialize();
SubscribeLocalEvent<AirAlarmComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
SubscribeLocalEvent<AirAlarmComponent, AtmosAlarmEvent>(OnAtmosAlarm);
SubscribeLocalEvent<AirAlarmComponent, PowerChangedEvent>(OnPowerChanged);
@ -178,7 +170,7 @@ public sealed partial class AirAlarmSystem : EntitySystem
SubscribeLocalEvent<AirAlarmComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<AirAlarmComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<AirAlarmComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<AirAlarmComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<AirAlarmComponent, AfterActivatableUIOpenEvent>(OnActivate);
Subs.BuiEvents<AirAlarmComponent>(SharedAirAlarmInterfaceKey.Key, subs =>
{
@ -252,21 +244,8 @@ public sealed partial class AirAlarmSystem : EntitySystem
_activeUserInterfaces.Remove(uid);
}
private void OnActivate(EntityUid uid, AirAlarmComponent component, ActivateInWorldEvent args)
private void OnActivate(EntityUid uid, AirAlarmComponent component, AfterActivatableUIOpenEvent args)
{
if (!args.Complex)
return;
if (TryComp<WiresPanelComponent>(uid, out var panel) && panel.Open)
{
args.Handled = false;
return;
}
if (!this.IsPowered(uid, EntityManager))
return;
_ui.OpenUi(uid, SharedAirAlarmInterfaceKey.Key, args.User);
AddActiveInterface(uid);
SyncAllDevices(uid);
UpdateUI(uid, component);
@ -360,7 +339,7 @@ public sealed partial class AirAlarmSystem : EntitySystem
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);
SetData(uid, addr, ventData);
}
break;
@ -368,11 +347,11 @@ public sealed partial class AirAlarmSystem : EntitySystem
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);
SetData(uid, addr, scrubberData);
}
break;
case AtmosSensorData sensorData:
case AtmosMonitorData sensorData:
foreach (string addr in component.SensorData.Keys)
{
SetAllThresholds(uid, addr, sensorData);
@ -430,14 +409,12 @@ public sealed partial class AirAlarmSystem : EntitySystem
if (component.State != args.AlarmType)
{
TryComp<DeviceLinkSourceComponent>(uid, out var source);
// send low to old state's port
_deviceLink.SendSignal(uid, GetPort(component), false, source);
_deviceLink.SendSignal(uid, GetPort(component), false);
// send high to new state's port, along with updating the cached state
component.State = args.AlarmType;
_deviceLink.SendSignal(uid, GetPort(component), true, source);
_deviceLink.SendSignal(uid, GetPort(component), true);
}
UpdateUI(uid, component);
@ -517,7 +494,7 @@ public sealed partial class AirAlarmSystem : EntitySystem
}
/// <summary>
/// Sets device data. Practically a wrapper around the packet sending function, SetData.
/// Sets device data.
/// </summary>
/// <param name="address">The address to send the new data to.</param>
/// <param name="devData">The device data to be sent.</param>
@ -529,52 +506,44 @@ public sealed partial class AirAlarmSystem : EntitySystem
}
devData.Dirty = true;
SetData(uid, address, devData);
devData.RaisePayload(uid, address, _deviceNet);
SetDeviceDataInternal(uid, address, devData);
}
private void OnPacketRecv(EntityUid uid, AirAlarmComponent controller, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnSetData(Entity<AirAlarmComponent> ent, ref DeviceNetworkPacketEvent<AirAlarmSetDataPayload> args)
{
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
var (uid, controller) = ent;
if (!controller.CanSync)
return;
switch (cmd)
// Save into component.
// Sync data to interface.
switch (args.Data.Payload)
{
case AtmosDeviceNetworkSystem.SyncData:
if (!args.Data.TryGetValue(AtmosDeviceNetworkSystem.SyncData, out IAtmosDeviceData? data)
|| !controller.CanSync)
break;
// Save into component.
// Sync data to interface.
switch (data)
{
case GasVentPumpData ventData:
if (!controller.VentData.TryAdd(args.SenderAddress, ventData))
controller.VentData[args.SenderAddress] = ventData;
break;
case GasVentScrubberData scrubberData:
if (!controller.ScrubberData.TryAdd(args.SenderAddress, scrubberData))
controller.ScrubberData[args.SenderAddress] = scrubberData;
break;
case AtmosSensorData sensorData:
if (!controller.SensorData.TryAdd(args.SenderAddress, sensorData))
controller.SensorData[args.SenderAddress] = sensorData;
break;
}
controller.KnownDevices.Add(args.SenderAddress);
UpdateUI(uid, controller);
return;
case AirAlarmSetMode:
if (!args.Data.TryGetValue(AirAlarmSetMode, out AirAlarmMode alarmMode))
break;
SetMode(uid, args.SenderAddress, alarmMode, uiOnly: false);
return;
case GasVentPumpData ventData:
if (!controller.VentData.TryAdd(args.SenderAddress, ventData))
controller.VentData[args.SenderAddress] = ventData;
break;
case GasVentScrubberData scrubberData:
if (!controller.ScrubberData.TryAdd(args.SenderAddress, scrubberData))
controller.ScrubberData[args.SenderAddress] = scrubberData;
break;
case AtmosMonitorData sensorData:
if (!controller.SensorData.TryAdd(args.SenderAddress, sensorData))
controller.SensorData[args.SenderAddress] = sensorData;
break;
}
controller.KnownDevices.Add(args.SenderAddress);
UpdateUI(uid, controller);
}
[SubscribeLocalEvent]
private void OnSetMode(Entity<AirAlarmComponent> ent, ref DeviceNetworkPacketEvent<AirAlarmSetModePayload> args)
{
SetMode(ent, args.SenderAddress, args.Data.Mode, uiOnly: false);
}
#endregion

View file

@ -1,16 +1,15 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Atmos.Monitor.Components;
using Content.Server.Atmos.Monitor.Payloads;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.Atmos.Monitor;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power;
using Content.Shared.Tag;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Content.Shared.DeviceNetwork.Components;
namespace Content.Server.Atmos.Monitor.Systems;
@ -22,30 +21,10 @@ public sealed partial class AtmosAlarmableSystem : EntitySystem
[Dependency] private DeviceNetworkSystem _deviceNet = default!;
[Dependency] private AtmosDeviceNetworkSystem _atmosDevNetSystem = default!;
/// <summary>
/// An alarm. Has three valid states: Normal, Warning, Danger.
/// Will attempt to fetch the tags from the alarming entity
/// to send over.
/// </summary>
public const string AlertCmd = "atmos_alarm";
public const string AlertSource = "atmos_alarm_source";
public const string AlertTypes = "atmos_alarm_types";
/// <summary>
/// Syncs alerts from this alarm receiver to other alarm receivers.
/// Creates a network effect as a result. Note: if the alert receiver
/// is not aware of the device beforehand, it will not sync.
/// </summary>
public const string SyncAlerts = "atmos_alarmable_sync_alerts";
public const string ResetAll = "atmos_alarmable_reset_all";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AtmosAlarmableComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<AtmosAlarmableComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
SubscribeLocalEvent<AtmosAlarmableComponent, PowerChangedEvent>(OnPowerChange);
}
@ -78,92 +57,73 @@ public sealed partial class AtmosAlarmableSystem : EntitySystem
}
}
private void OnPacketRecv(EntityUid uid, AtmosAlarmableComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnAlarmPayload(Entity<AtmosAlarmableComponent> ent, ref DeviceNetworkPacketEvent<AtmosAlarmPayload> args)
{
if (component.IgnoreAlarms) return;
if (!TryComp(uid, out DeviceNetworkComponent? netConn))
var payload = args.Data;
if (!CheckTags(ent, payload))
return;
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)
|| !args.Data.TryGetValue(AlertSource, out HashSet<ProtoId<TagPrototype>>? sourceTags))
var (uid, component) = ent;
var isValid = payload.TrippedThresholds == null
|| component.MonitorAlertTypes == AtmosMonitorThresholdTypeFlags.None
|| (payload.TrippedThresholds & component.MonitorAlertTypes) != 0;
if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))
{
if (!isValid)
return;
component.NetworkAlarmStates.Add(args.SenderAddress, payload.Type);
}
else
{
// This is because if the alert is no longer valid,
// it may mean that the threshold we need to look at has
// been removed from the threshold types passed:
// basically, we need to reset this state to normal here.
component.NetworkAlarmStates[args.SenderAddress] = isValid ? payload.Type : AtmosAlarmType.Normal;
}
if (!TryGetHighestAlert(uid, out var netMax, component))
{
netMax = AtmosAlarmType.Normal;
}
TryUpdateAlert(uid, netMax.Value, component);
}
[SubscribeLocalEvent]
private void OnResetAllPayload(Entity<AtmosAlarmableComponent> ent, ref DeviceNetworkPacketEvent<AtmosAlarmableResetAllPayload> args)
{
if (!CheckTags(ent, args.Data))
return;
}
var isValid = sourceTags.Any(source => component.SyncWithTags.Contains(source));
Reset(ent.Owner, ent.Comp);
}
if (!isValid)
{
[SubscribeLocalEvent]
private void OnSyncAlertsPayload(Entity<AtmosAlarmableComponent> ent, ref DeviceNetworkPacketEvent<AtmosAlarmableSyncAlertsPayload> args)
{
if (!CheckTags(ent, args.Data))
return;
}
switch (cmd)
foreach (var (key, alarm) in args.Data.AlarmStates)
{
case AlertCmd:
// Set the alert state, and then cache it so we can calculate
// the maximum alarm state at all times.
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out AtmosAlarmType state))
{
break;
}
if (args.Data.TryGetValue(AlertTypes, out AtmosMonitorThresholdTypeFlags types) && component.MonitorAlertTypes != AtmosMonitorThresholdTypeFlags.None)
{
isValid = (types & component.MonitorAlertTypes) != 0;
}
if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))
{
if (!isValid)
{
break;
}
component.NetworkAlarmStates.Add(args.SenderAddress, state);
}
else
{
// This is because if the alert is no longer valid,
// it may mean that the threshold we need to look at has
// been removed from the threshold types passed:
// basically, we need to reset this state to normal here.
component.NetworkAlarmStates[args.SenderAddress] = isValid ? state : AtmosAlarmType.Normal;
}
if (!TryGetHighestAlert(uid, out var netMax, component))
{
netMax = AtmosAlarmType.Normal;
}
TryUpdateAlert(uid, netMax.Value, component);
break;
case ResetAll:
Reset(uid, component);
break;
case SyncAlerts:
if (!args.Data.TryGetValue(SyncAlerts,
out IReadOnlyDictionary<string, AtmosAlarmType>? alarms))
{
break;
}
foreach (var (key, alarm) in alarms)
{
if (!component.NetworkAlarmStates.TryAdd(key, alarm))
{
component.NetworkAlarmStates[key] = alarm;
}
}
if (TryGetHighestAlert(uid, out var maxAlert, component))
{
TryUpdateAlert(uid, maxAlert.Value, component);
}
break;
ent.Comp.NetworkAlarmStates.TryAdd(key, alarm);
ent.Comp.NetworkAlarmStates[key] = alarm;
}
if (TryGetHighestAlert(ent, out var maxAlert, ent.Comp))
{
TryUpdateAlert(ent, maxAlert.Value, ent.Comp);
}
}
private static bool CheckTags<T>(Entity<AtmosAlarmableComponent> ent, T payload) where T : IAtmosAlarmableSourcePayload
{
return !ent.Comp.IgnoreAlarms && payload.Source.Any(source => ent.Comp.SyncWithTags.Contains(source));
}
private void TryUpdateAlert(EntityUid uid, AtmosAlarmType type, AtmosAlarmableComponent alarmable, bool sync = true)
@ -191,14 +151,13 @@ public sealed partial class AtmosAlarmableSystem : EntitySystem
return;
}
var payload = new NetworkPayload
var payload = new AtmosAlarmableSyncAlertsPayload
{
[DeviceNetworkConstants.Command] = SyncAlerts,
[SyncAlerts] = alarmable.NetworkAlarmStates,
[AlertSource] = tags.Tags
AlarmStates = alarmable.NetworkAlarmStates,
Source = tags.Tags,
};
_deviceNet.QueuePacket(uid, address, payload);
_deviceNet.SendPacket(uid, address, ref payload);
}
/// <summary>
@ -228,41 +187,36 @@ public sealed partial class AtmosAlarmableSystem : EntitySystem
alarmable.NetworkAlarmStates[devNet.Address] = alarmType;
}
var payload = new NetworkPayload
var payload = new AtmosAlarmPayload
{
[DeviceNetworkConstants.Command] = AlertCmd,
[DeviceNetworkConstants.CmdSetState] = alarmType,
[AlertSource] = tags.Tags
Type = alarmType,
Source = tags.Tags,
};
_deviceNet.QueuePacket(uid, null, payload);
_deviceNet.SendPacket(uid, null, ref payload);
}
/// <summary>
/// Resets the state of this alarmable to normal.
/// </summary>
/// <param name="uid"></param>
/// <param name="alarmable"></param>
public void Reset(EntityUid uid, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
public void Reset(EntityUid uid, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null, DeviceNetworkComponent? device = null)
{
if (!Resolve(uid, ref alarmable, ref tags, false) || alarmable.LastAlarmState == AtmosAlarmType.Normal)
if (!Resolve(uid, ref alarmable, ref tags, ref device, false) || alarmable.LastAlarmState == AtmosAlarmType.Normal)
{
return;
}
alarmable.NetworkAlarmStates.Clear();
TryUpdateAlert(uid, AtmosAlarmType.Normal, alarmable);
if (alarmable.NetworkAlarmStates.ContainsKey(device.Address))
alarmable.NetworkAlarmStates[device.Address] = AtmosAlarmType.Normal;
if (!alarmable.ReceiveOnly)
{
var payload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = ResetAll,
[AlertSource] = tags.Tags
};
_deviceNet.QueuePacket(uid, null, payload);
var payload = new AtmosMonitorResetPayload();
_deviceNet.SendPacket(uid, null, ref payload);
}
TryUpdateAlert(uid, AtmosAlarmType.Normal, alarmable);
alarmable.NetworkAlarmStates.Clear();
}
public void ResetAllOnNetwork(EntityUid uid, AtmosAlarmableComponent? alarmable = null)

View file

@ -1,6 +1,10 @@
using Content.Server.Atmos.Monitor.Payloads;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.DeviceNetwork;
namespace Content.Server.Atmos.Monitor.Systems;
@ -11,61 +15,23 @@ namespace Content.Server.Atmos.Monitor.Systems;
/// </summary>
public sealed partial class AtmosDeviceNetworkSystem : EntitySystem
{
/// <summary>
/// Register a device's address on this device.
/// </summary>
public const string RegisterDevice = "atmos_register_device";
/// <summary>
/// Deregister a device's address on this device.
/// </summary>
public const string DeregisterDevice = "atmos_deregister_device";
/// <summary>
/// Synchronize the data this device has with the sender.
/// </summary>
public const string SyncData = "atmos_sync_data";
[Dependency] private DeviceNetworkSystem _deviceNet = default!;
public void Register(EntityUid uid, string? address)
{
var registerPayload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = RegisterDevice
};
_deviceNet.QueuePacket(uid, address, registerPayload);
var registerPayload = new AtmosMonitorRegisterDevicePayload();
_deviceNet.SendPacket(uid, address, ref registerPayload);
}
public void Deregister(EntityUid uid, string? address)
{
var deregisterPayload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = DeregisterDevice
};
_deviceNet.QueuePacket(uid, address, deregisterPayload);
var deregisterPayload = new AtmosMonitorDeregisterDevicePayload();
_deviceNet.SendPacket(uid, address, ref deregisterPayload);
}
public void Sync(EntityUid uid, string? address)
{
var syncPayload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = SyncData
};
_deviceNet.QueuePacket(uid, address, syncPayload);
}
public void SetDeviceState(EntityUid uid, string address, IAtmosDeviceData data)
{
var payload = new NetworkPayload()
{
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdSetState,
[DeviceNetworkConstants.CmdSetState] = data
};
_deviceNet.QueuePacket(uid, address, payload);
var payload = new AtmosSyncPayload();
_deviceNet.SendPacket(uid, address, ref payload);
}
}

View file

@ -1,5 +1,6 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Monitor.Components;
using Content.Server.Atmos.Monitor.Payloads;
using Content.Server.Atmos.Piping.EntitySystems;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.NodeContainer.EntitySystems;
@ -12,7 +13,6 @@ using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power;
using Content.Shared.Tag;
@ -31,26 +31,15 @@ public sealed partial class AtmosMonitorSystem : EntitySystem
[Dependency] private DeviceNetworkSystem _deviceNetSystem = default!;
[Dependency] private NodeContainerSystem _nodeContainerSystem = default!;
// Commands
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
public const string AtmosMonitorSetAllThresholdsCmd = "atmos_monitor_set_all_thresholds";
// Packet data
public const string AtmosMonitorThresholdData = "atmos_monitor_threshold_data";
public const string AtmosMonitorAllThresholdData = "atmos_monitor_all_threshold_data";
public const string AtmosMonitorThresholdDataType = "atmos_monitor_threshold_type";
public const string AtmosMonitorThresholdGasType = "atmos_monitor_threshold_gas";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AtmosMonitorComponent, ComponentStartup>(OnAtmosMonitorStartup);
SubscribeLocalEvent<AtmosMonitorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
SubscribeLocalEvent<AtmosMonitorComponent, TileFireEvent>(OnFireEvent);
SubscribeLocalEvent<AtmosMonitorComponent, PowerChangedEvent>(OnPowerChangedEvent);
SubscribeLocalEvent<AtmosMonitorComponent, BeforePacketSentEvent>(BeforePacketRecv);
SubscribeLocalEvent<AtmosMonitorComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceDisabledEvent>(OnAtmosDeviceLeaveAtmosphere);
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceEnabledEvent>(OnAtmosDeviceEnterAtmosphere);
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceTileChangedEvent>(OnAtmosDeviceTileChangedEvent);
@ -112,74 +101,73 @@ public sealed partial class AtmosMonitorSystem : EntitySystem
}
}
private void BeforePacketRecv(EntityUid uid, AtmosMonitorComponent component, BeforePacketSentEvent args)
private void BeforePacketRecv(Entity<AtmosMonitorComponent> ent, ref BeforePacketSentEvent args)
{
if (!component.NetEnabled) args.Cancel();
if (!ent.Comp.NetEnabled)
args.Cancelled = true;
}
private void OnPacketRecv(EntityUid uid, AtmosMonitorComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnRegisterDevice(Entity<AtmosMonitorComponent> ent, ref DeviceNetworkPacketEvent<AtmosMonitorRegisterDevicePayload> args)
{
// sync the internal 'last alarm state' from
// the other alarms, so that we can calculate
// the highest network alarm state at any time
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
ent.Comp.RegisteredDevices.Add(args.SenderAddress);
}
[SubscribeLocalEvent]
private void OnDeregisterDevice(Entity<AtmosMonitorComponent> ent, ref DeviceNetworkPacketEvent<AtmosMonitorDeregisterDevicePayload> args)
{
ent.Comp.RegisteredDevices.Remove(args.SenderAddress);
}
[SubscribeLocalEvent]
private void OnReset(Entity<AtmosMonitorComponent> ent, ref DeviceNetworkPacketEvent<AtmosMonitorResetPayload> args)
{
Reset(ent);
}
[SubscribeLocalEvent]
private void OnSetThreshold(Entity<AtmosMonitorComponent> ent, ref DeviceNetworkPacketEvent<AtmosMonitorSetThresholdPayload> args)
{
SetThreshold(ent, args.Data.Type, args.Data.Threshold, args.Data.Gas);
}
[SubscribeLocalEvent]
private void OnSetAllThresholds(Entity<AtmosMonitorComponent> ent, ref DeviceNetworkPacketEvent<AtmosMonitorSetAllThresholdsPayload> args)
{
SetAllThresholds(ent, args.Data.Data);
}
[SubscribeLocalEvent]
private void OnSyncPayload(Entity<AtmosMonitorComponent> ent, ref DeviceNetworkPacketEvent<AtmosSyncPayload> args)
{
var dataPayload = new AtmosMonitorData();
if (ent.Comp.TileGas != null)
{
return;
var gases = new Dictionary<Gas, float>();
foreach (var gas in Enum.GetValues<Gas>())
{
gases.Add(gas, ent.Comp.TileGas.GetMoles(gas));
}
dataPayload = new AtmosMonitorData(
ent.Comp.TileGas.Pressure,
ent.Comp.TileGas.Temperature,
ent.Comp.TileGas.TotalMoles,
ent.Comp.LastAlarmState,
gases,
ent.Comp.PressureThreshold ?? new(),
ent.Comp.TemperatureThreshold ?? new(),
ent.Comp.GasThresholds ?? new());
}
switch (cmd)
var airAlarm = new AirAlarmSetDataPayload
{
case AtmosDeviceNetworkSystem.RegisterDevice:
component.RegisteredDevices.Add(args.SenderAddress);
break;
case AtmosDeviceNetworkSystem.DeregisterDevice:
component.RegisteredDevices.Remove(args.SenderAddress);
break;
case AtmosAlarmableSystem.ResetAll:
Reset(uid);
// Don't clear alarm states here.
break;
case AtmosMonitorSetThresholdCmd:
if (args.Data.TryGetValue(AtmosMonitorThresholdData, out AtmosAlarmThreshold? thresholdData)
&& args.Data.TryGetValue(AtmosMonitorThresholdDataType, out AtmosMonitorThresholdType? thresholdType))
{
args.Data.TryGetValue(AtmosMonitorThresholdGasType, out Gas? gas);
SetThreshold(uid, thresholdType.Value, thresholdData, gas);
}
break;
case AtmosMonitorSetAllThresholdsCmd:
if (args.Data.TryGetValue(AtmosMonitorAllThresholdData, out AtmosSensorData? allThresholdData))
{
SetAllThresholds(uid, allThresholdData);
}
break;
case AtmosDeviceNetworkSystem.SyncData:
var payload = new NetworkPayload();
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
if (component.TileGas != null)
{
var gases = new Dictionary<Gas, float>();
foreach (var gas in Enum.GetValues<Gas>())
{
gases.Add(gas, component.TileGas.GetMoles(gas));
}
Payload = dataPayload,
};
payload.Add(AtmosDeviceNetworkSystem.SyncData, new AtmosSensorData(
component.TileGas.Pressure,
component.TileGas.Temperature,
component.TileGas.TotalMoles,
component.LastAlarmState,
gases,
component.PressureThreshold ?? new(),
component.TemperatureThreshold ?? new(),
component.GasThresholds ?? new()
));
}
_deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload);
Alert(uid, component.LastAlarmState);
break;
}
dataPayload.RaisePayload(ent.Owner, args.SenderAddress, _deviceNetSystem);
_deviceNetSystem.SendPacket(ent.Owner, args.SenderAddress, ref airAlarm);
Alert(ent, ent.Comp.LastAlarmState);
}
private void OnPowerChangedEvent(Entity<AtmosMonitorComponent> ent, ref PowerChangedEvent args)
@ -375,17 +363,16 @@ public sealed partial class AtmosMonitorSystem : EntitySystem
return;
}
var payload = new NetworkPayload
var payload = new AtmosAlarmPayload
{
[DeviceNetworkConstants.Command] = AtmosAlarmableSystem.AlertCmd,
[DeviceNetworkConstants.CmdSetState] = monitor.LastAlarmState,
[AtmosAlarmableSystem.AlertSource] = tags.Tags,
[AtmosAlarmableSystem.AlertTypes] = monitor.TrippedThresholds
Type = monitor.LastAlarmState,
Source = tags.Tags,
TrippedThresholds = monitor.TrippedThresholds,
};
foreach (var addr in monitor.RegisteredDevices)
{
_deviceNetSystem.QueuePacket(owner, addr, payload);
_deviceNetSystem.SendPacket(owner, addr, ref payload);
}
}
@ -475,14 +462,14 @@ public sealed partial class AtmosMonitorSystem : EntitySystem
/// AtmosSensorData object's thresholds.
/// </summary>
/// <param name="uid">The entity's uid</param>
/// <param name="allThresholdData">An AtmosSensorData object from which the thresholds will be loaded.</param>
public void SetAllThresholds(EntityUid uid, AtmosSensorData allThresholdData)
/// <param name="allThresholdDataPayload">An AtmosSensorData object from which the thresholds will be loaded.</param>
public void SetAllThresholds(EntityUid uid, AtmosMonitorData allThresholdDataPayload)
{
SetThreshold(uid, AtmosMonitorThresholdType.Temperature, allThresholdData.TemperatureThreshold);
SetThreshold(uid, AtmosMonitorThresholdType.Pressure, allThresholdData.PressureThreshold);
SetThreshold(uid, AtmosMonitorThresholdType.Temperature, allThresholdDataPayload.TemperatureThreshold);
SetThreshold(uid, AtmosMonitorThresholdType.Pressure, allThresholdDataPayload.PressureThreshold);
foreach (var gas in Enum.GetValues<Gas>())
{
SetThreshold(uid, AtmosMonitorThresholdType.Gas, allThresholdData.GasThresholds[gas], gas);
SetThreshold(uid, AtmosMonitorThresholdType.Gas, allThresholdDataPayload.GasThresholds[gas], gas);
}
}
}

View file

@ -1,5 +1,5 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Monitor.Systems;
using Content.Server.Atmos.Monitor.Payloads;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
@ -9,11 +9,9 @@ using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Atmos.Piping.Binary.Systems;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Audio;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Content.Shared.DeviceNetwork.Components;
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
{
@ -32,8 +30,6 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
SubscribeLocalEvent<GasVolumePumpComponent, AtmosDeviceUpdateEvent>(OnVolumePumpUpdated);
SubscribeLocalEvent<GasVolumePumpComponent, AtmosDeviceDisabledEvent>(OnVolumePumpLeaveAtmosphere);
SubscribeLocalEvent<GasVolumePumpComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
}
private void OnVolumePumpUpdated(EntityUid uid, GasVolumePumpComponent pump, ref AtmosDeviceUpdateEvent args)
@ -107,25 +103,15 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
_userInterfaceSystem.CloseUi(uid, GasVolumePumpUiKey.Key);
}
private void OnPacketRecv(EntityUid uid, GasVolumePumpComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnSyncPayload(Entity<GasVolumePumpComponent> ent, ref DeviceNetworkPacketEvent<AtmosSyncPayload> args)
{
if (!TryComp(uid, out DeviceNetworkComponent? netConn)
|| !args.Data.TryGetValue(DeviceNetworkConstants.Command, out var cmd))
var data = new GasVolumePumpDataPayload
{
return;
}
LastMolesTransferred = ent.Comp.LastMolesTransferred,
};
var payload = new NetworkPayload();
switch (cmd)
{
case AtmosDeviceNetworkSystem.SyncData:
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
payload.Add(AtmosDeviceNetworkSystem.SyncData, new GasVolumePumpData(component.LastMolesTransferred));
_deviceNetwork.QueuePacket(uid, args.SenderAddress, payload, device: netConn);
return;
}
_deviceNetwork.SendPacket(ent.Owner, args.SenderAddress, ref data);
}
}
}

View file

@ -169,15 +169,15 @@ namespace Content.Server.Atmos.Piping.Unary.Components
};
}
public void FromAirAlarmData(GasVentPumpData data)
public void FromAirAlarmData(GasVentPumpData dataPayload)
{
Enabled = data.Enabled;
IsDirty = data.Dirty;
PumpDirection = data.PumpDirection;
PressureChecks = data.PressureChecks;
ExternalPressureBound = data.ExternalPressureBound;
InternalPressureBound = data.InternalPressureBound;
PressureLockoutOverride = data.PressureLockoutOverride;
Enabled = dataPayload.Enabled;
IsDirty = dataPayload.Dirty;
PumpDirection = dataPayload.PumpDirection;
PressureChecks = dataPayload.PressureChecks;
ExternalPressureBound = dataPayload.ExternalPressureBound;
InternalPressureBound = dataPayload.InternalPressureBound;
PressureLockoutOverride = dataPayload.PressureLockoutOverride;
}
#region GuidebookData

View file

@ -66,18 +66,18 @@ namespace Content.Server.Atmos.Piping.Unary.Components
};
}
public void FromAirAlarmData(GasVentScrubberData data)
public void FromAirAlarmData(GasVentScrubberData dataPayload)
{
Enabled = data.Enabled;
IsDirty = data.Dirty;
PumpDirection = data.PumpDirection;
TransferRate = data.VolumeRate;
WideNet = data.WideNet;
Enabled = dataPayload.Enabled;
IsDirty = dataPayload.Dirty;
PumpDirection = dataPayload.PumpDirection;
TransferRate = dataPayload.VolumeRate;
WideNet = dataPayload.WideNet;
if (!data.FilterGases.SequenceEqual(FilterGases))
if (!dataPayload.FilterGases.SequenceEqual(FilterGases))
{
FilterGases.Clear();
foreach (var gas in data.FilterGases)
foreach (var gas in dataPayload.FilterGases)
FilterGases.Add(gas);
}
}

View file

@ -1,5 +1,5 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Monitor.Systems;
using Content.Server.Atmos.Monitor.Payloads;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
@ -10,9 +10,7 @@ using JetBrains.Annotations;
using Content.Server.Power.EntitySystems;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Piping.Unary.Systems;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Components;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{
@ -27,11 +25,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasThermoMachineComponent, AtmosDeviceUpdateEvent>(OnThermoMachineUpdated);
// Device network
SubscribeLocalEvent<GasThermoMachineComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
}
private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, ref AtmosDeviceUpdateEvent args)
@ -117,24 +111,15 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
}
}
private void OnPacketRecv(EntityUid uid, GasThermoMachineComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnSyncPayload(Entity<GasThermoMachineComponent> ent, ref DeviceNetworkPacketEvent<AtmosSyncPayload> args)
{
if (!TryComp(uid, out DeviceNetworkComponent? netConn)
|| !args.Data.TryGetValue(DeviceNetworkConstants.Command, out var cmd))
return;
var payload = new NetworkPayload();
switch (cmd)
var data = new GasThermoMachineDataPayload
{
case AtmosDeviceNetworkSystem.SyncData:
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
payload.Add(AtmosDeviceNetworkSystem.SyncData, new GasThermoMachineData(component.LastEnergyDelta));
EnergyDelta = ent.Comp.LastEnergyDelta,
};
_deviceNetwork.QueuePacket(uid, args.SenderAddress, payload, device: netConn);
return;
}
_deviceNetwork.SendPacket(ent.Owner, args.SenderAddress, ref data);
}
}
}

View file

@ -1,4 +1,5 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Monitor.Payloads;
using Content.Server.Atmos.Monitor.Systems;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.DeviceLinking.Systems;
@ -17,8 +18,6 @@ using Content.Shared.Atmos.Visuals;
using Content.Shared.Audio;
using Content.Shared.Database;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DoAfter;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Examine;
@ -45,6 +44,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
[Dependency] private SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private IGameTiming _timing = default!;
[Dependency] private PowerReceiverSystem _powerReceiverSystem = default!;
public override void Initialize()
{
base.Initialize();
@ -54,7 +54,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
SubscribeLocalEvent<GasVentPumpComponent, AtmosDeviceEnabledEvent>(OnGasVentPumpEnterAtmosphere);
SubscribeLocalEvent<GasVentPumpComponent, AtmosAlarmEvent>(OnAtmosAlarm);
SubscribeLocalEvent<GasVentPumpComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<GasVentPumpComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
SubscribeLocalEvent<GasVentPumpComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<GasVentPumpComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<GasVentPumpComponent, SignalReceivedEvent>(OnSignalReceived);
@ -216,70 +215,67 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
UpdateState(uid, component);
}
private void OnPacketRecv(EntityUid uid, GasVentPumpComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnSyncPayload(Entity<GasVentPumpComponent> ent, ref DeviceNetworkPacketEvent<AtmosSyncPayload> args)
{
if (!TryComp(uid, out DeviceNetworkComponent? netConn)
|| !args.Data.TryGetValue(DeviceNetworkConstants.Command, out var cmd))
return;
var payload = new NetworkPayload();
switch (cmd)
var data = ent.Comp.ToAirAlarmData();
var airAlarm = new AirAlarmSetDataPayload
{
case AtmosDeviceNetworkSystem.SyncData:
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
payload.Add(AtmosDeviceNetworkSystem.SyncData, component.ToAirAlarmData());
Payload = data,
};
_deviceNetSystem.SendPacket(ent.Owner, args.SenderAddress, ref airAlarm);
}
_deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload, device: netConn);
[SubscribeLocalEvent]
private void OnSetPayload(Entity<GasVentPumpComponent> ent, ref DeviceNetworkPacketEvent<GasVentPumpSetDataPayload> args)
{
var setData = args.Data.Data;
var previous = ent.Comp.ToAirAlarmData();
return;
case DeviceNetworkConstants.CmdSetState:
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);
return;
if (previous.Enabled != setData.Enabled)
{
string enabled = setData.Enabled ? "enabled" : "disabled";
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(ent)} {enabled}");
}
if (previous.PumpDirection != setData.PumpDirection)
_adminLogger.Add(LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(ent)} direction changed to {setData.PumpDirection}");
if (previous.PressureChecks != setData.PressureChecks)
_adminLogger.Add(LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(ent)} pressure check changed to {setData.PressureChecks}");
if (previous.ExternalPressureBound != setData.ExternalPressureBound)
{
_adminLogger.Add(
LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(ent)} external pressure bound changed from {previous.ExternalPressureBound} kPa to {setData.ExternalPressureBound} kPa"
);
}
if (previous.InternalPressureBound != setData.InternalPressureBound)
{
_adminLogger.Add(
LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(ent)} 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(ent)} pressure lockout override {enabled}");
}
ent.Comp.FromAirAlarmData(setData);
UpdateState(ent, ent.Comp);
}
private void OnInit(EntityUid uid, GasVentPumpComponent component, ComponentInit args)

View file

@ -1,4 +1,5 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Monitor.Payloads;
using Content.Server.Atmos.Monitor.Systems;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.DeviceNetwork.Systems;
@ -14,8 +15,6 @@ using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Atmos.Piping.Unary.Visuals;
using Content.Shared.Audio;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power;
using Content.Shared.Tools.Systems;
@ -46,7 +45,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
SubscribeLocalEvent<GasVentScrubberComponent, AtmosDeviceDisabledEvent>(OnVentScrubberLeaveAtmosphere);
SubscribeLocalEvent<GasVentScrubberComponent, AtmosAlarmEvent>(OnAtmosAlarm);
SubscribeLocalEvent<GasVentScrubberComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<GasVentScrubberComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
SubscribeLocalEvent<GasVentScrubberComponent, WeldableChangedEvent>(OnWeldChanged);
}
@ -146,69 +144,68 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
UpdateState(uid, component);
}
private void OnPacketRecv(EntityUid uid, GasVentScrubberComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnSyncPayload(Entity<GasVentScrubberComponent> ent, ref DeviceNetworkPacketEvent<AtmosSyncPayload> args)
{
if (!TryComp(uid, out DeviceNetworkComponent? netConn)
|| !args.Data.TryGetValue(DeviceNetworkConstants.Command, out var cmd))
return;
var payload = new NetworkPayload();
switch (cmd)
var data = ent.Comp.ToAirAlarmData();
var airAlarm = new AirAlarmSetDataPayload
{
case AtmosDeviceNetworkSystem.SyncData:
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
payload.Add(AtmosDeviceNetworkSystem.SyncData, component.ToAirAlarmData());
Payload = data,
};
_deviceNetSystem.SendPacket(ent.Owner, args.SenderAddress, ref airAlarm);
}
_deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload, device: netConn);
[SubscribeLocalEvent]
private void OnSetPayload(Entity<GasVentScrubberComponent> ent, ref DeviceNetworkPacketEvent<GasVentScrubberSetDataPayload> args)
{
var setData = args.Data.Data;
var previous = ent.Comp.ToAirAlarmData();
return;
case DeviceNetworkConstants.CmdSetState:
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);
return;
if (previous.Enabled != setData.Enabled)
{
string enabled = setData.Enabled ? "enabled" : "disabled";
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(ent)} {enabled}");
}
// TODO: IgnoreAlarms?
if (previous.PumpDirection != setData.PumpDirection)
_adminLogger.Add(LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(ent)} 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(ent)} {gas} filtering disabled");
foreach (Gas gas in setData.FilterGases)
if (!previous.FilterGases.Contains(gas))
_adminLogger.Add(LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(ent)} {gas} filtering enabled");
if (previous.VolumeRate != setData.VolumeRate)
{
_adminLogger.Add(
LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(ent)} 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(ent)} WideNet {enabled}");
}
ent.Comp.FromAirAlarmData(setData);
UpdateState(ent, ent.Comp);
}
/// <summary>

View file

@ -3,7 +3,6 @@ using Content.Server.Chat.Systems;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Popups;
using Content.Server.RoundEnd;
using Content.Server.Screens.Components;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Systems;
using Content.Shared.Access.Components;
@ -13,10 +12,10 @@ using Content.Shared.CCVar;
using Content.Shared.Chat;
using Content.Shared.Communications;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Popups;
using Content.Shared.Screens;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
@ -252,12 +251,12 @@ namespace Content.Server.Communications
if (!TryComp<DeviceNetworkComponent>(uid, out var net))
return;
var payload = new NetworkPayload
var payload = new ScreenTextPayload
{
[ScreenMasks.Text] = message.Message
Text = message.Message,
};
_deviceNetworkSystem.QueuePacket(uid, null, payload, net.TransmitFrequency);
_deviceNetworkSystem.SendPacket(uid, null, ref payload, net.TransmitFrequency);
_adminLogger.Add(LogType.DeviceNetwork, LogImpact.Low, $"{ToPrettyString(message.Actor):player} has sent the following broadcast: {message.Message:msg}");
}

View file

@ -16,5 +16,5 @@ public sealed partial class SpawnOnOverloadComponent : Component
/// The entity prototype to spawn when the device overloads
/// </summary>
[DataField("spawnedPrototype")]
public EntProtoId Prototype = "PuddleSparkle";
public EntProtoId Prototype = "EffectSparks";
}

View file

@ -1,6 +1,3 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
@ -18,28 +15,65 @@ public sealed partial class DeviceLinkSystem : SharedDeviceLinkSystem
{
base.Initialize();
SubscribeLocalEvent<DeviceLinkSinkComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<DeviceLinkSourceComponent, NewLinkEvent>(OnNewLink);
// TODO: make an engine PR to allow for auto-generated relay subscriptions
// Should be doable by using reflection on marker interfaces and then adding them to the auto-generated subscriptions
// I know it looks absolutely hilarious and horrible, but uuuhhhh anything to avoid boxing allocations!!!!!!!! :godo:
SubscribeLocalEvent<DeviceLinkSinkComponent, DeviceNetworkPacketEvent<SignalPayload<LogicStatePayload>>>(OnSignalReceived);
}
private void OnSignalReceived<T>(Entity<DeviceLinkSinkComponent> ent,
ref DeviceNetworkPacketEvent<SignalPayload<T>> args) where T : ISignalNetworkPayload
{
var (uid, component) = ent;
if (!component.Ports.Contains(args.Data.InvokedPort))
return;
var eventArgs = new SignalReceivedEvent<T>(args.Data.InvokedPort, args.Data.Payload, args.Sender);
RaiseLocalEvent(uid, ref eventArgs);
}
#region Sending & Receiving
public override void InvokePort(EntityUid uid, string port, NetworkPayload? data = null, DeviceLinkSourceComponent? sourceComponent = null)
public override void InvokePort(Entity<DeviceLinkSourceComponent?> ent, string port)
{
if (!Resolve(uid, ref sourceComponent) || !sourceComponent.Outputs.TryGetValue(port, out var sinks))
if (!DeviceLinkSourceQuery.Resolve(ent.Owner, ref ent.Comp) ||
!ent.Comp.Outputs.TryGetValue(port, out var sinks))
return;
foreach (var sinkUid in sinks)
{
if (!sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var links))
if (!ent.Comp.LinkedPorts.TryGetValue(sinkUid, out var links))
continue;
if (!TryComp<DeviceLinkSinkComponent>(sinkUid, out var sinkComponent))
if (!DeviceLinkSinkQuery.TryComp(sinkUid, out var sinkComponent))
continue;
foreach (var (source, sink) in links)
{
if (source == port)
InvokeDirect((uid, sourceComponent), (sinkUid, sinkComponent), source, sink, data);
InvokeDirect((ent.Owner, ent.Comp), (sinkUid, sinkComponent), sink);
}
}
}
public override void InvokePort<T>(Entity<DeviceLinkSourceComponent?> ent, string port, ref T data)
{
if (!DeviceLinkSourceQuery.Resolve(ent.Owner, ref ent.Comp) ||
!ent.Comp.Outputs.TryGetValue(port, out var sinks))
return;
foreach (var sinkUid in sinks)
{
if (!ent.Comp.LinkedPorts.TryGetValue(sinkUid, out var links))
continue;
if (!DeviceLinkSinkQuery.TryComp(sinkUid, out var sinkComponent))
continue;
foreach (var (source, sink) in links)
{
if (source == port)
InvokeDirect(ent!, (sinkUid, sinkComponent), sink, ref data);
}
}
}
@ -47,9 +81,11 @@ public sealed partial class DeviceLinkSystem : SharedDeviceLinkSystem
/// <summary>
/// Raises an event on or sends a network packet directly to a sink from a source.
/// </summary>
private void InvokeDirect(Entity<DeviceLinkSourceComponent> source, Entity<DeviceLinkSinkComponent?> sink, string sourcePort, string sinkPort, NetworkPayload? data)
private void InvokeDirect(Entity<DeviceLinkSourceComponent> source,
Entity<DeviceLinkSinkComponent?> sink,
string sinkPort)
{
if (!Resolve(sink, ref sink.Comp))
if (!DeviceLinkSinkQuery.Resolve(sink, ref sink.Comp))
return;
var invokeCounter = GetEffectiveInvokeCounter(sink.Comp);
@ -72,41 +108,79 @@ public sealed partial class DeviceLinkSystem : SharedDeviceLinkSystem
return;
}
var payload = new NetworkPayload()
var payload = new SignalPayload
{
[InvokedPort] = sinkPort
InvokedPort = sinkPort,
};
if (data != null)
// force using wireless network so things like atmos devices are able to send signals
_deviceNetworkSystem.SendPacket(source.Owner,
sinkNetwork.Address,
ref payload,
sinkNetwork.ReceiveFrequency,
(int)DeviceNetIdDefaults.Wireless);
}
/// <summary>
/// Raises an event on or sends a network packet directly to a sink from a source.
/// </summary>
private void InvokeDirect<T>(Entity<DeviceLinkSourceComponent> source,
Entity<DeviceLinkSinkComponent?> sink,
string sinkPort,
ref T data) where T : ISignalNetworkPayload
{
if (!DeviceLinkSinkQuery.Resolve(sink, ref sink.Comp))
return;
var invokeCounter = GetEffectiveInvokeCounter(sink.Comp);
if (invokeCounter > sink.Comp.InvokeLimit)
{
//Prevent overriding the invoked port
data.Remove(InvokedPort);
foreach (var (key, value) in data)
{
payload.Add(key, value);
}
SetInvokeCounter(sink.Comp, 0);
var args = new DeviceLinkOverloadedEvent();
RaiseLocalEvent(sink, ref args);
RemoveAllFromSink(sink, sink.Comp);
return;
}
SetInvokeCounter(sink.Comp, invokeCounter + 1);
//Just skip using device networking if the source or the sink doesn't support it
if (!HasComp<DeviceNetworkComponent>(source) || !TryComp<DeviceNetworkComponent>(sink, out var sinkNetwork))
{
var eventArgs = new SignalReceivedEvent(sinkPort, source);
RaiseLocalEvent(sink, ref eventArgs);
return;
}
var payload = new SignalPayload<T>
{
InvokedPort = sinkPort,
Payload = data,
};
// force using wireless network so things like atmos devices are able to send signals
var network = (int) DeviceNetworkComponent.DeviceNetIdDefaults.Wireless;
_deviceNetworkSystem.QueuePacket(source, sinkNetwork.Address, payload, sinkNetwork.ReceiveFrequency, network);
_deviceNetworkSystem.SendPacket(source.Owner,
sinkNetwork.Address,
ref payload,
sinkNetwork.ReceiveFrequency,
(int)DeviceNetIdDefaults.Wireless);
}
/// <summary>
/// Helper function that invokes a port with a high/low binary logic signal.
/// </summary>
public void SendSignal(EntityUid uid, string port, bool signal, DeviceLinkSourceComponent? comp = null)
public void SendSignal(Entity<DeviceLinkSourceComponent?> ent, string port, bool signal)
{
if (!Resolve(uid, ref comp))
if (!DeviceLinkSourceQuery.Resolve(ent.Owner, ref ent.Comp))
return;
var data = new NetworkPayload
var data = new LogicStatePayload
{
[DeviceNetworkConstants.LogicState] = signal ? SignalState.High : SignalState.Low
State = signal ? SignalState.High : SignalState.Low
};
InvokePort(uid, port, data, comp);
InvokePort(ent, port, ref data);
comp.LastSignals[port] = signal;
ent.Comp.LastSignals[port] = signal;
}
/// <summary>
@ -116,7 +190,7 @@ public sealed partial class DeviceLinkSystem : SharedDeviceLinkSystem
/// </summary>
public void ClearSignal(Entity<DeviceLinkSourceComponent?> ent, string port)
{
if (!Resolve(ent, ref ent.Comp))
if (!DeviceLinkSourceQuery.Resolve(ent, ref ent.Comp))
return;
ent.Comp.LastSignals.Remove(port);
@ -126,18 +200,21 @@ public sealed partial class DeviceLinkSystem : SharedDeviceLinkSystem
/// Checks if the payload has a port defined and if the port is present on the sink.
/// Raises a <see cref="SignalReceivedEvent"/> containing the payload when the check passes
/// </summary>
private void OnPacketReceived(EntityUid uid, DeviceLinkSinkComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnSignalReceived(Entity<DeviceLinkSinkComponent> ent, ref DeviceNetworkPacketEvent<SignalPayload> args)
{
if (!args.Data.TryGetValue(InvokedPort, out string? port) || !(component.Ports?.Contains(port) ?? false))
var (uid, component) = ent;
if (!component.Ports.Contains(args.Data.InvokedPort))
return;
var eventArgs = new SignalReceivedEvent(port, args.Sender, args.Data);
RaiseLocalEvent(uid, ref eventArgs);
var eventArgs = new SignalReceivedEvent(args.Data.InvokedPort, args.Sender);
RaiseLocalEvent(uid, ref eventArgs);
}
/// <summary>
/// When linking from a port that currently has a signal being sent, invoke the new link with that signal.
/// </summary>
[SubscribeLocalEvent]
private void OnNewLink(Entity<DeviceLinkSourceComponent> ent, ref NewLinkEvent args)
{
if (args.Source != ent.Owner)
@ -147,11 +224,12 @@ public sealed partial class DeviceLinkSystem : SharedDeviceLinkSystem
if (!ent.Comp.LastSignals.TryGetValue(args.SourcePort, out var signal))
return;
var payload = new NetworkPayload()
var payload = new LogicStatePayload
{
[DeviceNetworkConstants.LogicState] = signal ? SignalState.High : SignalState.Low
State = signal ? SignalState.High : SignalState.Low
};
InvokeDirect(ent, args.Sink, args.SourcePort, args.SinkPort, payload);
InvokeDirect(ent, args.Sink, args.SinkPort, ref payload);
}
#endregion
}

View file

@ -1,9 +1,7 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Server.Doors.Systems;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork;
using Content.Shared.Doors.Components;
using Content.Shared.Doors;
using JetBrains.Annotations;
@ -36,10 +34,38 @@ namespace Content.Server.DeviceLinking.Systems
if (!TryComp(uid, out DoorComponent? door))
return;
var state = SignalState.Momentary;
args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state);
if (args.Port == component.OpenPort)
{
if (door.State == DoorState.Closed)
_doorSystem.TryOpen(uid, door);
}
else if (args.Port == component.ClosePort)
{
if (door.State == DoorState.Open)
_doorSystem.TryClose(uid, door);
}
else if (args.Port == component.TogglePort)
{
_doorSystem.TryToggleDoor(uid, door);
}
else if (args.Port == component.InBolt)
{
if (!TryComp<DoorBoltComponent>(uid, out var bolts))
return;
// if its a pulse toggle, otherwise set bolts to high/low
var bolt = !bolts.BoltsDown;
_doorSystem.SetBoltsDown((uid, bolts), bolt);
}
}
[SubscribeLocalEvent]
private void OnSignalReceived(EntityUid uid, DoorSignalControlComponent component, ref SignalReceivedEvent<LogicStatePayload> args)
{
if (!TryComp(uid, out DoorComponent? door))
return;
var state = args.Data.State;
if (args.Port == component.OpenPort)
{
if (state == SignalState.High || state == SignalState.Momentary)

View file

@ -1,7 +1,6 @@
using Content.Server.DeviceLinking.Components;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork;
namespace Content.Server.DeviceLinking.Systems;
@ -14,7 +13,6 @@ public sealed partial class EdgeDetectorSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<EdgeDetectorComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<EdgeDetectorComponent, SignalReceivedEvent>(OnSignalReceived);
}
private void OnInit(EntityUid uid, EdgeDetectorComponent comp, ComponentInit args)
@ -23,25 +21,21 @@ public sealed partial class EdgeDetectorSystem : EntitySystem
_deviceLink.EnsureSourcePorts(uid, comp.OutputHighPort, comp.OutputLowPort);
}
private void OnSignalReceived(EntityUid uid, EdgeDetectorComponent comp, ref SignalReceivedEvent args)
[SubscribeLocalEvent]
private void OnSignalReceived(EntityUid uid, EdgeDetectorComponent comp, ref SignalReceivedEvent<LogicStatePayload> args)
{
// only handle signals with edges
var state = SignalState.Momentary;
if (args.Data == null ||
!args.Data.TryGetValue(DeviceNetworkConstants.LogicState, out state) ||
state == SignalState.Momentary)
return;
var state = args.Data.State;
if (args.Port != comp.InputPort)
return;
// make sure the level changed, multiple devices sending the same level are treated as one spamming
if (comp.State != state)
{
comp.State = state;
if (comp.State == state)
return;
var port = state == SignalState.High ? comp.OutputHighPort : comp.OutputLowPort;
_deviceLink.InvokePort(uid, port);
}
comp.State = state;
var port = state == SignalState.High ? comp.OutputHighPort : comp.OutputLowPort;
_deviceLink.InvokePort(uid, port);
}
}

View file

@ -1,8 +1,6 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
@ -96,8 +94,26 @@ public sealed partial class LogicGateSystem : EntitySystem
{
// default to momentary for compatibility with non-logic signals.
// currently only door status and logic gates have logic signal state.
var state = SignalState.Momentary;
args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state);
// update the state for the correct port
if (args.Port == comp.InputPortA)
{
comp.StateA = SignalState.Momentary;
_appearance.SetData(uid, LogicGateVisuals.InputA, false); //If A == High => Sets input A sprite to True
}
else if (args.Port == comp.InputPortB)
{
comp.StateB = SignalState.Momentary;
_appearance.SetData(uid, LogicGateVisuals.InputB, false); //If B == High => Sets input B sprite to True
}
UpdateOutput(uid, comp);
}
[SubscribeLocalEvent]
private void OnSignalReceived(EntityUid uid, LogicGateComponent comp, ref SignalReceivedEvent<LogicStatePayload> args)
{
var state = args.Data.State;
// update the state for the correct port
if (args.Port == comp.InputPortA)

View file

@ -1,7 +1,6 @@
using Content.Server.DeviceLinking.Components;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork;
namespace Content.Server.DeviceLinking.Systems;
@ -46,7 +45,19 @@ public sealed partial class MemoryCellSystem : EntitySystem
private void OnSignalReceived(Entity<MemoryCellComponent> ent, ref SignalReceivedEvent args)
{
var state = SignalState.Momentary;
args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state);
if (args.Port == ent.Comp.InputPort)
ent.Comp.InputState = state;
else if (args.Port == ent.Comp.EnablePort)
ent.Comp.EnableState = state;
UpdateOutput(ent);
}
[SubscribeLocalEvent]
private void OnSignalReceived(Entity<MemoryCellComponent> ent, ref SignalReceivedEvent<LogicStatePayload> args)
{
var state = args.Data.State;
if (args.Port == ent.Comp.InputPort)
ent.Comp.InputState = state;
@ -58,9 +69,6 @@ public sealed partial class MemoryCellSystem : EntitySystem
private void UpdateOutput(Entity<MemoryCellComponent, DeviceLinkSourceComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp2))
return;
if (ent.Comp1.EnableState == SignalState.Low)
return;
@ -69,6 +77,6 @@ public sealed partial class MemoryCellSystem : EntitySystem
return;
ent.Comp1.LastOutput = value;
_deviceLink.SendSignal(ent, ent.Comp1.OutputPort, value, ent.Comp2);
_deviceLink.SendSignal((ent.Owner, ent.Comp2), ent.Comp1.OutputPort, value);
}
}

View file

@ -27,13 +27,14 @@ namespace Content.Server.DeviceNetwork.Systems
/// <summary>
/// Checks if both devices are connected to the same apc
/// </summary>
private void OnBeforePacketSent(EntityUid uid, ApcNetworkComponent receiver, BeforePacketSentEvent args)
private void OnBeforePacketSent(Entity<ApcNetworkComponent> ent, ref BeforePacketSentEvent args)
{
if (!TryComp(args.Sender, out ApcNetworkComponent? sender)) return;
if (!TryComp(args.Sender, out ApcNetworkComponent? sender))
return;
if (sender.ConnectedNode?.NodeGroup == null || !sender.ConnectedNode.NodeGroup.Equals(receiver.ConnectedNode?.NodeGroup))
if (sender.ConnectedNode?.NodeGroup == null || !sender.ConnectedNode.NodeGroup.Equals(ent.Comp.ConnectedNode?.NodeGroup))
{
args.Cancel();
args.Cancelled = true;
}
}

View file

@ -1,4 +1,5 @@
using System.Linq;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Systems;
@ -11,6 +12,7 @@ namespace Content.Server.DeviceNetwork.Systems;
public sealed partial class DeviceListSystem : SharedDeviceListSystem
{
[Dependency] private NetworkConfiguratorSystem _configurator = default!;
[Dependency] private EntityQuery<DeviceNetworkComponent> _deviceNetworkQuery = default!;
public override void Initialize()
@ -82,17 +84,18 @@ public sealed partial class DeviceListSystem : SharedDeviceListSystem
/// <summary>
/// Filters the broadcasts recipient list against the device list as either an allow or deny list depending on the components IsAllowList field
/// </summary>
private void OnBeforeBroadcast(EntityUid uid, DeviceListComponent component, BeforeBroadcastAttemptEvent args)
private void OnBeforeBroadcast(Entity<DeviceListComponent> ent, ref BeforeBroadcastAttemptEvent args)
{
var component = ent.Comp;
//Don't filter anything if the device list is empty
if (component.Devices.Count == 0)
{
if (component.IsAllowList)
args.Cancel();
args.Cancelled = true;
return;
}
HashSet<DeviceNetworkComponent> filteredRecipients = new(args.Recipients.Count);
HashSet<Device> filteredRecipients = new(args.Recipients.Count);
foreach (var recipient in args.Recipients)
{
@ -106,10 +109,10 @@ public sealed partial class DeviceListSystem : SharedDeviceListSystem
/// <summary>
/// Filters incoming packets if that is enabled <see cref="OnBeforeBroadcast"/>
/// </summary>
private void OnBeforePacketSent(EntityUid uid, DeviceListComponent component, BeforePacketSentEvent args)
private void OnBeforePacketSent(Entity<DeviceListComponent> ent, ref BeforePacketSentEvent args)
{
if (component.HandleIncomingPackets && component.Devices.Contains(args.Sender) != component.IsAllowList)
args.Cancel();
if (ent.Comp.HandleIncomingPackets && ent.Comp.Devices.Contains(args.Sender) != ent.Comp.IsAllowList)
args.Cancelled = true;
}
public void OnDeviceShutdown(Entity<DeviceListComponent?> list, Entity<DeviceNetworkComponent> device)

View file

@ -27,7 +27,7 @@ public sealed partial class DeviceNetworkJammerSystem : SharedDeviceNetworkJamme
while (query.MoveNext(out var uid, out var jammerComp, out var jammerXform))
{
if (!_jammer.GetJammableNetworks((uid, jammerComp)).Contains(ev.NetworkId))
if (!_jammer.GetJammableNetworks((uid, jammerComp)).Contains(ev.NetId))
continue;
if (jammerComp.FrequenciesExcluded != null &&
@ -37,10 +37,9 @@ public sealed partial class DeviceNetworkJammerSystem : SharedDeviceNetworkJamme
if (_transform.InRange(jammerXform.Coordinates, ev.SenderTransform.Coordinates, jammerComp.Range)
|| _transform.InRange(jammerXform.Coordinates, xform.Comp.Coordinates, jammerComp.Range))
{
ev.Cancel();
ev.Cancelled = true;
return;
}
}
}
}

View file

@ -1,5 +1,4 @@
using Content.Server.DeviceNetwork.Components;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.DeviceNetwork.Events;
@ -9,15 +8,15 @@ public sealed partial class DeviceNetworkRequiresPowerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DeviceNetworkRequiresPowerComponent, BeforePacketSentEvent>(OnBeforePacketSent);
}
private void OnBeforePacketSent(EntityUid uid, DeviceNetworkRequiresPowerComponent component,
BeforePacketSentEvent args)
private void OnBeforePacketSent(Entity<DeviceNetworkRequiresPowerComponent> ent, ref BeforePacketSentEvent args)
{
if (!this.IsPowered(uid, EntityManager))
if (!this.IsPowered(ent, EntityManager))
{
args.Cancel();
args.Cancelled = true;
}
}
}

View file

@ -0,0 +1,232 @@
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using JetBrains.Annotations;
namespace Content.Server.DeviceNetwork.Systems;
public sealed partial class DeviceNetworkSystem
{
[PublicAPI]
public override bool SendPacket<T>(
Entity<DeviceNetworkComponent?> ent,
string? address,
ref T data,
uint? frequency = null,
int? network = null)
{
if (!_deviceQuery.Resolve(ent.Owner, ref ent.Comp, false))
return false;
var device = ent.Comp;
if (device.Address == string.Empty)
return false;
frequency ??= device.TransmitFrequency;
if (frequency == null)
return false;
network ??= device.DeviceNetId;
var packet = new DeviceNetworkPacketEvent<T>(network.Value, address, frequency.Value, device.Address, ent!, data);
SendPacket(ref packet);
return true;
}
/// <summary>
/// Connect an entity with a DeviceNetworkComponent. Note that this will re-use an existing address if the
/// device already had one configured. If there is a clash, the device cannot join the network.
/// </summary>
[PublicAPI]
public bool ConnectDevice(Entity<DeviceNetworkComponent?> ent)
{
if (!_deviceQuery.Resolve(ent.Owner, ref ent.Comp, false))
return false;
if (!TryEnsureNetwork(ent.Comp.DeviceNetId, out var deviceNet))
return false;
var success = deviceNet.Add(ent!);
Dirty(ent);
return success;
}
/// <summary>
/// Disconnect an entity with a DeviceNetworkComponent.
/// </summary>
/// <param name="ent">The entity to disconnect from its network.</param>
/// <param name="preventAutoConnect">
/// If true, sets <see cref="DeviceNetworkComponent.AutoConnect"/> to false.
/// That way the device doesn't auto reconnect when a game state is loaded.
/// </param>
/// <returns>True if the device was removed from the network successfully.</returns>
[PublicAPI]
public bool DisconnectDevice(Entity<DeviceNetworkComponent?> ent, bool preventAutoConnect = true)
{
if (!_deviceQuery.Resolve(ent.Owner, ref ent.Comp, false))
return false;
if (!TryGetNetwork(ent.Comp.DeviceNetId, out var deviceNet))
return false;
if (preventAutoConnect)
ent.Comp.AutoConnect = false;
var result = deviceNet.Remove(ent!);
return result;
}
/// <summary>
/// Checks if a device is already connected to its network.
/// </summary>
/// <returns>True if the device was found in the network with its corresponding network id.</returns>
[PublicAPI]
public bool IsDeviceConnected(Entity<DeviceNetworkComponent?> ent)
{
var (uid, deviceComp) = ent;
if (!_deviceQuery.Resolve(uid, ref deviceComp, false))
return false;
if (!_networks.TryGetValue(deviceComp.DeviceNetId, out var deviceNet))
return false;
var device = new Device(ent!);
return deviceNet.Devices.ContainsValue(device);
}
/// <summary>
/// Checks if an address exists in the network with the given netId.
/// </summary>
[PublicAPI]
public bool IsAddressPresent(int netId, string? address)
{
if (address == null
|| !_networks.TryGetValue(netId, out var network))
return false;
return network.Devices.ContainsKey(address);
}
/// <summary>
/// Sets the receive frequency of an entity.
/// </summary>
/// <param name="ent">The target device.</param>
/// <param name="frequency">The new frequency.</param>
[PublicAPI]
public void SetReceiveFrequency(Entity<DeviceNetworkComponent?> ent, uint? frequency)
{
if (!_deviceQuery.Resolve(ent.Owner, ref ent.Comp, false))
return;
if (ent.Comp.ReceiveFrequency == frequency)
return;
if (!TryGetNetwork(ent.Comp.DeviceNetId, out var deviceNet))
return;
var oldFrequency = ent.Comp.ReceiveFrequency;
deviceNet.Remove(ent!);
ent.Comp.ReceiveFrequency = frequency;
deviceNet.Add(ent!);
var ev = new DeviceReceiveFrequencyChangedEvent(oldFrequency, frequency);
RaiseLocalEvent(ent, ref ev);
Dirty(ent);
}
/// <summary>
/// Sets the transmit frequency of an entity.
/// </summary>
/// <param name="ent">The target device.</param>
/// <param name="frequency">The new frequency.</param>
[PublicAPI]
public void SetTransmitFrequency(Entity<DeviceNetworkComponent?> ent, uint? frequency)
{
if (!_deviceQuery.Resolve(ent.Owner, ref ent.Comp, false))
return;
var oldFrequency = ent.Comp.TransmitFrequency;
ent.Comp.TransmitFrequency = frequency;
var ev = new DeviceReceiveFrequencyChangedEvent(oldFrequency, frequency);
RaiseLocalEvent(ent, ref ev);
}
/// <summary>
/// Sets the target device's ability to receive all network packets, regardless of the address.
/// </summary>
[PublicAPI]
public void SetReceiveAll(Entity<DeviceNetworkComponent?> ent, bool receiveAll)
{
if (!_deviceQuery.Resolve(ent.Owner, ref ent.Comp, false))
return;
if (ent.Comp.ReceiveAll == receiveAll)
return;
if (!TryGetNetwork(ent.Comp.DeviceNetId, out var deviceNet))
return;
deviceNet.Remove(ent!);
ent.Comp.ReceiveAll = receiveAll;
deviceNet.Add(ent!);
var ev = new DeviceReceiveAllChangedEvent(receiveAll);
RaiseLocalEvent(ent, ref ev);
Dirty(ent);
}
/// <summary>
/// Sets the address of the target device.
/// </summary>
[PublicAPI]
public void SetAddress(Entity<DeviceNetworkComponent?> ent, string address)
{
if (!_deviceQuery.Resolve(ent.Owner, ref ent.Comp, false))
return;
if (ent.Comp.Address == address && ent.Comp.CustomAddress)
return;
if (!TryGetNetwork(ent.Comp.DeviceNetId, out var deviceNet))
return;
var oldAddress = ent.Comp.Address;
deviceNet.Remove(ent!);
ent.Comp.CustomAddress = true;
ent.Comp.Address = address;
deviceNet.Add(ent!);
var ev = new DeviceAddressChangedEvent(oldAddress, address, ent.Comp.CustomAddress);
RaiseLocalEvent(ent, ref ev);
Dirty(ent);
}
/// <summary>
/// Randomizes the address of the target device.
/// </summary>
[PublicAPI]
public void RandomizeAddress(Entity<DeviceNetworkComponent?> ent)
{
if (!_deviceQuery.Resolve(ent.Owner, ref ent.Comp, false))
return;
if (!TryGetNetwork(ent.Comp.DeviceNetId, out var deviceNet))
return;
var oldAddress = ent.Comp.Address;
deviceNet.Remove(ent!);
ent.Comp.CustomAddress = false;
ent.Comp.Address = "";
deviceNet.Add(ent!);
var ev = new DeviceAddressChangedEvent(oldAddress, ent.Comp.Address, ent.Comp.CustomAddress);
RaiseLocalEvent(ent, ref ev);
Dirty(ent);
}
}

View file

@ -4,357 +4,224 @@ using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Systems;
using Content.Shared.Examine;
using JetBrains.Annotations;
using Robust.Shared.Random;
namespace Content.Server.DeviceNetwork.Systems
namespace Content.Server.DeviceNetwork.Systems;
/// <inheritdoc/>
public sealed partial class DeviceNetworkSystem : SharedDeviceNetworkSystem
{
[Dependency] private IRobustRandom _random = default!;
[Dependency] private SharedTransformSystem _transformSystem = default!;
[Dependency] private DeviceListSystem _deviceLists = default!;
[Dependency] private NetworkConfiguratorSystem _configurator = default!;
[Dependency] private EntityQuery<DeviceNetworkComponent> _deviceQuery = default!;
/// <summary>
/// Entity system that handles everything device network related.
/// Device networking allows machines and devices to communicate with each other while adhering to restrictions like range or being connected to the same powernet.
/// Basically a cache of devices to connect them together faster.
/// </summary>
[UsedImplicitly]
public sealed partial class DeviceNetworkSystem : SharedDeviceNetworkSystem
private readonly Dictionary<int, DeviceNet> _networks = new(4);
/// <summary>
/// Automatically attempt to connect some devices when a map starts.
/// </summary>
[SubscribeLocalEvent]
private void OnMapInit(Entity<DeviceNetworkComponent> ent, ref MapInitEvent args)
{
[Dependency] private IRobustRandom _random = default!;
[Dependency] private SharedTransformSystem _transformSystem = default!;
[Dependency] private DeviceListSystem _deviceLists = default!;
[Dependency] private NetworkConfiguratorSystem _configurator = default!;
private readonly Dictionary<int, DeviceNet> _networks = new(4);
private readonly Queue<DeviceNetworkPacketEvent> _queueA = new();
private readonly Queue<DeviceNetworkPacketEvent> _queueB = new();
/// <summary>
/// The queue being processed in the current tick
/// </summary>
private Queue<DeviceNetworkPacketEvent> _activeQueue = null!;
/// <summary>
/// The queue that will be processed in the next tick
/// </summary>
private Queue<DeviceNetworkPacketEvent> _nextQueue = null!;
public override void Initialize()
var device = ent.Comp;
if (device.ReceiveFrequency == null
&& device.ReceiveFrequencyId != null
&& ProtoMan.TryIndex(device.ReceiveFrequencyId, out var receive))
{
SubscribeLocalEvent<DeviceNetworkComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<DeviceNetworkComponent, ComponentShutdown>(OnNetworkShutdown);
SubscribeLocalEvent<DeviceNetworkComponent, ExaminedEvent>(OnExamine);
_activeQueue = _queueA;
_nextQueue = _queueB;
device.ReceiveFrequency = receive.Frequency;
}
public override void Update(float frameTime)
if (device.TransmitFrequency == null
&& device.TransmitFrequencyId != null
&& ProtoMan.TryIndex(device.TransmitFrequencyId, out var xmit))
{
while (_activeQueue.TryDequeue(out var packet))
{
SendPacket(packet);
}
SwapQueues();
device.TransmitFrequency = xmit.Frequency;
}
public override bool QueuePacket(EntityUid uid, string? address, NetworkPayload data, uint? frequency = null, int? network = null, DeviceNetworkComponent? device = null)
if (device.AutoConnect)
ConnectDevice(ent.AsNullable());
}
/// <summary>
/// Automatically disconnect when an entity with a DeviceNetworkComponent shuts down.
/// </summary>
[SubscribeLocalEvent]
private void OnNetworkShutdown(Entity<DeviceNetworkComponent> ent, ref ComponentShutdown args)
{
var component = ent.Comp;
foreach (var list in component.DeviceLists)
{
if (!Resolve(uid, ref device, false))
return false;
if (Deleted(list))
return;
if (device.Address == string.Empty)
return false;
_deviceLists.OnDeviceShutdown(list, ent);
}
frequency ??= device.TransmitFrequency;
foreach (var list in component.Configurators)
{
if (Deleted(list))
return;
if (frequency == null)
return false;
_configurator.OnDeviceShutdown(list, ent);
}
network ??= device.DeviceNetId;
if (TryGetNetwork(component.DeviceNetId, out var network))
network.Remove(ent);
}
_nextQueue.Enqueue(new DeviceNetworkPacketEvent(network.Value, address, frequency.Value, device.Address, uid, data));
/// <summary>
/// Try to find a device on a network using its address.
/// </summary>
private bool TryGetDevice(int netId, string address, [NotNullWhen(true)] out Device? device)
{
device = null;
if (!TryGetNetwork(netId, out var network)
|| !network.Devices.TryGetValue(address, out var foundDevice))
return false;
device = foundDevice;
return true;
}
/// <summary>
/// Tries to get an already existing device network, and creates a new network if it doesn't exist.
/// </summary>
/// <returns>False if the manager is not initialized.</returns>
/// <returns></returns>
private bool TryEnsureNetwork(int netId, [NotNullWhen(true)] out DeviceNet? network)
{
network = null;
if (_networks.TryGetValue(netId, out var deviceNet))
{
network = deviceNet;
return true;
}
/// <summary>
/// Swaps the active queue.
/// Queues are swapped so that packets being sent in the current tick get processed in the next tick.
/// </summary>
/// <remarks>
/// This prevents infinite loops while sending packets
/// </remarks>
private void SwapQueues()
{
_nextQueue = _activeQueue;
_activeQueue = _activeQueue == _queueA ? _queueB : _queueA;
}
var newDeviceNet = new DeviceNet(netId, _random);
_networks[netId] = newDeviceNet;
network = newDeviceNet;
return true;
}
private void OnExamine(EntityUid uid, DeviceNetworkComponent device, ExaminedEvent args)
/// <summary>
/// Tries to get an already existing network.
/// </summary>
/// <returns>False if the manager is not initialized, or the network wasn't found.</returns>
private bool TryGetNetwork(int netId, [NotNullWhen(true)] out DeviceNet? network)
{
network = null;
if (!_networks.TryGetValue(netId, out var deviceNet))
return false;
network = deviceNet;
return true;
}
private void SendPacket<T>(ref DeviceNetworkPacketEvent<T> packet) where T : INetworkPayload
{
if (!TryEnsureNetwork(packet.NetId, out var network))
return;
if (packet.Address == null)
{
if (device.ExaminableAddress)
// Broadcast to all listening devices
if (network.ListeningDevices.TryGetValue(packet.Frequency, out var devices) && CheckRecipientsList(packet, ref devices))
{
args.PushText(Loc.GetString("device-address-examine-message", ("address", device.Address)));
var deviceCopy = ArrayPool<Device>.Shared.Rent(devices.Count);
devices.CopyTo(deviceCopy);
SendToConnections(deviceCopy.AsSpan(0, devices.Count), packet);
ArrayPool<Device>.Shared.Return(deviceCopy);
}
}
/// <summary>
/// Automatically attempt to connect some devices when a map starts.
/// </summary>
private void OnMapInit(EntityUid uid, DeviceNetworkComponent device, MapInitEvent args)
else
{
if (device.ReceiveFrequency == null
&& device.ReceiveFrequencyId != null
&& ProtoMan.TryIndex<DeviceFrequencyPrototype>(device.ReceiveFrequencyId, out var receive))
var totalDevices = 0;
var hasTargetedDevice = false;
if (network.ReceiveAllDevices.TryGetValue(packet.Frequency, out var devices))
{
device.ReceiveFrequency = receive.Frequency;
totalDevices += devices.Count;
}
if (device.TransmitFrequency == null
&& device.TransmitFrequencyId != null
&& ProtoMan.TryIndex<DeviceFrequencyPrototype>(device.TransmitFrequencyId, out var xmit))
{
device.TransmitFrequency = xmit.Frequency;
}
if (device.AutoConnect)
ConnectDevice(uid, device);
}
private DeviceNet GetNetwork(int netId)
{
if (_networks.TryGetValue(netId, out var deviceNet))
return deviceNet;
var newDeviceNet = new DeviceNet(netId, _random);
_networks[netId] = newDeviceNet;
return newDeviceNet;
}
/// <summary>
/// Automatically disconnect when an entity with a DeviceNetworkComponent shuts down.
/// </summary>
private void OnNetworkShutdown(EntityUid uid, DeviceNetworkComponent component, ComponentShutdown args)
{
foreach (var list in component.DeviceLists)
{
_deviceLists.OnDeviceShutdown(list, (uid, component));
}
foreach (var list in component.Configurators)
{
_configurator.OnDeviceShutdown(list, (uid, component));
}
GetNetwork(component.DeviceNetId).Remove(component);
}
/// <summary>
/// Connect an entity with a DeviceNetworkComponent. Note that this will re-use an existing address if the
/// device already had one configured. If there is a clash, the device cannot join the network.
/// </summary>
public bool ConnectDevice(EntityUid uid, DeviceNetworkComponent? device = null)
{
if (!Resolve(uid, ref device, false))
return false;
return GetNetwork(device.DeviceNetId).Add(device);
}
/// <summary>
/// Disconnect an entity with a DeviceNetworkComponent.
/// </summary>
public bool DisconnectDevice(EntityUid uid, DeviceNetworkComponent? device, bool preventAutoConnect = true)
{
if (!Resolve(uid, ref device, false))
return false;
// If manually disconnected, don't auto reconnect when a game state is loaded.
if (preventAutoConnect)
device.AutoConnect = false;
return GetNetwork(device.DeviceNetId).Remove(device);
}
/// <summary>
/// Checks if a device is already connected to its network
/// </summary>
/// <returns>True if the device was found in the network with its corresponding network id</returns>
public bool IsDeviceConnected(EntityUid uid, DeviceNetworkComponent? device)
{
if (!Resolve(uid, ref device, false))
return false;
if (!_networks.TryGetValue(device.DeviceNetId, out var deviceNet))
return false;
return deviceNet.Devices.ContainsValue(device);
}
/// <summary>
/// Checks if an address exists in the network with the given netId
/// </summary>
public bool IsAddressPresent(int netId, string? address)
{
if (address == null || !_networks.TryGetValue(netId, out var network))
return false;
return network.Devices.ContainsKey(address);
}
public void SetReceiveFrequency(EntityUid uid, uint? frequency, DeviceNetworkComponent? device = null)
{
if (!Resolve(uid, ref device, false))
if (!TryGetDevice(packet.NetId, packet.Address, out var device))
return;
if (device.ReceiveFrequency == frequency) return;
var deviceNet = GetNetwork(device.DeviceNetId);
deviceNet.Remove(device);
device.ReceiveFrequency = frequency;
deviceNet.Add(device);
}
public void SetTransmitFrequency(EntityUid uid, uint? frequency, DeviceNetworkComponent? device = null)
{
if (Resolve(uid, ref device, false))
device.TransmitFrequency = frequency;
}
public void SetReceiveAll(EntityUid uid, bool receiveAll, DeviceNetworkComponent? device = null)
{
if (!Resolve(uid, ref device, false))
return;
if (device.ReceiveAll == receiveAll) return;
var deviceNet = GetNetwork(device.DeviceNetId);
deviceNet.Remove(device);
device.ReceiveAll = receiveAll;
deviceNet.Add(device);
}
public void SetAddress(EntityUid uid, string address, DeviceNetworkComponent? device = null)
{
if (!Resolve(uid, ref device, false))
return;
if (device.Address == address && device.CustomAddress) return;
var deviceNet = GetNetwork(device.DeviceNetId);
deviceNet.Remove(device);
device.CustomAddress = true;
device.Address = address;
deviceNet.Add(device);
}
public void RandomizeAddress(EntityUid uid, DeviceNetworkComponent? device = null)
{
if (!Resolve(uid, ref device, false))
return;
var deviceNet = GetNetwork(device.DeviceNetId);
deviceNet.Remove(device);
device.CustomAddress = false;
device.Address = "";
deviceNet.Add(device);
}
/// <summary>
/// Try to find a device on a network using its address.
/// </summary>
private bool TryGetDevice(int netId, string address, [NotNullWhen(true)] out DeviceNetworkComponent? device) =>
GetNetwork(netId).Devices.TryGetValue(address, out device);
private void SendPacket(DeviceNetworkPacketEvent packet)
{
var network = GetNetwork(packet.NetId);
if (packet.Address == null)
if (!device.Value.ReceiveAll &&
device.Value.ReceiveFrequency == packet.Frequency)
{
// Broadcast to all listening devices
if (network.ListeningDevices.TryGetValue(packet.Frequency, out var devices) && CheckRecipientsList(packet, ref devices))
{
var deviceCopy = ArrayPool<DeviceNetworkComponent>.Shared.Rent(devices.Count);
devices.CopyTo(deviceCopy);
SendToConnections(deviceCopy.AsSpan(0, devices.Count), packet);
ArrayPool<DeviceNetworkComponent>.Shared.Return(deviceCopy);
}
totalDevices += 1;
hasTargetedDevice = true;
}
else
var deviceCopy = ArrayPool<Device>.Shared.Rent(totalDevices);
if (devices != null)
{
var totalDevices = 0;
var hasTargetedDevice = false;
if (network.ReceiveAllDevices.TryGetValue(packet.Frequency, out var devices))
{
totalDevices += devices.Count;
}
if (TryGetDevice(packet.NetId, packet.Address, out var device) &&
!device.ReceiveAll &&
device.ReceiveFrequency == packet.Frequency)
{
totalDevices += 1;
hasTargetedDevice = true;
}
var deviceCopy = ArrayPool<DeviceNetworkComponent>.Shared.Rent(totalDevices);
if (devices != null)
{
devices.CopyTo(deviceCopy);
}
if (hasTargetedDevice)
{
deviceCopy[totalDevices - 1] = device!;
}
SendToConnections(deviceCopy.AsSpan(0, totalDevices), packet);
ArrayPool<DeviceNetworkComponent>.Shared.Return(deviceCopy);
devices.CopyTo(deviceCopy);
}
if (hasTargetedDevice)
{
deviceCopy[totalDevices - 1] = device.Value;
}
SendToConnections(deviceCopy.AsSpan(0, totalDevices), packet);
ArrayPool<Device>.Shared.Return(deviceCopy);
}
}
/// <summary>
/// Sends the <see cref="BeforeBroadcastAttemptEvent"/> to the sending entity if the packets SendBeforeBroadcastAttemptEvent field is set to true.
/// The recipients is set to the modified recipient list.
/// </summary>
/// <returns>false if the broadcast was canceled</returns>
private bool CheckRecipientsList(DeviceNetworkPacketEvent packet, ref HashSet<DeviceNetworkComponent> recipients)
{
if (!_networks.ContainsKey(packet.NetId) || !_networks[packet.NetId].Devices.ContainsKey(packet.SenderAddress))
return false;
var sender = _networks[packet.NetId].Devices[packet.SenderAddress];
if (!sender.SendBroadcastAttemptEvent)
return true;
var beforeBroadcastAttemptEvent = new BeforeBroadcastAttemptEvent(recipients);
RaiseLocalEvent(packet.Sender, beforeBroadcastAttemptEvent, true);
if (beforeBroadcastAttemptEvent.Cancelled || beforeBroadcastAttemptEvent.ModifiedRecipients == null)
return false;
recipients = beforeBroadcastAttemptEvent.ModifiedRecipients;
/// <summary>
/// Sends the <see cref="BeforeBroadcastAttemptEvent"/> to the sending entity if the packets SendBeforeBroadcastAttemptEvent field is set to true.
/// The recipients is set to the modified recipient list.
/// </summary>
/// <returns>false if the broadcast was canceled</returns>
private bool CheckRecipientsList<T>(DeviceNetworkPacketEvent<T> packet, ref HashSet<Device> recipients) where T : INetworkPayload
{
if (!packet.Sender.Comp.SendBroadcastAttemptEvent)
return true;
var beforeBroadcastAttemptEvent = new BeforeBroadcastAttemptEvent(recipients);
RaiseLocalEvent(packet.Sender, ref beforeBroadcastAttemptEvent, true);
if (beforeBroadcastAttemptEvent.Cancelled || beforeBroadcastAttemptEvent.ModifiedRecipients == null)
return false;
recipients = beforeBroadcastAttemptEvent.ModifiedRecipients;
return true;
}
private void SendToConnections<T>(ReadOnlySpan<Device> connections, DeviceNetworkPacketEvent<T> packet) where T : INetworkPayload
{
if (Deleted(packet.Sender))
{
return;
}
private void SendToConnections(ReadOnlySpan<DeviceNetworkComponent> connections, DeviceNetworkPacketEvent packet)
var xform = Transform(packet.Sender);
var senderPos = _transformSystem.GetWorldPosition(xform);
foreach (var connection in connections)
{
if (Deleted(packet.Sender))
{
return;
}
if (connection.Owner == packet.Sender.Owner)
continue;
var xform = Transform(packet.Sender);
var beforeEv = new BeforePacketSentEvent(packet.NetId,
packet.Address,
packet.Frequency,
packet.SenderAddress,
packet.Sender,
xform,
senderPos);
RaiseLocalEvent(connection.Owner, ref beforeEv);
var senderPos = _transformSystem.GetWorldPosition(xform);
if (beforeEv.Cancelled)
continue;
foreach (var connection in connections)
{
if (connection.Owner == packet.Sender)
continue;
BeforePacketSentEvent beforeEv = new(packet.Sender, xform, senderPos, connection.NetIdEnum.ToString(), packet.Frequency);
RaiseLocalEvent(connection.Owner, beforeEv, false);
if (!beforeEv.Cancelled)
RaiseLocalEvent(connection.Owner, packet, false);
else
beforeEv.Uncancel();
}
RaiseLocalEvent(connection.Owner, ref packet);
}
}
}

View file

@ -1,8 +1,8 @@
using Content.Server.DeviceNetwork.Components.Devices;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Interaction;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Payloads;
namespace Content.Server.DeviceNetwork.Systems.Devices
{
@ -15,43 +15,38 @@ namespace Content.Server.DeviceNetwork.Systems.Devices
base.Initialize();
SubscribeLocalEvent<ApcNetSwitchComponent, InteractHandEvent>(OnInteracted);
SubscribeLocalEvent<ApcNetSwitchComponent, DeviceNetworkPacketEvent>(OnPackedReceived);
}
/// <summary>
/// Toggles the state of the switch and sents a <see cref="DeviceNetworkConstants.CmdSetState"/> command with the
/// <see cref="DeviceNetworkConstants.StateEnabled"/> value set to state.
/// </summary>
private void OnInteracted(EntityUid uid, ApcNetSwitchComponent component, InteractHandEvent args)
private void OnInteracted(Entity<ApcNetSwitchComponent> ent, ref InteractHandEvent args)
{
if (!TryComp(uid, out DeviceNetworkComponent? networkComponent)) return;
var (uid, component) = ent;
if (!TryComp(uid, out DeviceNetworkComponent? networkComponent))
return;
component.State = !component.State;
if (networkComponent.TransmitFrequency == null)
return;
var payload = new NetworkPayload
var payload = new ApcNetTogglePayload
{
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdSetState,
[DeviceNetworkConstants.StateEnabled] = component.State,
Enabled = component.State,
};
_deviceNetworkSystem.QueuePacket(uid, null, payload, device: networkComponent);
_deviceNetworkSystem.SendPacket(uid, null, ref payload);
args.Handled = true;
}
/// <summary>
/// Listens to the <see cref="DeviceNetworkConstants.CmdSetState"/> command of other switches to sync state
/// </summary>
private void OnPackedReceived(EntityUid uid, ApcNetSwitchComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnPackedReceived(Entity<ApcNetSwitchComponent> ent, ref DeviceNetworkPacketEvent<ApcNetTogglePayload> args)
{
if (!TryComp(uid, out DeviceNetworkComponent? networkComponent) || args.SenderAddress == networkComponent.Address) return;
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return;
if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return;
var (uid, component) = ent;
if (!TryComp(uid, out DeviceNetworkComponent? networkComponent)
|| args.SenderAddress == networkComponent.Address)
return;
component.State = enabled;
component.State = args.Data.Enabled;
}
}
}

View file

@ -103,10 +103,10 @@ public sealed partial class SingletonDeviceNetServerSystem : EntitySystem
var connectedEvent = new DeviceNetServerConnectedEvent();
RaiseLocalEvent(uid, ref connectedEvent);
if (_deviceNetworkSystem.IsDeviceConnected(uid, device))
if (_deviceNetworkSystem.IsDeviceConnected((uid, device)))
return;
_deviceNetworkSystem.ConnectDevice(uid, device);
_deviceNetworkSystem.ConnectDevice((uid, device));
}
/// <summary>
@ -122,7 +122,7 @@ public sealed partial class SingletonDeviceNetServerSystem : EntitySystem
var disconnectedEvent = new DeviceNetServerDisconnectedEvent();
RaiseLocalEvent(uid, ref disconnectedEvent);
_deviceNetworkSystem.DisconnectDevice(uid, device, false);
_deviceNetworkSystem.DisconnectDevice((uid, device), false);
}
}

View file

@ -2,7 +2,6 @@ using Content.Server.DeviceNetwork.Components;
using Content.Server.Station.Systems;
using Content.Shared.DeviceNetwork.Events;
using JetBrains.Annotations;
using Robust.Shared.Map;
namespace Content.Server.DeviceNetwork.Systems
{
@ -54,14 +53,15 @@ namespace Content.Server.DeviceNetwork.Systems
/// <summary>
/// Checks if both devices are limited to the same station
/// </summary>
private void OnBeforePacketSent(EntityUid uid, StationLimitedNetworkComponent component, BeforePacketSentEvent args)
private void OnBeforePacketSent(Entity<StationLimitedNetworkComponent> ent, ref BeforePacketSentEvent args)
{
var (uid, component) = ent;
if (!component.StationId.HasValue)
TrySetStationId(uid, component);
if (!CheckStationId(args.Sender, component.AllowNonStationPackets, component.StationId))
{
args.Cancel();
args.Cancelled = true;
}
}

View file

@ -16,11 +16,11 @@ namespace Content.Server.DeviceNetwork.Systems
/// <summary>
/// Checks if both devices are on the same grid
/// </summary>
private void OnBeforePacketSent(EntityUid uid, WiredNetworkComponent component, BeforePacketSentEvent args)
private void OnBeforePacketSent(Entity<WiredNetworkComponent> ent, ref BeforePacketSentEvent args)
{
if (Transform(uid).GridUid != args.SenderTransform.GridUid)
if (Transform(ent).GridUid != args.SenderTransform.GridUid)
{
args.Cancel();
args.Cancelled = true;
}
}

View file

@ -18,10 +18,10 @@ namespace Content.Server.DeviceNetwork.Systems
/// <summary>
/// Gets the position of both the sending and receiving entity and checks if the receiver is in range of the sender.
/// </summary>
private void OnBeforePacketSent(EntityUid uid, WirelessNetworkComponent component, BeforePacketSentEvent args)
private void OnBeforePacketSent(Entity<WirelessNetworkComponent> ent, ref BeforePacketSentEvent args)
{
var ownPosition = args.SenderPosition;
var xform = Transform(uid);
var xform = Transform(ent);
// not a wireless to wireless connection, just let it happen
if (!TryComp<WirelessNetworkComponent>(args.Sender, out var sendingComponent))
@ -30,7 +30,7 @@ namespace Content.Server.DeviceNetwork.Systems
if (xform.MapID != args.SenderTransform.MapID
|| (ownPosition - _transformSystem.GetWorldPosition(xform)).Length() > sendingComponent.Range)
{
args.Cancel();
args.Cancelled = true;
}
}
}

View file

@ -1,5 +1,3 @@
using Content.Server.Construction.Conditions;
using Content.Server.DeviceNetwork.Components;
using Content.Server.EUI;
using Content.Shared.Eui;
using Content.Shared.Fax.Components;

View file

@ -1,34 +0,0 @@
namespace Content.Server.Fax;
public static class FaxConstants
{
// Commands
/**
* Used to get other faxes connected to current network
*/
public const string FaxPingCommand = "fax_ping";
/**
* Used as response to ping command
*/
public const string FaxPongCommand = "fax_pong";
/**
* Used when fax sending data to destination fax
*/
public const string FaxPrintCommand = "fax_print";
// Data
public const string FaxNameData = "fax_data_name";
public const string FaxPaperNameData = "fax_data_title";
public const string FaxPaperLabelData = "fax_data_label";
public const string FaxPaperPrototypeData = "fax_data_prototype";
public const string FaxPaperContentData = "fax_data_content";
public const string FaxPaperStampStateData = "fax_data_stamp_state";
public const string FaxPaperStampedByData = "fax_data_stamped_by";
public const string FaxSyndicateData = "fax_data_i_am_syndicate";
public const string FaxPaperLockedData = "fax_data_locked";
public const string FaxPaperSenderFaxNameData = "fax_data_sender_fax_name";
}

View file

@ -8,8 +8,6 @@ using Content.Server.Tools;
using Content.Shared.Administration.Logs;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Emag.Systems;
using Content.Shared.Fax;
@ -31,7 +29,6 @@ using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.Fax;
@ -71,7 +68,6 @@ public sealed partial class FaxSystem : EntitySystem
SubscribeLocalEvent<FaxMachineComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
SubscribeLocalEvent<FaxMachineComponent, EntRemovedFromContainerMessage>(OnItemSlotChanged);
SubscribeLocalEvent<FaxMachineComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<FaxMachineComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
// Interaction
SubscribeLocalEvent<FaxMachineComponent, InteractUsingEvent>(OnInteractUsing);
@ -261,56 +257,32 @@ public sealed partial class FaxSystem : EntitySystem
args.Handled = true;
}
private void OnPacketReceived(EntityUid uid, FaxMachineComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnPingPayload(Entity<FaxMachineComponent> ent, ref DeviceNetworkPacketEvent<FaxPingPayload> args)
{
if (!HasComp<DeviceNetworkComponent>(uid) || string.IsNullOrEmpty(args.SenderAddress))
var isForSyndie = _emag.CheckFlag(ent.Owner, EmagType.Interaction) && args.Data.IsSyndicate;
if (!isForSyndie && !ent.Comp.ResponsePings)
return;
if (args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
var pong = new FaxPongPayload
{
switch (command)
{
case FaxConstants.FaxPingCommand:
var isForSyndie = _emag.CheckFlag(uid, EmagType.Interaction) &&
args.Data.ContainsKey(FaxConstants.FaxSyndicateData);
if (!isForSyndie && !component.ResponsePings)
return;
FaxName = ent.Comp.FaxName,
};
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, FaxConstants.FaxPongCommand },
{ FaxConstants.FaxNameData, component.FaxName }
};
_deviceNetworkSystem.QueuePacket(uid, args.SenderAddress, payload);
_deviceNetworkSystem.SendPacket(ent.Owner, args.SenderAddress, ref pong);
}
break;
case FaxConstants.FaxPongCommand:
if (!args.Data.TryGetValue(FaxConstants.FaxNameData, out string? faxName))
return;
[SubscribeLocalEvent]
private void OnPongPayload(Entity<FaxMachineComponent> ent, ref DeviceNetworkPacketEvent<FaxPongPayload> args)
{
ent.Comp.KnownFaxes[args.SenderAddress] = args.Data.FaxName;
UpdateUserInterface(ent.Owner, ent.Comp);
}
component.KnownFaxes[args.SenderAddress] = faxName;
UpdateUserInterface(uid, component);
break;
case FaxConstants.FaxPrintCommand:
if (!args.Data.TryGetValue(FaxConstants.FaxPaperNameData, out string? name) ||
!args.Data.TryGetValue(FaxConstants.FaxPaperContentData, out string? content))
return;
args.Data.TryGetValue(FaxConstants.FaxPaperLabelData, out string? label);
args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState);
args.Data.TryGetValue(FaxConstants.FaxPaperStampedByData, out List<StampDisplayInfo>? stampedBy);
args.Data.TryGetValue(FaxConstants.FaxPaperPrototypeData, out string? prototypeId);
args.Data.TryGetValue(FaxConstants.FaxPaperLockedData, out bool? locked);
args.Data.TryGetValue(FaxConstants.FaxPaperSenderFaxNameData, out string? senderFaxName);
var printout = new FaxPrintout(content, name, label, prototypeId, stampState, stampedBy, locked ?? false, senderFaxName);
Receive(uid, printout, args.SenderAddress);
break;
}
}
[SubscribeLocalEvent]
private void OnPrintPayload(Entity<FaxMachineComponent> ent, ref DeviceNetworkPacketEvent<FaxPrintPayload> args)
{
Receive(ent, args.Data.Data, args.SenderAddress);
}
private void OnToggleInterface(EntityUid uid, FaxMachineComponent component, AfterActivatableUIOpenEvent args)
@ -413,15 +385,12 @@ public sealed partial class FaxSystem : EntitySystem
component.DestinationFaxAddress = null;
component.KnownFaxes.Clear();
var payload = new NetworkPayload()
var payload = new FaxPingPayload
{
{ DeviceNetworkConstants.Command, FaxConstants.FaxPingCommand }
IsSyndicate = _emag.CheckFlag(uid, EmagType.Interaction),
};
if (_emag.CheckFlag(uid, EmagType.Interaction))
payload.Add(FaxConstants.FaxSyndicateData, true);
_deviceNetworkSystem.QueuePacket(uid, null, payload);
_deviceNetworkSystem.SendPacket(uid, null, ref payload);
}
/// <summary>
@ -522,57 +491,26 @@ public sealed partial class FaxSystem : EntitySystem
!TryComp<PaperComponent>(sendEntity, out var paper))
return;
if (metadata.EntityPrototype == null)
return;
TryComp<NameModifierComponent>(sendEntity, out var nameMod);
TryComp<LabelComponent>(sendEntity, out var labelComponent);
var content = paper.Content;
if (component.AddSenderInfo)
var payload = new FaxPrintPayload
{
var faxMachineAddress = TryComp<DeviceNetworkComponent>(uid, out var deviceNetworkComponent)
? deviceNetworkComponent.Address
: Loc.GetString("device-address-unknown");
var time = _gameTicker.RoundDuration();
var timeString = TimeSpan.FromSeconds(Math.Truncate(time.TotalSeconds)).ToString();
content += "\n";
content += Loc.GetString(component.SenderInfo,
("sender_name", component.FaxName),
("sender_addr", faxMachineAddress),
("recipient_name", component.DestinationFaxName ?? Loc.GetString("fax-machine-popup-source-unknown")),
("recipient_addr", component.DestinationFaxAddress),
("time", timeString)
);
}
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, FaxConstants.FaxPrintCommand },
{ FaxConstants.FaxPaperNameData, nameMod?.BaseName ?? metadata.EntityName },
{ FaxConstants.FaxPaperLabelData, labelComponent?.CurrentLabel },
{ FaxConstants.FaxPaperContentData, content },
{ FaxConstants.FaxPaperLockedData, paper.EditingDisabled },
{ FaxConstants.FaxPaperSenderFaxNameData, component.FaxName ?? Loc.GetString("fax-machine-popup-source-unknown") }
Data = new FaxPrintout(
paper.Content,
nameMod?.BaseName ?? metadata.EntityName,
labelComponent?.CurrentLabel,
metadata.EntityPrototype.ID,
paper.StampState,
paper.StampedBy,
paper.EditingDisabled),
};
if (metadata.EntityPrototype != null)
{
// TODO: Ideally, we could just make a copy of the whole entity when it's
// faxed, in order to preserve visuals, etc.. This functionality isn't
// available yet, so we'll pass along the originating prototypeId and fall
// back to component.PrintPaperId in SpawnPaperFromQueue if we can't find one here.
payload[FaxConstants.FaxPaperPrototypeData] = metadata.EntityPrototype.ID;
}
if (paper.StampState != null)
{
payload[FaxConstants.FaxPaperStampStateData] = paper.StampState;
payload[FaxConstants.FaxPaperStampedByData] = paper.StampedBy;
}
_deviceNetworkSystem.QueuePacket(uid, component.DestinationFaxAddress, payload);
_deviceNetworkSystem.SendPacket(uid, component.DestinationFaxAddress, ref payload);
_adminLogger.Add(LogType.Action,
LogImpact.Low,

View file

@ -5,7 +5,7 @@ using Content.Server.Medical.SuitSensors;
using Content.Server.Objectives.Components;
using Content.Shared.GameTicking.Components;
using Content.Shared.Gibbing.Components;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Medical.SuitSensors;
using Content.Shared.Mind;
using Content.Shared.Objectives.Systems;
using Robust.Shared.Random;

View file

@ -1,4 +1,4 @@
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Medical.SuitSensors;
namespace Content.Server.Medical.CrewMonitoring;

View file

@ -1,12 +1,8 @@
using System.Linq;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.PowerCell;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Medical.CrewMonitoring;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Pinpointer;
using Content.Shared.PowerCell;
using Robust.Server.GameObjects;
namespace Content.Server.Medical.CrewMonitoring;
@ -20,7 +16,6 @@ public sealed partial class CrewMonitoringConsoleSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<CrewMonitoringConsoleComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<CrewMonitoringConsoleComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<CrewMonitoringConsoleComponent, BoundUIOpenedEvent>(OnUIOpened);
}
@ -29,22 +24,11 @@ public sealed partial class CrewMonitoringConsoleSystem : EntitySystem
component.ConnectedSensors.Clear();
}
private void OnPacketReceived(EntityUid uid, CrewMonitoringConsoleComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnSuitSensorBroadcast(Entity<CrewMonitoringConsoleComponent> ent, ref DeviceNetworkPacketEvent<BroadcastSuitSensorStatePayload> args)
{
var payload = args.Data;
// Check command
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
return;
if (command != DeviceNetworkConstants.CmdUpdatedState)
return;
if (!payload.TryGetValue(SuitSensorConstants.NET_STATUS_COLLECTION, out Dictionary<string, SuitSensorStatus>? sensorStatus))
return;
component.ConnectedSensors = sensorStatus;
UpdateUserInterface(uid, component);
ent.Comp.ConnectedSensors = args.Data.SensorStatus;
UpdateUserInterface(ent, ent.Comp);
}
private void OnUIOpened(EntityUid uid, CrewMonitoringConsoleComponent component, BoundUIOpenedEvent args)

View file

@ -1,5 +1,4 @@
using Content.Shared.Medical.SuitSensor;
using Robust.Shared.Map;
using Content.Shared.Medical.SuitSensors;
namespace Content.Server.Medical.CrewMonitoring;

View file

@ -1,16 +1,14 @@
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Medical.SuitSensors;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Medical.SuitSensor;
using Robust.Shared.Timing;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.Medical.CrewMonitoring;
using Content.Shared.Medical.SuitSensors;
namespace Content.Server.Medical.CrewMonitoring;
public sealed partial class CrewMonitoringServerSystem : EntitySystem
{
[Dependency] private SuitSensorSystem _sensors = default!;
[Dependency] private IGameTiming _gameTiming = default!;
[Dependency] private DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private SingletonDeviceNetServerSystem _singletonServerSystem = default!;
@ -22,7 +20,6 @@ public sealed partial class CrewMonitoringServerSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<CrewMonitoringServerComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<CrewMonitoringServerComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<CrewMonitoringServerComponent, DeviceNetServerDisconnectedEvent>(OnDisconnected);
}
@ -51,14 +48,12 @@ public sealed partial class CrewMonitoringServerSystem : EntitySystem
/// <summary>
/// Adds or updates a sensor status entry if the received package is a sensor status update
/// </summary>
private void OnPacketReceived(EntityUid uid, CrewMonitoringServerComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnSensorStatus(Entity<CrewMonitoringServerComponent> ent, ref DeviceNetworkPacketEvent<SuitSensorStatusPayload> args)
{
var sensorStatus = _sensors.PacketToSuitSensor(args.Data);
if (sensorStatus == null)
return;
sensorStatus.Timestamp = _gameTiming.CurTime;
component.SensorStatus[args.SenderAddress] = sensorStatus;
var sensorData = args.Data.Data;
sensorData.Timestamp = _gameTiming.CurTime;
ent.Comp.SensorStatus[args.SenderAddress] = sensorData;
}
/// <summary>
@ -93,13 +88,12 @@ public sealed partial class CrewMonitoringServerSystem : EntitySystem
if (!Resolve(uid, ref serverComponent, ref device))
return;
var payload = new NetworkPayload()
var payload = new BroadcastSuitSensorStatePayload
{
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
[SuitSensorConstants.NET_STATUS_COLLECTION] = serverComponent.SensorStatus
SensorStatus = serverComponent.SensorStatus,
};
_deviceNetworkSystem.QueuePacket(uid, null, payload, device: device);
_deviceNetworkSystem.SendPacket((uid, device), null, ref payload);
}
/// <summary>

View file

@ -46,9 +46,6 @@ public sealed partial class SuitSensorSystem : SharedSuitSensorSystem
sensor.ConnectedServer = address;
}
// Send it to the connected server
var payload = SuitSensorToPacket(status);
// Clear the connected server if its address isn't on the network
if (!_deviceNetworkSystem.IsAddressPresent(device.DeviceNetId, sensor.ConnectedServer))
{
@ -56,7 +53,11 @@ public sealed partial class SuitSensorSystem : SharedSuitSensorSystem
continue;
}
_deviceNetworkSystem.QueuePacket(uid, sensor.ConnectedServer, payload, device: device);
var payload = new SuitSensorStatusPayload
{
Data = status.Value,
};
_deviceNetworkSystem.SendPacket((uid, device), sensor.ConnectedServer, ref payload);
}
}
}

View file

@ -1,13 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Access.Systems;
using Content.Server.CartridgeLoader;
using Content.Server.Chat.Managers;
using Content.Server.Instruments;
using Content.Server.PDA.Ringer;
using Content.Server.Station.Systems;
using Content.Server.Store.Systems;
using Content.Server.Traitor.Uplink;
using Content.Shared.Access.Components;
using Content.Shared.AlertLevel;
using Content.Shared.CartridgeLoader;
@ -343,7 +341,7 @@ namespace Content.Server.PDA
if (TryComp(uid, out DeviceNetworkComponent? deviceNetworkComponent))
{
address = deviceNetworkComponent?.Address;
address = deviceNetworkComponent.Address;
}
return address;

View file

@ -1,52 +0,0 @@
using Content.Server.Power.Components;
namespace Content.Server.Power.Generation.Teg;
/// <summary>
/// Sensor data reported by the <see cref="TegGeneratorComponent"/> when queried over the device network.
/// </summary>
/// <seealso cref="TegSystem"/>
public sealed class TegSensorData
{
/// <summary>
/// Information for the A-side circulator.
/// </summary>
public Circulator CirculatorA;
/// <summary>
/// Information for the B-side circulator.
/// </summary>
public Circulator CirculatorB;
/// <summary>
/// The amount of energy (Joules) generated by the TEG last atmos tick.
/// </summary>
/// <seealso cref="TegGeneratorComponent.LastGeneration"/>
public float LastGeneration;
/// <summary>
/// Ramping position for the TEG power generation.
/// </summary>
/// <seealso cref="TegGeneratorComponent.RampPosition"/>
public float RampPosition;
/// <summary>
/// Power (Watts) actually being supplied by the TEG to connected power network.
/// </summary>
/// <seealso cref="PowerSupplierComponent.CurrentSupply"/>
public float PowerOutput;
/// <summary>
/// Information for a single TEG circulator.
/// </summary>
/// <param name="InletPressure">Pressure measured at the circulator's input pipe</param>
/// <param name="OutletPressure">Pressure measured at the circulator's output pipe</param>
/// <param name="InletTemperature">Temperature measured at the circulator's input pipe</param>
/// <param name="OutletTemperature">Temperature measured at the circulator's output pipe</param>
public record struct Circulator(
float InletPressure,
float OutletPressure,
float InletTemperature,
float OutletTemperature);
}

View file

@ -5,7 +5,6 @@ using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Examine;
using Content.Shared.NodeContainer;
@ -43,7 +42,7 @@ namespace Content.Server.Power.Generation.Teg;
/// <seealso cref="TegGeneratorComponent"/>
/// <seealso cref="TegCirculatorComponent"/>
/// <seealso cref="TegNodeGroup"/>
/// <seealso cref="TegSensorData"/>
/// <seealso cref="TegSensorPayload"/>
public sealed partial class TegSystem : EntitySystem
{
/// <summary>
@ -61,11 +60,6 @@ public sealed partial class TegSystem : EntitySystem
/// </summary>
private const string NodeNameOutlet = "outlet";
/// <summary>
/// Device network command to have the TEG output a <see cref="TegSensorData"/> object for its last statistics.
/// </summary>
public const string DeviceNetworkCommandSyncData = "teg_sync_data";
[Dependency] private AmbientSoundSystem _ambientSound = default!;
[Dependency] private AppearanceSystem _appearance = default!;
[Dependency] private AtmosphereSystem _atmosphere = default!;
@ -80,7 +74,6 @@ public sealed partial class TegSystem : EntitySystem
SubscribeLocalEvent<TegGeneratorComponent, AtmosDeviceUpdateEvent>(GeneratorUpdate);
SubscribeLocalEvent<TegGeneratorComponent, PowerChangedEvent>(GeneratorPowerChange);
SubscribeLocalEvent<TegGeneratorComponent, DeviceNetworkPacketEvent>(DeviceNetworkPacketReceived);
SubscribeLocalEvent<TegGeneratorComponent, ExaminedEvent>(GeneratorExamined);
}
@ -361,46 +354,32 @@ public sealed partial class TegSystem : EntitySystem
return (inlet, outlet);
}
private void DeviceNetworkPacketReceived(
EntityUid uid,
TegGeneratorComponent component,
DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnSyncPayload(Entity<TegGeneratorComponent> ent, ref DeviceNetworkPacketEvent<TegSensorSyncPayload> args)
{
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
var group = GetNodeGroup(ent.Owner);
if (group is not { IsFullyBuilt: true })
return;
switch (cmd)
var supplier = Comp<PowerSupplierComponent>(ent);
var dataPayload = new TegSensorPayload
{
case DeviceNetworkCommandSyncData:
var group = GetNodeGroup(uid);
if (group is not { IsFullyBuilt: true })
return;
CirculatorA = GetCirculatorSensorData(group.CirculatorA!.Owner),
CirculatorB = GetCirculatorSensorData(group.CirculatorB!.Owner),
LastGeneration = ent.Comp.LastGeneration,
PowerOutput = supplier.CurrentSupply,
RampPosition = ent.Comp.RampPosition,
};
var supplier = Comp<PowerSupplierComponent>(uid);
var payload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = DeviceNetworkCommandSyncData,
[DeviceNetworkCommandSyncData] = new TegSensorData
{
CirculatorA = GetCirculatorSensorData(group.CirculatorA!.Owner),
CirculatorB = GetCirculatorSensorData(group.CirculatorB!.Owner),
LastGeneration = component.LastGeneration,
PowerOutput = supplier.CurrentSupply,
RampPosition = component.RampPosition
}
};
_deviceNetwork.QueuePacket(uid, args.SenderAddress, payload);
break;
}
_deviceNetwork.SendPacket(ent.Owner, args.SenderAddress, ref dataPayload);
}
private TegSensorData.Circulator GetCirculatorSensorData(EntityUid circulator)
private Circulator GetCirculatorSensorData(EntityUid circulator)
{
var (inlet, outlet) = GetPipes(circulator);
return new TegSensorData.Circulator(
return new Circulator(
inlet.Air.Pressure,
outlet.Air.Pressure,
inlet.Air.Temperature,

View file

@ -3,7 +3,6 @@ using Content.Server.DeviceNetwork.Systems;
using Content.Server.Radio.EntitySystems;
using Content.Shared.Lock;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.Robotics;
using Content.Shared.Robotics.Components;
using Content.Shared.Robotics.Systems;
@ -33,7 +32,6 @@ public sealed partial class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
{
base.Initialize();
SubscribeLocalEvent<RoboticsConsoleComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
Subs.BuiEvents<RoboticsConsoleComponent>(RoboticsConsoleUiKey.Key, subs =>
{
subs.Event<BoundUIOpenedEvent>(OnOpened);
@ -70,20 +68,12 @@ public sealed partial class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
}
}
private void OnPacketReceived(Entity<RoboticsConsoleComponent> ent, ref DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnPacketReceived(Entity<RoboticsConsoleComponent> ent, ref DeviceNetworkPacketEvent<RoboticsCyborgDataPayload> args)
{
var payload = args.Data;
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
return;
if (command != DeviceNetworkConstants.CmdUpdatedState)
return;
if (!payload.TryGetValue(RoboticsConsoleConstants.NET_CYBORG_DATA, out CyborgControlData? data))
return;
var real = data.Value;
real.Timeout = _timing.CurTime + ent.Comp.Timeout;
ent.Comp.Cyborgs[args.SenderAddress] = real;
var data = args.Data.Data;
data.Timeout = _timing.CurTime + ent.Comp.Timeout;
ent.Comp.Cyborgs[args.SenderAddress] = data;
UpdateUserInterface(ent);
}
@ -104,12 +94,9 @@ public sealed partial class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
if (!ent.Comp.Cyborgs.TryGetValue(args.Address, out var data))
return;
var payload = new NetworkPayload()
{
[DeviceNetworkConstants.Command] = RoboticsConsoleConstants.NET_DISABLE_COMMAND
};
var payload = new RoboticsCyborgDisablePayload();
_deviceNetwork.SendPacket(ent.Owner, args.Address, ref payload);
_deviceNetwork.QueuePacket(ent, args.Address, payload);
_adminLogger.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(args.Actor):user} disabled borg {data.Name} with address {args.Address}");
}
@ -128,12 +115,8 @@ public sealed partial class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
if (!ent.Comp.Cyborgs.Remove(args.Address, out var data))
return;
var payload = new NetworkPayload()
{
[DeviceNetworkConstants.Command] = RoboticsConsoleConstants.NET_DESTROY_COMMAND
};
_deviceNetwork.QueuePacket(ent, args.Address, payload);
var payload = new RoboticsCyborgDestroyPayload();
_deviceNetwork.SendPacket(ent.Owner, args.Address, ref payload);
var message = Loc.GetString(ent.Comp.DestroyMessage, ("name", data.Name));
_radio.SendRadioMessage(ent, message, ent.Comp.RadioChannel, ent);

View file

@ -6,18 +6,17 @@ using Content.Server.Chat.Managers;
using Content.Server.Chat.Systems;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.GameTicking;
using Content.Server.Screens.Components;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Systems;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.GameTicking;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.RoundEnd;
using Content.Shared.Station.Components;
using Timer = Robust.Shared.Timing.Timer;
@ -223,16 +222,16 @@ namespace Content.Server.RoundEnd
var shuttle = _shuttle.GetShuttle();
if (shuttle != null && TryComp<DeviceNetworkComponent>(shuttle, out var net))
{
var payload = new NetworkPayload
var payload = new ScreenShuttlePayload
{
[ShuttleTimerMasks.ShuttleMap] = shuttle,
[ShuttleTimerMasks.SourceMap] = GetCentcomm(),
[ShuttleTimerMasks.DestMap] = GetStation(),
[ShuttleTimerMasks.ShuttleTime] = countdownTime,
[ShuttleTimerMasks.SourceTime] = countdownTime + TimeSpan.FromSeconds(_shuttle.TransitTime + _cfg.GetCVar(CCVars.EmergencyShuttleDockTime)),
[ShuttleTimerMasks.DestTime] = countdownTime,
Shuttle = shuttle,
SourceMap = GetCentcomm(),
DestinationMap = GetStation(),
ShuttleTime = countdownTime,
SourceTime = countdownTime + TimeSpan.FromSeconds(_shuttle.TransitTime + _cfg.GetCVar(CCVars.EmergencyShuttleDockTime)),
DestinationTime = countdownTime,
};
_deviceNetworkSystem.QueuePacket(shuttle.Value, null, payload, net.TransmitFrequency);
_deviceNetworkSystem.SendPacket(shuttle.Value, null, ref payload, net.TransmitFrequency);
}
}
@ -271,16 +270,16 @@ namespace Content.Server.RoundEnd
var shuttle = _shuttle.GetShuttle();
if (shuttle != null && TryComp<DeviceNetworkComponent>(shuttle, out var net))
{
var payload = new NetworkPayload
var payload = new ScreenShuttlePayload
{
[ShuttleTimerMasks.ShuttleMap] = shuttle,
[ShuttleTimerMasks.SourceMap] = GetCentcomm(),
[ShuttleTimerMasks.DestMap] = GetStation(),
[ShuttleTimerMasks.ShuttleTime] = zero,
[ShuttleTimerMasks.SourceTime] = zero,
[ShuttleTimerMasks.DestTime] = zero,
Shuttle = shuttle,
SourceMap = GetCentcomm(),
DestinationMap = GetStation(),
ShuttleTime = zero,
SourceTime = zero,
DestinationTime = zero,
};
_deviceNetworkSystem.QueuePacket(shuttle.Value, null, payload, net.TransmitFrequency);
_deviceNetworkSystem.SendPacket(shuttle.Value, null, ref payload, net.TransmitFrequency);
}
}

View file

@ -1,7 +1,8 @@
using Content.Shared.TextScreen;
using Content.Server.Screens.Components;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.RoundEnd;
using Content.Shared.Screens;
using Robust.Shared.Timing;
namespace Content.Server.Screens.Systems;
@ -14,24 +15,13 @@ public sealed partial class ScreenSystem : EntitySystem
[Dependency] private IGameTiming _gameTiming = default!;
[Dependency] private SharedAppearanceSystem _appearanceSystem = default!;
/// <summary>
/// Calls either a normal screen text update or shuttle timer update based on the presence of
/// <see cref="ShuttleTimerMasks.ShuttleMap"/> in <see cref="args.Data"/>
/// </summary>
[SubscribeLocalEvent]
private void OnPacketReceived(Entity<ScreenComponent> ent, ref DeviceNetworkPacketEvent args)
{
if (args.Data.TryGetValue(ShuttleTimerMasks.ShuttleMap, out _))
ShuttleTimer(ent, args);
else
ScreenText(ent, args);
}
/// <summary>
/// Send a text update to every screen on the same MapUid as the originating comms console.
/// </summary>
private void ScreenText(Entity<ScreenComponent> ent, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnScreenText(Entity<ScreenComponent> ent, ref DeviceNetworkPacketEvent<ScreenTextPayload> args)
{
var text = args.Data.Text;
// don't allow text updates if there's an active timer
// (and just check here so the server doesn't have to track them)
if (_appearanceSystem.TryGetData(ent, TextScreenVisuals.TargetTime, out TimeSpan target)
@ -41,17 +31,15 @@ public sealed partial class ScreenSystem : EntitySystem
var screenMap = Transform(ent).MapUid;
var argsMap = Transform(args.Sender).MapUid;
if (screenMap != null
&& argsMap != null
&& screenMap == argsMap
&& args.Data.TryGetValue(ScreenMasks.Text, out string? text)
&& text != null
)
{
_appearanceSystem.SetData(ent, TextScreenVisuals.DefaultText, text);
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenText, text);
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenTextTime, _gameTiming.CurTime);
}
if (screenMap == null
|| argsMap == null
|| screenMap != argsMap
|| text == null)
return;
_appearanceSystem.SetData(ent, TextScreenVisuals.DefaultText, text);
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenText, text);
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenTextTime, _gameTiming.CurTime);
}
/// <summary>
@ -62,49 +50,46 @@ public sealed partial class ScreenSystem : EntitySystem
/// Subnets are the shuttle, source, and dest. Source/dest change each jump.
/// This is required to send different timers to the shuttle/terminal/station.
/// </summary>
private void ShuttleTimer(Entity<ScreenComponent> ent, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnShuttleTimer(Entity<ScreenComponent> ent, ref DeviceNetworkPacketEvent<ScreenShuttlePayload> args)
{
var payload = args.Data;
var timerXform = Transform(ent);
// no false positives.
if (timerXform.MapUid == null)
return;
string key;
args.Data.TryGetValue(ShuttleTimerMasks.ShuttleMap, out EntityUid? shuttleMap);
args.Data.TryGetValue(ShuttleTimerMasks.SourceMap, out EntityUid? source);
args.Data.TryGetValue(ShuttleTimerMasks.DestMap, out EntityUid? dest);
args.Data.TryGetValue(ShuttleTimerMasks.Docked, out bool docked);
string text = docked ? ShuttleTimerMasks.ETD : ShuttleTimerMasks.ETA;
string? text = null;
TimeSpan time;
switch (timerXform.MapUid)
{
// sometimes the timer transforms on FTL shuttles have a hyperspace mapuid, so matching by grid works as a fallback.
case var local when local == shuttleMap || timerXform.GridUid == shuttleMap:
key = ShuttleTimerMasks.ShuttleTime;
// sometimes the timer transforms on FTL shuttles have a hyperspace mapent, so matching by grid works as a fallback.
case var local when local == payload.Shuttle || timerXform.GridUid == payload.Shuttle:
time = payload.ShuttleTime;
break;
case var origin when origin == source:
key = ShuttleTimerMasks.SourceTime;
case var origin when origin == payload.SourceMap:
time = payload.SourceTime;
break;
case var remote when remote == dest:
key = ShuttleTimerMasks.DestTime;
case var remote when remote == payload.DestinationMap:
time = payload.DestinationTime;
text = ShuttleTimerMasks.ETA;
break;
default:
return;
}
if (!args.Data.TryGetValue(key, out TimeSpan duration))
return;
if (payload.OverrideText != null)
text = payload.OverrideText;
if (args.Data.TryGetValue(ScreenMasks.Text, out string? label) && label != null)
text = label;
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenText, text);
_appearanceSystem.SetData(ent, TextScreenVisuals.TargetTime, _gameTiming.CurTime + duration);
_appearanceSystem.SetData(ent, TextScreenVisuals.TargetTime, _gameTiming.CurTime + time);
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenTextTime, _gameTiming.CurTime);
if (args.Data.TryGetValue(ScreenMasks.Color, out Color color))
_appearanceSystem.SetData(ent, TextScreenVisuals.Color, color);
if (text != null)
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenText, text);
if (payload.OverrideColor != null)
_appearanceSystem.SetData(ent, TextScreenVisuals.Color, payload.OverrideColor);
}
}

View file

@ -14,20 +14,3 @@ public sealed partial class BatterySensorComponent : Component
{
}
/// <summary>
/// Device network data sent by a <see cref="BatterySensorComponent"/>.
/// </summary>
/// <param name="Charge">The current energy charge of the battery, in joules (J).</param>
/// <param name="MaxCharge">The maximum energy capacity of the battery, in joules (J).</param>
/// <param name="Receiving">The current amount of power being received by the battery, in watts (W).</param>
/// <param name="MaxReceiving">The maximum amount of power that can be received by the battery, in watts (W).</param>
/// <param name="Supplying">The current amount of power being supplied by the battery, in watts (W).</param>
/// <param name="MaxSupplying">The maximum amount of power that can be received by the battery, in watts (W).</param>
public sealed record BatterySensorData(
float Charge,
float MaxCharge,
float Receiving,
float MaxReceiving,
float Supplying,
float MaxSupplying
);

View file

@ -1,50 +1,35 @@
using Content.Server.DeviceNetwork.Systems;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Power.Components;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
using Content.Shared.SensorMonitoring;
namespace Content.Server.SensorMonitoring;
public sealed partial class BatterySensorSystem : EntitySystem
{
public const string DeviceNetworkCommandSyncData = "bat_sync_data";
[Dependency] private DeviceNetworkSystem _deviceNetwork = default!;
[Dependency] private SharedBatterySystem _battery = default!;
public override void Initialize()
[SubscribeLocalEvent]
private void OnSensorRequest(Entity<BatterySensorComponent> ent, ref DeviceNetworkPacketEvent<BatterySensorSyncPayload> args)
{
SubscribeLocalEvent<BatterySensorComponent, DeviceNetworkPacketEvent>(PacketReceived);
}
var battery = Comp<BatteryComponent>(ent);
var currentCharge = _battery.GetCharge((ent.Owner, battery));
var netBattery = Comp<PowerNetworkBatteryComponent>(ent);
private void PacketReceived(EntityUid uid, BatterySensorComponent component, DeviceNetworkPacketEvent args)
{
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
return;
switch (cmd)
var dataPayload = new BatterySensorDataPayload
{
case DeviceNetworkCommandSyncData:
var battery = Comp<BatteryComponent>(uid);
var currentCharge = _battery.GetCharge((uid, battery));
var netBattery = Comp<PowerNetworkBatteryComponent>(uid);
Data = new BatterySensorData(
currentCharge,
battery.MaxCharge,
netBattery.CurrentReceiving,
netBattery.MaxChargeRate,
netBattery.CurrentSupply,
netBattery.MaxSupply),
};
var payload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = DeviceNetworkCommandSyncData,
[DeviceNetworkCommandSyncData] = new BatterySensorData(
currentCharge,
battery.MaxCharge,
netBattery.CurrentReceiving,
netBattery.MaxChargeRate,
netBattery.CurrentSupply,
netBattery.MaxSupply)
};
_deviceNetwork.QueuePacket(uid, args.SenderAddress, payload);
break;
}
_deviceNetwork.SendPacket(ent.Owner, args.SenderAddress, ref dataPayload);
}
}

View file

@ -1,5 +1,5 @@
using Content.Server.Atmos.Monitor.Components;
using Content.Server.Atmos.Monitor.Systems;
using Content.Server.Atmos.Monitor.Payloads;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Power.Generation.Teg;
using Content.Shared.Atmos.Components;
@ -10,6 +10,7 @@ using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Systems;
using Content.Shared.Power.Generation.Teg;
using Content.Shared.SensorMonitoring;
using Robust.Server.GameObjects;
using Robust.Shared.Timing;
@ -41,7 +42,6 @@ public sealed partial class SensorMonitoringConsoleSystem : EntitySystem
SubscribeLocalEvent<SensorMonitoringConsoleComponent, DeviceListUpdateEvent>(DeviceListUpdated);
SubscribeLocalEvent<SensorMonitoringConsoleComponent, ComponentStartup>(ConsoleStartup);
SubscribeLocalEvent<SensorMonitoringConsoleComponent, DeviceNetworkPacketEvent>(DevicePacketReceived);
SubscribeLocalEvent<SensorMonitoringConsoleComponent, AtmosDeviceUpdateEvent>(AtmosUpdate);
}
@ -143,99 +143,86 @@ public sealed partial class SensorMonitoringConsoleSystem : EntitySystem
return SensorDeviceType.Unknown;
}
private void DevicePacketReceived(EntityUid uid, SensorMonitoringConsoleComponent component,
DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnTegReceived(Entity<SensorMonitoringConsoleComponent> ent, ref DeviceNetworkPacketEvent<TegSensorPayload> args)
{
if (!component.Sensors.TryGetValue(args.Sender, out var sensorData))
var payload = args.Data;
if (!ent.Comp.Sensors.TryGetValue(args.Sender, out var sensorData))
return;
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
// @formatter:off
WriteSample(ent, sensorData, "teg_last_generated", SensorUnit.EnergyJ, payload.LastGeneration);
WriteSample(ent, sensorData, "teg_power", SensorUnit.PowerW, payload.PowerOutput);
if (ent.Comp.DebugStreams)
WriteSample(ent, sensorData, "teg_ramp_pos", SensorUnit.PowerW, payload.RampPosition);
WriteSample(ent, sensorData, "teg_circ_a_in_pressure", SensorUnit.PressureKpa, payload.CirculatorA.InletPressure);
WriteSample(ent, sensorData, "teg_circ_a_in_temperature", SensorUnit.TemperatureK, payload.CirculatorA.InletTemperature);
WriteSample(ent, sensorData, "teg_circ_a_out_pressure", SensorUnit.PressureKpa, payload.CirculatorA.OutletPressure);
WriteSample(ent, sensorData, "teg_circ_a_out_temperature", SensorUnit.TemperatureK, payload.CirculatorA.OutletTemperature);
WriteSample(ent, sensorData, "teg_circ_b_in_pressure", SensorUnit.PressureKpa, payload.CirculatorB.InletPressure);
WriteSample(ent, sensorData, "teg_circ_b_in_temperature", SensorUnit.TemperatureK, payload.CirculatorB.InletTemperature);
WriteSample(ent, sensorData, "teg_circ_b_out_pressure", SensorUnit.PressureKpa, payload.CirculatorB.OutletPressure);
WriteSample(ent, sensorData, "teg_circ_b_out_temperature", SensorUnit.TemperatureK, payload.CirculatorB.OutletTemperature);
// @formatter:on
}
[SubscribeLocalEvent]
private void OnAtmosSensorReceived(Entity<SensorMonitoringConsoleComponent> ent, ref DeviceNetworkPacketEvent<AtmosMonitorDataPayload> args)
{
var payload = args.Data.Data;
if (!ent.Comp.Sensors.TryGetValue(args.Sender, out var sensorData))
return;
switch (sensorData.DeviceType)
{
case SensorDeviceType.Teg:
if (command != TegSystem.DeviceNetworkCommandSyncData)
return;
// @formatter:off
WriteSample(ent, sensorData, "atmo_pressure", SensorUnit.PressureKpa, payload.Pressure);
WriteSample(ent, sensorData, "atmo_temperature", SensorUnit.TemperatureK, payload.Temperature);
// @formatter:on
}
if (!args.Data.TryGetValue(TegSystem.DeviceNetworkCommandSyncData, out TegSensorData? tegData))
return;
[SubscribeLocalEvent]
private void OnThermoMachineReceived(Entity<SensorMonitoringConsoleComponent> ent, ref DeviceNetworkPacketEvent<GasThermoMachineDataPayload> args)
{
var payload = args.Data;
if (!ent.Comp.Sensors.TryGetValue(args.Sender, out var sensorData))
return;
// @formatter:off
WriteSample(component, sensorData, "teg_last_generated", SensorUnit.EnergyJ, tegData.LastGeneration);
WriteSample(component, sensorData, "teg_power", SensorUnit.PowerW, tegData.PowerOutput);
if (component.DebugStreams)
WriteSample(component, sensorData, "teg_ramp_pos", SensorUnit.PowerW, tegData.RampPosition);
// @formatter:off
WriteSample(ent, sensorData, "abs_energy_delta", SensorUnit.EnergyJ, MathF.Abs(payload.EnergyDelta));
// @formatter:on
}
WriteSample(component, sensorData, "teg_circ_a_in_pressure", SensorUnit.PressureKpa, tegData.CirculatorA.InletPressure);
WriteSample(component, sensorData, "teg_circ_a_in_temperature", SensorUnit.TemperatureK, tegData.CirculatorA.InletTemperature);
WriteSample(component, sensorData, "teg_circ_a_out_pressure", SensorUnit.PressureKpa, tegData.CirculatorA.OutletPressure);
WriteSample(component, sensorData, "teg_circ_a_out_temperature", SensorUnit.TemperatureK, tegData.CirculatorA.OutletTemperature);
[SubscribeLocalEvent]
private void OnVolumePipeReceived(Entity<SensorMonitoringConsoleComponent> ent, ref DeviceNetworkPacketEvent<GasVolumePumpDataPayload> args)
{
var payload = args.Data;
if (!ent.Comp.Sensors.TryGetValue(args.Sender, out var sensorData))
return;
WriteSample(component, sensorData, "teg_circ_b_in_pressure", SensorUnit.PressureKpa, tegData.CirculatorB.InletPressure);
WriteSample(component, sensorData, "teg_circ_b_in_temperature", SensorUnit.TemperatureK, tegData.CirculatorB.InletTemperature);
WriteSample(component, sensorData, "teg_circ_b_out_pressure", SensorUnit.PressureKpa, tegData.CirculatorB.OutletPressure);
WriteSample(component, sensorData, "teg_circ_b_out_temperature", SensorUnit.TemperatureK, tegData.CirculatorB.OutletTemperature);
// @formatter:on
break;
// @formatter:off
WriteSample(ent, sensorData, "moles_transferred", SensorUnit.Moles, payload.LastMolesTransferred);
// @formatter:on
}
case SensorDeviceType.AtmosSensor:
if (command != AtmosDeviceNetworkSystem.SyncData)
return;
[SubscribeLocalEvent]
private void OnBatteryReceived(Entity<SensorMonitoringConsoleComponent> ent, ref DeviceNetworkPacketEvent<BatterySensorDataPayload> args)
{
if (!ent.Comp.Sensors.TryGetValue(args.Sender, out var sensorData))
return;
if (!args.Data.TryGetValue(AtmosDeviceNetworkSystem.SyncData, out AtmosSensorData? atmosData))
return;
var batteryData = args.Data.Data;
// @formatter:off
WriteSample(component, sensorData, "atmo_pressure", SensorUnit.PressureKpa, atmosData.Pressure);
WriteSample(component, sensorData, "atmo_temperature", SensorUnit.TemperatureK, atmosData.Temperature);
// @formatter:on
break;
// @formatter:off
WriteSample(ent, sensorData, "charge", SensorUnit.EnergyJ, batteryData.Charge);
WriteSample(ent, sensorData, "charge_max", SensorUnit.EnergyJ, batteryData.MaxCharge);
case SensorDeviceType.ThermoMachine:
if (command != AtmosDeviceNetworkSystem.SyncData)
return;
WriteSample(ent, sensorData, "receiving", SensorUnit.PowerW, batteryData.Receiving);
WriteSample(ent, sensorData, "receiving_max", SensorUnit.PowerW, batteryData.MaxReceiving);
if (!args.Data.TryGetValue(AtmosDeviceNetworkSystem.SyncData, out GasThermoMachineData? thermoData))
return;
// @formatter:off
WriteSample(component, sensorData, "abs_energy_delta", SensorUnit.EnergyJ, MathF.Abs(thermoData.EnergyDelta));
// @formatter:on
break;
case SensorDeviceType.VolumePump:
if (command != AtmosDeviceNetworkSystem.SyncData)
return;
if (!args.Data.TryGetValue(AtmosDeviceNetworkSystem.SyncData, out GasVolumePumpData? volumePumpData))
return;
// @formatter:off
WriteSample(component, sensorData, "moles_transferred", SensorUnit.Moles, volumePumpData.LastMolesTransferred);
// @formatter:on
break;
case SensorDeviceType.Battery:
if (command != BatterySensorSystem.DeviceNetworkCommandSyncData)
return;
if (!args.Data.TryGetValue(BatterySensorSystem.DeviceNetworkCommandSyncData, out BatterySensorData? batteryData))
return;
// @formatter:off
WriteSample(component, sensorData, "charge", SensorUnit.EnergyJ, batteryData.Charge);
WriteSample(component, sensorData, "charge_max", SensorUnit.EnergyJ, batteryData.MaxCharge);
WriteSample(component, sensorData, "receiving", SensorUnit.PowerW, batteryData.Receiving);
WriteSample(component, sensorData, "receiving_max", SensorUnit.PowerW, batteryData.MaxReceiving);
WriteSample(component, sensorData, "supplying", SensorUnit.PowerW, batteryData.Supplying);
WriteSample(component, sensorData, "supplying_max", SensorUnit.PowerW, batteryData.MaxSupplying);
// @formatter:on
break;
}
WriteSample(ent, sensorData, "supplying", SensorUnit.PowerW, batteryData.Supplying);
WriteSample(ent, sensorData, "supplying_max", SensorUnit.PowerW, batteryData.MaxSupplying);
// @formatter:on
}
private void WriteSample(
@ -267,32 +254,22 @@ public sealed partial class SensorMonitoringConsoleSystem : EntitySystem
foreach (var (ent, data) in comp.Sensors)
{
// Send network requests for new data!
NetworkPayload payload;
INetworkPayload payload;
switch (data.DeviceType)
{
case SensorDeviceType.Teg:
payload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = TegSystem.DeviceNetworkCommandSyncData
};
break;
case SensorDeviceType.AtmosSensor:
case SensorDeviceType.ThermoMachine:
case SensorDeviceType.VolumePump:
payload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = AtmosDeviceNetworkSystem.SyncData
};
payload = new AtmosSyncPayload();
break;
default:
// Unknown device type, don't do anything.
continue;
}
var address = _deviceNetworkQuery.GetComponent(ent);
_deviceNetwork.QueuePacket(uid, address.Address, payload);
_deviceNetwork.SendPacket(uid, address.Address, ref payload);
}
}
@ -301,23 +278,19 @@ public sealed partial class SensorMonitoringConsoleSystem : EntitySystem
foreach (var (ent, data) in comp.Sensors)
{
// Send network requests for new data!
NetworkPayload payload;
INetworkPayload payload;
switch (data.DeviceType)
{
case SensorDeviceType.Battery:
payload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = BatterySensorSystem.DeviceNetworkCommandSyncData
};
payload = new BatterySensorSyncPayload();
break;
default:
// Unknown device type, don't do anything.
continue;
}
var address = _deviceNetworkQuery.GetComponent(ent);
_deviceNetwork.QueuePacket(uid, address.Address, payload);
_deviceNetwork.SendPacket(uid, address.Address, ref payload);
}
}
}

View file

@ -7,7 +7,6 @@ using Content.Server.DeviceNetwork.Systems;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Events;
using Content.Server.Parallax;
using Content.Server.Screens.Components;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.Spawners.Components;
@ -17,12 +16,12 @@ using Content.Server.Station.Systems;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Damage.Components;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.GameTicking;
using Content.Shared.Mobs.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Parallax.Biomes;
using Content.Shared.RoundEnd;
using Content.Shared.Salvage;
using Content.Shared.Shuttles.Components;
using Content.Shared.Tiles;
@ -208,10 +207,10 @@ public sealed partial class ArrivalsSystem : EntitySystem
TryComp<FTLComponent>(shuttleUid, out var ftlComp);
var ftlTime = TimeSpan.FromSeconds(ftlComp?.TravelTime ?? _shuttles.DefaultTravelTime);
var payload = new NetworkPayload
var payload = new ScreenShuttlePayload
{
[ShuttleTimerMasks.ShuttleMap] = shuttleUid,
[ShuttleTimerMasks.ShuttleTime] = ftlTime
Shuttle = shuttleUid,
ShuttleTime = ftlTime,
};
// unfortunate levels of spaghetti due to roundstart arrivals ftl behavior
@ -224,16 +223,16 @@ public sealed partial class ArrivalsSystem : EntitySystem
sourceMap = station == null ? null : Transform(station.Value)?.MapUid;
arrivalsDelay += RoundStartFTLDuration;
component.FirstRun = false;
payload.Add(ShuttleTimerMasks.DestMap, Transform(args.TargetCoordinates.EntityId).MapUid);
payload.Add(ShuttleTimerMasks.DestTime, ftlTime);
payload.DestinationMap = Transform(args.TargetCoordinates.EntityId).MapUid;
payload.DestinationTime = ftlTime;
}
else
sourceMap = args.FromMapUid;
payload.Add(ShuttleTimerMasks.SourceMap, sourceMap);
payload.Add(ShuttleTimerMasks.SourceTime, ftlTime + TimeSpan.FromSeconds(arrivalsDelay));
payload.SourceMap = sourceMap;
payload.SourceTime = ftlTime + TimeSpan.FromSeconds(arrivalsDelay);
_deviceNetworkSystem.QueuePacket(shuttleUid, null, payload, netComp.TransmitFrequency);
_deviceNetworkSystem.SendPacket(shuttleUid, null, ref payload, netComp.TransmitFrequency);
}
// Don't do anything here when leaving arrivals.
@ -282,15 +281,15 @@ public sealed partial class ArrivalsSystem : EntitySystem
if (TryComp<DeviceNetworkComponent>(uid, out var netComp))
{
var payload = new NetworkPayload
var payload = new ScreenShuttlePayload
{
[ShuttleTimerMasks.ShuttleMap] = uid,
[ShuttleTimerMasks.ShuttleTime] = dockTime,
[ShuttleTimerMasks.SourceMap] = args.MapUid,
[ShuttleTimerMasks.SourceTime] = dockTime,
[ShuttleTimerMasks.Docked] = true
Shuttle = uid,
ShuttleTime = dockTime,
SourceMap = args.MapUid,
SourceTime = dockTime,
Docked = true,
};
_deviceNetworkSystem.QueuePacket(uid, null, payload, netComp.TransmitFrequency);
_deviceNetworkSystem.SendPacket(uid, null, ref payload, netComp.TransmitFrequency);
}
}

View file

@ -18,11 +18,11 @@ public sealed partial class DockingSignalControlSystem : EntitySystem
private void OnDocked(Entity<DockingSignalControlComponent> ent, ref DockEvent args)
{
_deviceLinkSystem.SendSignal(ent, ent.Comp.DockStatusSignalPort, signal: true);
_deviceLinkSystem.SendSignal(ent.Owner, ent.Comp.DockStatusSignalPort, signal: true);
}
private void OnUndocked(Entity<DockingSignalControlComponent> ent, ref UndockEvent args)
{
_deviceLinkSystem.SendSignal(ent, ent.Comp.DockStatusSignalPort, signal: false);
_deviceLinkSystem.SendSignal(ent.Owner, ent.Comp.DockStatusSignalPort, signal: false);
}
}

View file

@ -1,13 +1,12 @@
using System.Threading;
using Content.Server.Screens.Components;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Shared.Access;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.Popups;
using Content.Shared.RoundEnd;
using Content.Shared.Shuttles.BUIStates;
using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Events;
@ -376,17 +375,17 @@ public sealed partial class EmergencyShuttleSystem
var shuttle = GetShuttle();
if (shuttle != null && TryComp<DeviceNetworkComponent>(shuttle, out var net))
{
var payload = new NetworkPayload
var payload = new ScreenShuttlePayload
{
[ShuttleTimerMasks.ShuttleMap] = shuttle,
[ShuttleTimerMasks.SourceMap] = _roundEnd.GetStation(),
[ShuttleTimerMasks.DestMap] = _roundEnd.GetCentcomm(),
[ShuttleTimerMasks.ShuttleTime] = time,
[ShuttleTimerMasks.SourceTime] = time,
[ShuttleTimerMasks.DestTime] = time + TimeSpan.FromSeconds(TransitTime),
[ShuttleTimerMasks.Docked] = true
Shuttle = shuttle,
SourceMap = _roundEnd.GetStation(),
DestinationMap = _roundEnd.GetCentcomm(),
ShuttleTime = time,
SourceTime = time,
DestinationTime = time + TimeSpan.FromSeconds(TransitTime),
Docked = true,
};
_deviceNetworkSystem.QueuePacket(shuttle.Value, null, payload, net.TransmitFrequency);
_deviceNetworkSystem.SendPacket(shuttle.Value, null, ref payload, net.TransmitFrequency);
}
return true;

View file

@ -19,10 +19,10 @@ using Content.Server.Station.Systems;
using Content.Shared.Access.Systems;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.GameTicking;
using Content.Shared.Localizations;
using Content.Shared.RoundEnd;
using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Events;
using Content.Shared.Shuttles.Systems;
@ -210,16 +210,16 @@ public sealed partial class EmergencyShuttleSystem : SharedEmergencyShuttleSyste
if (TryComp<DeviceNetworkComponent>(uid, out var netComp))
{
var payload = new NetworkPayload
var payload = new ScreenShuttlePayload
{
[ShuttleTimerMasks.ShuttleMap] = uid,
[ShuttleTimerMasks.SourceMap] = args.FromMapUid,
[ShuttleTimerMasks.DestMap] = _transformSystem.GetMap(args.TargetCoordinates),
[ShuttleTimerMasks.ShuttleTime] = ftlTime,
[ShuttleTimerMasks.SourceTime] = ftlTime,
[ShuttleTimerMasks.DestTime] = ftlTime
Shuttle = uid,
SourceMap = args.FromMapUid,
DestinationMap = _transformSystem.GetMap(args.TargetCoordinates),
ShuttleTime = ftlTime,
SourceTime = ftlTime,
DestinationTime = ftlTime,
};
_deviceNetworkSystem.QueuePacket(uid, null, payload, netComp.TransmitFrequency);
_deviceNetworkSystem.SendPacket(uid, null, ref payload, netComp.TransmitFrequency);
}
}
@ -232,27 +232,27 @@ public sealed partial class EmergencyShuttleSystem : SharedEmergencyShuttleSyste
var shuttle = args.Entity;
if (TryComp<DeviceNetworkComponent>(shuttle, out var net))
{
var payload = new NetworkPayload
var payload = new ScreenShuttlePayload
{
[ShuttleTimerMasks.ShuttleMap] = shuttle,
[ShuttleTimerMasks.SourceMap] = _roundEnd.GetCentcomm(),
[ShuttleTimerMasks.DestMap] = _roundEnd.GetStation(),
[ShuttleTimerMasks.ShuttleTime] = countdownTime,
[ShuttleTimerMasks.SourceTime] = countdownTime,
[ShuttleTimerMasks.DestTime] = countdownTime,
Shuttle = shuttle,
SourceMap = _roundEnd.GetCentcomm(),
DestinationMap = _roundEnd.GetStation(),
ShuttleTime = countdownTime,
SourceTime = countdownTime,
DestinationTime = countdownTime,
};
// by popular request
// https://discord.com/channels/310555209753690112/770682801607278632/1189989482234126356
if (_random.Next(1000) == 0)
{
payload.Add(ScreenMasks.Text, ShuttleTimerMasks.Kill);
payload.Add(ScreenMasks.Color, Color.Red);
payload.OverrideText = ShuttleTimerMasks.Kill;
payload.OverrideColor = Color.Red;
}
else
payload.Add(ScreenMasks.Text, ShuttleTimerMasks.Bye);
payload.OverrideText = ShuttleTimerMasks.Bye;
_deviceNetworkSystem.QueuePacket(shuttle, null, payload, net.TransmitFrequency);
_deviceNetworkSystem.SendPacket(shuttle, null, ref payload, net.TransmitFrequency);
}
}
@ -377,17 +377,17 @@ public sealed partial class EmergencyShuttleSystem : SharedEmergencyShuttleSyste
var time = TimeSpan.FromSeconds(_consoleAccumulator);
if (TryComp<DeviceNetworkComponent>(shuttle, out var netComp))
{
var payload = new NetworkPayload
var payload = new ScreenShuttlePayload
{
[ShuttleTimerMasks.ShuttleMap] = shuttle,
[ShuttleTimerMasks.SourceMap] = targetXform.MapUid,
[ShuttleTimerMasks.DestMap] = _roundEnd.GetCentcomm(),
[ShuttleTimerMasks.ShuttleTime] = time,
[ShuttleTimerMasks.SourceTime] = time,
[ShuttleTimerMasks.DestTime] = time + TimeSpan.FromSeconds(TransitTime),
[ShuttleTimerMasks.Docked] = true,
Shuttle = shuttle,
SourceMap = targetXform.MapUid,
DestinationMap = _roundEnd.GetCentcomm(),
ShuttleTime = time,
SourceTime = time,
DestinationTime = time + TimeSpan.FromSeconds(TransitTime),
Docked = true,
};
_deviceNetworkSystem.QueuePacket(shuttle.Value, null, payload, netComp.TransmitFrequency);
_deviceNetworkSystem.SendPacket(shuttle.Value, null, ref payload, netComp.TransmitFrequency);
}
// Play announcement audio.

View file

@ -1,4 +1,3 @@
using Content.Shared.DeviceNetwork;
using Content.Shared.Damage.Components;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs;
@ -16,11 +15,6 @@ namespace Content.Server.Silicons.Borgs;
/// <inheritdoc/>
public sealed partial class BorgSystem
{
private void InitializeTransponder()
{
SubscribeLocalEvent<BorgTransponderComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
}
public void UpdateTransponder(float frameTime)
{
var now = _timing.CurTime;
@ -52,12 +46,11 @@ public sealed partial class BorgSystem
hasBrain,
canDisable);
var payload = new NetworkPayload()
var payload = new RoboticsCyborgDataPayload
{
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
[RoboticsConsoleConstants.NET_CYBORG_DATA] = data
Data = data,
};
_deviceNetwork.QueuePacket(uid, null, payload, device: device);
_deviceNetwork.SendPacket((uid, device), null, ref payload);
comp.NextBroadcast = now + comp.BroadcastDelay;
}
@ -81,16 +74,16 @@ public sealed partial class BorgSystem
_container.Remove(brain, ent.Comp2.BrainContainer);
}
private void OnPacketReceived(Entity<BorgTransponderComponent> ent, ref DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnDisable(Entity<BorgTransponderComponent> ent, ref DeviceNetworkPacketEvent<RoboticsCyborgDisablePayload> args)
{
var payload = args.Data;
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
return;
Disable(ent);
}
if (command == RoboticsConsoleConstants.NET_DISABLE_COMMAND)
Disable(ent);
else if (command == RoboticsConsoleConstants.NET_DESTROY_COMMAND)
Destroy(ent.Owner);
[SubscribeLocalEvent]
private void OnDestroy(Entity<BorgTransponderComponent> ent, ref DeviceNetworkPacketEvent<RoboticsCyborgDestroyPayload> args)
{
Destroy(ent.AsNullable());
}
private void Disable(Entity<BorgTransponderComponent, BorgChassisComponent?> ent)

View file

@ -36,14 +36,6 @@ public sealed partial class BorgSystem : SharedBorgSystem
public static readonly ProtoId<JobPrototype> BorgJobId = "Borg";
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
InitializeTransponder();
}
public override bool CanPlayerBeBorged(ICommonSession session)
{
if (_banManager.GetJobBans(session.UserId)?.Contains(BorgJobId) == true)

View file

@ -8,22 +8,29 @@ public sealed partial class SurveillanceCameraRouterComponent : Component
{
[ViewVariables] public bool Active { get; set; }
// The name of the subnet connected to this router.
[DataField("subnetName")]
public string SubnetName { get; set; } = string.Empty;
/// <summary>
/// The name of the subnet connected to this router.
/// </summary>
[ViewVariables]
public string SubnetName = string.Empty;
/// <summary>
/// The monitors to route to. This raises an issue related to
/// camera monitors disappearing before sending a D/C packet,
/// this could probably be refreshed every time a new monitor
/// is added or removed from active routing.
/// </summary>
[ViewVariables]
// The monitors to route to. This raises an issue related to
// camera monitors disappearing before sending a D/C packet,
// this could probably be refreshed every time a new monitor
// is added or removed from active routing.
public HashSet<string> MonitorRoutes { get; } = new();
/// <summary>
/// The frequency that talks to this router's subnet.
/// </summary>
[ViewVariables]
// The frequency that talks to this router's subnet.
public uint SubnetFrequency;
[DataField("subnetFrequency")]
public ProtoId<DeviceFrequencyPrototype>? SubnetFrequencyId { get; set; }
public ProtoId<DeviceFrequencyPrototype>? SubnetFrequencyId { get; set; }
[DataField("setupAvailableNetworks")]
public List<ProtoId<DeviceFrequencyPrototype>> AvailableNetworks { get; private set; } = new();

View file

@ -76,6 +76,9 @@ public sealed partial class SurveillanceCameraMapSystem : EntitySystem
if (gridUid is null)
return;
if (deviceNet.ReceiveFrequencyId == null)
return;
var netEntity = GetNetEntity(uid);
var mapComp = EnsureComp<SurveillanceCameraMapComponent>(gridUid.Value);
@ -84,7 +87,7 @@ public sealed partial class SurveillanceCameraMapSystem : EntitySystem
var localPos = Vector2.Transform(worldPos, gridMatrix);
var address = deviceNet.Address;
var subnet = deviceNet.ReceiveFrequencyId ?? string.Empty;
var subnet = deviceNet.ReceiveFrequencyId.Value;
var powered = CompOrNull<ApcPowerReceiverComponent>(uid)?.Powered ?? true;
var active = comp.Active && powered;

View file

@ -2,6 +2,7 @@ using System.Linq;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Systems;
using Content.Shared.Power;
using Content.Shared.UserInterface;
using Content.Shared.SurveillanceCamera;
@ -16,13 +17,14 @@ public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
[Dependency] private SurveillanceCameraSystem _surveillanceCameras = default!;
[Dependency] private UserInterfaceSystem _userInterface = default!;
[Dependency] private DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private DeviceNetworkRouterSystem _deviceNetworkRouter = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, SurveillanceCameraDeactivateEvent>(OnSurveillanceCameraDeactivate);
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, AfterActivatableUIOpenEvent>(OnToggleInterface);
Subs.BuiEvents<SurveillanceCameraMonitorComponent>(SurveillanceCameraMonitorUiKey.Key, subs =>
@ -93,72 +95,52 @@ public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
}
}
private void OnPacketReceived(EntityUid uid, SurveillanceCameraMonitorComponent component,
DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnCameraConnect(Entity<SurveillanceCameraMonitorComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraConnectPayload> args)
{
if (string.IsNullOrEmpty(args.SenderAddress))
var payload = args.Data;
if (ent.Comp.NextCameraAddress == payload.SenderAddress)
{
return;
if (payload.SenderAddress != null)
ent.Comp.ActiveCameraAddress = payload.SenderAddress;
TrySwitchCameraByUid(ent, payload.Sender, ent.Comp);
}
if (args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
ent.Comp.NextCameraAddress = null;
}
[SubscribeLocalEvent]
private void OnCameraHeartbeat(Entity<SurveillanceCameraMonitorComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraHeartbeatPayload> args)
{
if (args.Data.SenderAddress == ent.Comp.ActiveCameraAddress)
{
switch (command)
{
case SurveillanceCameraSystem.CameraConnectMessage:
if (component.NextCameraAddress == args.SenderAddress)
{
component.ActiveCameraAddress = args.SenderAddress;
TrySwitchCameraByUid(uid, args.Sender, component);
}
component.NextCameraAddress = null;
break;
case SurveillanceCameraSystem.CameraHeartbeatMessage:
if (args.SenderAddress == component.ActiveCameraAddress)
{
component.LastHeartbeat = 0;
}
break;
case SurveillanceCameraSystem.CameraDataMessage:
{
if (!args.Data.TryGetValue(SurveillanceCameraSystem.CameraNameData, out string? name)
|| !args.Data.TryGetValue(SurveillanceCameraSystem.CameraSubnetData, out ProtoId<DeviceFrequencyPrototype>? subnetData)
|| !args.Data.TryGetValue(SurveillanceCameraSystem.CameraAddressData, out string? address))
{
return;
}
if (component.ActiveSubnet != subnetData && subnetData is { } subnet)
{
DisconnectFromSubnet(uid, subnet);
}
if (!component.KnownCameras.ContainsKey(address))
{
component.KnownCameras.Add(address, name);
}
UpdateUserInterface(uid, component);
break;
}
case SurveillanceCameraSystem.CameraSubnetData:
{
if (args.Data.TryGetValue(SurveillanceCameraSystem.CameraSubnetData, out ProtoId<DeviceFrequencyPrototype>? subnetValue)
&& subnetValue is { } subnet
&& !component.KnownSubnets.ContainsKey(subnet))
{
component.KnownSubnets.Add(subnet, args.SenderAddress);
}
UpdateUserInterface(uid, component);
break;
}
}
ent.Comp.LastHeartbeat = 0;
}
}
[SubscribeLocalEvent]
private void OnCameraData(Entity<SurveillanceCameraMonitorComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraDataPayload> args)
{
var payload = args.Data;
var subnetData = payload.Subnet;
if (ent.Comp.ActiveSubnet != subnetData)
{
DisconnectFromSubnet(ent, subnetData);
}
if (payload.SenderAddress != null)
ent.Comp.KnownCameras.TryAdd(payload.SenderAddress, payload.Name);
UpdateUserInterface(ent, ent.Comp);
}
[SubscribeLocalEvent]
private void OnSubnetData(Entity<SurveillanceCameraMonitorComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraSubnetDataPayload> args)
{
ent.Comp.KnownSubnets.TryAdd(args.Data.TransmitFrequency, args.SenderAddress);
UpdateUserInterface(ent, ent.Comp);
}
private void OnDisconnectMessage(EntityUid uid, SurveillanceCameraMonitorComponent component,
SurveillanceCameraDisconnectMessage message)
{
@ -169,7 +151,7 @@ public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
SurveillanceCameraRefreshCamerasMessage message)
{
component.KnownCameras.Clear();
RequestActiveSubnetInfo(uid, component);
PingCameraNetwork(uid, component);
}
private void OnRefreshSubnetsMessage(EntityUid uid, SurveillanceCameraMonitorComponent component,
@ -231,13 +213,9 @@ public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
return;
}
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraHeartbeatMessage },
{ SurveillanceCameraSystem.CameraAddressData, monitor.ActiveCameraAddress }
};
var payload = new SurveillanceCameraHeartbeatPayload();
_deviceNetworkRouter.SendPacketRouted(uid, ref payload, subnetAddress, monitor.ActiveCameraAddress, ProtoMan.Index(activeSubnet).Frequency);
_deviceNetworkSystem.QueuePacket(uid, subnetAddress, payload);
monitor.LastHeartbeatSent = 0;
}
@ -267,21 +245,18 @@ public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
}
monitor.KnownSubnets.Clear();
PingCameraNetwork(uid, monitor);
PingSubnets(uid, monitor);
}
private void PingCameraNetwork(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
{
if (!Resolve(uid, ref monitor))
{
if (!Resolve(uid, ref monitor)
|| monitor.ActiveSubnet == null
|| !monitor.KnownSubnets.TryGetValue(monitor.ActiveSubnet.Value, out var subnetData))
return;
}
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraPingMessage }
};
_deviceNetworkSystem.QueuePacket(uid, null, payload);
var payload = new SurveillanceCameraPingPayload { Subnet = monitor.ActiveSubnet };
_deviceNetworkRouter.SendPacketRouted(uid, ref payload, null, null, ProtoMan.Index(monitor.ActiveSubnet.Value).Frequency);
}
private void SetActiveSubnet(EntityUid uid, ProtoId<DeviceFrequencyPrototype> subnet,
@ -303,20 +278,15 @@ public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
ConnectToSubnet(uid, subnet);
}
private void RequestActiveSubnetInfo(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
private void PingSubnets(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
{
if (!Resolve(uid, ref monitor)
|| monitor.ActiveSubnet is not { } activeSubnet
|| !monitor.KnownSubnets.TryGetValue(activeSubnet, out var address))
if (!Resolve(uid, ref monitor))
{
return;
}
var payload = new NetworkPayload()
{
{DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraPingSubnetMessage},
};
_deviceNetworkSystem.QueuePacket(uid, address, payload);
var payload = new SurveillanceCameraPingSubnetPayload();
_deviceNetworkSystem.SendPacket(uid, null, ref payload);
}
private void ConnectToSubnet(EntityUid uid, string subnet, SurveillanceCameraMonitorComponent? monitor = null)
@ -328,13 +298,10 @@ public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
return;
}
var payload = new NetworkPayload()
{
{DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraSubnetConnectMessage},
};
_deviceNetworkSystem.QueuePacket(uid, address, payload);
var payload = new SurveillanceCameraSubnetConnectPayload();
_deviceNetworkSystem.SendPacket(uid, address, ref payload);
RequestActiveSubnetInfo(uid);
PingSubnets(uid);
}
private void DisconnectFromSubnet(EntityUid uid, ProtoId<DeviceFrequencyPrototype> subnet, SurveillanceCameraMonitorComponent? monitor = null)
@ -345,11 +312,8 @@ public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
return;
}
var payload = new NetworkPayload()
{
{DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraSubnetDisconnectMessage},
};
_deviceNetworkSystem.QueuePacket(uid, address, payload);
var payload = new SurveillanceCameraSubnetDisconnectPayload();
_deviceNetworkSystem.SendPacket(uid, address, ref payload);
}
// Adds a viewer to the camera and the monitor.
@ -436,14 +400,9 @@ public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
|| !monitor.KnownSubnets.TryGetValue(activeSubnet, out var subnetAddress))
return;
var payload = new NetworkPayload()
{
{DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraConnectMessage},
{SurveillanceCameraSystem.CameraAddressData, address}
};
var payload = new SurveillanceCameraConnectRequestPayload();
monitor.NextCameraAddress = address;
_deviceNetworkSystem.QueuePacket(uid, subnetAddress, payload);
_deviceNetworkRouter.SendPacketRouted(uid, ref payload, subnetAddress, address, ProtoMan.Index(activeSubnet).Frequency);
}
// Attempts to switch over the current viewed camera on this monitor
@ -498,7 +457,12 @@ public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
return;
}
var state = new SurveillanceCameraMonitorUiState(GetNetEntity(monitor.ActiveCamera), monitor.KnownSubnets.Keys.ToHashSet(), monitor.ActiveCameraAddress, monitor.ActiveSubnet, monitor.KnownCameras);
var state = new SurveillanceCameraMonitorUiState(
GetNetEntity(monitor.ActiveCamera),
monitor.KnownSubnets.Keys.ToHashSet(),
monitor.ActiveCameraAddress,
monitor.ActiveSubnet,
monitor.KnownCameras);
_userInterface.SetUiState(uid, SurveillanceCameraMonitorUiKey.Key, state);
}
}

View file

@ -1,6 +1,5 @@
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.ActionBlocker;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power;
using Content.Shared.SurveillanceCamera;
@ -15,249 +14,163 @@ public sealed partial class SurveillanceCameraRouterSystem : EntitySystem
[Dependency] private DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private ActionBlockerSystem _actionBlocker = default!;
[Dependency] private UserInterfaceSystem _userInterface = default!;
[Dependency] private EntityQuery<SurveillanceCameraRouterComponent> _query = default!;
public override void Initialize()
{
SubscribeLocalEvent<SurveillanceCameraRouterComponent, ComponentInit>(OnInitialize);
SubscribeLocalEvent<SurveillanceCameraRouterComponent, DeviceNetworkPacketEvent>(OnPacketReceive);
base.Initialize();
SubscribeLocalEvent<SurveillanceCameraRouterComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SurveillanceCameraRouterComponent, SurveillanceCameraSetupSetNetwork>(OnSetNetwork);
SubscribeLocalEvent<SurveillanceCameraRouterComponent, GetVerbsEvent<AlternativeVerb>>(AddVerbs);
SubscribeLocalEvent<SurveillanceCameraRouterComponent, PowerChangedEvent>(OnPowerChanged);
}
private void OnInitialize(EntityUid uid, SurveillanceCameraRouterComponent router, ComponentInit args)
private void OnMapInit(Entity<SurveillanceCameraRouterComponent> ent, ref MapInitEvent args)
{
if (router.SubnetFrequencyId == null ||
!ProtoMan.TryIndex(router.SubnetFrequencyId, out DeviceFrequencyPrototype? subnetFrequency))
if (ent.Comp.SubnetFrequencyId == null
|| !ProtoMan.TryIndex(ent.Comp.SubnetFrequencyId, out var subnetFrequency))
return;
ent.Comp.SubnetFrequency = subnetFrequency.Frequency;
ent.Comp.Active = true;
if (string.IsNullOrEmpty(ent.Comp.SubnetName) && subnetFrequency.Name != null)
ent.Comp.SubnetName = Loc.GetString(subnetFrequency.Name);
}
[SubscribeLocalEvent]
private void OnSubnetConnect(Entity<SurveillanceCameraRouterComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraSubnetConnectPayload> args)
{
AddMonitorToRoute(ent.AsNullable(), args.SenderAddress);
PingSubnet(ent.AsNullable());
}
[SubscribeLocalEvent]
private void OnSubnetDisconnect(Entity<SurveillanceCameraRouterComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraSubnetDisconnectPayload> args)
{
RemoveMonitorFromRoute(ent.AsNullable(), args.SenderAddress);
}
[SubscribeLocalEvent]
private void OnSubnetPing(Entity<SurveillanceCameraRouterComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraPingSubnetPayload> args)
{
if (ent.Comp.SubnetFrequencyId == null)
return;
var response = new SurveillanceCameraSubnetDataPayload
{
Subnet = ent.Comp.SubnetName,
TransmitFrequency = ent.Comp.SubnetFrequencyId.Value,
};
_deviceNetworkSystem.SendPacket(ent.Owner, args.SenderAddress, ref response);
}
private void OnPowerChanged(Entity<SurveillanceCameraRouterComponent> ent, ref PowerChangedEvent args)
{
ent.Comp.MonitorRoutes.Clear();
ent.Comp.Active = args.Powered;
}
private void AddVerbs(Entity<SurveillanceCameraRouterComponent> ent, ref GetVerbsEvent<AlternativeVerb> verbs)
{
if (!_actionBlocker.CanInteract(verbs.User, ent.Owner) || !_actionBlocker.CanComplexInteract(verbs.User))
{
return;
}
router.SubnetFrequency = subnetFrequency.Frequency;
router.Active = true;
}
private void OnPacketReceive(EntityUid uid, SurveillanceCameraRouterComponent router, DeviceNetworkPacketEvent args)
{
if (!router.Active
|| string.IsNullOrEmpty(args.SenderAddress)
|| !args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
{
return;
}
switch (command)
{
case SurveillanceCameraSystem.CameraConnectMessage:
if (!args.Data.TryGetValue(SurveillanceCameraSystem.CameraAddressData, out string? address))
{
return;
}
ConnectCamera(uid, args.SenderAddress, address, router);
break;
case SurveillanceCameraSystem.CameraHeartbeatMessage:
if (!args.Data.TryGetValue(SurveillanceCameraSystem.CameraAddressData, out string? camera))
{
return;
}
SendHeartbeat(uid, args.SenderAddress, camera, router);
break;
case SurveillanceCameraSystem.CameraSubnetConnectMessage:
AddMonitorToRoute(uid, args.SenderAddress, router);
PingSubnet(uid, router);
break;
case SurveillanceCameraSystem.CameraSubnetDisconnectMessage:
RemoveMonitorFromRoute(uid, args.SenderAddress, router);
break;
case SurveillanceCameraSystem.CameraPingSubnetMessage:
PingSubnet(uid, router);
break;
case SurveillanceCameraSystem.CameraPingMessage:
SubnetPingResponse(uid, args.SenderAddress, router);
break;
case SurveillanceCameraSystem.CameraDataMessage:
SendCameraInfo(uid, args.Data, router);
break;
}
}
private void OnPowerChanged(EntityUid uid, SurveillanceCameraRouterComponent component, ref PowerChangedEvent args)
{
component.MonitorRoutes.Clear();
component.Active = args.Powered;
}
private void AddVerbs(EntityUid uid, SurveillanceCameraRouterComponent component, GetVerbsEvent<AlternativeVerb> verbs)
{
if (!_actionBlocker.CanInteract(verbs.User, uid) || !_actionBlocker.CanComplexInteract(verbs.User))
{
return;
}
if (component.SubnetFrequencyId != null)
if (ent.Comp.SubnetFrequencyId != null)
{
return;
}
AlternativeVerb verb = new();
var user = verbs.User;
verb.Text = Loc.GetString("surveillance-camera-setup");
verb.Act = () => OpenSetupInterface(uid, verbs.User, component);
verb.Act = () => OpenSetupInterface(ent.AsNullable(), user);
verbs.Verbs.Add(verb);
}
private void OnSetNetwork(EntityUid uid, SurveillanceCameraRouterComponent component,
SurveillanceCameraSetupSetNetwork args)
private void OnSetNetwork(Entity<SurveillanceCameraRouterComponent> ent, ref SurveillanceCameraSetupSetNetwork args)
{
if (args.UiKey is not SurveillanceCameraSetupUiKey key
|| key != SurveillanceCameraSetupUiKey.Router)
{
return;
}
if (args.Network < 0 || args.Network >= component.AvailableNetworks.Count)
if (args.Network < 0 || args.Network >= ent.Comp.AvailableNetworks.Count)
{
return;
}
if (!ProtoMan.Resolve<DeviceFrequencyPrototype>(component.AvailableNetworks[args.Network],
out var frequency))
if (!ProtoMan.Resolve(ent.Comp.AvailableNetworks[args.Network], out var frequency))
{
return;
}
component.SubnetFrequencyId = component.AvailableNetworks[args.Network];
component.SubnetFrequency = frequency.Frequency;
component.Active = true;
UpdateSetupInterface(uid, component);
ent.Comp.SubnetFrequencyId = ent.Comp.AvailableNetworks[args.Network];
ent.Comp.SubnetFrequency = frequency.Frequency;
ent.Comp.Active = true;
UpdateSetupInterface(ent.AsNullable());
}
private void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraRouterComponent? camera = null)
private void OpenSetupInterface(Entity<SurveillanceCameraRouterComponent?> ent, EntityUid player)
{
if (!Resolve(uid, ref camera))
if (!_query.Resolve(ref ent))
return;
if (!_userInterface.TryOpenUi(uid, SurveillanceCameraSetupUiKey.Router, player))
if (!_userInterface.TryOpenUi(ent.Owner, SurveillanceCameraSetupUiKey.Router, player))
return;
UpdateSetupInterface(uid, camera);
UpdateSetupInterface(ent.AsNullable());
}
private void UpdateSetupInterface(EntityUid uid, SurveillanceCameraRouterComponent? router = null, DeviceNetworkComponent? deviceNet = null)
private void UpdateSetupInterface(Entity<SurveillanceCameraRouterComponent?, DeviceNetworkComponent?> ent)
{
if (!Resolve(uid, ref router, ref deviceNet))
if (!_query.Resolve(ent.Owner, ref ent.Comp1) || !Resolve(ent.Owner, ref ent.Comp2))
return;
if (ent.Comp1.AvailableNetworks.Count == 0 || ent.Comp1.SubnetFrequencyId != null)
{
_userInterface.CloseUi(ent.Owner, SurveillanceCameraSetupUiKey.Router);
return;
}
if (router.AvailableNetworks.Count == 0 || router.SubnetFrequencyId != null)
{
_userInterface.CloseUi(uid, SurveillanceCameraSetupUiKey.Router);
return;
}
var state = new SurveillanceCameraSetupBoundUiState(router.SubnetName, deviceNet.ReceiveFrequency ?? 0,
router.AvailableNetworks, true, router.SubnetFrequencyId != null);
_userInterface.SetUiState(uid, SurveillanceCameraSetupUiKey.Router, state);
}
private void SendHeartbeat(EntityUid uid, string origin, string destination,
SurveillanceCameraRouterComponent? router = null)
{
if (!Resolve(uid, ref router))
{
return;
}
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraHeartbeatMessage },
{ SurveillanceCameraSystem.CameraAddressData, origin }
};
_deviceNetworkSystem.QueuePacket(uid, destination, payload, router.SubnetFrequency);
}
private void SubnetPingResponse(EntityUid uid, string origin, SurveillanceCameraRouterComponent? router = null)
{
if (!Resolve(uid, ref router) || router.SubnetFrequencyId == null)
{
return;
}
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraSubnetData },
{ SurveillanceCameraSystem.CameraSubnetData, router.SubnetFrequencyId }
};
_deviceNetworkSystem.QueuePacket(uid, origin, payload);
}
private void ConnectCamera(EntityUid uid, string origin, string address, SurveillanceCameraRouterComponent? router = null)
{
if (!Resolve(uid, ref router))
{
return;
}
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraConnectMessage },
{ SurveillanceCameraSystem.CameraAddressData, origin }
};
_deviceNetworkSystem.QueuePacket(uid, address, payload, router.SubnetFrequency);
var state = new SurveillanceCameraSetupBoundUiState(ent.Comp1.SubnetName,
ent.Comp2.ReceiveFrequency ?? 0,
ent.Comp1.AvailableNetworks,
true,
ent.Comp1.SubnetFrequencyId != null);
_userInterface.SetUiState(ent.Owner, SurveillanceCameraSetupUiKey.Router, state);
}
// Adds a monitor to the set of routes.
private void AddMonitorToRoute(EntityUid uid, string address, SurveillanceCameraRouterComponent? router = null)
private void AddMonitorToRoute(Entity<SurveillanceCameraRouterComponent?> ent, string address)
{
if (!Resolve(uid, ref router))
{
if (!_query.Resolve(ref ent) || ent.Comp == null)
return;
}
router.MonitorRoutes.Add(address);
ent.Comp.MonitorRoutes.Add(address);
}
private void RemoveMonitorFromRoute(EntityUid uid, string address, SurveillanceCameraRouterComponent? router = null)
private void RemoveMonitorFromRoute(Entity<SurveillanceCameraRouterComponent?> ent, string address)
{
if (!Resolve(uid, ref router))
{
if (!_query.Resolve(ref ent) || ent.Comp == null)
return;
}
router.MonitorRoutes.Remove(address);
ent.Comp.MonitorRoutes.Remove(address);
}
// Pings a subnet to get all camera information.
private void PingSubnet(EntityUid uid, SurveillanceCameraRouterComponent? router = null)
private void PingSubnet(Entity<SurveillanceCameraRouterComponent?> ent)
{
if (!Resolve(uid, ref router))
{
if (!_query.Resolve(ref ent) || ent.Comp == null)
return;
}
var payload = new NetworkPayload()
var payload = new SurveillanceCameraPingPayload
{
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraPingMessage },
{ SurveillanceCameraSystem.CameraSubnetData, router.SubnetFrequencyId }
Subnet = ent.Comp.SubnetName,
};
_deviceNetworkSystem.QueuePacket(uid, null, payload, router.SubnetFrequency);
}
// Sends camera information to all monitors currently interested.
private void SendCameraInfo(EntityUid uid, NetworkPayload payload, SurveillanceCameraRouterComponent? router = null)
{
if (!Resolve(uid, ref router))
{
return;
}
foreach (var address in router.MonitorRoutes)
{
_deviceNetworkSystem.QueuePacket(uid, address, payload);
}
_deviceNetworkSystem.SendPacket(ent.Owner, null, ref payload, ent.Comp.SubnetFrequency);
}
}

View file

@ -6,7 +6,7 @@ using Robust.Shared.Physics.Systems;
namespace Content.Server.SurveillanceCamera;
public partial class SurveillanceCameraSystem
public sealed partial class SurveillanceCameraSystem
{
[Dependency] private SharedPhysicsSystem _physics = default!;
[Dependency] private SharedPowerReceiverSystem _power = default!;

View file

@ -1,15 +1,14 @@
using Content.Server.Administration.Logs;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Systems;
using Content.Shared.Power;
using Content.Shared.SurveillanceCamera;
using Content.Shared.SurveillanceCamera.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.SurveillanceCamera;
@ -17,37 +16,13 @@ public sealed partial class SurveillanceCameraSystem : SharedSurveillanceCameraS
{
[Dependency] private ViewSubscriberSystem _viewSubscriberSystem = default!;
[Dependency] private DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private DeviceNetworkRouterSystem _deviceNetworkRouter = default!;
[Dependency] private UserInterfaceSystem _userInterface = default!;
[Dependency] private IAdminLogManager _adminLogger = default!;
[Dependency] private SurveillanceCameraMapSystem _cameraMapSystem = default!;
[Dependency] private SharedAppearanceSystem _appearance = default!;
// Pings a surveillance camera subnet. All cameras will always respond
// with a data message if they are on the same subnet.
public const string CameraPingSubnetMessage = "surveillance_camera_ping_subnet";
// Pings a surveillance camera. Useful to ensure that the camera is still on
// before connecting fully.
public const string CameraPingMessage = "surveillance_camera_ping";
// Camera heartbeat. Monitors ping this to ensure that a camera is still able to
// be contacted. If this doesn't get sent after some time, the monitor will
// automatically disconnect.
public const string CameraHeartbeatMessage = "surveillance_camera_heartbeat";
// Surveillance camera data. This generally should contain nothing
// except for the subnet that this camera is on -
// this is because of the fact that the PacketEvent already
// contains the sender UID, and that this will always be targeted
// towards the sender that pinged the camera.
public const string CameraDataMessage = "surveillance_camera_data";
public const string CameraConnectMessage = "surveillance_camera_connect";
public const string CameraSubnetConnectMessage = "surveillance_camera_subnet_connect";
public const string CameraSubnetDisconnectMessage = "surveillance_camera_subnet_disconnect";
public const string CameraAddressData = "surveillance_camera_data_origin";
public const string CameraNameData = "surveillance_camera_data_name";
public const string CameraSubnetData = "surveillance_camera_data_subnet";
[Dependency] private EntityQuery<SurveillanceCameraRouterComponent> _routerQuery = default!;
public const int CameraNameLimit = 32;
@ -57,74 +32,53 @@ public sealed partial class SurveillanceCameraSystem : SharedSurveillanceCameraS
SubscribeLocalEvent<SurveillanceCameraComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<SurveillanceCameraComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<SurveillanceCameraComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetName>(OnSetName);
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetNetwork>(OnSetNetwork);
InitializeCollide();
}
private void OnPacketReceived(EntityUid uid, SurveillanceCameraComponent component, DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnConnectRequest(Entity<SurveillanceCameraComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraConnectRequestPayload> args)
{
if (!component.Active)
{
var payload = args.Data;
if (!ent.Comp.Active)
return;
}
if (!TryComp(uid, out DeviceNetworkComponent? deviceNet))
{
var responsePayload = new SurveillanceCameraConnectPayload();
_deviceNetworkRouter.SendPacketRouted(ent.Owner, ref responsePayload, args.SenderAddress, payload.SenderAddress);
}
[SubscribeLocalEvent]
private void OnHeartbeatRequest(Entity<SurveillanceCameraComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraHeartbeatRequestPayload> args)
{
var payload = args.Data;
if (!ent.Comp.Active)
return;
}
if (args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
var responsePayload = new SurveillanceCameraHeartbeatPayload();
_deviceNetworkRouter.SendPacketRouted(ent.Owner, ref responsePayload, args.SenderAddress, payload.SenderAddress);
}
[SubscribeLocalEvent]
private void OnPing(Entity<SurveillanceCameraComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraPingPayload> args)
{
var payload = args.Data;
if (!ent.Comp.Active)
return;
if (!_routerQuery.TryComp(args.Sender, out var routerComp))
return;
if (routerComp.SubnetName != payload.Subnet)
return;
var name = ent.Comp.UseEntityNameAsCameraId ? MetaData(ent).EntityName : ent.Comp.CameraId;
var responsePayload = new SurveillanceCameraDataPayload
{
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, string.Empty },
{ CameraAddressData, deviceNet.Address },
{ CameraNameData, component.UseEntityNameAsCameraId ? MetaData(uid).EntityName : component.CameraId },
{ CameraSubnetData, null }
};
var dest = string.Empty;
switch (command)
{
case CameraConnectMessage:
if (!args.Data.TryGetValue(CameraAddressData, out dest)
|| string.IsNullOrEmpty(args.Address))
{
return;
}
payload[DeviceNetworkConstants.Command] = CameraConnectMessage;
break;
case CameraHeartbeatMessage:
if (!args.Data.TryGetValue(CameraAddressData, out dest)
|| string.IsNullOrEmpty(args.Address))
{
return;
}
payload[DeviceNetworkConstants.Command] = CameraHeartbeatMessage;
break;
case CameraPingMessage:
if (!args.Data.TryGetValue(CameraSubnetData, out ProtoId<DeviceFrequencyPrototype>? subnet))
{
return;
}
dest = args.SenderAddress;
payload[CameraSubnetData] = subnet;
payload[DeviceNetworkConstants.Command] = CameraDataMessage;
break;
}
_deviceNetworkSystem.QueuePacket(
uid,
dest,
payload);
}
Name = name,
};
_deviceNetworkRouter.SendPacketRouted(ent.Owner, ref responsePayload, args.SenderAddress, payload.SenderAddress);
}
private void OnPowerChanged(EntityUid camera, SurveillanceCameraComponent component, ref PowerChangedEvent args)
@ -154,9 +108,9 @@ public sealed partial class SurveillanceCameraSystem : SharedSurveillanceCameraS
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor)} set the name of {ToPrettyString(uid)} to \"{args.Name}.\"");
}
private void OnSetNetwork(EntityUid uid, SurveillanceCameraComponent component,
SurveillanceCameraSetupSetNetwork args)
private void OnSetNetwork(Entity<SurveillanceCameraComponent> ent, ref SurveillanceCameraSetupSetNetwork args)
{
var (uid, component) = ent;
if (args.UiKey is not SurveillanceCameraSetupUiKey key
|| key != SurveillanceCameraSetupUiKey.Camera)
{
@ -167,8 +121,7 @@ public sealed partial class SurveillanceCameraSystem : SharedSurveillanceCameraS
return;
}
if (!ProtoMan.Resolve<DeviceFrequencyPrototype>(component.AvailableNetworks[args.Network],
out var frequency))
if (!ProtoMan.Resolve(component.AvailableNetworks[args.Network], out var frequency))
{
return;
}
@ -217,8 +170,11 @@ public sealed partial class SurveillanceCameraSystem : SharedSurveillanceCameraS
}
var name = camera.UseEntityNameAsCameraId ? MetaData(uid).EntityName : camera.CameraId;
var state = new SurveillanceCameraSetupBoundUiState(name, deviceNet.ReceiveFrequency ?? 0,
camera.AvailableNetworks, camera.NameSet, camera.NetworkSet);
var state = new SurveillanceCameraSetupBoundUiState(name,
deviceNet.ReceiveFrequency ?? 0,
camera.AvailableNetworks,
camera.NameSet,
camera.NetworkSet);
_userInterface.SetUiState(uid, SurveillanceCameraSetupUiKey.Camera, state);
}

View file

@ -1,6 +1,5 @@
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.Access;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Systems;
@ -19,26 +18,25 @@ public sealed partial class DeployableTurretControllerSystem : SharedDeployableT
{
[Dependency] private UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private DeviceNetworkSystem _deviceNetwork = default!;
[Dependency] private DeviceListSystem _deviceList = default!;
[Dependency] private IAdminLogManager _adminLogger = default!;
/// Keys for the device network. See <see cref="DeviceNetworkConstants"/> for further examples.
public const string CmdSetArmamemtState = "set_armament_state";
public const string CmdSetAccessExemptions = "set_access_exemption";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DeployableTurretControllerComponent, BoundUIOpenedEvent>(OnBUIOpened);
SubscribeLocalEvent<DeployableTurretControllerComponent, DeviceListUpdateEvent>(OnDeviceListUpdate);
SubscribeLocalEvent<DeployableTurretControllerComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
}
[SubscribeLocalEvent]
private void OnBUIOpened(Entity<DeployableTurretControllerComponent> ent, ref BoundUIOpenedEvent args)
{
if (!TryComp<DeviceNetworkComponent>(ent, out var deviceNetwork))
return;
var payload = new TurretControllerRequestPayload();
foreach (var (address, _) in _deviceList.GetDeviceList(ent.Owner))
{
_deviceNetwork.SendPacket((ent.Owner, deviceNetwork), address, ref payload);
}
UpdateUIState(ent);
}
[SubscribeLocalEvent]
private void OnDeviceListUpdate(Entity<DeployableTurretControllerComponent> ent, ref DeviceListUpdateEvent args)
{
if (!TryComp<DeviceNetworkComponent>(ent, out var deviceNetwork))
@ -48,10 +46,7 @@ public sealed partial class DeployableTurretControllerSystem : SharedDeployableT
var turretsToAdd = args.Devices.Except(args.OldDevices);
// Request data from newly linked devices
var payload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
};
var payload = new TurretControllerRequestPayload();
foreach (var turretUid in turretsToAdd)
{
@ -61,7 +56,7 @@ public sealed partial class DeployableTurretControllerSystem : SharedDeployableT
if (!TryComp<DeviceNetworkComponent>(turretUid, out var turretDeviceNetwork))
continue;
_deviceNetwork.QueuePacket(ent, turretDeviceNetwork.Address, payload, device: deviceNetwork);
_deviceNetwork.SendPacket((ent.Owner, deviceNetwork), turretDeviceNetwork.Address, ref payload);
}
// Remove newly unlinked devices
@ -81,21 +76,16 @@ public sealed partial class DeployableTurretControllerSystem : SharedDeployableT
UpdateUIState(ent);
}
private void OnPacketReceived(Entity<DeployableTurretControllerComponent> ent, ref DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnPacketReceived(Entity<DeployableTurretControllerComponent> ent, ref DeviceNetworkPacketEvent<TurretStatePayload> args)
{
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
return;
if (!TryComp<DeviceNetworkComponent>(ent, out var deviceNetwork) || deviceNetwork.ReceiveFrequency != args.Frequency)
return;
// If an update was received from a turret, connect to it and update the UI
if (command == DeviceNetworkConstants.CmdUpdatedState &&
args.Data.TryGetValue(command, out DeployableTurretState updatedState))
{
ent.Comp.LinkedTurrets[args.SenderAddress] = updatedState;
UpdateUIState(ent);
}
ent.Comp.LinkedTurrets[args.SenderAddress] = args.Data.State;
UpdateUIState(ent);
}
protected override void ChangeArmamentSetting(Entity<DeployableTurretControllerComponent> ent, int armamentState, EntityUid? user = null)
@ -106,15 +96,14 @@ public sealed partial class DeployableTurretControllerSystem : SharedDeployableT
return;
// Update linked turrets' armament statuses
var payload = new NetworkPayload
var payload = new TurretControllerSetArmamentPayload
{
[DeviceNetworkConstants.Command] = CmdSetArmamemtState,
[CmdSetArmamemtState] = armamentState,
ArmamentState = armamentState,
};
_adminLogger.Add(LogType.ItemConfigure, LogImpact.Medium, $"{ToPrettyString(user)} set {ToPrettyString(ent)} to {armamentState}");
_deviceNetwork.QueuePacket(ent, null, payload, device: device);
_deviceNetwork.SendPacket((ent.Owner, device), null, ref payload);
}
protected override void ChangeExemptAccessLevels(
@ -131,10 +120,9 @@ public sealed partial class DeployableTurretControllerSystem : SharedDeployableT
return;
// Update linked turrets' target selection exemptions
var payload = new NetworkPayload
var payload = new TurretControllerSetAccessPayload
{
[DeviceNetworkConstants.Command] = CmdSetAccessExemptions,
[CmdSetAccessExemptions] = turretTargetingSettings.ExemptAccessLevels,
AccessExemptions = turretTargetingSettings.ExemptAccessLevels,
};
foreach (var exemption in exemptions)
@ -142,7 +130,7 @@ public sealed partial class DeployableTurretControllerSystem : SharedDeployableT
_adminLogger.Add(LogType.ItemConfigure, LogImpact.Medium, $"{ToPrettyString(user)} set {ToPrettyString(ent)} authorization of {exemption} to {enabled}");
}
_deviceNetwork.QueuePacket(ent, null, payload, device: device);
_deviceNetwork.SendPacket((ent.Owner, device), null, ref payload);
}
private void UpdateUIState(Entity<DeployableTurretControllerComponent> ent)

View file

@ -2,22 +2,19 @@ using Content.Server.Destructible;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.NPC.HTN;
using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat.Ranged;
using Content.Server.Power.Components;
using Content.Server.TurretController;
using Content.Shared.Access;
using Content.Shared.Destructible;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power;
using Content.Shared.Repairable;
using Content.Shared.TurretController;
using Content.Shared.Turrets;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.Turrets;
@ -32,6 +29,8 @@ public sealed partial class DeployableTurretSystem : SharedDeployableTurretSyste
[Dependency] private TurretTargetSettingsSystem _turretTargetingSettings = default!;
[Dependency] private IGameTiming _timing = default!;
[Dependency] private EntityQuery<DeviceNetworkComponent> _deviceQuery = default!;
public override void Initialize()
{
base.Initialize();
@ -41,7 +40,6 @@ public sealed partial class DeployableTurretSystem : SharedDeployableTurretSyste
SubscribeLocalEvent<DeployableTurretComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<DeployableTurretComponent, BreakageEventArgs>(OnBroken);
SubscribeLocalEvent<DeployableTurretComponent, RepairedEvent>(OnRepaired);
SubscribeLocalEvent<DeployableTurretComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<DeployableTurretComponent, BeforeBroadcastAttemptEvent>(OnBeforeBroadcast);
}
@ -74,37 +72,28 @@ public sealed partial class DeployableTurretSystem : SharedDeployableTurretSyste
_appearance.SetData(ent, DeployableTurretVisuals.Broken, false, appearance);
}
private void OnPacketReceived(Entity<DeployableTurretComponent> ent, ref DeviceNetworkPacketEvent args)
[SubscribeLocalEvent]
private void OnSetArmament(Entity<DeployableTurretComponent> ent, ref DeviceNetworkPacketEvent<TurretControllerSetArmamentPayload> args)
{
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
if (TryComp<BatteryWeaponFireModesComponent>(ent, out var batteryWeaponFireModes))
_fireModes.TrySetFireMode((ent.Owner, batteryWeaponFireModes), args.Data.ArmamentState);
TrySetState(ent, args.Data.ArmamentState >= 0);
}
[SubscribeLocalEvent]
private void OnSetAccess(Entity<DeployableTurretComponent> ent, ref DeviceNetworkPacketEvent<TurretControllerSetAccessPayload> args)
{
if (!TryComp<TurretTargetSettingsComponent>(ent, out var targetSettings))
return;
// Received a command to change armament state
if (command == DeployableTurretControllerSystem.CmdSetArmamemtState &&
args.Data.TryGetValue(command, out int? armamentState))
{
if (TryComp<BatteryWeaponFireModesComponent>(ent, out var batteryWeaponFireModes))
_fireModes.TrySetFireMode((ent, batteryWeaponFireModes), armamentState.Value);
_turretTargetingSettings.SyncAccessLevelExemptions((ent, targetSettings), args.Data.AccessExemptions);
}
TrySetState(ent, armamentState.Value >= 0);
return;
}
// Received a command to change access exemptions
if (command == DeployableTurretControllerSystem.CmdSetAccessExemptions &&
args.Data.TryGetValue(command, out HashSet<ProtoId<AccessLevelPrototype>>? accessExemptions) &&
TryComp<TurretTargetSettingsComponent>(ent, out var turretTargetSettings))
{
_turretTargetingSettings.SyncAccessLevelExemptions((ent, turretTargetSettings), accessExemptions);
return;
}
// Received a command to update the device network
if (command == DeviceNetworkConstants.CmdUpdatedState)
{
SendStateUpdateToDeviceNetwork(ent);
return;
}
[SubscribeLocalEvent]
private void OnRequest(Entity<DeployableTurretComponent> ent, ref DeviceNetworkPacketEvent<TurretControllerRequestPayload> args)
{
SendStateUpdateToDeviceNetwork(ent);
}
private void OnBeforeBroadcast(Entity<DeployableTurretComponent> ent, ref BeforeBroadcastAttemptEvent args)
@ -112,15 +101,15 @@ public sealed partial class DeployableTurretSystem : SharedDeployableTurretSyste
if (!TryComp<DeviceNetworkComponent>(ent, out var deviceNetwork))
return;
var recipientDeviceNetworks = new HashSet<DeviceNetworkComponent>();
var recipientDeviceNetworks = new HashSet<Device>();
// Only broadcast to connected devices
foreach (var recipient in deviceNetwork.DeviceLists)
{
if (!TryComp<DeviceNetworkComponent>(recipient, out var recipientDeviceNetwork))
if (!_deviceQuery.TryComp(recipient, out var recipientDeviceNetwork))
continue;
recipientDeviceNetworks.Add(recipientDeviceNetwork);
recipientDeviceNetworks.Add(new Device((recipient, recipientDeviceNetwork)));
}
if (recipientDeviceNetworks.Count > 0)
@ -132,13 +121,11 @@ public sealed partial class DeployableTurretSystem : SharedDeployableTurretSyste
if (!TryComp<DeviceNetworkComponent>(ent, out var device))
return;
var payload = new NetworkPayload
var payload = new TurretStatePayload
{
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
[DeviceNetworkConstants.CmdUpdatedState] = GetTurretState(ent)
State = GetTurretState(ent),
};
_deviceNetwork.QueuePacket(ent, null, payload, device: device);
_deviceNetwork.SendPacket((ent.Owner, device), null, ref payload);
}
protected override void SetState(Entity<DeployableTurretComponent> ent, bool enabled, EntityUid? user = null)

View file

@ -1,12 +1,28 @@
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Systems;
using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Monitor;
[Serializable, NetSerializable]
public sealed class AtmosSensorData : IAtmosDeviceData
/// <summary>
/// Contains <see cref="AtmosMonitorData"/>.
/// </summary>
public partial record struct AtmosMonitorDataPayload : INetworkPayload
{
public AtmosSensorData(float pressure, float temperature, float totalMoles, AtmosAlarmType alarmState, Dictionary<Gas, float> gases, AtmosAlarmThreshold pressureThreshold, AtmosAlarmThreshold temperatureThreshold, Dictionary<Gas, AtmosAlarmThreshold> gasThresholds)
[DataField]
public AtmosMonitorData Data;
}
[Serializable, NetSerializable]
public sealed partial class AtmosMonitorData : BaseAtmosDeviceData
{
public override void RaisePayload(EntityUid uid, string address, SharedDeviceNetworkSystem deviceNetSys)
{
var payload = new AtmosMonitorDataPayload { Data = this };
deviceNetSys.SendPacket(uid, address, ref payload);
}
public AtmosMonitorData(float pressure, float temperature, float totalMoles, AtmosAlarmType alarmState, Dictionary<Gas, float> gases, AtmosAlarmThreshold pressureThreshold, AtmosAlarmThreshold temperatureThreshold, Dictionary<Gas, AtmosAlarmThreshold> gasThresholds)
{
Pressure = pressure;
Temperature = temperature;
@ -18,10 +34,6 @@ public sealed class AtmosSensorData : IAtmosDeviceData
GasThresholds = gasThresholds;
}
public bool Enabled { get; set; }
public bool Dirty { get; set; }
public bool IgnoreAlarms { get; set; }
/// Most fields are readonly, because it's data that's meant to be transmitted.
/// <summary>

View file

@ -0,0 +1,19 @@
using Content.Shared.DeviceNetwork.Systems;
using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Monitor;
[Serializable, NetSerializable]
public abstract partial class BaseAtmosDeviceData : IAtmosDeviceData
{
[DataField]
public bool Enabled { get; set; }
[DataField]
public bool Dirty { get; set; }
[DataField]
public bool IgnoreAlarms { get; set; }
public abstract void RaisePayload(EntityUid uid, string address, SharedDeviceNetworkSystem deviceNetSys);
}

View file

@ -27,13 +27,6 @@ public enum AirAlarmWireStatus
DeviceSync
}
public interface IAtmosDeviceData
{
public bool Enabled { get; set; }
public bool Dirty { get; set; }
public bool IgnoreAlarms { get; set; }
}
[Serializable, NetSerializable]
public sealed class AirAlarmUIState : BoundUserInterfaceState
{
@ -69,8 +62,7 @@ public sealed class AirAlarmUIState : BoundUserInterfaceState
}
[Serializable, NetSerializable]
public sealed class AirAlarmResyncAllDevicesMessage : BoundUserInterfaceMessage
{}
public sealed class AirAlarmResyncAllDevicesMessage : BoundUserInterfaceMessage;
[Serializable, NetSerializable]
public sealed class AirAlarmUpdateAlarmModeMessage : BoundUserInterfaceMessage

View file

@ -0,0 +1,21 @@
using Content.Shared.DeviceNetwork.Systems;
namespace Content.Shared.Atmos.Monitor;
[ImplicitDataDefinitionForInheritors]
public partial interface IAtmosDeviceData
{
bool Enabled { get; set; }
bool Dirty { get; set; }
bool IgnoreAlarms { get; set; }
/// <summary>
/// Creates a payload that contains this atmos device data and send it to the specified address.
/// </summary>
/// <param name="uid">Owner of this atmos data.</param>
/// <param name="address">The target address.</param>
/// <param name="deviceNetSys">The device network system.</param>
void RaisePayload(EntityUid uid, string address, SharedDeviceNetworkSystem deviceNetSys);
}

View file

@ -1,34 +1,41 @@
using Robust.Shared.Serialization;
using Content.Shared.DeviceNetwork;
using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Piping.Binary.Components
namespace Content.Shared.Atmos.Piping.Binary.Components;
/// <summary>
/// Contains data about a <see cref="GasVolumePumpComponent"/>.
/// </summary>
public partial record struct GasVolumePumpDataPayload : INetworkPayload
{
public sealed record GasVolumePumpData(float LastMolesTransferred);
[DataField]
public float LastMolesTransferred;
}
[Serializable, NetSerializable]
public enum GasVolumePumpUiKey : byte
[Serializable, NetSerializable]
public enum GasVolumePumpUiKey : byte
{
Key,
}
[Serializable, NetSerializable]
public sealed class GasVolumePumpToggleStatusMessage : BoundUserInterfaceMessage
{
public bool Enabled { get; }
public GasVolumePumpToggleStatusMessage(bool enabled)
{
Key,
}
[Serializable, NetSerializable]
public sealed class GasVolumePumpToggleStatusMessage : BoundUserInterfaceMessage
{
public bool Enabled { get; }
public GasVolumePumpToggleStatusMessage(bool enabled)
{
Enabled = enabled;
}
}
[Serializable, NetSerializable]
public sealed class GasVolumePumpChangeTransferRateMessage : BoundUserInterfaceMessage
{
public float TransferRate { get; }
public GasVolumePumpChangeTransferRateMessage(float transferRate)
{
TransferRate = transferRate;
}
Enabled = enabled;
}
}
[Serializable, NetSerializable]
public sealed class GasVolumePumpChangeTransferRateMessage : BoundUserInterfaceMessage
{
public float TransferRate { get; }
public GasVolumePumpChangeTransferRateMessage(float transferRate)
{
TransferRate = transferRate;
}
}

View file

@ -1,9 +1,16 @@
using Robust.Shared.Serialization;
using Content.Shared.DeviceNetwork;
using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Piping.Unary.Components;
[Serializable, NetSerializable]
public sealed record GasThermoMachineData(float EnergyDelta);
/// <summary>
/// Contains data about <see cref="GasThermoMachineComponent"/>.
/// </summary>
public partial record struct GasThermoMachineDataPayload : INetworkPayload
{
[DataField]
public float EnergyDelta;
}
[Serializable]
[NetSerializable]

View file

@ -1,81 +1,94 @@
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Monitor;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Systems;
using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Piping.Unary.Components
namespace Content.Shared.Atmos.Piping.Unary.Components;
[Serializable, NetSerializable]
public sealed partial class GasVentPumpData : BaseAtmosDeviceData
{
[Serializable, NetSerializable]
public sealed class GasVentPumpData : IAtmosDeviceData
public override void RaisePayload(EntityUid uid, string address, SharedDeviceNetworkSystem deviceNetSys)
{
public bool Enabled { get; set; }
public bool Dirty { get; set; }
public bool IgnoreAlarms { get; set; } = false;
public VentPumpDirection PumpDirection { get; set; } = VentPumpDirection.Releasing;
public VentPressureBound PressureChecks { get; set; } = VentPressureBound.ExternalBound;
public float ExternalPressureBound { get; set; } = Atmospherics.OneAtmosphere;
public float InternalPressureBound { get; set; } = 0f;
public bool PressureLockoutOverride { get; set; } = false;
// Presets for 'dumb' air alarm modes
public static GasVentPumpData FilterModePreset = new GasVentPumpData
{
Enabled = true,
PumpDirection = VentPumpDirection.Releasing,
PressureChecks = VentPressureBound.ExternalBound,
ExternalPressureBound = Atmospherics.OneAtmosphere,
InternalPressureBound = 0f,
PressureLockoutOverride = false
};
public static GasVentPumpData FillModePreset = new GasVentPumpData
{
Enabled = true,
Dirty = true,
PumpDirection = VentPumpDirection.Releasing,
PressureChecks = VentPressureBound.ExternalBound,
ExternalPressureBound = Atmospherics.OneAtmosphere * 50,
InternalPressureBound = 0f,
PressureLockoutOverride = true
};
public static GasVentPumpData PanicModePreset = new GasVentPumpData
{
Enabled = false,
Dirty = true,
PumpDirection = VentPumpDirection.Releasing,
PressureChecks = VentPressureBound.ExternalBound,
ExternalPressureBound = Atmospherics.OneAtmosphere,
InternalPressureBound = 0f,
PressureLockoutOverride = false
};
public static GasVentPumpData ReplaceModePreset = new GasVentPumpData
{
Enabled = false,
IgnoreAlarms = true,
Dirty = true,
PumpDirection = VentPumpDirection.Releasing,
PressureChecks = VentPressureBound.ExternalBound,
ExternalPressureBound = Atmospherics.OneAtmosphere,
InternalPressureBound = 0f,
PressureLockoutOverride = false
};
var payload = new GasVentPumpSetDataPayload { Data = this };
deviceNetSys.SendPacket(uid, address, ref payload);
}
[Serializable, NetSerializable]
public enum VentPumpDirection : sbyte
{
Siphoning = 0,
Releasing = 1,
}
public VentPumpDirection PumpDirection { get; set; } = VentPumpDirection.Releasing;
public VentPressureBound PressureChecks { get; set; } = VentPressureBound.ExternalBound;
public float ExternalPressureBound { get; set; } = Atmospherics.OneAtmosphere;
public float InternalPressureBound { get; set; } = 0f;
public bool PressureLockoutOverride { get; set; } = false;
[Flags]
[Serializable, NetSerializable]
public enum VentPressureBound : sbyte
// Presets for 'dumb' air alarm modes
public static GasVentPumpData FilterModePreset = new GasVentPumpData
{
NoBound = 0,
InternalBound = 1,
ExternalBound = 2,
Both = 3,
}
Enabled = true,
PumpDirection = VentPumpDirection.Releasing,
PressureChecks = VentPressureBound.ExternalBound,
ExternalPressureBound = Atmospherics.OneAtmosphere,
InternalPressureBound = 0f,
PressureLockoutOverride = false
};
public static GasVentPumpData FillModePreset = new GasVentPumpData
{
Enabled = true,
Dirty = true,
PumpDirection = VentPumpDirection.Releasing,
PressureChecks = VentPressureBound.ExternalBound,
ExternalPressureBound = Atmospherics.OneAtmosphere * 50,
InternalPressureBound = 0f,
PressureLockoutOverride = true
};
public static GasVentPumpData PanicModePreset = new GasVentPumpData
{
Enabled = false,
Dirty = true,
PumpDirection = VentPumpDirection.Releasing,
PressureChecks = VentPressureBound.ExternalBound,
ExternalPressureBound = Atmospherics.OneAtmosphere,
InternalPressureBound = 0f,
PressureLockoutOverride = false
};
public static GasVentPumpData ReplaceModePreset = new GasVentPumpData
{
Enabled = false,
IgnoreAlarms = true,
Dirty = true,
PumpDirection = VentPumpDirection.Releasing,
PressureChecks = VentPressureBound.ExternalBound,
ExternalPressureBound = Atmospherics.OneAtmosphere,
InternalPressureBound = 0f,
PressureLockoutOverride = false
};
}
/// <summary>
/// Used to set <see cref="GasVentPumpData"/>.
/// </summary>
public partial record struct GasVentPumpSetDataPayload : INetworkPayload
{
[DataField]
public GasVentPumpData Data;
}
[Serializable, NetSerializable]
public enum VentPumpDirection : sbyte
{
Siphoning = 0,
Releasing = 1,
}
[Flags]
[Serializable, NetSerializable]
public enum VentPressureBound : sbyte
{
NoBound = 0,
InternalBound = 1,
ExternalBound = 2,
Both = 3,
}

View file

@ -1,87 +1,100 @@
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Monitor;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Systems;
using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Piping.Unary.Components
namespace Content.Shared.Atmos.Piping.Unary.Components;
[Serializable, NetSerializable]
public sealed partial class GasVentScrubberData : BaseAtmosDeviceData
{
[Serializable, NetSerializable]
public sealed class GasVentScrubberData : IAtmosDeviceData
public override void RaisePayload(EntityUid uid, string address, SharedDeviceNetworkSystem deviceNetSys)
{
public bool Enabled { get; set; }
public bool Dirty { get; set; }
public bool IgnoreAlarms { get; set; } = false;
public HashSet<Gas> FilterGases { get; set; } = new(DefaultFilterGases);
public ScrubberPumpDirection PumpDirection { get; set; } = ScrubberPumpDirection.Scrubbing;
public float VolumeRate { get; set; } = 200f;
public bool WideNet { get; set; } = false;
public bool AirAlarmPanicWireCut { get; set; }
public static HashSet<Gas> DefaultFilterGases = new()
{
Gas.CarbonDioxide,
Gas.Plasma,
Gas.Tritium,
Gas.WaterVapor,
Gas.Ammonia,
Gas.NitrousOxide,
Gas.Frezon
};
// Presets for 'dumb' air alarm modes
public static GasVentScrubberData FilterModePreset = new GasVentScrubberData
{
Enabled = true,
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
PumpDirection = ScrubberPumpDirection.Scrubbing,
VolumeRate = 200f,
WideNet = false
};
public static GasVentScrubberData WideFilterModePreset = new GasVentScrubberData
{
Enabled = true,
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
PumpDirection = ScrubberPumpDirection.Scrubbing,
VolumeRate = 200f,
WideNet = true
};
public static GasVentScrubberData FillModePreset = new GasVentScrubberData
{
Enabled = false,
Dirty = true,
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
PumpDirection = ScrubberPumpDirection.Scrubbing,
VolumeRate = 200f,
WideNet = false
};
public static GasVentScrubberData PanicModePreset = new GasVentScrubberData
{
Enabled = true,
Dirty = true,
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
PumpDirection = ScrubberPumpDirection.Siphoning,
VolumeRate = 200f,
WideNet = true
};
public static GasVentScrubberData ReplaceModePreset = new GasVentScrubberData
{
Enabled = true,
IgnoreAlarms = true,
Dirty = true,
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
PumpDirection = ScrubberPumpDirection.Siphoning,
VolumeRate = 200f,
WideNet = false
};
var payload = new GasVentScrubberSetDataPayload { Data = this };
deviceNetSys.SendPacket(uid, address, ref payload);
}
[Serializable, NetSerializable]
public enum ScrubberPumpDirection : sbyte
public HashSet<Gas> FilterGases { get; set; } = new(DefaultFilterGases);
public ScrubberPumpDirection PumpDirection { get; set; } = ScrubberPumpDirection.Scrubbing;
public float VolumeRate { get; set; } = 200f;
public bool WideNet { get; set; } = false;
public bool AirAlarmPanicWireCut { get; set; }
public static HashSet<Gas> DefaultFilterGases = new()
{
Siphoning = 0,
Scrubbing = 1,
}
Gas.CarbonDioxide,
Gas.Plasma,
Gas.Tritium,
Gas.WaterVapor,
Gas.Ammonia,
Gas.NitrousOxide,
Gas.Frezon
};
// Presets for 'dumb' air alarm modes
public static GasVentScrubberData FilterModePreset = new GasVentScrubberData
{
Enabled = true,
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
PumpDirection = ScrubberPumpDirection.Scrubbing,
VolumeRate = 200f,
WideNet = false
};
public static GasVentScrubberData WideFilterModePreset = new GasVentScrubberData
{
Enabled = true,
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
PumpDirection = ScrubberPumpDirection.Scrubbing,
VolumeRate = 200f,
WideNet = true
};
public static GasVentScrubberData FillModePreset = new GasVentScrubberData
{
Enabled = false,
Dirty = true,
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
PumpDirection = ScrubberPumpDirection.Scrubbing,
VolumeRate = 200f,
WideNet = false
};
public static GasVentScrubberData PanicModePreset = new GasVentScrubberData
{
Enabled = true,
Dirty = true,
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
PumpDirection = ScrubberPumpDirection.Siphoning,
VolumeRate = 200f,
WideNet = true
};
public static GasVentScrubberData ReplaceModePreset = new GasVentScrubberData
{
Enabled = true,
IgnoreAlarms = true,
Dirty = true,
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
PumpDirection = ScrubberPumpDirection.Siphoning,
VolumeRate = 200f,
WideNet = false
};
}
/// <summary>
/// Used to set <see cref="GasVentScrubberData"/>.
/// </summary>
public partial record struct GasVentScrubberSetDataPayload : INetworkPayload
{
[DataField]
public GasVentScrubberData Data;
}
[Serializable, NetSerializable]
public enum ScrubberPumpDirection : sbyte
{
Siphoning = 0,
Scrubbing = 1,
}

View file

@ -1,4 +1,3 @@
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Interaction;
namespace Content.Shared.CartridgeLoader;
@ -9,7 +8,6 @@ public sealed partial class CartridgeLoaderSystem
{
SubscribeLocalEvent<CartridgeLoaderComponent, AfterInteractEvent>(RelayEvent);
SubscribeLocalEvent<CartridgeLoaderComponent, InteractUsingEvent>(RelayEvent);
SubscribeLocalEvent<CartridgeLoaderComponent, DeviceNetworkPacketEvent>(RelayEvent);
}
private void RefRelayEvent<T>(EntityUid uid, CartridgeLoaderComponent component, ref T args) where T : struct

View file

@ -56,8 +56,8 @@ public sealed partial class NetProbeCartridgeSystem : EntitySystem
var device = new ProbedNetworkDevice(
Name(target),
networkComponent.Address,
networkComponent.ReceiveFrequency?.FrequencyToString() ?? string.Empty,
networkComponent.DeviceNetId.DeviceNetIdToLocalizedName()
DeviceLocalizationHelpers.FrequencyToString(networkComponent.ReceiveFrequency),
DeviceLocalizationHelpers.DeviceNetIdToLocalizedName(networkComponent.DeviceNetId, Loc)
);
component.ProbedDevices.Add(device);

View file

@ -1,6 +1,7 @@
using Content.Shared.DeviceNetwork;
namespace Content.Shared.DeviceLinking.Events;
[ByRefEvent]
public readonly record struct SignalReceivedEvent(string Port, EntityUid? Trigger = null, NetworkPayload? Data = null);
public readonly record struct SignalReceivedEvent(string Port, EntityUid? Trigger = null);
[ByRefEvent]
public readonly record struct SignalReceivedEvent<T>(string Port, T Data, EntityUid? Trigger = null) where T : ISignalNetworkPayload;

View file

@ -0,0 +1,10 @@
namespace Content.Shared.DeviceLinking;
/// <summary>
/// Contains a logic state of a <see cref="SignalPayload"/>.
/// </summary>
public partial record struct LogicStatePayload : ISignalNetworkPayload
{
[DataField]
public SignalState State;
}

View file

@ -17,13 +17,13 @@ public abstract partial class SharedDeviceLinkSystem : EntitySystem
[Dependency] private SharedTransformSystem _transform = default!;
[Dependency] private IGameTiming _gameTiming = default!;
[Dependency] private EntityQuery<DeviceLinkSinkComponent> _deviceLinkSinkQuery = default!;
public const string InvokedPort = "link_port";
[Dependency] protected EntityQuery<DeviceLinkSinkComponent> DeviceLinkSinkQuery = default!;
[Dependency] protected EntityQuery<DeviceLinkSourceComponent> DeviceLinkSourceQuery = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DeviceLinkSourceComponent, ComponentStartup>(OnSourceStartup);
SubscribeLocalEvent<DeviceLinkSourceComponent, ComponentRemove>(OnSourceRemoved);
SubscribeLocalEvent<DeviceLinkSinkComponent, ComponentRemove>(OnSinkRemoved);
@ -85,7 +85,7 @@ public abstract partial class SharedDeviceLinkSystem : EntitySystem
{
foreach (var sinkUid in source.Comp.LinkedPorts.Keys)
{
if (_deviceLinkSinkQuery.TryGetComponent(sinkUid, out var sink))
if (DeviceLinkSinkQuery.TryGetComponent(sinkUid, out var sink))
RemoveSinkFromSourceInternal(source, sinkUid, source, sink);
else
Log.Error($"Device source {ToPrettyString(source)} links to invalid entity: {ToPrettyString(sinkUid)}");
@ -539,12 +539,21 @@ public abstract partial class SharedDeviceLinkSystem : EntitySystem
/// Sends a network payload directed at the sink entity.
/// Just raises a <see cref="SignalReceivedEvent"/> without data if the source or the sink doesn't have a <see cref="DeviceNetworkComponent"/>
/// </summary>
/// <param name="uid">The source uid that invokes the port</param>
/// <param name="ent">The source uid that invokes the port</param>
/// <param name="port">The port to invoke</param>
public virtual void InvokePort(Entity<DeviceLinkSourceComponent?> ent, string port)
{
// NOOP on client for the moment.
}
/// <summary>
/// Sends a network payload directed at the sink entity.
/// Just raises a <see cref="SignalReceivedEvent"/> without data if the source or the sink doesn't have a <see cref="DeviceNetworkComponent"/>
/// </summary>
/// <param name="ent">The source entity that invokes the port</param>
/// <param name="port">The port to invoke</param>
/// <param name="data">Optional data to send along</param>
/// <param name="sourceComponent"></param>
public virtual void InvokePort(EntityUid uid, string port, NetworkPayload? data = null,
DeviceLinkSourceComponent? sourceComponent = null)
public virtual void InvokePort<T>(Entity<DeviceLinkSourceComponent?> ent, string port, ref T data) where T : ISignalNetworkPayload
{
// NOOP on client for the moment.
}

View file

@ -0,0 +1,41 @@
using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork;
namespace Content.Shared.DeviceLinking;
/// <summary>
/// A network payload that can be nested inside <see cref="SignalPayload{T}"/>.
/// </summary>
public partial interface ISignalNetworkPayload : INetworkPayload;
/// <summary>
/// A network payload that raises <see cref="SignalReceivedEvent"/> when successfully delivered.
/// Optionally contains a <see cref="INetworkPayload"/> to provide additional data for the event.
/// </summary>
public partial record struct SignalPayload : INetworkPayload
{
/// <summary>
/// A signal port that was invoked.
/// </summary>
[DataField]
public string InvokedPort;
}
/// <summary>
/// A network payload that raises <see cref="SignalReceivedEvent"/> when successfully delivered.
/// Optionally contains a <see cref="INetworkPayload"/> to provide additional data for the event.
/// </summary>
public partial record struct SignalPayload<T> : INetworkPayload where T : ISignalNetworkPayload
{
/// <summary>
/// A signal port that was invoked.
/// </summary>
[DataField]
public string InvokedPort;
/// <summary>
/// Optional additional data about the signal.
/// </summary>
[DataField]
public T Payload;
}

View file

@ -1,120 +1,107 @@
using Content.Shared.DeviceNetwork.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.DeviceNetwork.Components
namespace Content.Shared.DeviceNetwork.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedDeviceNetworkSystem), typeof(DeviceNet))]
public sealed partial class DeviceNetworkComponent : Component
{
[RegisterComponent]
[Access(typeof(SharedDeviceNetworkSystem), typeof(DeviceNet))]
public sealed partial class DeviceNetworkComponent : Component
{
public enum DeviceNetIdDefaults
{
Private,
Wired,
Wireless,
Apc,
AtmosDevices,
Reserved = 100,
// Ids outside this enum may exist
// This exists to let yml use nice names instead of numbers
}
/// <summary>
/// Default device network ID to connect to.
/// </summary>
[DataField("deviceNetId")]
public DeviceNetIdDefaults NetIdEnum { get; set; }
[DataField("deviceNetId")]
public DeviceNetIdDefaults NetIdEnum { get; set; }
public int DeviceNetId => (int) NetIdEnum;
public int DeviceNetId => (int) NetIdEnum;
/// <summary>
/// The frequency that this device is listening on.
/// </summary>
[DataField]
public uint? ReceiveFrequency;
/// <summary>
/// The frequency that this device is listening on.
/// </summary>
[DataField]
public uint? ReceiveFrequency;
/// <summary>
/// The address ID of the device, either on the network it is currently connected to or whatever address it
/// most recently used.
/// </summary>
[DataField]
public string Address = string.Empty;
/// <summary>
/// frequency prototype. Used to select a default frequency to listen to on. Used when the map is
/// initialized.
/// </summary>
[DataField]
public ProtoId<DeviceFrequencyPrototype>? ReceiveFrequencyId;
/// <summary>
/// Whether the device should listen for all device messages, regardless of the intended recipient.
/// </summary>
[DataField]
public bool ReceiveAll;
/// <summary>
/// The frequency that this device going to try transmit on.
/// </summary>
[DataField]
public uint? TransmitFrequency;
/// <summary>
/// The frequency that this device going to try to transmit on.
/// </summary>
[DataField]
public uint? TransmitFrequency;
/// <summary>
/// frequency prototype. Used to select a default frequency to transmit on. Used when the map is
/// initialized.
/// </summary>
[DataField]
public ProtoId<DeviceFrequencyPrototype>? TransmitFrequencyId;
/// <summary>
/// Frequency prototype, used to select a default frequency to listen to on.
/// </summary>
[DataField]
public ProtoId<DeviceFrequencyPrototype>? ReceiveFrequencyId;
/// <summary>
/// The address of the device, either on the network it is currently connected to or whatever address it
/// most recently used.
/// </summary>
[DataField]
public string Address = string.Empty;
/// <summary>
/// Frequency prototype, used to select a default frequency to transmit on.
/// </summary>
[DataField]
public ProtoId<DeviceFrequencyPrototype>? TransmitFrequencyId;
/// <summary>
/// If true, the address was customized and should be preserved across networks. If false, a randomly
/// generated address will be created whenever this device connects to a network.
/// </summary>
[DataField]
public bool CustomAddress = false;
/// <summary>
/// Whether to send the broadcast recipients list to the sender so it can be filtered.
/// </summary>
[DataField]
public bool SendBroadcastAttemptEvent;
/// <summary>
/// Prefix to prepend to any automatically generated addresses. Helps players to identify devices. This gets
/// localized.
/// </summary>
[DataField]
public string? Prefix;
/// <summary>
/// If the device should show its address upon an examine.
/// Useful for devices that do not have a visible UI.
/// </summary>
[DataField, AutoNetworkedField]
public bool ExaminableAddress;
/// <summary>
/// Whether the device should listen for all device messages, regardless of the intended recipient.
/// </summary>
[DataField]
public bool ReceiveAll;
/// <summary>
/// Prefix to prepend to any automatically generated addresses. Helps players to identify devices.
/// </summary>
[DataField]
public LocId? Prefix;
/// <summary>
/// If the device should show its address upon an examine. Useful for devices
/// that do not have a visible UI.
/// </summary>
[DataField]
public bool ExaminableAddress;
/// <summary>
/// Whether the device should attempt to join the network on map init.
/// </summary>
[DataField]
public bool AutoConnect = true;
/// <summary>
/// Whether the device should attempt to join the network on map init.
/// </summary>
[DataField]
public bool AutoConnect = true;
/// <summary>
/// Whether this device's address can be saved to device-lists
/// </summary>
[DataField]
public bool SavableAddress = true;
/// <summary>
/// Whether to send the broadcast recipients list to the sender so it can be filtered.
/// <see cref="DeviceListSystem"/>
/// </summary>
[DataField]
public bool SendBroadcastAttemptEvent = false;
/// <summary>
/// If true, the address was customized and should be preserved across networks. If false, a randomly
/// generated address will be created whenever this device connects to a network.
/// </summary>
[DataField]
public bool CustomAddress;
/// <summary>
/// Whether this device's address can be saved to device-lists
/// </summary>
[DataField]
public bool SavableAddress = true;
/// <summary>
/// A list of device-lists that this device is on.
/// </summary>
[DataField]
[Access(typeof(SharedDeviceListSystem))]
public HashSet<EntityUid> DeviceLists = new();
/// <summary>
/// A list of device-lists that this device is on.
/// </summary>
[DataField]
[Access(typeof(SharedDeviceListSystem))]
public HashSet<EntityUid> DeviceLists = new();
/// <summary>
/// A list of configurators that this device is on.
/// </summary>
[DataField]
[Access(typeof(SharedNetworkConfiguratorSystem))]
public HashSet<EntityUid> Configurators = new();
}
/// <summary>
/// A list of configurators that this device is on.
/// </summary>
[DataField]
[Access(typeof(SharedNetworkConfiguratorSystem))]
public HashSet<EntityUid> Configurators = new();
}

View file

@ -21,7 +21,7 @@ public sealed partial class DeviceNetworkJammerComponent : Component
/// Network ids are not guaranteed to be limited to DeviceNetIdDefaults.
/// </summary>
[DataField, AutoNetworkedField]
public HashSet<string> JammableNetworks = [];
public HashSet<int> JammableNetworks = [];
/// <summary>
/// Device networks frequencies that wont be jammed.

View file

@ -0,0 +1,11 @@
using Content.Shared.DeviceNetwork.Payloads;
using Robust.Shared.GameStates;
namespace Content.Shared.DeviceNetwork.Components;
/// <summary>
/// Re-routs <see cref="RoutedNetworkPayload"/>s to some other devices.
/// Useful for server setups where there are lots of payloads that have to be relayed.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class DeviceNetworkRouterComponent : Component;

View file

@ -0,0 +1,25 @@
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Systems;
namespace Content.Shared.DeviceNetwork;
/// <summary>
/// Represents a device in a <see cref="DeviceNet"/>.
/// </summary>
/// <remarks>
/// This type is read-only. To change any parameters of the device, use <see cref="SharedDeviceNetworkSystem"/>'s API.
/// </remarks>
[DataRecord]
public readonly partial record struct Device(EntityUid Owner, uint? ReceiveFrequency, string Address, bool ReceiveAll)
{
public Device(Entity<DeviceNetworkComponent> ent) : this(
ent.Owner,
ent.Comp.ReceiveFrequency,
ent.Comp.Address,
ent.Comp.ReceiveAll)
{
Owner = ent.Owner;
ReceiveFrequency = ent.Comp.ReceiveFrequency;
ReceiveAll = ent.Comp.ReceiveAll;
}
}

View file

@ -13,13 +13,12 @@ public sealed partial class DeviceFrequencyPrototype : IPrototype
public string ID { get; private set; } = default!;
// TODO Somehow Allow per-station or some other type of named but randomized frequencies?
[DataField("frequency", required: true)]
[DataField(required: true)]
public uint Frequency;
/// <summary>
/// Optional name for this frequency, for displaying in game.
/// </summary>
[DataField("name")]
public string? Name;
[DataField]
public LocId? Name;
}

View file

@ -0,0 +1,42 @@
using Robust.Shared.Utility;
namespace Content.Shared.DeviceNetwork;
/// <summary>
/// A helper class for localization of frequencies and device network IDs.
/// </summary>
public static class DeviceLocalizationHelpers
{
/// <summary>
/// Converts the unsigned int to string and inserts a number before the last digit
/// </summary>
public static string FrequencyToString(uint? frequency)
{
if (frequency == null)
return string.Empty;
var result = frequency.Value.ToString();
if (result.Length <= 2)
return result + ".0";
return result.Insert(result.Length - 1, ".");
}
/// <summary>
/// Either returns the localized name representation of the corresponding <see cref="DeviceNetIdDefaults"/>
/// or converts the id to string
/// </summary>
public static string DeviceNetIdToLocalizedName(int? id, ILocalizationManager localeMan)
{
if (id == null)
return string.Empty;
if (!Enum.IsDefined(typeof(DeviceNetIdDefaults), id))
return id.Value.ToString();
var result = ((DeviceNetIdDefaults) id).ToString();
var resultKebab = "device-net-id-" + CaseConversion.PascalToKebab(result);
return !localeMan.TryGetString(resultKebab, out var name) ? result : name;
}
}

View file

@ -17,17 +17,20 @@ public sealed class DeviceNet
/// This dictionary contains all devices connected to this network, though they may not be listening to any
/// specific frequency.
/// </summary>
public readonly Dictionary<string, DeviceNetworkComponent> Devices = new();
[ViewVariables]
public readonly Dictionary<string, Device> Devices = new();
/// <summary>
/// Devices listening on a given frequency.
/// </summary>
public readonly Dictionary<uint, HashSet<DeviceNetworkComponent>> ListeningDevices = new();
[ViewVariables]
public readonly Dictionary<uint, HashSet<Device>> ListeningDevices = new();
/// <summary>
/// Devices listening to all packets on a given frequency, regardless of the intended recipient.
/// </summary>
public readonly Dictionary<uint, HashSet<DeviceNetworkComponent>> ReceiveAllDevices = new();
[ViewVariables]
public readonly Dictionary<uint, HashSet<Device>> ReceiveAllDevices = new();
private readonly IRobustRandom _random;
public readonly int NetId;
@ -41,9 +44,11 @@ public sealed class DeviceNet
/// <summary>
/// Add a device to the network.
/// </summary>
public bool Add(DeviceNetworkComponent device)
public bool Add(Entity<DeviceNetworkComponent> ent)
{
if (device.CustomAddress)
var deviceComp = ent.Comp;
var device = new Device(ent);
if (deviceComp.CustomAddress)
{
// Only add if the device's existing address is available.
if (!Devices.TryAdd(device.Address, device))
@ -53,12 +58,15 @@ public sealed class DeviceNet
{
// Randomly generate a new address if the existing random one is invalid. Otherwise, keep the existing address
if (string.IsNullOrWhiteSpace(device.Address) || Devices.ContainsKey(device.Address))
device.Address = GenerateValidAddress(device.Prefix);
{
deviceComp.Address = GenerateValidAddress(deviceComp.Prefix);
device = new Device(ent); // Reallocate because the data had changed
}
Devices[device.Address] = device;
}
if (device.ReceiveFrequency is not uint freq)
if (device.ReceiveFrequency is not { } freq)
return true;
if (!ListeningDevices.TryGetValue(freq, out var devices))
@ -79,12 +87,14 @@ public sealed class DeviceNet
/// <summary>
/// Remove a device from the network.
/// </summary>
public bool Remove(DeviceNetworkComponent device)
public bool Remove(Entity<DeviceNetworkComponent> ent)
{
if (device.Address == null || !Devices.Remove(device.Address))
var deviceComp = ent.Comp;
var device = new Device(ent);
if (!Devices.Remove(device.Address))
return false;
if (device.ReceiveFrequency is not uint freq)
if (device.ReceiveFrequency is not { } freq)
return true;
if (ListeningDevices.TryGetValue(freq, out var listening))
@ -104,121 +114,6 @@ public sealed class DeviceNet
return true;
}
/// <summary>
/// Give an existing device a new randomly generated address. Useful if the device's address prefix was updated
/// and they want a new address to reflect that, or something like that.
/// </summary>
public bool RandomizeAddress(string oldAddress, string? prefix = null)
{
if (!Devices.Remove(oldAddress, out var device))
return false;
device.Address = GenerateValidAddress(prefix ?? device.Prefix);
device.CustomAddress = false;
Devices[device.Address] = device;
return true;
}
/// <summary>
/// Update the address of an existing device.
/// </summary>
public bool UpdateAddress(string oldAddress, string newAddress)
{
if (Devices.ContainsKey(newAddress))
return false;
if (!Devices.Remove(oldAddress, out var device))
return false;
device.Address = newAddress;
device.CustomAddress = true;
Devices[newAddress] = device;
return true;
}
/// <summary>
/// Make an existing network device listen to a new frequency.
/// </summary>
public bool UpdateReceiveFrequency(string address, uint? newFrequency)
{
if (!Devices.TryGetValue(address, out var device))
return false;
if (device.ReceiveFrequency == newFrequency)
return true;
if (device.ReceiveFrequency is uint freq)
{
if (ListeningDevices.TryGetValue(freq, out var listening))
{
listening.Remove(device);
if (listening.Count == 0)
ListeningDevices.Remove(freq);
}
if (device.ReceiveAll && ReceiveAllDevices.TryGetValue(freq, out var receiveAll))
{
receiveAll.Remove(device);
if (receiveAll.Count == 0)
ListeningDevices.Remove(freq);
}
}
device.ReceiveFrequency = newFrequency;
if (newFrequency == null)
return true;
if (!ListeningDevices.TryGetValue(newFrequency.Value, out var devices))
ListeningDevices[newFrequency.Value] = devices = new();
devices.Add(device);
if (!device.ReceiveAll)
return true;
if (!ReceiveAllDevices.TryGetValue(newFrequency.Value, out var receiveAlldevices))
ReceiveAllDevices[newFrequency.Value] = receiveAlldevices = new();
receiveAlldevices.Add(device);
return true;
}
/// <summary>
/// Make an existing network device listen to a new frequency.
/// </summary>
public bool UpdateReceiveAll(string address, bool receiveAll)
{
if (!Devices.TryGetValue(address, out var device))
return false;
if (device.ReceiveAll == receiveAll)
return true;
device.ReceiveAll = receiveAll;
if (device.ReceiveFrequency is not uint freq)
return true;
// remove or add to set of listening devices
HashSet<DeviceNetworkComponent>? devices;
if (receiveAll)
{
if (!ReceiveAllDevices.TryGetValue(freq, out devices))
ReceiveAllDevices[freq] = devices = new();
devices.Add(device);
}
else if (ReceiveAllDevices.TryGetValue(freq, out devices))
{
devices.Remove(device);
if (devices.Count == 0)
ReceiveAllDevices.Remove(freq);
}
return true;
}
/// <summary>
/// Generates a valid address by randomly generating one and checking if it already exists on the network.
/// </summary>
@ -230,8 +125,7 @@ public sealed class DeviceNet
{
var num = _random.Next();
address = $"{prefix}{num >> 16:X4}-{num & 0xFFFF:X4}";
}
while (Devices.ContainsKey(address));
} while (Devices.ContainsKey(address));
return address;
}

Some files were not shown because too many files have changed in this diff Show more