diff --git a/Content.Client/Computer/ComputerBoundUserInterface.cs b/Content.Client/Computer/ComputerBoundUserInterface.cs index 58820245ae..bdbfe03fa1 100644 --- a/Content.Client/Computer/ComputerBoundUserInterface.cs +++ b/Content.Client/Computer/ComputerBoundUserInterface.cs @@ -51,6 +51,11 @@ namespace Content.Client.Computer _window?.Dispose(); } } + + protected override void ReceiveMessage(BoundUserInterfaceMessage message) + { + _window?.ReceiveMessage(message); + } } /// @@ -79,6 +84,10 @@ namespace Content.Client.Computer void UpdateState(TState state) { } + + void ReceiveMessage(BoundUserInterfaceMessage message) + { + } } } diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index f1ca7d3707..180777f374 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -359,6 +359,9 @@ namespace Content.Client.Examine _idCounter = 0; RaiseNetworkEvent(new ExamineSystemMessages.RequestExamineInfoMessage(entity, _idCounter, true)); } + + RaiseLocalEvent(entity, new ClientExaminedEvent(entity, playerEnt.Value)); + _lastExaminedEntity = entity; } @@ -384,4 +387,26 @@ namespace Content.Client.Examine } } } + + /// + /// An entity was examined on the client. + /// + public sealed class ClientExaminedEvent : EntityEventArgs + { + /// + /// The entity performing the examining. + /// + public readonly EntityUid Examiner; + + /// + /// Entity being examined, for broadcast event purposes. + /// + public readonly EntityUid Examined; + + public ClientExaminedEvent(EntityUid examined, EntityUid examiner) + { + Examined = examined; + Examiner = examiner; + } + } } diff --git a/Content.Client/Guidebook/Controls/GuideEntityEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideEntityEmbed.xaml.cs index e6321d8ba9..ee8b46efb0 100644 --- a/Content.Client/Guidebook/Controls/GuideEntityEmbed.xaml.cs +++ b/Content.Client/Guidebook/Controls/GuideEntityEmbed.xaml.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Linq; using System.Numerics; using Content.Client.ContextMenu.UI; using Content.Client.Examine; @@ -15,6 +16,7 @@ using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Input; using Robust.Shared.Map; +using Robust.Shared.Utility; namespace Content.Client.Guidebook.Controls; @@ -169,10 +171,17 @@ public sealed partial class GuideEntityEmbed : BoxContainer, IDocumentTag if (args.TryGetValue("Rotation", out var rotation)) { - Sprite.Rotation = Angle.FromDegrees(double.Parse(rotation)); + View.OverrideDirection = Angle.FromDegrees(double.Parse(rotation)).GetDir(); } - Margin = new Thickness(4, 8); + if (args.TryGetValue("Margin", out var margin)) + { + Margin = ParseThickness(margin); + } + else + { + Margin = new Thickness(4, 8); + } // By default, we will map-initialize guidebook entities. if (!args.TryGetValue("Init", out var mapInit) || !bool.Parse(mapInit)) @@ -181,4 +190,20 @@ public sealed partial class GuideEntityEmbed : BoxContainer, IDocumentTag control = this; return true; } + + private static Thickness ParseThickness(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return default; + + var split = value.Split(" ", StringSplitOptions.RemoveEmptyEntries).Select(x => Parse.Float(x)).ToArray(); + if (split.Length == 1) + return new Thickness(split[0]); + if (split.Length == 2) + return new Thickness(split[0], split[1]); + if (split.Length == 4) + return new Thickness(split[0], split[1], split[2], split[3]); + + throw new Exception("Invalid Thickness format!"); + } } diff --git a/Content.Client/Power/Generation/Teg/TegCirculatorComponent.cs b/Content.Client/Power/Generation/Teg/TegCirculatorComponent.cs new file mode 100644 index 0000000000..486f5505f9 --- /dev/null +++ b/Content.Client/Power/Generation/Teg/TegCirculatorComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Client.Power.Generation.Teg; + +/// +[RegisterComponent] +public sealed class TegCirculatorComponent : Component +{ + +} diff --git a/Content.Client/Power/Generation/Teg/TegSystem.cs b/Content.Client/Power/Generation/Teg/TegSystem.cs new file mode 100644 index 0000000000..e1f0874e8a --- /dev/null +++ b/Content.Client/Power/Generation/Teg/TegSystem.cs @@ -0,0 +1,26 @@ +using Content.Client.Examine; +using Robust.Shared.Map; + +namespace Content.Client.Power.Generation.Teg; + +/// +/// Handles client-side logic for the thermo-electric generator (TEG). +/// +/// +/// +/// TEG circulators show which direction the in- and outlet port is by popping up two floating arrows when examined. +/// +/// +/// +public sealed class TegSystem : EntitySystem +{ + public override void Initialize() + { + SubscribeLocalEvent(CirculatorExamined); + } + + private void CirculatorExamined(EntityUid uid, TegCirculatorComponent component, ClientExaminedEvent args) + { + Spawn("TegCirculatorArrow", new EntityCoordinates(uid, 0, 0)); + } +} diff --git a/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml b/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml new file mode 100644 index 0000000000..a2dd57f364 --- /dev/null +++ b/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs b/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs new file mode 100644 index 0000000000..9fc132c747 --- /dev/null +++ b/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs @@ -0,0 +1,264 @@ +using System.Linq; +using System.Numerics; +using Content.Client.Computer; +using Content.Client.Stylesheets; +using Content.Client.UserInterface.Controls; +using Content.Shared.SensorMonitoring; +using JetBrains.Annotations; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Timing; +using ConsoleUIState = Content.Shared.SensorMonitoring.SensorMonitoringConsoleBoundInterfaceState; +using IncrementalUIState = Content.Shared.SensorMonitoring.SensorMonitoringIncrementalUpdate; + +namespace Content.Client.SensorMonitoring; + +[GenerateTypedNameReferences] +public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindow +{ + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ILocalizationManager _loc = default!; + + private TimeSpan _retentionTime; + private readonly Dictionary _sensorData = new(); + + public SensorMonitoringWindow() + { + RobustXamlLoader.Load(this); + } + + public void UpdateState(ConsoleUIState state) + { + _retentionTime = state.RetentionTime; + + _sensorData.Clear(); + + foreach (var netSensor in state.Sensors) + { + var sensor = new SensorData + { + Name = netSensor.Name, + Address = netSensor.Address, + DeviceType = netSensor.DeviceType + }; + + _sensorData.Add(netSensor.NetId, sensor); + + foreach (var netStream in netSensor.Streams) + { + var stream = new SensorStream + { + Name = netStream.Name, + Unit = netStream.Unit + }; + + sensor.Streams.Add(netStream.NetId, stream); + + foreach (var sample in netStream.Samples) + { + stream.Samples.Enqueue(sample); + } + } + } + + Update(); + } + + public void ReceiveMessage(BoundUserInterfaceMessage message) + { + if (message is not IncrementalUIState incremental) + return; + + foreach (var removed in incremental.RemovedSensors) + { + _sensorData.Remove(removed); + } + + foreach (var netSensor in incremental.Sensors) + { + // TODO: Fuck this doesn't work if a sensor is added while the UI is open. + if (!_sensorData.TryGetValue(netSensor.NetId, out var sensor)) + continue; + + foreach (var netStream in netSensor.Streams) + { + // TODO: Fuck this doesn't work if a stream is added while the UI is open. + if (!sensor.Streams.TryGetValue(netStream.NetId, out var stream)) + continue; + + foreach (var (time, value) in netStream.Samples) + { + stream.Samples.Enqueue(new SensorSample(time + incremental.RelTime, value)); + } + } + } + + CullOldSamples(); + Update(); + } + + private void Update() + { + Asdf.RemoveAllChildren(); + + var curTime = _gameTiming.CurTime; + var startTime = curTime - _retentionTime; + + foreach (var sensor in _sensorData.Values) + { + var labelName = new Label { Text = sensor.Name, StyleClasses = { StyleBase.StyleClassLabelHeading } }; + var labelAddress = new Label + { + Text = sensor.Address, + Margin = new Thickness(4, 0), + VerticalAlignment = VAlignment.Bottom, + StyleClasses = { StyleNano.StyleClassLabelSecondaryColor } + }; + + Asdf.AddChild(new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, Children = + { + labelName, + labelAddress + } + }); + + foreach (var stream in sensor.Streams.Values) + { + var maxValue = stream.Unit switch + { + SensorUnit.PressureKpa => 5000, // 5 MPa + SensorUnit.Ratio => 1, + SensorUnit.PowerW => 1_000_000, // 1 MW + SensorUnit.EnergyJ => 2_000_000, // 2 MJ + _ => 1000 + }; + + // TODO: Better way to do this? + var lastSample = stream.Samples.Last(); + + Asdf.AddChild(new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + Children = + { + new Label { Text = stream.Name, StyleClasses = { "monospace" }, HorizontalExpand = true }, + new Label { Text = FormatValue(stream.Unit, lastSample.Value) } + } + }); + + Asdf.AddChild(new GraphView(stream.Samples, startTime, curTime, maxValue) { MinHeight = 150 }); + Asdf.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } }); + } + } + } + + private string FormatValue(SensorUnit unit, float value) + { + return _loc.GetString( + "sensor-monitoring-value-display", + ("unit", unit.ToString()), + ("value", value)); + } + + private void CullOldSamples() + { + var startTime = _gameTiming.CurTime - _retentionTime; + + foreach (var sensor in _sensorData.Values) + { + foreach (var stream in sensor.Streams.Values) + { + while (stream.Samples.TryPeek(out var sample) && sample.Time < startTime) + { + stream.Samples.Dequeue(); + } + } + } + } + + private sealed class SensorData + { + public string Name = ""; + public string Address = ""; + public SensorDeviceType DeviceType; + + public readonly Dictionary Streams = new(); + } + + private sealed class SensorStream + { + public string Name = ""; + public SensorUnit Unit; + public readonly Queue Samples = new(); + } + + private sealed class GraphView : Control + { + private readonly Queue _samples; + private readonly TimeSpan _startTime; + private readonly TimeSpan _curTime; + private readonly float _maxY; + + public GraphView(Queue samples, TimeSpan startTime, TimeSpan curTime, float maxY) + { + _samples = samples; + _startTime = startTime; + _curTime = curTime; + _maxY = maxY; + RectClipContent = true; + } + + protected override void Draw(DrawingHandleScreen handle) + { + base.Draw(handle); + + var window = (float) (_curTime - _startTime).TotalSeconds; + + // TODO: omg this is terrible don't fucking hardcode this size to something uncached huge omfg. + var vertices = new Vector2[25000]; + var countVtx = 0; + + var lastPoint = new Vector2(float.NaN, float.NaN); + + foreach (var (time, sample) in _samples) + { + var relTime = (float) (time - _startTime).TotalSeconds; + + var posY = PixelHeight - (sample / _maxY) * PixelHeight; + var posX = (relTime / window) * PixelWidth; + + var newPoint = new Vector2(posX, posY); + + if (float.IsFinite(lastPoint.X)) + { + handle.DrawLine(lastPoint, newPoint, Color.White); + + vertices[countVtx++] = lastPoint; + vertices[countVtx++] = lastPoint with { Y = PixelHeight }; + vertices[countVtx++] = newPoint; + vertices[countVtx++] = newPoint; + vertices[countVtx++] = lastPoint with { Y = PixelHeight }; + vertices[countVtx++] = newPoint with { Y = PixelHeight }; + } + + lastPoint = newPoint; + } + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, vertices.AsSpan(0, countVtx), Color.White.WithAlpha(0.1f)); + } + } +} + +[UsedImplicitly] +public sealed class + SensorMonitoringConsoleBoundUserInterface : ComputerBoundUserInterface +{ + public SensorMonitoringConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } +} diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index fb06d9f310..fd525c073d 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -45,6 +45,7 @@ namespace Content.IntegrationTests.Tests private static readonly string[] GameMaps = { "Dev", + "TestTeg", "Fland", "Meta", "Packed", diff --git a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs index 9f57510d1f..4ee9a9ac8e 100644 --- a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs @@ -166,7 +166,7 @@ public sealed class AirAlarmSystem : EntitySystem SubscribeLocalEvent(OnDeviceListUpdate); SubscribeLocalEvent(OnClose); SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnActivate); } private void OnDeviceListUpdate(EntityUid uid, AirAlarmComponent component, DeviceListUpdateEvent args) @@ -225,7 +225,7 @@ public sealed class AirAlarmSystem : EntitySystem _activeUserInterfaces.Remove(uid); } - private void OnInteract(EntityUid uid, AirAlarmComponent component, InteractHandEvent args) + private void OnActivate(EntityUid uid, AirAlarmComponent component, ActivateInWorldEvent args) { if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target)) return; diff --git a/Content.Server/Atmos/Piping/Binary/Components/GasVolumePumpComponent.cs b/Content.Server/Atmos/Piping/Binary/Components/GasVolumePumpComponent.cs index 3826887097..b79fb3b254 100644 --- a/Content.Server/Atmos/Piping/Binary/Components/GasVolumePumpComponent.cs +++ b/Content.Server/Atmos/Piping/Binary/Components/GasVolumePumpComponent.cs @@ -40,5 +40,8 @@ namespace Content.Server.Atmos.Piping.Binary.Components [DataField("overclockThreshold")] public float OverclockThreshold { get; set; } = 1000; + + [DataField("lastMolesTransferred")] + public float LastMolesTransferred; } } diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs index 3052fb313b..e193de4b3d 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs @@ -36,7 +36,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems SubscribeLocalEvent(OnPumpUpdated); SubscribeLocalEvent(OnPumpLeaveAtmosphere); SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnPumpInteractHand); + SubscribeLocalEvent(OnPumpActivate); // Bound UI subscriptions SubscribeLocalEvent(OnOutputPressureChangeMessage); SubscribeLocalEvent(OnToggleStatusMessage); @@ -99,7 +99,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems _userInterfaceSystem.TryCloseAll(uid, GasPressurePumpUiKey.Key); } - private void OnPumpInteractHand(EntityUid uid, GasPressurePumpComponent pump, InteractHandEvent args) + private void OnPumpActivate(EntityUid uid, GasPressurePumpComponent pump, ActivateInWorldEvent args) { if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor)) return; diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs index 54bc0d6fde..6b3815fdfa 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs @@ -1,12 +1,17 @@ using Content.Server.Administration.Logs; using Content.Server.Atmos.EntitySystems; +using Content.Server.Atmos.Monitor.Systems; using Content.Server.Atmos.Piping.Binary.Components; using Content.Server.Atmos.Piping.Components; +using Content.Server.DeviceNetwork; +using Content.Server.DeviceNetwork.Components; +using Content.Server.DeviceNetwork.Systems; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; using Content.Shared.Atmos.Piping; using Content.Shared.Atmos.Piping.Binary.Components; +using Content.Shared.Atmos.Piping.Unary.Components; using Content.Shared.Audio; using Content.Shared.Database; using Content.Shared.Examine; @@ -29,6 +34,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly NodeContainerSystem _nodeContainer = default!; + [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!; + public override void Initialize() { @@ -38,10 +45,12 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems SubscribeLocalEvent(OnVolumePumpUpdated); SubscribeLocalEvent(OnVolumePumpLeaveAtmosphere); SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnPumpInteractHand); + SubscribeLocalEvent(OnPumpActivate); // Bound UI subscriptions SubscribeLocalEvent(OnTransferRateChangeMessage); SubscribeLocalEvent(OnToggleStatusMessage); + + SubscribeLocalEvent(OnPacketRecv); } private void OnInit(EntityUid uid, GasVolumePumpComponent pump, ComponentInit args) @@ -101,6 +110,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems } } + pump.LastMolesTransferred = removed.TotalMoles; + _atmosphereSystem.Merge(outlet.Air, removed); _ambientSoundSystem.SetAmbience(uid, removed.TotalMoles > 0f); } @@ -114,7 +125,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems _userInterfaceSystem.TryCloseAll(uid, GasVolumePumpUiKey.Key); } - private void OnPumpInteractHand(EntityUid uid, GasVolumePumpComponent pump, InteractHandEvent args) + private void OnPumpActivate(EntityUid uid, GasVolumePumpComponent pump, ActivateInWorldEvent args) { if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor)) return; @@ -165,5 +176,24 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems _appearance.SetData(uid, PumpVisuals.Enabled, pump.Enabled, appearance); } + + private void OnPacketRecv(EntityUid uid, GasVolumePumpComponent component, DeviceNetworkPacketEvent args) + { + if (!TryComp(uid, out DeviceNetworkComponent? netConn) + || !args.Data.TryGetValue(DeviceNetworkConstants.Command, out var cmd)) + return; + + 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; + } + } } } diff --git a/Content.Server/Atmos/Piping/Unary/Components/GasThermoMachineComponent.cs b/Content.Server/Atmos/Piping/Unary/Components/GasThermoMachineComponent.cs index f9122caf3c..903ad64c95 100644 --- a/Content.Server/Atmos/Piping/Unary/Components/GasThermoMachineComponent.cs +++ b/Content.Server/Atmos/Piping/Unary/Components/GasThermoMachineComponent.cs @@ -11,10 +11,6 @@ namespace Content.Server.Atmos.Piping.Unary.Components [DataField("inlet")] public string InletName = "pipe"; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("enabled")] - public bool Enabled = false; - /// /// Current maximum temperature, calculated from and the quality of matter /// bins. The heat capacity effectively determines the rate at which the thermo machine can add or remove @@ -93,5 +89,11 @@ namespace Content.Server.Atmos.Piping.Unary.Components [DataField("machinePartTemperature", customTypeSerializer: typeof(PrototypeIdSerializer))] public string MachinePartTemperature = "Capacitor"; + /// + /// Last amount of energy added/removed from the attached pipe network + /// + [DataField("lastEnergyDelta")] + [ViewVariables(VVAccess.ReadWrite)] + public float LastEnergyDelta; } } diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs index 872ea12c1b..756942ecca 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs @@ -1,10 +1,15 @@ using Content.Server.Atmos.EntitySystems; +using Content.Server.Atmos.Monitor.Systems; using Content.Server.Atmos.Piping.Components; using Content.Server.Atmos.Piping.Unary.Components; using Content.Server.Construction; +using Content.Server.DeviceNetwork; +using Content.Server.DeviceNetwork.Components; +using Content.Server.DeviceNetwork.Systems; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; +using Content.Server.Power.Components; using Content.Shared.Atmos; using Content.Shared.Atmos.Piping.Unary.Components; using JetBrains.Annotations; @@ -22,6 +27,8 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly PowerReceiverSystem _power = default!; [Dependency] private readonly NodeContainerSystem _nodeContainer = default!; + [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!; + public override void Initialize() { @@ -35,12 +42,15 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems // UI events SubscribeLocalEvent(OnToggleMessage); SubscribeLocalEvent(OnChangeTemperature); + + // Device network + SubscribeLocalEvent(OnPacketRecv); } private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, AtmosDeviceUpdateEvent args) { - if (!(thermoMachine.Enabled && _power.IsPowered(uid)) + if (!(_power.IsPowered(uid)) || !TryComp(uid, out NodeContainerComponent? nodeContainer) || !_nodeContainer.TryGetNode(nodeContainer, thermoMachine.InletName, out PipeNode? inlet)) { @@ -50,10 +60,14 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems var airHeatCapacity = _atmosphereSystem.GetHeatCapacity(inlet.Air); var combinedHeatCapacity = airHeatCapacity + thermoMachine.HeatCapacity; + var startEnergy = inlet.Air.Temperature * airHeatCapacity; + if (!MathHelper.CloseTo(combinedHeatCapacity, 0, 0.001f)) { var combinedEnergy = thermoMachine.HeatCapacity * thermoMachine.TargetTemperature + airHeatCapacity * inlet.Air.Temperature; inlet.Air.Temperature = combinedEnergy / combinedHeatCapacity; + + thermoMachine.LastEnergyDelta = inlet.Air.Temperature * airHeatCapacity - startEnergy; } } @@ -98,7 +112,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems private void OnToggleMessage(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineToggleMessage args) { - SetEnabled(uid, thermoMachine, _power.TogglePower(uid)); + _power.TogglePower(uid); DirtyUI(uid, thermoMachine); } @@ -115,13 +129,12 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (!Resolve(uid, ref thermoMachine, ref ui, false)) return; - _userInterfaceSystem.TrySetUiState(uid, ThermomachineUiKey.Key, - new GasThermomachineBoundUserInterfaceState(thermoMachine.MinTemperature, thermoMachine.MaxTemperature, thermoMachine.TargetTemperature, thermoMachine.Enabled, thermoMachine.Mode), null, ui); - } + ApcPowerReceiverComponent? powerReceiver = null; + if (!Resolve(uid, ref powerReceiver)) + return; - private void SetEnabled(EntityUid uid, GasThermoMachineComponent thermoMachine, bool enabled) - { - thermoMachine.Enabled = enabled; + _userInterfaceSystem.TrySetUiState(uid, ThermomachineUiKey.Key, + new GasThermomachineBoundUserInterfaceState(thermoMachine.MinTemperature, thermoMachine.MaxTemperature, thermoMachine.TargetTemperature, !powerReceiver.PowerDisabled, thermoMachine.Mode), null, ui); } private void OnExamined(EntityUid uid, GasThermoMachineComponent thermoMachine, ExaminedEvent args) @@ -137,5 +150,25 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems args.PushMarkup(str); } + + private void OnPacketRecv(EntityUid uid, GasThermoMachineComponent component, DeviceNetworkPacketEvent args) + { + if (!TryComp(uid, out DeviceNetworkComponent? netConn) + || !args.Data.TryGetValue(DeviceNetworkConstants.Command, out var cmd)) + return; + + var payload = new NetworkPayload(); + + switch (cmd) + { + case AtmosDeviceNetworkSystem.SyncData: + payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData); + payload.Add(AtmosDeviceNetworkSystem.SyncData, new GasThermoMachineData(component.LastEnergyDelta)); + + _deviceNetwork.QueuePacket(uid, args.SenderAddress, payload, device: netConn); + + return; + } + } } } diff --git a/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs b/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs index 56b1cee3d1..2f53bff7ad 100644 --- a/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs +++ b/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs @@ -438,6 +438,7 @@ namespace Content.Server.NodeContainer.EntitySystems NodeGroupID.AMEngine => Color.Purple, NodeGroupID.Pipe => Color.Blue, NodeGroupID.WireNet => Color.DarkMagenta, + NodeGroupID.Teg => Color.Red, _ => Color.White }; } diff --git a/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs b/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs index e4dac00683..1af938da83 100644 --- a/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs +++ b/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Content.Server.Power.Generation.Teg; using Robust.Shared.Reflection; namespace Content.Server.NodeContainer.NodeGroups @@ -61,5 +62,12 @@ namespace Content.Server.NodeContainer.NodeGroups Pipe, WireNet, Spreader, + + /// + /// Group used by the TEG. + /// + /// + /// + Teg, } } diff --git a/Content.Server/Power/Generation/Teg/TegCirculatorComponent.cs b/Content.Server/Power/Generation/Teg/TegCirculatorComponent.cs new file mode 100644 index 0000000000..e2f7e59e35 --- /dev/null +++ b/Content.Server/Power/Generation/Teg/TegCirculatorComponent.cs @@ -0,0 +1,43 @@ +using Content.Shared.Atmos; + +namespace Content.Server.Power.Generation.Teg; + +/// +/// A "circulator" for the thermo-electric generator (TEG). +/// Circulators are used by the TEG to take in a side of either hot or cold gas. +/// +/// +[RegisterComponent] +[Access(typeof(TegSystem))] +public sealed class TegCirculatorComponent : Component +{ + /// + /// The difference between the inlet and outlet pressure at the start of the previous tick. + /// + [DataField("last_pressure_delta")] [ViewVariables(VVAccess.ReadWrite)] + public float LastPressureDelta; + + /// + /// The amount of moles transferred by the circulator last tick. + /// + [DataField("last_moles_transferred")] [ViewVariables(VVAccess.ReadWrite)] + public float LastMolesTransferred; + + /// + /// Minimum pressure delta between inlet and outlet for which the circulator animation speed is "fast". + /// + [DataField("visual_speed_delta")] [ViewVariables(VVAccess.ReadWrite)] + public float VisualSpeedDelta = 5 * Atmospherics.OneAtmosphere; + + /// + /// Light color of this circulator when it's running at "slow" speed. + /// + [DataField("light_color_slow")] [ViewVariables(VVAccess.ReadWrite)] + public Color LightColorSlow; + + /// + /// Light color of this circulator when it's running at "fast" speed. + /// + [DataField("light_color_fast")] [ViewVariables(VVAccess.ReadWrite)] + public Color LightColorFast; +} diff --git a/Content.Server/Power/Generation/Teg/TegGeneratorComponent.cs b/Content.Server/Power/Generation/Teg/TegGeneratorComponent.cs new file mode 100644 index 0000000000..3bd1616ce3 --- /dev/null +++ b/Content.Server/Power/Generation/Teg/TegGeneratorComponent.cs @@ -0,0 +1,69 @@ +namespace Content.Server.Power.Generation.Teg; + +/// +/// The centerpiece for the thermo-electric generator (TEG). +/// +/// +[RegisterComponent] +[Access(typeof(TegSystem))] +public sealed class TegGeneratorComponent : Component +{ + /// + /// When transferring energy from the hot to cold side, + /// determines how much of that energy can be extracted as electricity. + /// + /// + /// A value of 0.9 means that 90% of energy transferred goes to electricity. + /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("thermal_efficiency")] + public float ThermalEfficiency = 0.65f; + + /// + /// Simple factor that scales effective electricity generation. + /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("power_factor")] + public float PowerFactor = 1; + + /// + /// Amount of energy (Joules) generated by the TEG last atmos tick. + /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("last_generation")] + public float LastGeneration; + + /// + /// The current target for TEG power generation. + /// Drifts towards actual power draw of the network with . + /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("ramp_position")] + public float RampPosition; + + /// + /// Factor by which TEG power generation scales, both up and down. + /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("ramp_factor")] + public float RampFactor = 1.05f; + + /// + /// Minimum position for the ramp. Avoids TEG taking too long to start. + /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("ramp_threshold")] + public float RampMinimum = 5000; + + /// + /// Power output value at which the sprite appearance and sound volume should cap out. + /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("max_visual_power")] + public float MaxVisualPower = 200_000; + + /// + /// Minimum ambient sound volume, when we're producing just barely any power at all. + /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("volume_min")] + public float VolumeMin = -9; + + /// + /// Maximum ambient sound volume, when we're producing >= power. + /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("volume_max")] + public float VolumeMax = -4; +} diff --git a/Content.Server/Power/Generation/Teg/TegNodeGroup.cs b/Content.Server/Power/Generation/Teg/TegNodeGroup.cs new file mode 100644 index 0000000000..7d844afcc4 --- /dev/null +++ b/Content.Server/Power/Generation/Teg/TegNodeGroup.cs @@ -0,0 +1,206 @@ +using System.Linq; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.NodeGroups; +using Content.Server.NodeContainer.Nodes; +using Robust.Shared.Map.Components; +using Robust.Shared.Utility; + +namespace Content.Server.Power.Generation.Teg; + +/// +/// Node group that connects the central TEG with its two circulators. +/// +/// +/// +/// +[NodeGroup(NodeGroupID.Teg)] +public sealed class TegNodeGroup : BaseNodeGroup +{ + /// + /// If true, this TEG is fully built and has all its parts properly connected. + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool IsFullyBuilt { get; private set; } + + /// + /// The central generator component. + /// + /// + [ViewVariables(VVAccess.ReadWrite)] + public TegNodeGenerator? Generator { get; private set; } + + // Illustration for how the TEG A/B circulators are laid out. + // Circulator B Generator Circulator A + // ^ -> | + // | V + // They have rotations like the arrows point out. + + /// + /// The A-side circulator. This is the circulator that is in the direction FACING the center component's rotation. + /// + /// + /// Not filled in if there is no center piece to deduce relative rotation from. + /// + /// + [ViewVariables(VVAccess.ReadWrite)] + public TegNodeCirculator? CirculatorA { get; private set; } + + /// + /// The B-side circulator. This circulator is opposite . + /// + /// + /// Not filled in if there is no center piece to deduce relative rotation from. + /// + /// + [ViewVariables(VVAccess.ReadWrite)] + public TegNodeCirculator? CirculatorB { get; private set; } + + private IEntityManager? _entityManager; + + public override void Initialize(Node sourceNode, IEntityManager entMan) + { + base.Initialize(sourceNode, entMan); + + _entityManager = entMan; + } + + public override void LoadNodes(List groupNodes) + { + DebugTools.Assert(groupNodes.Count <= 3, "The TEG has at most 3 parts"); + DebugTools.Assert(_entityManager != null); + + base.LoadNodes(groupNodes); + + Generator = groupNodes.OfType().SingleOrDefault(); + if (Generator != null) + { + // If we have a generator, we can assign CirculatorA and CirculatorB based on relative rotation. + var xformGenerator = _entityManager.GetComponent(Generator.Owner); + var genDir = xformGenerator.LocalRotation.GetDir(); + + foreach (var node in groupNodes) + { + if (node is not TegNodeCirculator circulator) + continue; + + var xform = _entityManager.GetComponent(node.Owner); + var dir = xform.LocalRotation.GetDir(); + if (genDir.GetClockwise90Degrees() == dir) + { + CirculatorA = circulator; + } + else + { + CirculatorB = circulator; + } + } + + } + + IsFullyBuilt = Generator != null && CirculatorA != null && CirculatorB != null; + + var tegSystem = _entityManager.EntitySysManager.GetEntitySystem(); + foreach (var node in groupNodes) + { + if (node is TegNodeGenerator generator) + tegSystem.UpdateGeneratorConnectivity(generator.Owner, this); + + if (node is TegNodeCirculator circulator) + tegSystem.UpdateCirculatorConnectivity(circulator.Owner, this); + } + } +} + +/// +/// Node used by the central TEG generator component. +/// +/// +/// +[DataDefinition] +public sealed class TegNodeGenerator : Node +{ + public override IEnumerable GetReachableNodes( + TransformComponent xform, + EntityQuery nodeQuery, + EntityQuery xformQuery, + MapGridComponent? grid, + IEntityManager entMan) + { + if (!xform.Anchored || grid == null) + yield break; + + var gridIndex = grid.TileIndicesFor(xform.Coordinates); + + var dir = xform.LocalRotation.GetDir(); + var a = FindCirculator(dir); + var b = FindCirculator(dir.GetOpposite()); + + if (a != null) + yield return a; + + if (b != null) + yield return b; + + TegNodeCirculator? FindCirculator(Direction searchDir) + { + var targetIdx = gridIndex.Offset(searchDir); + + foreach (var node in NodeHelpers.GetNodesInTile(nodeQuery, grid, targetIdx)) + { + if (node is not TegNodeCirculator circulator) + continue; + + var entity = node.Owner; + var entityXform = xformQuery.GetComponent(entity); + var entityDir = entityXform.LocalRotation.GetDir(); + + if (entityDir == searchDir.GetClockwise90Degrees()) + return circulator; + } + + return null; + } + } +} + +/// +/// Node used by the central TEG circulator entities. +/// +/// +/// +[DataDefinition] +public sealed class TegNodeCirculator : Node +{ + public override IEnumerable GetReachableNodes( + TransformComponent xform, + EntityQuery nodeQuery, + EntityQuery xformQuery, + MapGridComponent? grid, + IEntityManager entMan) + { + if (!xform.Anchored || grid == null) + yield break; + + var gridIndex = grid.TileIndicesFor(xform.Coordinates); + + var dir = xform.LocalRotation.GetDir(); + var searchDir = dir.GetClockwise90Degrees(); + var targetIdx = gridIndex.Offset(searchDir); + + foreach (var node in NodeHelpers.GetNodesInTile(nodeQuery, grid, targetIdx)) + { + if (node is not TegNodeGenerator generator) + continue; + + var entity = node.Owner; + var entityXform = xformQuery.GetComponent(entity); + var entityDir = entityXform.LocalRotation.GetDir(); + + if (entityDir == searchDir || entityDir == searchDir.GetOpposite()) + { + yield return generator; + break; + } + } + } +} diff --git a/Content.Server/Power/Generation/Teg/TegSensorData.cs b/Content.Server/Power/Generation/Teg/TegSensorData.cs new file mode 100644 index 0000000000..f994b5762c --- /dev/null +++ b/Content.Server/Power/Generation/Teg/TegSensorData.cs @@ -0,0 +1,52 @@ +using Content.Server.Power.Components; + +namespace Content.Server.Power.Generation.Teg; + +/// +/// Sensor data reported by the when queried over the device network. +/// +/// +public sealed class TegSensorData +{ + /// + /// Information for the A-side circulator. + /// + public Circulator CirculatorA; + + /// + /// Information for the B-side circulator. + /// + public Circulator CirculatorB; + + /// + /// The amount of energy (Joules) generated by the TEG last atmos tick. + /// + /// + public float LastGeneration; + + /// + /// Ramping position for the TEG power generation. + /// + /// + public float RampPosition; + + /// + /// Power (Watts) actually being supplied by the TEG to connected power network. + /// + /// + public float PowerOutput; + + /// + /// Information for a single TEG circulator. + /// + /// Pressure measured at the circulator's input pipe + /// Pressure measured at the circulator's output pipe + /// Temperature measured at the circulator's input pipe + /// Temperature measured at the circulator's output pipe + public record struct Circulator( + float InletPressure, + float OutletPressure, + float InletTemperature, + float OutletTemperature); +} + diff --git a/Content.Server/Power/Generation/Teg/TegSystem.cs b/Content.Server/Power/Generation/Teg/TegSystem.cs new file mode 100644 index 0000000000..4bd1c0a14c --- /dev/null +++ b/Content.Server/Power/Generation/Teg/TegSystem.cs @@ -0,0 +1,383 @@ +using Content.Server.Atmos; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Atmos.Piping.Components; +using Content.Server.Audio; +using Content.Server.DeviceNetwork; +using Content.Server.DeviceNetwork.Systems; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.Nodes; +using Content.Server.Power.Components; +using Content.Shared.Examine; +using Content.Shared.Power.Generation.Teg; +using Content.Shared.Rounding; +using Robust.Server.GameObjects; + +namespace Content.Server.Power.Generation.Teg; + +/// +/// Handles processing logic for the thermo-electric generator (TEG). +/// +/// +/// +/// The TEG generates power by exchanging heat between gases flowing through its two sides. +/// The gas flows through a "circulator" entity on each side, which have both an inlet and an outlet port. +/// +/// +/// Connecting the TEG core to its circulators is implemented via a node group. See . +/// +/// +/// The TEG center does HV power output, and must also be connected to an LV wire for the TEG to function. +/// +/// +/// Unlike in SS13, the TEG actually adjusts gas heat exchange to match the energy demand of the power network. +/// To achieve this, the TEG implements its own ramping logic instead of using the built-in Pow3r ramping. +/// The TEG actually has a maximum output of +n% more than was really generated, +/// which allows Pow3r to draw more power to "signal" that there is more network load. +/// The ramping is also exponential instead of linear like in normal Pow3r. +/// This system does mean a fully-loaded TEG creates +n% power out of thin air, but this is considered acceptable. +/// +/// +/// +/// +/// +/// +public sealed class TegSystem : EntitySystem +{ + /// + /// Node name for the TEG part connection nodes (). + /// + private const string NodeNameTeg = "teg"; + + /// + /// Node name for the inlet pipe of a circulator. + /// + private const string NodeNameInlet = "inlet"; + + /// + /// Node name for the outlet pipe of a circulator. + /// + private const string NodeNameOutlet = "outlet"; + + /// + /// Device network command to have the TEG output a object for its last statistics. + /// + public const string DeviceNetworkCommandSyncData = "teg_sync_data"; + + [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly PointLightSystem _pointLight = default!; + [Dependency] private readonly AmbientSoundSystem _ambientSound = default!; + + private EntityQuery _nodeContainerQuery; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(GeneratorUpdate); + SubscribeLocalEvent(GeneratorPowerChange); + SubscribeLocalEvent(DeviceNetworkPacketReceived); + + SubscribeLocalEvent(GeneratorExamined); + + _nodeContainerQuery = GetEntityQuery(); + } + + private void GeneratorExamined(EntityUid uid, TegGeneratorComponent component, ExaminedEvent args) + { + if (GetNodeGroup(uid) is not { IsFullyBuilt: true }) + { + args.PushMarkup(Loc.GetString("teg-generator-examine-connection")); + } + else + { + var supplier = Comp(uid); + args.PushMarkup(Loc.GetString("teg-generator-examine-power", ("power", supplier.CurrentSupply))); + } + } + + private void GeneratorUpdate(EntityUid uid, TegGeneratorComponent component, AtmosDeviceUpdateEvent args) + { + var tegGroup = GetNodeGroup(uid); + if (tegGroup is not { IsFullyBuilt: true }) + return; + + var supplier = Comp(uid); + var powerReceiver = Comp(uid); + if (!powerReceiver.Powered) + { + supplier.MaxSupply = 0; + return; + } + + var circA = tegGroup.CirculatorA!.Owner; + var circB = tegGroup.CirculatorB!.Owner; + + var (inletA, outletA) = GetPipes(circA); + var (inletB, outletB) = GetPipes(circB); + + var (airA, δpA) = GetCirculatorAirTransfer(inletA.Air, outletA.Air); + var (airB, δpB) = GetCirculatorAirTransfer(inletB.Air, outletB.Air); + + var cA = _atmosphere.GetHeatCapacity(airA); + var cB = _atmosphere.GetHeatCapacity(airB); + + // Shift ramp position based on demand and generation from previous tick. + var curRamp = component.RampPosition; + var lastDraw = supplier.CurrentSupply; + // Limit amount lost/gained based on power factor. + curRamp = MathHelper.Clamp(lastDraw, curRamp / component.RampFactor, curRamp * component.RampFactor); + curRamp = MathF.Max(curRamp, component.RampMinimum); + component.RampPosition = curRamp; + + var electricalEnergy = 0f; + + if (airA.Pressure > 0 && airB.Pressure > 0) + { + var hotA = airA.Temperature > airB.Temperature; + var cHot = hotA ? cA : cB; + + // Calculate maximum amount of energy to generate this tick based on ramping above. + // This clamps the thermal energy transfer as well. + var targetEnergy = curRamp / _atmosphere.AtmosTickRate; + var transferMax = targetEnergy / (component.ThermalEfficiency * component.PowerFactor); + + // Calculate thermal and electrical energy transfer between the two sides. + var δT = MathF.Abs(airA.Temperature - airB.Temperature); + var transfer = Math.Min(δT * cA * cB / (cA + cB - cHot * component.ThermalEfficiency), transferMax); + electricalEnergy = transfer * component.ThermalEfficiency * component.PowerFactor; + var outTransfer = transfer * (1 - component.ThermalEfficiency); + + // Adjust thermal energy in transferred gas mixtures. + if (hotA) + { + // A -> B + airA.Temperature -= transfer / cA; + airB.Temperature += outTransfer / cB; + } + else + { + // B -> A + airA.Temperature += outTransfer / cA; + airB.Temperature -= transfer / cB; + } + } + + component.LastGeneration = electricalEnergy; + + // Turn energy (at atmos tick rate) into wattage. + var power = electricalEnergy * _atmosphere.AtmosTickRate; + // Add ramp factor. This magics slight power into existence, but allows us to ramp up. + supplier.MaxSupply = power * component.RampFactor; + + var circAComp = Comp(circA); + var circBComp = Comp(circB); + + circAComp.LastPressureDelta = δpA; + circAComp.LastMolesTransferred = airA.TotalMoles; + circBComp.LastPressureDelta = δpB; + circBComp.LastMolesTransferred = airB.TotalMoles; + + _atmosphere.Merge(outletA.Air, airA); + _atmosphere.Merge(outletB.Air, airB); + + UpdateAppearance(uid, component, powerReceiver, tegGroup); + } + + private void UpdateAppearance( + EntityUid uid, + TegGeneratorComponent component, + ApcPowerReceiverComponent powerReceiver, + TegNodeGroup nodeGroup) + { + int powerLevel; + if (powerReceiver.Powered) + { + powerLevel = ContentHelpers.RoundToLevels( + component.RampPosition - component.RampMinimum, + component.MaxVisualPower - component.RampMinimum, + 12); + } + else + { + powerLevel = 0; + } + + _ambientSound.SetAmbience(uid, powerLevel >= 1); + // TODO: Ok so this introduces popping which is a major shame big rip. + // _ambientSound.SetVolume(uid, MathHelper.Lerp(component.VolumeMin, component.VolumeMax, MathHelper.Clamp01(component.RampPosition / component.MaxVisualPower))); + + _appearance.SetData(uid, TegVisuals.PowerOutput, powerLevel); + + if (nodeGroup.IsFullyBuilt) + { + UpdateCirculatorAppearance(nodeGroup.CirculatorA!.Owner, powerReceiver.Powered); + UpdateCirculatorAppearance(nodeGroup.CirculatorB!.Owner, powerReceiver.Powered); + } + } + + [Access(typeof(TegNodeGroup))] + public void UpdateGeneratorConnectivity( + EntityUid uid, + TegNodeGroup group, + TegGeneratorComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + var powerReceiver = Comp(uid); + + powerReceiver.PowerDisabled = !group.IsFullyBuilt; + + UpdateAppearance(uid, component, powerReceiver, group); + } + + [Access(typeof(TegNodeGroup))] + public void UpdateCirculatorConnectivity( + EntityUid uid, + TegNodeGroup group, + TegCirculatorComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + // If the group IS fully built, the generator will update its circulators. + // Otherwise, make sure circulator is set to nothing. + if (!group.IsFullyBuilt) + { + UpdateCirculatorAppearance(uid, false); + } + } + + private void UpdateCirculatorAppearance(EntityUid uid, bool powered) + { + var circ = Comp(uid); + + TegCirculatorSpeed speed; + if (powered && circ.LastPressureDelta > 0 && circ.LastMolesTransferred > 0) + { + if (circ.LastPressureDelta > circ.VisualSpeedDelta) + speed = TegCirculatorSpeed.SpeedFast; + else + speed = TegCirculatorSpeed.SpeedSlow; + } + else + { + speed = TegCirculatorSpeed.SpeedStill; + } + + _appearance.SetData(uid, TegVisuals.CirculatorSpeed, speed); + _appearance.SetData(uid, TegVisuals.CirculatorPower, powered); + + if (TryComp(uid, out PointLightComponent? pointLight)) + { + _pointLight.SetEnabled(uid, powered, pointLight); + pointLight.Color = speed == TegCirculatorSpeed.SpeedFast ? circ.LightColorFast : circ.LightColorSlow; + } + } + + private void GeneratorPowerChange(EntityUid uid, TegGeneratorComponent component, ref PowerChangedEvent args) + { + var nodeGroup = GetNodeGroup(uid); + if (nodeGroup == null) + return; + + UpdateAppearance(uid, component, Comp(uid), nodeGroup); + } + + /// Null if the node group is not yet available. This can happen during initialization. + private TegNodeGroup? GetNodeGroup(EntityUid uidGenerator) + { + NodeContainerComponent? nodeContainer = null; + if (!_nodeContainerQuery.Resolve(uidGenerator, ref nodeContainer)) + return null; + + if (!nodeContainer.Nodes.TryGetValue(NodeNameTeg, out var tegNode)) + return null; + + if (tegNode.NodeGroup is not TegNodeGroup tegGroup) + return null; + + return tegGroup; + } + + private static (GasMixture, float δp) GetCirculatorAirTransfer(GasMixture airInlet, GasMixture airOutlet) + { + var n1 = airInlet.TotalMoles; + var n2 = airOutlet.TotalMoles; + var p1 = airInlet.Pressure; + var p2 = airOutlet.Pressure; + var V1 = airInlet.Volume; + var V2 = airOutlet.Volume; + var T1 = airInlet.Temperature; + var T2 = airOutlet.Temperature; + + var δp = p1 - p2; + + var denom = T1 * V2 + T2 * V1; + + if (δp > 0 && p1 > 0 && denom > 0) + { + var transferMoles = n1 - (n1 + n2) * T2 * V1 / denom; + return (airInlet.Remove(transferMoles), δp); + } + + return (new GasMixture(), δp); + } + + private (PipeNode inlet, PipeNode outlet) GetPipes(EntityUid uidCirculator) + { + var nodeContainer = _nodeContainerQuery.GetComponent(uidCirculator); + var inlet = (PipeNode) nodeContainer.Nodes[NodeNameInlet]; + var outlet = (PipeNode) nodeContainer.Nodes[NodeNameOutlet]; + + return (inlet, outlet); + } + + private void DeviceNetworkPacketReceived( + EntityUid uid, + TegGeneratorComponent component, + DeviceNetworkPacketEvent args) + { + if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)) + return; + + switch (cmd) + { + case DeviceNetworkCommandSyncData: + var group = GetNodeGroup(uid); + if (group is not { IsFullyBuilt: true }) + return; + + var supplier = Comp(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; + } + } + + private TegSensorData.Circulator GetCirculatorSensorData(EntityUid circulator) + { + var (inlet, outlet) = GetPipes(circulator); + + return new TegSensorData.Circulator( + inlet.Air.Pressure, + outlet.Air.Pressure, + inlet.Air.Temperature, + outlet.Air.Temperature); + } +} diff --git a/Content.Server/SensorMonitoring/BatterySensorComponent.cs b/Content.Server/SensorMonitoring/BatterySensorComponent.cs new file mode 100644 index 0000000000..c99573882c --- /dev/null +++ b/Content.Server/SensorMonitoring/BatterySensorComponent.cs @@ -0,0 +1,32 @@ +using Content.Server.Power.Components; + +namespace Content.Server.SensorMonitoring; + +/// +/// Enables a battery entity (such as an SMES) to be monitored via the sensor monitoring console. +/// +/// +/// The entity should also have a and . +/// +[RegisterComponent] +public sealed class BatterySensorComponent : Component +{ +} + +/// +/// Device network data sent by a . +/// +/// The current energy charge of the battery, in joules (J). +/// The maximum energy capacity of the battery, in joules (J). +/// The current amount of power being received by the battery, in watts (W). +/// The maximum amount of power that can be received by the battery, in watts (W). +/// The current amount of power being supplied by the battery, in watts (W). +/// The maximum amount of power that can be received by the battery, in watts (W). +public sealed record BatterySensorData( + float Charge, + float MaxCharge, + float Receiving, + float MaxReceiving, + float Supplying, + float MaxSupplying +); diff --git a/Content.Server/SensorMonitoring/BatterySensorSystem.cs b/Content.Server/SensorMonitoring/BatterySensorSystem.cs new file mode 100644 index 0000000000..20e990b14c --- /dev/null +++ b/Content.Server/SensorMonitoring/BatterySensorSystem.cs @@ -0,0 +1,45 @@ +using Content.Server.DeviceNetwork; +using Content.Server.DeviceNetwork.Systems; +using Content.Server.Power.Components; + +namespace Content.Server.SensorMonitoring; + +public sealed class BatterySensorSystem : EntitySystem +{ + public const string DeviceNetworkCommandSyncData = "bat_sync_data"; + + [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!; + + public override void Initialize() + { + SubscribeLocalEvent(PacketReceived); + } + + private void PacketReceived(EntityUid uid, BatterySensorComponent component, DeviceNetworkPacketEvent args) + { + if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)) + return; + + switch (cmd) + { + case DeviceNetworkCommandSyncData: + var battery = Comp(uid); + var netBattery = Comp(uid); + + var payload = new NetworkPayload + { + [DeviceNetworkConstants.Command] = DeviceNetworkCommandSyncData, + [DeviceNetworkCommandSyncData] = new BatterySensorData( + battery.Charge, + battery.MaxCharge, + netBattery.CurrentReceiving, + netBattery.MaxChargeRate, + netBattery.CurrentSupply, + netBattery.MaxSupply) + }; + + _deviceNetwork.QueuePacket(uid, args.SenderAddress, payload); + break; + } + } +} diff --git a/Content.Server/SensorMonitoring/SensorMonitoringConsoleComponent.cs b/Content.Server/SensorMonitoring/SensorMonitoringConsoleComponent.cs new file mode 100644 index 0000000000..a13eb34b91 --- /dev/null +++ b/Content.Server/SensorMonitoring/SensorMonitoringConsoleComponent.cs @@ -0,0 +1,64 @@ +using Content.Shared.SensorMonitoring; +using Robust.Server.Player; +using Robust.Shared.Collections; + +namespace Content.Server.SensorMonitoring; + +[RegisterComponent] +public sealed class SensorMonitoringConsoleComponent : Component +{ + /// + /// Used to assign network IDs for sensors and sensor streams. + /// + public int IdCounter; + + /// + /// If enabled, additional data streams are shown intended to only be visible for debugging. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("debug_streams")] + public bool DebugStreams = false; + + [ViewVariables(VVAccess.ReadWrite)] + public Dictionary Sensors = new(); + + [DataField("retentionTime")] + public TimeSpan RetentionTime = TimeSpan.FromMinutes(1); + + // UI update tracking stuff. + public HashSet InitialUIStateSent = new(); + public TimeSpan LastUIUpdate; + public ValueList RemovedSensors; + + public sealed class SensorData + { + [ViewVariables(VVAccess.ReadWrite)] + public int NetId; + + [ViewVariables(VVAccess.ReadWrite)] + public SensorDeviceType DeviceType; + + [ViewVariables(VVAccess.ReadWrite)] + public Dictionary Streams = new(); + } + + public sealed class SensorStream + { + [ViewVariables(VVAccess.ReadWrite)] + public int NetId; + + [ViewVariables(VVAccess.ReadWrite)] + public SensorUnit Unit; + + // Queue is a ring buffer internally, and we can still iterate over it. + // I don't wanna write a ring buffer myself, so this is pretty convenient! + [ViewVariables] + public Queue Samples = new(); + } + + public sealed class ViewingPlayer + { + + } +} + diff --git a/Content.Server/SensorMonitoring/SensorMonitoringConsoleSystem.UI.cs b/Content.Server/SensorMonitoring/SensorMonitoringConsoleSystem.UI.cs new file mode 100644 index 0000000000..e08e9fed6c --- /dev/null +++ b/Content.Server/SensorMonitoring/SensorMonitoringConsoleSystem.UI.cs @@ -0,0 +1,138 @@ +using Content.Server.DeviceNetwork.Components; +using Content.Shared.SensorMonitoring; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.Collections; +using ConsoleUIState = Content.Shared.SensorMonitoring.SensorMonitoringConsoleBoundInterfaceState; +using IncrementalUIState = Content.Shared.SensorMonitoring.SensorMonitoringIncrementalUpdate; + +namespace Content.Server.SensorMonitoring; + +public sealed partial class SensorMonitoringConsoleSystem +{ + private void InitUI() + { + SubscribeLocalEvent(ConsoleUIClosed); + } + + private void UpdateConsoleUI(EntityUid uid, SensorMonitoringConsoleComponent comp) + { + if (!_userInterface.TryGetUi(uid, SensorMonitoringConsoleUiKey.Key, out var ui)) + return; + + if (ui.SubscribedSessions.Count == 0) + return; + + ConsoleUIState? fullState = null; + SensorMonitoringIncrementalUpdate? incrementalUpdate = null; + + foreach (var session in ui.SubscribedSessions) + { + if (comp.InitialUIStateSent.Contains(session)) + { + incrementalUpdate ??= CalculateIncrementalUpdate(); + _userInterface.TrySendUiMessage(ui, incrementalUpdate, session); + } + else + { + fullState ??= CalculateFullState(); + UserInterfaceSystem.SetUiState(ui, fullState, session); + comp.InitialUIStateSent.Add(session); + } + } + + comp.LastUIUpdate = _gameTiming.CurTime; + comp.RemovedSensors.Clear(); + + ConsoleUIState CalculateFullState() + { + var sensors = new ValueList(); + var streams = new ValueList(); + + foreach (var (ent, data) in comp.Sensors) + { + streams.Clear(); + var name = MetaData(ent).EntityName; + var address = Comp(ent).Address; + + foreach (var (streamName, stream) in data.Streams) + { + streams.Add(new ConsoleUIState.SensorStream + { + NetId = stream.NetId, + Name = streamName, + Unit = stream.Unit, + Samples = stream.Samples.ToArray() + }); + } + + sensors.Add(new ConsoleUIState.SensorData + { + NetId = data.NetId, + Name = name, + Address = address, + DeviceType = data.DeviceType, + Streams = streams.ToArray() + }); + } + + return new ConsoleUIState + { + RetentionTime = comp.RetentionTime, + Sensors = sensors.ToArray() + }; + } + + SensorMonitoringIncrementalUpdate CalculateIncrementalUpdate() + { + var sensors = new ValueList(); + var streams = new ValueList(); + var samples = new ValueList(); + + foreach (var data in comp.Sensors.Values) + { + streams.Clear(); + + foreach (var stream in data.Streams.Values) + { + samples.Clear(); + foreach (var (sampleTime, value) in stream.Samples) + { + if (sampleTime >= comp.LastUIUpdate) + samples.Add(new SensorSample(sampleTime - comp.LastUIUpdate, value)); + } + + streams.Add(new IncrementalUIState.SensorStream + { + NetId = stream.NetId, + Unit = stream.Unit, + Samples = samples.ToArray() + }); + } + + sensors.Add(new IncrementalUIState.SensorData { NetId = data.NetId, Streams = streams.ToArray() }); + } + + return new IncrementalUIState + { + RelTime = comp.LastUIUpdate, + RemovedSensors = comp.RemovedSensors.ToArray(), + Sensors = sensors.ToArray(), + }; + } + } + + private static void ConsoleUIClosed( + EntityUid uid, + SensorMonitoringConsoleComponent component, + BoundUIClosedEvent args) + { + if (!args.UiKey.Equals(SensorMonitoringConsoleUiKey.Key)) + return; + + if (args.Session is not IPlayerSession player) + return; + + component.InitialUIStateSent.Remove(player); + } +} diff --git a/Content.Server/SensorMonitoring/SensorMonitoringConsoleSystem.cs b/Content.Server/SensorMonitoring/SensorMonitoringConsoleSystem.cs new file mode 100644 index 0000000000..fdd340ba0a --- /dev/null +++ b/Content.Server/SensorMonitoring/SensorMonitoringConsoleSystem.cs @@ -0,0 +1,329 @@ +using Content.Server.Atmos.Monitor.Components; +using Content.Server.Atmos.Monitor.Systems; +using Content.Server.Atmos.Piping.Binary.Components; +using Content.Server.Atmos.Piping.Components; +using Content.Server.Atmos.Piping.Unary.Components; +using Content.Server.DeviceNetwork; +using Content.Server.DeviceNetwork.Components; +using Content.Server.DeviceNetwork.Systems; +using Content.Server.Power.Generation.Teg; +using Content.Shared.Atmos.Monitor; +using Content.Shared.Atmos.Piping.Binary.Components; +using Content.Shared.Atmos.Piping.Unary.Components; +using Content.Shared.DeviceNetwork.Components; +using Content.Shared.DeviceNetwork.Systems; +using Content.Shared.SensorMonitoring; +using Robust.Server.GameObjects; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using ConsoleUIState = Content.Shared.SensorMonitoring.SensorMonitoringConsoleBoundInterfaceState; + +namespace Content.Server.SensorMonitoring; + +public sealed partial class SensorMonitoringConsoleSystem : EntitySystem +{ + // TODO: THIS THING IS HEAVILY WIP AND NOT READY FOR GENERAL USE BY PLAYERS. + // Some of the issues, off the top of my head: + // Way too huge network load when opened + // UI doesn't update properly in cases like adding new streams/devices + // Deleting connected devices causes exceptions + // UI sucks. need a way to make basic dashboards like Grafana, and save them. + + private EntityQuery _deviceNetworkQuery; + + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!; + [Dependency] private readonly UserInterfaceSystem _userInterface = default!; + + public override void Initialize() + { + base.Initialize(); + + InitUI(); + + UpdatesBefore.Add(typeof(UserInterfaceSystem)); + + SubscribeLocalEvent(DeviceListUpdated); + SubscribeLocalEvent(ConsoleStartup); + SubscribeLocalEvent(DevicePacketReceived); + SubscribeLocalEvent(AtmosUpdate); + + _deviceNetworkQuery = GetEntityQuery(); + } + + public override void Update(float frameTime) + { + var consoles = EntityQueryEnumerator(); + while (consoles.MoveNext(out var entityUid, out var comp)) + { + UpdateConsole(entityUid, comp); + } + } + + private void UpdateConsole(EntityUid uid, SensorMonitoringConsoleComponent comp) + { + var minTime = _gameTiming.CurTime - comp.RetentionTime; + + SensorUpdate(uid, comp); + + foreach (var data in comp.Sensors.Values) + { + // Cull old data. + foreach (var stream in data.Streams.Values) + { + while (stream.Samples.TryPeek(out var sample) && sample.Time < minTime) + { + stream.Samples.Dequeue(); + } + } + } + + UpdateConsoleUI(uid, comp); + } + + private void ConsoleStartup(EntityUid uid, SensorMonitoringConsoleComponent component, ComponentStartup args) + { + if (TryComp(uid, out DeviceListComponent? network)) + UpdateDevices(uid, component, network.Devices, Array.Empty()); + } + + private void DeviceListUpdated( + EntityUid uid, + SensorMonitoringConsoleComponent component, + DeviceListUpdateEvent args) + { + UpdateDevices(uid, component, args.Devices, args.OldDevices); + } + + private void UpdateDevices( + EntityUid uid, + SensorMonitoringConsoleComponent component, + IEnumerable newDevices, + IEnumerable oldDevices) + { + var kept = new HashSet(); + + foreach (var newDevice in newDevices) + { + var deviceType = DetectDeviceType(newDevice); + if (deviceType == SensorDeviceType.Unknown) + continue; + + kept.Add(newDevice); + var sensor = component.Sensors.GetOrNew(newDevice); + sensor.DeviceType = deviceType; + if (sensor.NetId == 0) + sensor.NetId = MakeNetId(component); + } + + foreach (var oldDevice in oldDevices) + { + if (kept.Contains(oldDevice)) + continue; + + if (component.Sensors.TryGetValue(oldDevice, out var sensorData)) + { + component.RemovedSensors.Add(sensorData.NetId); + component.Sensors.Remove(oldDevice); + } + } + } + + private SensorDeviceType DetectDeviceType(EntityUid entity) + { + if (HasComp(entity)) + return SensorDeviceType.Teg; + + if (HasComp(entity)) + return SensorDeviceType.AtmosSensor; + + if (HasComp(entity)) + return SensorDeviceType.ThermoMachine; + + if (HasComp(entity)) + return SensorDeviceType.VolumePump; + + if (HasComp(entity)) + return SensorDeviceType.Battery; + + return SensorDeviceType.Unknown; + } + + private void DevicePacketReceived(EntityUid uid, SensorMonitoringConsoleComponent component, + DeviceNetworkPacketEvent args) + { + if (!component.Sensors.TryGetValue(args.Sender, out var sensorData)) + return; + + if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command)) + return; + + switch (sensorData.DeviceType) + { + case SensorDeviceType.Teg: + if (command != TegSystem.DeviceNetworkCommandSyncData) + return; + + if (!args.Data.TryGetValue(TegSystem.DeviceNetworkCommandSyncData, out TegSensorData? tegData)) + 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); + + 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); + + 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; + + case SensorDeviceType.AtmosSensor: + if (command != AtmosDeviceNetworkSystem.SyncData) + return; + + if (!args.Data.TryGetValue(AtmosDeviceNetworkSystem.SyncData, out AtmosSensorData? atmosData)) + return; + + // @formatter:off + WriteSample(component, sensorData, "atmo_pressure", SensorUnit.PressureKpa, atmosData.Pressure); + WriteSample(component, sensorData, "atmo_temperature", SensorUnit.TemperatureK, atmosData.Temperature); + // @formatter:on + break; + + case SensorDeviceType.ThermoMachine: + if (command != AtmosDeviceNetworkSystem.SyncData) + return; + + 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; + } + } + + private void WriteSample( + SensorMonitoringConsoleComponent component, + SensorMonitoringConsoleComponent.SensorData sensorData, + string streamName, + SensorUnit unit, + float value) + { + var stream = sensorData.Streams.GetOrNew(streamName); + stream.Unit = unit; + if (stream.NetId == 0) + stream.NetId = MakeNetId(component); + + var time = _gameTiming.CurTime; + stream.Samples.Enqueue(new SensorSample(time, value)); + } + + private static int MakeNetId(SensorMonitoringConsoleComponent component) + { + return ++component.IdCounter; + } + + private void AtmosUpdate( + EntityUid uid, + SensorMonitoringConsoleComponent comp, + AtmosDeviceUpdateEvent args) + { + foreach (var (ent, data) in comp.Sensors) + { + // Send network requests for new data! + NetworkPayload 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 + }; + break; + + default: + // Unknown device type, don't do anything. + continue; + } + + var address = _deviceNetworkQuery.GetComponent(ent); + _deviceNetwork.QueuePacket(uid, address.Address, payload); + } + } + + private void SensorUpdate(EntityUid uid, SensorMonitoringConsoleComponent comp) + { + foreach (var (ent, data) in comp.Sensors) + { + // Send network requests for new data! + NetworkPayload payload; + switch (data.DeviceType) + { + case SensorDeviceType.Battery: + payload = new NetworkPayload + { + [DeviceNetworkConstants.Command] = BatterySensorSystem.DeviceNetworkCommandSyncData + }; + break; + + default: + // Unknown device type, don't do anything. + continue; + } + + var address = _deviceNetworkQuery.GetComponent(ent); + _deviceNetwork.QueuePacket(uid, address.Address, payload); + } + } +} diff --git a/Content.Shared/Atmos/Piping/Binary/Components/SharedGasVolumePumpComponent.cs b/Content.Shared/Atmos/Piping/Binary/Components/SharedGasVolumePumpComponent.cs index a89a5a7ed8..d4315f86f5 100644 --- a/Content.Shared/Atmos/Piping/Binary/Components/SharedGasVolumePumpComponent.cs +++ b/Content.Shared/Atmos/Piping/Binary/Components/SharedGasVolumePumpComponent.cs @@ -2,6 +2,8 @@ namespace Content.Shared.Atmos.Piping.Binary.Components { + public sealed record GasVolumePumpData(float LastMolesTransferred); + [Serializable, NetSerializable] public enum GasVolumePumpUiKey { diff --git a/Content.Shared/Atmos/Piping/Unary/Components/SharedGasThermomachineComponent.cs b/Content.Shared/Atmos/Piping/Unary/Components/SharedGasThermomachineComponent.cs index d48a113f39..4bdd6a0e5f 100644 --- a/Content.Shared/Atmos/Piping/Unary/Components/SharedGasThermomachineComponent.cs +++ b/Content.Shared/Atmos/Piping/Unary/Components/SharedGasThermomachineComponent.cs @@ -2,6 +2,9 @@ namespace Content.Shared.Atmos.Piping.Unary.Components; +[Serializable, NetSerializable] +public sealed record GasThermoMachineData(float EnergyDelta); + [Serializable] [NetSerializable] public enum ThermomachineUiKey diff --git a/Content.Shared/Power/Generation/Teg/SharedTeg.cs b/Content.Shared/Power/Generation/Teg/SharedTeg.cs new file mode 100644 index 0000000000..b534ff1fc3 --- /dev/null +++ b/Content.Shared/Power/Generation/Teg/SharedTeg.cs @@ -0,0 +1,36 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Power.Generation.Teg; + +/// +/// Appearance keys for the TEG & its circulators. +/// +[Serializable, NetSerializable] +public enum TegVisuals +{ + PowerOutput, + CirculatorSpeed, + CirculatorPower, +} + +/// +/// Visual sprite layers for the TEG & its circulators. +/// +[Serializable, NetSerializable] +public enum TegVisualLayers +{ + PowerOutput, + CirculatorBase, + CirculatorLight +} + +/// +/// Visual speed levels for the TEG circulators. +/// +[Serializable, NetSerializable] +public enum TegCirculatorSpeed +{ + SpeedStill, + SpeedSlow, + SpeedFast +} diff --git a/Content.Shared/SensorMonitoring/SharedSensorMonitoringConsoleComponent.cs b/Content.Shared/SensorMonitoring/SharedSensorMonitoringConsoleComponent.cs new file mode 100644 index 0000000000..472697d974 --- /dev/null +++ b/Content.Shared/SensorMonitoring/SharedSensorMonitoringConsoleComponent.cs @@ -0,0 +1,112 @@ +using Robust.Shared.Serialization; +using ConsoleUIState = Content.Shared.SensorMonitoring.SensorMonitoringConsoleBoundInterfaceState; + +namespace Content.Shared.SensorMonitoring; + +[Serializable, NetSerializable] +public sealed class SensorMonitoringConsoleBoundInterfaceState : BoundUserInterfaceState +{ + public TimeSpan RetentionTime; + public SensorData[] Sensors = Array.Empty(); + + [Serializable, NetSerializable] + public sealed class SensorData + { + public int NetId; + public string Name = ""; + public string Address = ""; + public SensorDeviceType DeviceType; + + public SensorStream[] Streams = Array.Empty(); + } + + [Serializable, NetSerializable] + public sealed class SensorStream + { + public int NetId; + public string Name = ""; + public SensorUnit Unit; + public SensorSample[] Samples = Array.Empty(); + } +} + +[Serializable, NetSerializable] +public sealed class SensorMonitoringIncrementalUpdate : BoundUserInterfaceMessage +{ + public TimeSpan RelTime; + public SensorData[] Sensors = Array.Empty(); + public int[] RemovedSensors = Array.Empty(); + + [Serializable, NetSerializable] + public sealed class SensorData + { + public int NetId; + public SensorStream[] Streams = Array.Empty(); + } + + [Serializable, NetSerializable] + public sealed class SensorStream + { + public int NetId; + public SensorUnit Unit; + // Note: these samples have their time values relative to RelTime. + // This improves effectiveness of integer compression in NetSerializer. + public SensorSample[] Samples = Array.Empty(); + } +} + +[Serializable, NetSerializable] +public enum SensorMonitoringConsoleUiKey +{ + Key +} + +[Serializable, NetSerializable] +public enum SensorUnit : byte +{ + Undetermined = 0, + + /// + /// A pressure value in kilopascals (kPa). + /// + PressureKpa, + + /// + /// A temperature value in Kelvin (K). + /// + TemperatureK, + + /// + /// An amount of matter in moles. + /// + Moles, + + /// + /// A value in the range 0-1. + /// + /* L + */ Ratio, + + /// + /// Power in Watts (W). + /// + PowerW, + + /// + /// Energy in Joules (J). + /// + EnergyJ +} + +[Serializable, NetSerializable] +public enum SensorDeviceType +{ + Unknown = 0, + Teg, + AtmosSensor, + ThermoMachine, + VolumePump, + Battery, +} + +[Serializable, NetSerializable] +public record struct SensorSample(TimeSpan Time, float Value); diff --git a/Resources/Locale/en-US/devices/device-network.ftl b/Resources/Locale/en-US/devices/device-network.ftl index 9d8a9e91b8..8ce90bb237 100644 --- a/Resources/Locale/en-US/devices/device-network.ftl +++ b/Resources/Locale/en-US/devices/device-network.ftl @@ -24,12 +24,20 @@ device-frequency-prototype-name-surveillance-camera-entertainment = Entertainmen device-address-prefix-vent = VNT- device-address-prefix-scrubber = SCR- device-address-prefix-sensor = SNS- +# Damn bet you couldn't see this one coming. +device-address-prefix-teg = TEG- +device-address-prefix-heater = HTR- +device-address-prefix-freezer = FZR- +device-address-prefix-volume-pump = VPP- +device-address-prefix-smes = SMS- #PDAs and terminals device-address-prefix-console = CLS- device-address-prefix-fire-alarm = FIR- device-address-prefix-air-alarm = AIR- +device-address-prefix-sensor-monitor = MON- + device-address-examine-message = The device's address is {$address}. #Device net ID names @@ -39,3 +47,4 @@ device-net-id-wireless = Wireless device-net-id-apc = Apc device-net-id-atmos-devices = Atmos Devices device-net-id-reserved = Reserved + diff --git a/Resources/Locale/en-US/guidebook/guides.ftl b/Resources/Locale/en-US/guidebook/guides.ftl index 7e2d50b13a..b6619d49f2 100644 --- a/Resources/Locale/en-US/guidebook/guides.ftl +++ b/Resources/Locale/en-US/guidebook/guides.ftl @@ -9,6 +9,7 @@ guide-entry-network-configurator = Network Configurator guide-entry-power = Power guide-entry-ame = Antimatter Engine (AME) guide-entry-singularity = Singularity +guide-entry-teg = Thermo-electric Generator (TEG) guide-entry-controls = Controls guide-entry-radio = Radio guide-entry-jobs = Jobs diff --git a/Resources/Locale/en-US/power/teg.ftl b/Resources/Locale/en-US/power/teg.ftl new file mode 100644 index 0000000000..6c34781977 --- /dev/null +++ b/Resources/Locale/en-US/power/teg.ftl @@ -0,0 +1,2 @@ +teg-generator-examine-power = It's generating [color=yellow]{ POWERWATTS($power) }[/color]. +teg-generator-examine-connection = To function, a [color=white]circulator[/color] must be attached on both sides. diff --git a/Resources/Locale/en-US/sensor-monitoring/sensor-monitoring.ftl b/Resources/Locale/en-US/sensor-monitoring/sensor-monitoring.ftl new file mode 100644 index 0000000000..3450c1be3e --- /dev/null +++ b/Resources/Locale/en-US/sensor-monitoring/sensor-monitoring.ftl @@ -0,0 +1,13 @@ +sensor-monitoring-window-title = Sensor Monitoring Console + +sensor-monitoring-value-display = {$unit -> + [PressureKpa] { PRESSURE($value) } + [PowerW] { POWERWATTS($value) } + [EnergyJ] { POWERJOULES($value) } + [TemperatureK] { TOSTRING($value, "N3") } K + [Ratio] { NATURALPERCENT($value) } + [Moles] { TOSTRING($value, "N3") } mol + *[Other] { $value } +} + +# ({ TOSTRING(SUB($value, 273.15), "N3") } °C) diff --git a/Resources/Maps/Test/test_teg.yml b/Resources/Maps/Test/test_teg.yml new file mode 100644 index 0000000000..11dd0de1c6 --- /dev/null +++ b/Resources/Maps/Test/test_teg.yml @@ -0,0 +1,2896 @@ +meta: + format: 5 + postmapinit: false +tilemap: + 0: Space + 69: FloorSteel + 80: FloorTechMaint2 + 94: Lattice + 95: Plating +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + - type: Transform + - type: Map + - type: PhysicsMap + - type: GridTree + - type: MovedGrids + - type: Broadphase + - type: OccluderTree + - type: LoadedMap + - uid: 2 + components: + - type: MetaData + - pos: -3.0649385,-1.2334023 + parent: 1 + type: Transform + - chunks: + 0,0: + ind: 0,0 + tiles: XwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAAAAAABeAAAAAAAAAAAAAAAAAAAAAAAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAAAAAAAAXgAAAAAAAAAAAAAAAAAAAAAAAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAAAAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAAAAAABeAAAAAAAAAAAAAAAAAAAAAAAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAAAAAAAAXgAAAAAAAAAAAAAAAAAAAAAAAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABFAAAAXwAAAF8AAABfAAAAXgAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAXwAAAF8AAABfAAAAXwAAAEUAAABFAAAARQAAAEUAAABFAAAAXwAAAAAAAABeAAAAAAAAAAAAAAAAAAAAAAAAAF8AAABfAAAAXwAAAEUAAABFAAAARQAAAEUAAABFAAAARQAAAF8AAAAAAAAAXgAAAAAAAAAAAAAAAAAAAAAAAABQAAAAUAAAAFAAAABfAAAARQAAAEUAAABFAAAARQAAAEUAAABfAAAAAAAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF4AAABeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAAAAAAAAXgAAAAAAAAAAAAAAAAAAAAAAAABeAAAAXgAAAF4AAABeAAAAXgAAAF4AAABeAAAAXgAAAF4AAABeAAAAXgAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAABeAAAAXgAAAF4AAABeAAAAXgAAAF4AAABeAAAAXgAAAF4AAABeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAAAAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAF4AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAAAAAAAAXwAAAF8AAABfAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAAAAAAF8AAABfAAAAXwAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAAAAAABfAAAAXwAAAF8AAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAABeAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAAAAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAA== + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAAAAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAAAAAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAAAAAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAAAAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAAAAAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAABeAAAAXwAAAFAAAABQAAAAXwAAAFAAAABQAAAAXwAAAF8AAABfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAAAAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAAAAAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAAAAAAAAXwAAAFAAAABQAAAAUAAAAFAAAABQAAAAXwAAAFAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAXgAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAEUAAABfAAAAXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAAAAAABeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAABeAAAAXgAAAF4AAABeAAAAXgAAAF4AAABeAAAAXgAAAF4AAABeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAXgAAAF4AAABeAAAAXgAAAF4AAABeAAAAXgAAAF4AAABeAAAAXgAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAAAAAABeAAAAAAAAAAAAAAAAAAAAAAAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABeAAAAXgAAAAAAAAAAAAAAAAAAAAAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAXwAAAF8AAABfAAAAAAAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAF8AAABfAAAAXwAAAAAAAABeAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABfAAAAXwAAAF8AAAAAAAAAXgAAAAAAAAAAAAAAAAAAAAAAAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXgAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAAAAAABeAAAAAAAAAAAAAAAAAAAAAAAAAA== + type: MapGrid + - type: Broadphase + - bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + type: Physics + - fixtures: {} + type: Fixtures + - type: OccluderTree + - type: SpreaderGrid + - type: Shuttle + - type: GridPathfinding + - gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + type: Gravity + - chunkCollection: + version: 2 + nodes: + - node: + color: '#639EEB93' + id: BotGreyscale + decals: + 38: 4,-5 + 39: 4,-4 + 40: 4,-3 + 41: 5,-3 + 42: 5,-4 + 43: 5,-5 + 44: 6,-5 + 45: 6,-4 + 46: 6,-3 + - node: + color: '#9BE59D93' + id: BotGreyscale + decals: + 11: -4,-3 + 12: -4,-4 + 13: -4,-5 + 14: -5,-5 + 15: -5,-4 + 16: -5,-3 + 17: -6,-3 + 18: -6,-4 + 19: -6,-5 + - node: + color: '#FB2F2B93' + id: BotGreyscale + decals: + 29: 1,-3 + 30: 1,-4 + 31: 1,-5 + 32: 2,-5 + 33: 2,-4 + 34: 2,-3 + 35: 3,-3 + 36: 3,-4 + 37: 3,-5 + - node: + color: '#FBB22B93' + id: BotGreyscale + decals: + 20: -3,-5 + 21: -3,-4 + 22: -3,-3 + 23: -2,-3 + 24: -2,-4 + 25: -2,-5 + 26: -1,-5 + 27: -1,-4 + 28: -1,-3 + - node: + color: '#EFB34196' + id: HalfTileOverlayGreyscale + decals: + 5: 5,8 + 6: 6,8 + 7: 7,8 + - node: + color: '#EFB34196' + id: HalfTileOverlayGreyscale180 + decals: + 8: 7,6 + 9: 6,6 + 10: 5,6 + - node: + color: '#EFB34196' + id: HalfTileOverlayGreyscale90 + decals: + 4: 8,7 + - node: + color: '#EFB34196' + id: ThreeQuarterTileOverlayGreyscale + decals: + 0: 4,8 + - node: + color: '#EFB34196' + id: ThreeQuarterTileOverlayGreyscale180 + decals: + 2: 8,6 + - node: + color: '#EFB34196' + id: ThreeQuarterTileOverlayGreyscale270 + decals: + 3: 4,6 + - node: + color: '#EFB34196' + id: ThreeQuarterTileOverlayGreyscale90 + decals: + 1: 8,8 + type: DecalGrid + - version: 2 + data: + tiles: + 0,0: + 0: 65535 + -1,-1: + 0: 65535 + -1,0: + 0: 65535 + 0,-1: + 0: 65535 + 0,1: + 0: 65535 + 0,2: + 0: 63743 + 1,0: + 0: 65535 + 1,1: + 0: 65535 + 1,2: + 0: 61695 + 2,0: + 0: 48059 + 2,1: + 0: 48123 + 2,2: + 0: 64251 + -3,-2: + 0: 44718 + -3,-1: + 0: 44714 + -2,-2: + 0: 65295 + -2,-1: + 0: 65535 + -1,-2: + 0: 65327 + -3,0: + 0: 43690 + -3,1: + 0: 43754 + -3,2: + 0: 60138 + -2,0: + 0: 65535 + -2,1: + 0: 65535 + -2,2: + 0: 61695 + -1,1: + 0: 65535 + -1,2: + 0: 62207 + 0,-2: + 0: 65423 + 1,-2: + 0: 65295 + 1,-1: + 0: 65535 + 2,-2: + 0: 49071 + 2,-1: + 0: 49083 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + type: GridAtmosphere + - type: GasTileOverlay + - type: RadiationGridResistance + - id: TEG + type: BecomesStation +- proto: AirAlarm + entities: + - uid: 436 + components: + - pos: -2.5,5.5 + parent: 2 + type: Transform + - ShutdownSubscribers: + - 437 + type: DeviceNetwork + - devices: + - 437 + type: DeviceList +- proto: AirlockEngineeringGlass + entities: + - uid: 128 + components: + - pos: 6.5,5.5 + parent: 2 + type: Transform + - uid: 143 + components: + - pos: 3.5,7.5 + parent: 2 + type: Transform +- proto: AirSensor + entities: + - uid: 437 + components: + - pos: -5.5,1.5 + parent: 2 + type: Transform + - ShutdownSubscribers: + - 436 + type: DeviceNetwork +- proto: Bed + entities: + - uid: 371 + components: + - pos: 8.5,8.5 + parent: 2 + type: Transform +- proto: BedsheetCE + entities: + - uid: 372 + components: + - pos: 8.5,8.5 + parent: 2 + type: Transform +- proto: CableApcExtension + entities: + - uid: 246 + components: + - pos: -7.5,7.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 247 + components: + - pos: -7.5,8.5 + parent: 2 + type: Transform + - uid: 248 + components: + - pos: -7.5,6.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 249 + components: + - pos: -7.5,5.5 + parent: 2 + type: Transform + - uid: 250 + components: + - pos: -7.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 251 + components: + - pos: -7.5,3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 252 + components: + - pos: -7.5,2.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 253 + components: + - pos: -7.5,1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 254 + components: + - pos: -7.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 255 + components: + - pos: -7.5,-0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 256 + components: + - pos: -7.5,-1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 257 + components: + - pos: -7.5,-2.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 258 + components: + - pos: -7.5,-3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 259 + components: + - pos: -7.5,-4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 260 + components: + - pos: -6.5,-4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 261 + components: + - pos: -5.5,-4.5 + parent: 2 + type: Transform + - uid: 262 + components: + - pos: -4.5,-4.5 + parent: 2 + type: Transform + - uid: 263 + components: + - pos: -3.5,-4.5 + parent: 2 + type: Transform + - uid: 264 + components: + - pos: -2.5,-4.5 + parent: 2 + type: Transform + - uid: 265 + components: + - pos: -1.5,-4.5 + parent: 2 + type: Transform + - uid: 266 + components: + - pos: -0.5,-4.5 + parent: 2 + type: Transform + - uid: 267 + components: + - pos: 0.5,-4.5 + parent: 2 + type: Transform + - uid: 268 + components: + - pos: 1.5,-4.5 + parent: 2 + type: Transform + - uid: 269 + components: + - pos: 2.5,-4.5 + parent: 2 + type: Transform + - uid: 270 + components: + - pos: 3.5,-4.5 + parent: 2 + type: Transform + - uid: 271 + components: + - pos: 4.5,-4.5 + parent: 2 + type: Transform + - uid: 272 + components: + - pos: 5.5,-4.5 + parent: 2 + type: Transform + - uid: 273 + components: + - pos: 6.5,-4.5 + parent: 2 + type: Transform + - uid: 274 + components: + - pos: 7.5,-4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 275 + components: + - pos: 8.5,-4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 276 + components: + - pos: 8.5,-3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 277 + components: + - pos: 8.5,-2.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 278 + components: + - pos: 8.5,-1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 279 + components: + - pos: 8.5,-0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 280 + components: + - pos: 8.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 281 + components: + - pos: 8.5,1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 282 + components: + - pos: 8.5,2.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 283 + components: + - pos: 8.5,3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 284 + components: + - pos: 8.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 285 + components: + - pos: 8.5,5.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 286 + components: + - pos: 8.5,6.5 + parent: 2 + type: Transform + - uid: 287 + components: + - pos: 8.5,7.5 + parent: 2 + type: Transform + - uid: 288 + components: + - pos: 8.5,8.5 + parent: 2 + type: Transform + - uid: 289 + components: + - pos: 7.5,8.5 + parent: 2 + type: Transform + - uid: 290 + components: + - pos: 6.5,8.5 + parent: 2 + type: Transform + - uid: 291 + components: + - pos: 5.5,8.5 + parent: 2 + type: Transform + - uid: 292 + components: + - pos: 4.5,8.5 + parent: 2 + type: Transform + - uid: 293 + components: + - pos: 3.5,8.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 294 + components: + - pos: 2.5,8.5 + parent: 2 + type: Transform + - uid: 295 + components: + - pos: 1.5,8.5 + parent: 2 + type: Transform + - uid: 296 + components: + - pos: 0.5,8.5 + parent: 2 + type: Transform + - uid: 297 + components: + - pos: -0.5,8.5 + parent: 2 + type: Transform + - uid: 298 + components: + - pos: -1.5,8.5 + parent: 2 + type: Transform + - uid: 299 + components: + - pos: -2.5,8.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 300 + components: + - pos: -3.5,8.5 + parent: 2 + type: Transform + - uid: 301 + components: + - pos: -4.5,8.5 + parent: 2 + type: Transform + - uid: 302 + components: + - pos: -5.5,8.5 + parent: 2 + type: Transform + - uid: 303 + components: + - pos: -6.5,8.5 + parent: 2 + type: Transform + - uid: 380 + components: + - pos: -0.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 381 + components: + - pos: -0.5,7.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 382 + components: + - pos: -0.5,5.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 392 + components: + - pos: -0.5,6.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 393 + components: + - pos: -1.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 398 + components: + - pos: -2.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 399 + components: + - pos: -3.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 400 + components: + - pos: 0.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 401 + components: + - pos: 1.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 402 + components: + - pos: 2.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 403 + components: + - pos: 3.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 404 + components: + - pos: 4.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 405 + components: + - pos: 5.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 406 + components: + - pos: 6.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 407 + components: + - pos: 7.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 408 + components: + - pos: -4.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 409 + components: + - pos: -5.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 410 + components: + - pos: -6.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 411 + components: + - pos: 0.5,3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 412 + components: + - pos: 0.5,2.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 413 + components: + - pos: 0.5,1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 414 + components: + - pos: 0.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 415 + components: + - pos: 0.5,-0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 416 + components: + - pos: 0.5,-1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 417 + components: + - pos: -0.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 418 + components: + - pos: -1.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 419 + components: + - pos: -2.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 420 + components: + - pos: -3.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 421 + components: + - pos: -4.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 422 + components: + - pos: -5.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 423 + components: + - pos: -6.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 424 + components: + - pos: 1.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 425 + components: + - pos: 2.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 426 + components: + - pos: 3.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 427 + components: + - pos: 4.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 428 + components: + - pos: 5.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 429 + components: + - pos: 6.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 430 + components: + - pos: 7.5,0.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound +- proto: CableApcStack + entities: + - uid: 385 + components: + - pos: -3.4853406,5.751219 + parent: 2 + type: Transform +- proto: CableHV + entities: + - uid: 9 + components: + - pos: 0.5,2.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 10 + components: + - pos: 0.5,3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 11 + components: + - pos: 0.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 32 + components: + - pos: 0.5,8.5 + parent: 2 + type: Transform + - uid: 33 + components: + - pos: -0.5,8.5 + parent: 2 + type: Transform + - uid: 101 + components: + - pos: 1.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 137 + components: + - pos: 2.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 138 + components: + - pos: 3.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 139 + components: + - pos: 4.5,4.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 140 + components: + - pos: 4.5,5.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 142 + components: + - pos: 4.5,6.5 + parent: 2 + type: Transform + - uid: 435 + components: + - pos: 2.5,5.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound +- proto: CableHVStack + entities: + - uid: 386 + components: + - pos: -3.5165906,5.532316 + parent: 2 + type: Transform +- proto: CableMV + entities: + - uid: 36 + components: + - pos: -0.5,8.5 + parent: 2 + type: Transform + - uid: 37 + components: + - pos: -1.5,8.5 + parent: 2 + type: Transform +- proto: CableMVStack + entities: + - uid: 384 + components: + - pos: -3.4853406,5.3029904 + parent: 2 + type: Transform +- proto: CableTerminal + entities: + - uid: 30 + components: + - rot: 3.141592653589793 rad + pos: 0.5,4.5 + parent: 2 + type: Transform +- proto: Catwalk + entities: + - uid: 53 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,6.5 + parent: 2 + type: Transform + - uid: 54 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,5.5 + parent: 2 + type: Transform + - uid: 55 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,6.5 + parent: 2 + type: Transform + - uid: 56 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,4.5 + parent: 2 + type: Transform + - uid: 59 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,-0.5 + parent: 2 + type: Transform + - uid: 60 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,6.5 + parent: 2 + type: Transform + - uid: 61 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,0.5 + parent: 2 + type: Transform + - uid: 62 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,1.5 + parent: 2 + type: Transform + - uid: 86 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,3.5 + parent: 2 + type: Transform + - uid: 87 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,4.5 + parent: 2 + type: Transform + - uid: 88 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,-1.5 + parent: 2 + type: Transform + - uid: 102 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,5.5 + parent: 2 + type: Transform + - uid: 134 + components: + - pos: -1.5,4.5 + parent: 2 + type: Transform + - uid: 136 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,4.5 + parent: 2 + type: Transform + - uid: 241 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,7.5 + parent: 2 + type: Transform + - uid: 242 + components: + - rot: 1.5707963267948966 rad + pos: -0.5,7.5 + parent: 2 + type: Transform + - uid: 243 + components: + - rot: 1.5707963267948966 rad + pos: 0.5,7.5 + parent: 2 + type: Transform + - uid: 244 + components: + - rot: 1.5707963267948966 rad + pos: 1.5,7.5 + parent: 2 + type: Transform + - uid: 245 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,7.5 + parent: 2 + type: Transform + - uid: 317 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,6.5 + parent: 2 + type: Transform + - uid: 318 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,6.5 + parent: 2 + type: Transform + - uid: 319 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,6.5 + parent: 2 + type: Transform + - uid: 320 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,7.5 + parent: 2 + type: Transform + - uid: 321 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,7.5 + parent: 2 + type: Transform + - uid: 322 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,6.5 + parent: 2 + type: Transform + - uid: 323 + components: + - rot: 1.5707963267948966 rad + pos: -4.5,7.5 + parent: 2 + type: Transform + - uid: 324 + components: + - rot: 1.5707963267948966 rad + pos: -4.5,6.5 + parent: 2 + type: Transform + - uid: 325 + components: + - rot: 1.5707963267948966 rad + pos: -5.5,7.5 + parent: 2 + type: Transform + - uid: 326 + components: + - rot: 1.5707963267948966 rad + pos: -5.5,6.5 + parent: 2 + type: Transform + - uid: 327 + components: + - rot: 1.5707963267948966 rad + pos: -6.5,7.5 + parent: 2 + type: Transform + - uid: 328 + components: + - rot: 1.5707963267948966 rad + pos: -6.5,6.5 + parent: 2 + type: Transform + - uid: 329 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,7.5 + parent: 2 + type: Transform + - uid: 330 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,6.5 + parent: 2 + type: Transform + - uid: 337 + components: + - pos: -5.5,5.5 + parent: 2 + type: Transform + - uid: 338 + components: + - pos: -5.5,4.5 + parent: 2 + type: Transform + - uid: 339 + components: + - pos: -2.5,4.5 + parent: 2 + type: Transform + - uid: 340 + components: + - pos: -3.5,4.5 + parent: 2 + type: Transform + - uid: 341 + components: + - pos: -4.5,4.5 + parent: 2 + type: Transform + - uid: 342 + components: + - pos: 2.5,4.5 + parent: 2 + type: Transform + - uid: 343 + components: + - pos: 3.5,4.5 + parent: 2 + type: Transform + - uid: 344 + components: + - pos: 4.5,4.5 + parent: 2 + type: Transform + - uid: 345 + components: + - pos: 5.5,4.5 + parent: 2 + type: Transform + - uid: 346 + components: + - pos: 6.5,4.5 + parent: 2 + type: Transform + - uid: 347 + components: + - pos: -7.5,4.5 + parent: 2 + type: Transform + - uid: 348 + components: + - pos: -7.5,3.5 + parent: 2 + type: Transform + - uid: 349 + components: + - pos: -7.5,2.5 + parent: 2 + type: Transform + - uid: 350 + components: + - pos: -7.5,1.5 + parent: 2 + type: Transform + - uid: 351 + components: + - pos: -7.5,0.5 + parent: 2 + type: Transform + - uid: 352 + components: + - pos: -7.5,-0.5 + parent: 2 + type: Transform + - uid: 353 + components: + - pos: -7.5,-1.5 + parent: 2 + type: Transform + - uid: 354 + components: + - pos: -7.5,-2.5 + parent: 2 + type: Transform + - uid: 355 + components: + - pos: -7.5,-3.5 + parent: 2 + type: Transform + - uid: 356 + components: + - pos: -7.5,-4.5 + parent: 2 + type: Transform + - uid: 357 + components: + - pos: -6.5,-4.5 + parent: 2 + type: Transform + - uid: 358 + components: + - pos: -6.5,4.5 + parent: 2 + type: Transform + - uid: 359 + components: + - pos: 7.5,4.5 + parent: 2 + type: Transform + - uid: 360 + components: + - pos: 8.5,4.5 + parent: 2 + type: Transform + - uid: 361 + components: + - pos: 8.5,3.5 + parent: 2 + type: Transform + - uid: 362 + components: + - pos: 8.5,2.5 + parent: 2 + type: Transform + - uid: 363 + components: + - pos: 8.5,1.5 + parent: 2 + type: Transform + - uid: 364 + components: + - pos: 8.5,0.5 + parent: 2 + type: Transform + - uid: 365 + components: + - pos: 8.5,-0.5 + parent: 2 + type: Transform + - uid: 366 + components: + - pos: 8.5,-1.5 + parent: 2 + type: Transform + - uid: 367 + components: + - pos: 8.5,-2.5 + parent: 2 + type: Transform + - uid: 368 + components: + - pos: 8.5,-3.5 + parent: 2 + type: Transform + - uid: 369 + components: + - pos: 8.5,-4.5 + parent: 2 + type: Transform + - uid: 370 + components: + - pos: 7.5,-4.5 + parent: 2 + type: Transform +- proto: ClothingBeltChiefEngineerFilled + entities: + - uid: 332 + components: + - pos: -4.699811,5.342529 + parent: 2 + type: Transform + - uid: 333 + components: + - pos: -4.696474,5.7329693 + parent: 2 + type: Transform +- proto: ClothingHandsGlovesColorYellow + entities: + - uid: 237 + components: + - pos: -6.408145,5.6760936 + parent: 2 + type: Transform + - uid: 238 + components: + - pos: -6.491478,5.311256 + parent: 2 + type: Transform +- proto: ComputerPowerMonitoring + entities: + - uid: 141 + components: + - rot: 3.141592653589793 rad + pos: 4.5,6.5 + parent: 2 + type: Transform + - uid: 434 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,5.5 + parent: 2 + type: Transform +- proto: ComputerSensorMonitoring + entities: + - uid: 433 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,5.5 + parent: 2 + type: Transform + - debug_streams: True + type: SensorMonitoringConsole + - ShutdownSubscribers: + - 3 + - 6 + - 146 + - 7 + - 147 + - 29 + type: DeviceNetwork + - devices: + - 3 + - 6 + - 146 + - 7 + - 147 + - 29 + type: DeviceList +- proto: DebugAPC + entities: + - uid: 38 + components: + - pos: -1.5,8.5 + parent: 2 + type: Transform +- proto: DebugGenerator + entities: + - uid: 31 + components: + - pos: 0.5,8.5 + parent: 2 + type: Transform + - supplyRampTolerance: 500000 + supplyRate: 300000 + type: PowerSupplier +- proto: DebugSubstation + entities: + - uid: 34 + components: + - pos: -0.5,8.5 + parent: 2 + type: Transform +- proto: DrinkMugOne + entities: + - uid: 378 + components: + - pos: 8.710588,6.8378325 + parent: 2 + type: Transform +- proto: FoodBoxDonkpocket + entities: + - uid: 377 + components: + - pos: 8.656412,6.4768915 + parent: 2 + type: Transform +- proto: FoodBoxDonkpocketPizza + entities: + - uid: 376 + components: + - pos: 8.323078,6.8417277 + parent: 2 + type: Transform +- proto: GasAnalyzer + entities: + - uid: 431 + components: + - pos: -4.1251473,5.6412144 + parent: 2 + type: Transform +- proto: GasPipeBend + entities: + - uid: 8 + components: + - pos: -0.5,3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 12 + components: + - rot: 3.141592653589793 rad + pos: 1.5,1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 13 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 19 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 20 + components: + - rot: 3.141592653589793 rad + pos: -2.5,1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 21 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 23 + components: + - pos: 3.5,3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 24 + components: + - rot: 1.5707963267948966 rad + pos: 1.5,3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound +- proto: GasPipeFourway + entities: + - uid: 16 + components: + - pos: 4.5,2.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 18 + components: + - pos: -3.5,2.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound +- proto: GasPipeStraight + entities: + - uid: 22 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,3.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 25 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,1.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound +- proto: GasPipeTJunction + entities: + - uid: 14 + components: + - rot: 1.5707963267948966 rad + pos: 3.5,2.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound + - uid: 15 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,2.5 + parent: 2 + type: Transform + - enabled: True + type: AmbientSound +- proto: GasPort + entities: + - uid: 27 + components: + - rot: 3.141592653589793 rad + pos: -3.5,0.5 + parent: 2 + type: Transform + - uid: 28 + components: + - rot: 3.141592653589793 rad + pos: 4.5,0.5 + parent: 2 + type: Transform +- proto: GasThermoMachineFreezer + entities: + - uid: 147 + components: + - pos: 4.5,3.5 + parent: 2 + type: Transform + - targetTemperature: 73.15 + type: GasThermoMachine + - ShutdownSubscribers: + - 433 + type: DeviceNetwork +- proto: GasThermoMachineHeater + entities: + - uid: 146 + components: + - pos: -3.5,3.5 + parent: 2 + type: Transform + - targetTemperature: 593.15 + type: GasThermoMachine + - ShutdownSubscribers: + - 433 + type: DeviceNetwork +- proto: GasVolumePump + entities: + - uid: 6 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,3.5 + parent: 2 + type: Transform + - ShutdownSubscribers: + - 433 + type: DeviceNetwork + - uid: 7 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,1.5 + parent: 2 + type: Transform + - ShutdownSubscribers: + - 433 + type: DeviceNetwork + - uid: 17 + components: + - rot: 3.141592653589793 rad + pos: -3.5,1.5 + parent: 2 + type: Transform + - uid: 26 + components: + - rot: 3.141592653589793 rad + pos: 4.5,1.5 + parent: 2 + type: Transform +- proto: GravityGeneratorMini + entities: + - uid: 432 + components: + - pos: 2.5,8.5 + parent: 2 + type: Transform +- proto: Grille + entities: + - uid: 35 + components: + - pos: -1.5,9.5 + parent: 2 + type: Transform + - uid: 39 + components: + - pos: -0.5,9.5 + parent: 2 + type: Transform + - uid: 40 + components: + - pos: 0.5,9.5 + parent: 2 + type: Transform + - uid: 41 + components: + - pos: 2.5,9.5 + parent: 2 + type: Transform + - uid: 42 + components: + - pos: 1.5,9.5 + parent: 2 + type: Transform + - uid: 50 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,-5.5 + parent: 2 + type: Transform + - uid: 51 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,-5.5 + parent: 2 + type: Transform + - uid: 52 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,-5.5 + parent: 2 + type: Transform + - uid: 57 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,-5.5 + parent: 2 + type: Transform + - uid: 58 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,-5.5 + parent: 2 + type: Transform + - uid: 111 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,9.5 + parent: 2 + type: Transform + - uid: 113 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,9.5 + parent: 2 + type: Transform + - uid: 114 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,9.5 + parent: 2 + type: Transform + - uid: 115 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,9.5 + parent: 2 + type: Transform + - uid: 116 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,9.5 + parent: 2 + type: Transform + - uid: 123 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,6.5 + parent: 2 + type: Transform + - uid: 124 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,8.5 + parent: 2 + type: Transform + - uid: 126 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,5.5 + parent: 2 + type: Transform + - uid: 127 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,5.5 + parent: 2 + type: Transform + - uid: 130 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,5.5 + parent: 2 + type: Transform + - uid: 133 + components: + - pos: 7.5,5.5 + parent: 2 + type: Transform + - uid: 152 + components: + - pos: -7.5,9.5 + parent: 2 + type: Transform + - uid: 153 + components: + - pos: -8.5,8.5 + parent: 2 + type: Transform + - uid: 154 + components: + - pos: -6.5,9.5 + parent: 2 + type: Transform + - uid: 155 + components: + - pos: -5.5,9.5 + parent: 2 + type: Transform + - uid: 156 + components: + - pos: -4.5,9.5 + parent: 2 + type: Transform + - uid: 157 + components: + - pos: -3.5,9.5 + parent: 2 + type: Transform + - uid: 158 + components: + - pos: -8.5,7.5 + parent: 2 + type: Transform + - uid: 159 + components: + - pos: -8.5,6.5 + parent: 2 + type: Transform + - uid: 160 + components: + - pos: -8.5,4.5 + parent: 2 + type: Transform + - uid: 161 + components: + - pos: -8.5,3.5 + parent: 2 + type: Transform + - uid: 162 + components: + - pos: -8.5,2.5 + parent: 2 + type: Transform + - uid: 163 + components: + - pos: -8.5,-4.5 + parent: 2 + type: Transform + - uid: 164 + components: + - pos: -8.5,1.5 + parent: 2 + type: Transform + - uid: 165 + components: + - pos: -8.5,-3.5 + parent: 2 + type: Transform + - uid: 166 + components: + - pos: -8.5,-2.5 + parent: 2 + type: Transform + - uid: 167 + components: + - pos: -8.5,0.5 + parent: 2 + type: Transform + - uid: 168 + components: + - pos: -7.5,-5.5 + parent: 2 + type: Transform + - uid: 169 + components: + - pos: -8.5,-0.5 + parent: 2 + type: Transform + - uid: 170 + components: + - pos: -6.5,-5.5 + parent: 2 + type: Transform + - uid: 171 + components: + - pos: -5.5,-5.5 + parent: 2 + type: Transform + - uid: 172 + components: + - pos: -4.5,-5.5 + parent: 2 + type: Transform + - uid: 173 + components: + - pos: -3.5,-5.5 + parent: 2 + type: Transform + - uid: 174 + components: + - pos: 4.5,-5.5 + parent: 2 + type: Transform + - uid: 175 + components: + - pos: 5.5,-5.5 + parent: 2 + type: Transform + - uid: 176 + components: + - pos: 6.5,-5.5 + parent: 2 + type: Transform + - uid: 177 + components: + - pos: 7.5,-5.5 + parent: 2 + type: Transform + - uid: 178 + components: + - pos: 8.5,-5.5 + parent: 2 + type: Transform + - uid: 179 + components: + - pos: 9.5,-4.5 + parent: 2 + type: Transform + - uid: 180 + components: + - pos: 9.5,-3.5 + parent: 2 + type: Transform + - uid: 181 + components: + - pos: 9.5,-2.5 + parent: 2 + type: Transform + - uid: 182 + components: + - pos: 9.5,-0.5 + parent: 2 + type: Transform + - uid: 183 + components: + - pos: 9.5,0.5 + parent: 2 + type: Transform + - uid: 184 + components: + - pos: 9.5,1.5 + parent: 2 + type: Transform + - uid: 185 + components: + - pos: 9.5,2.5 + parent: 2 + type: Transform + - uid: 186 + components: + - pos: 9.5,3.5 + parent: 2 + type: Transform + - uid: 187 + components: + - pos: 9.5,4.5 + parent: 2 + type: Transform + - uid: 188 + components: + - pos: 9.5,6.5 + parent: 2 + type: Transform + - uid: 189 + components: + - pos: 9.5,7.5 + parent: 2 + type: Transform + - uid: 190 + components: + - pos: 9.5,8.5 + parent: 2 + type: Transform +- proto: KitchenMicrowave + entities: + - uid: 374 + components: + - pos: 8.5,7.5 + parent: 2 + type: Transform +- proto: LockerChiefEngineerFilledHardsuit + entities: + - uid: 396 + components: + - pos: -4.5,8.5 + parent: 2 + type: Transform +- proto: LockerElectricalSuppliesFilled + entities: + - uid: 397 + components: + - pos: -5.5,8.5 + parent: 2 + type: Transform +- proto: LockerWeldingSuppliesFilled + entities: + - uid: 236 + components: + - pos: -3.5,8.5 + parent: 2 + type: Transform +- proto: NitrogenCanister + entities: + - uid: 72 + components: + - pos: 3.5,-4.5 + parent: 2 + type: Transform + - uid: 73 + components: + - pos: 3.5,-3.5 + parent: 2 + type: Transform + - uid: 74 + components: + - pos: 3.5,-2.5 + parent: 2 + type: Transform + - uid: 79 + components: + - pos: 2.5,-4.5 + parent: 2 + type: Transform + - uid: 95 + components: + - pos: 2.5,-2.5 + parent: 2 + type: Transform + - uid: 96 + components: + - pos: 1.5,-3.5 + parent: 2 + type: Transform + - uid: 98 + components: + - pos: 2.5,-3.5 + parent: 2 + type: Transform + - uid: 99 + components: + - pos: 1.5,-2.5 + parent: 2 + type: Transform + - uid: 100 + components: + - pos: 1.5,-4.5 + parent: 2 + type: Transform +- proto: OxygenCanister + entities: + - uid: 68 + components: + - pos: 5.5,-2.5 + parent: 2 + type: Transform + - uid: 69 + components: + - pos: 5.5,-3.5 + parent: 2 + type: Transform + - uid: 70 + components: + - pos: 5.5,-4.5 + parent: 2 + type: Transform + - uid: 71 + components: + - pos: 4.5,-4.5 + parent: 2 + type: Transform + - uid: 75 + components: + - pos: 4.5,-2.5 + parent: 2 + type: Transform + - uid: 76 + components: + - pos: 4.5,-3.5 + parent: 2 + type: Transform + - uid: 91 + components: + - pos: 6.5,-2.5 + parent: 2 + type: Transform + - uid: 93 + components: + - pos: 6.5,-4.5 + parent: 2 + type: Transform + - uid: 94 + components: + - pos: 6.5,-3.5 + parent: 2 + type: Transform +- proto: PartRodMetal + entities: + - uid: 391 + components: + - pos: -6.820592,5.657404 + parent: 2 + type: Transform +- proto: PlasmaCanister + entities: + - uid: 77 + components: + - pos: -2.5,-3.5 + parent: 2 + type: Transform + - uid: 78 + components: + - pos: -2.5,-2.5 + parent: 2 + type: Transform + - uid: 80 + components: + - pos: -1.5,-2.5 + parent: 2 + type: Transform + - uid: 81 + components: + - pos: -0.5,-2.5 + parent: 2 + type: Transform + - uid: 82 + components: + - pos: -0.5,-3.5 + parent: 2 + type: Transform + - uid: 83 + components: + - pos: -0.5,-4.5 + parent: 2 + type: Transform + - uid: 84 + components: + - pos: -1.5,-4.5 + parent: 2 + type: Transform + - uid: 85 + components: + - pos: -1.5,-3.5 + parent: 2 + type: Transform + - uid: 97 + components: + - pos: -2.5,-4.5 + parent: 2 + type: Transform +- proto: PlushieSharkBlue + entities: + - uid: 379 + components: + - pos: 8.481326,8.370146 + parent: 2 + type: Transform +- proto: PoweredlightLED + entities: + - uid: 304 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,5.5 + parent: 2 + type: Transform + - enabled: False + type: AmbientSound + - uid: 305 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-1.5 + parent: 2 + type: Transform + - enabled: False + type: AmbientSound + - uid: 306 + components: + - rot: 3.141592653589793 rad + pos: -2.5,-4.5 + parent: 2 + type: Transform + - enabled: False + type: AmbientSound + - uid: 307 + components: + - rot: 3.141592653589793 rad + pos: 3.5,-4.5 + parent: 2 + type: Transform + - enabled: False + type: AmbientSound + - uid: 308 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,-1.5 + parent: 2 + type: Transform + - enabled: False + type: AmbientSound + - uid: 309 + components: + - pos: 3.5,4.5 + parent: 2 + type: Transform + - enabled: False + type: AmbientSound + - uid: 312 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,7.5 + parent: 2 + type: Transform + - enabled: False + type: AmbientSound + - uid: 313 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,3.5 + parent: 2 + type: Transform + - enabled: False + type: AmbientSound + - uid: 314 + components: + - pos: 0.5,8.5 + parent: 2 + type: Transform + - enabled: False + type: AmbientSound + - uid: 315 + components: + - pos: -2.5,4.5 + parent: 2 + type: Transform + - enabled: False + type: AmbientSound +- proto: Railing + entities: + - uid: 240 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,8.5 + parent: 2 + type: Transform +- proto: ReinforcedWindow + entities: + - uid: 43 + components: + - pos: 2.5,9.5 + parent: 2 + type: Transform + - uid: 44 + components: + - pos: -1.5,9.5 + parent: 2 + type: Transform + - uid: 45 + components: + - pos: -0.5,9.5 + parent: 2 + type: Transform + - uid: 46 + components: + - pos: 0.5,9.5 + parent: 2 + type: Transform + - uid: 47 + components: + - pos: 1.5,9.5 + parent: 2 + type: Transform + - uid: 104 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,-5.5 + parent: 2 + type: Transform + - uid: 105 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,-5.5 + parent: 2 + type: Transform + - uid: 106 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,-5.5 + parent: 2 + type: Transform + - uid: 107 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,-5.5 + parent: 2 + type: Transform + - uid: 108 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,-5.5 + parent: 2 + type: Transform + - uid: 117 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,9.5 + parent: 2 + type: Transform + - uid: 118 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,9.5 + parent: 2 + type: Transform + - uid: 119 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,9.5 + parent: 2 + type: Transform + - uid: 120 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,9.5 + parent: 2 + type: Transform + - uid: 121 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,9.5 + parent: 2 + type: Transform + - uid: 122 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,8.5 + parent: 2 + type: Transform + - uid: 125 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,6.5 + parent: 2 + type: Transform + - uid: 129 + components: + - pos: 7.5,5.5 + parent: 2 + type: Transform + - uid: 131 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,5.5 + parent: 2 + type: Transform + - uid: 132 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,5.5 + parent: 2 + type: Transform + - uid: 135 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,5.5 + parent: 2 + type: Transform + - uid: 195 + components: + - pos: -7.5,-5.5 + parent: 2 + type: Transform + - uid: 196 + components: + - pos: -6.5,-5.5 + parent: 2 + type: Transform + - uid: 197 + components: + - pos: -5.5,-5.5 + parent: 2 + type: Transform + - uid: 198 + components: + - pos: -4.5,-5.5 + parent: 2 + type: Transform + - uid: 199 + components: + - pos: -3.5,-5.5 + parent: 2 + type: Transform + - uid: 200 + components: + - pos: -8.5,-4.5 + parent: 2 + type: Transform + - uid: 201 + components: + - pos: -8.5,-3.5 + parent: 2 + type: Transform + - uid: 202 + components: + - pos: -8.5,-2.5 + parent: 2 + type: Transform + - uid: 203 + components: + - pos: -8.5,-0.5 + parent: 2 + type: Transform + - uid: 204 + components: + - pos: -8.5,0.5 + parent: 2 + type: Transform + - uid: 205 + components: + - pos: -8.5,1.5 + parent: 2 + type: Transform + - uid: 206 + components: + - pos: -8.5,2.5 + parent: 2 + type: Transform + - uid: 207 + components: + - pos: -8.5,3.5 + parent: 2 + type: Transform + - uid: 208 + components: + - pos: -8.5,4.5 + parent: 2 + type: Transform + - uid: 209 + components: + - pos: -8.5,6.5 + parent: 2 + type: Transform + - uid: 210 + components: + - pos: -8.5,7.5 + parent: 2 + type: Transform + - uid: 211 + components: + - pos: -8.5,8.5 + parent: 2 + type: Transform + - uid: 212 + components: + - pos: -7.5,9.5 + parent: 2 + type: Transform + - uid: 213 + components: + - pos: -6.5,9.5 + parent: 2 + type: Transform + - uid: 214 + components: + - pos: -5.5,9.5 + parent: 2 + type: Transform + - uid: 215 + components: + - pos: -4.5,9.5 + parent: 2 + type: Transform + - uid: 216 + components: + - pos: -3.5,9.5 + parent: 2 + type: Transform + - uid: 217 + components: + - pos: 9.5,8.5 + parent: 2 + type: Transform + - uid: 218 + components: + - pos: 9.5,7.5 + parent: 2 + type: Transform + - uid: 219 + components: + - pos: 9.5,6.5 + parent: 2 + type: Transform + - uid: 220 + components: + - pos: 9.5,4.5 + parent: 2 + type: Transform + - uid: 221 + components: + - pos: 9.5,3.5 + parent: 2 + type: Transform + - uid: 222 + components: + - pos: 9.5,2.5 + parent: 2 + type: Transform + - uid: 223 + components: + - pos: 9.5,1.5 + parent: 2 + type: Transform + - uid: 224 + components: + - pos: 9.5,0.5 + parent: 2 + type: Transform + - uid: 225 + components: + - pos: 9.5,-0.5 + parent: 2 + type: Transform + - uid: 226 + components: + - pos: 9.5,-2.5 + parent: 2 + type: Transform + - uid: 227 + components: + - pos: 9.5,-3.5 + parent: 2 + type: Transform + - uid: 228 + components: + - pos: 9.5,-4.5 + parent: 2 + type: Transform + - uid: 229 + components: + - pos: 8.5,-5.5 + parent: 2 + type: Transform + - uid: 230 + components: + - pos: 7.5,-5.5 + parent: 2 + type: Transform + - uid: 231 + components: + - pos: 6.5,-5.5 + parent: 2 + type: Transform + - uid: 232 + components: + - pos: 5.5,-5.5 + parent: 2 + type: Transform + - uid: 233 + components: + - pos: 4.5,-5.5 + parent: 2 + type: Transform +- proto: SheetGlass + entities: + - uid: 390 + components: + - pos: -7.193118,5.2925663 + parent: 2 + type: Transform +- proto: SheetPlasteel + entities: + - uid: 387 + components: + - pos: -7.609784,5.6052837 + parent: 2 + type: Transform +- proto: SheetRGlass + entities: + - uid: 388 + components: + - pos: -7.630618,5.323839 + parent: 2 + type: Transform +- proto: SheetSteel + entities: + - uid: 389 + components: + - pos: -7.203534,5.6469793 + parent: 2 + type: Transform +- proto: SMESBasicEmpty + entities: + - uid: 29 + components: + - pos: 0.5,5.5 + parent: 2 + type: Transform + - maxCharge: 8E+12 + type: Battery + - maxChargeRate: 5E+09 + type: PowerNetworkBattery + - ShutdownSubscribers: + - 433 + type: DeviceNetwork + missingComponents: + - UpgradeBattery +- proto: SpawnPointChiefEngineer + entities: + - uid: 145 + components: + - pos: 0.5,-0.5 + parent: 2 + type: Transform +- proto: SpawnPointLatejoin + entities: + - uid: 144 + components: + - rot: 3.141592653589793 rad + pos: 0.5,0.5 + parent: 2 + type: Transform +- proto: SuitStorageCE + entities: + - uid: 239 + components: + - pos: 4.5,8.5 + parent: 2 + type: Transform +- proto: Table + entities: + - uid: 331 + components: + - pos: -6.5,5.5 + parent: 2 + type: Transform + - uid: 334 + components: + - pos: -7.5,5.5 + parent: 2 + type: Transform + - uid: 335 + components: + - pos: -4.5,5.5 + parent: 2 + type: Transform + - uid: 336 + components: + - pos: -3.5,5.5 + parent: 2 + type: Transform + - uid: 373 + components: + - pos: 8.5,7.5 + parent: 2 + type: Transform + - uid: 375 + components: + - pos: 8.5,6.5 + parent: 2 + type: Transform +- proto: TegCenter + entities: + - uid: 3 + components: + - anchored: True + rot: 1.5707963267948966 rad + pos: 0.5,2.5 + parent: 2 + type: Transform + - ShutdownSubscribers: + - 433 + type: DeviceNetwork +- proto: TegCirculator + entities: + - uid: 4 + components: + - anchored: True + pos: 1.5,2.5 + parent: 2 + type: Transform + - uid: 5 + components: + - anchored: True + rot: 3.141592653589793 rad + pos: -0.5,2.5 + parent: 2 + type: Transform +- proto: TritiumCanister + entities: + - uid: 63 + components: + - pos: -4.5,-2.5 + parent: 2 + type: Transform + - locked: False + type: Lock + - uid: 64 + components: + - pos: -4.5,-3.5 + parent: 2 + type: Transform + - uid: 65 + components: + - pos: -4.5,-4.5 + parent: 2 + type: Transform + - uid: 66 + components: + - pos: -5.5,-2.5 + parent: 2 + type: Transform + - uid: 67 + components: + - pos: -5.5,-4.5 + parent: 2 + type: Transform + - uid: 89 + components: + - pos: -3.5,-2.5 + parent: 2 + type: Transform + - uid: 90 + components: + - pos: -3.5,-4.5 + parent: 2 + type: Transform + - uid: 92 + components: + - pos: -3.5,-3.5 + parent: 2 + type: Transform + - uid: 103 + components: + - pos: -5.5,-3.5 + parent: 2 + type: Transform +- proto: VendingMachineEngivend + entities: + - uid: 235 + components: + - flags: SessionSpecific + type: MetaData + - pos: -7.5,8.5 + parent: 2 + type: Transform +- proto: VendingMachineTankDispenserEngineering + entities: + - uid: 383 + components: + - flags: SessionSpecific + type: MetaData + - pos: -6.5,-4.5 + parent: 2 + type: Transform +- proto: VendingMachineYouTool + entities: + - uid: 234 + components: + - flags: SessionSpecific + type: MetaData + - pos: -6.5,8.5 + parent: 2 + type: Transform +- proto: WallReinforced + entities: + - uid: 48 + components: + - pos: -2.5,9.5 + parent: 2 + type: Transform + - uid: 49 + components: + - pos: 3.5,9.5 + parent: 2 + type: Transform + - uid: 109 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,-5.5 + parent: 2 + type: Transform + - uid: 110 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-5.5 + parent: 2 + type: Transform + - uid: 148 + components: + - pos: 9.5,9.5 + parent: 2 + type: Transform + - uid: 149 + components: + - pos: 9.5,5.5 + parent: 2 + type: Transform + - uid: 150 + components: + - pos: 9.5,-5.5 + parent: 2 + type: Transform + - uid: 151 + components: + - pos: 9.5,-1.5 + parent: 2 + type: Transform + - uid: 191 + components: + - pos: -8.5,9.5 + parent: 2 + type: Transform + - uid: 192 + components: + - pos: -8.5,5.5 + parent: 2 + type: Transform + - uid: 193 + components: + - pos: -8.5,-1.5 + parent: 2 + type: Transform + - uid: 194 + components: + - pos: -8.5,-5.5 + parent: 2 + type: Transform +- proto: WallSolid + entities: + - uid: 112 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,5.5 + parent: 2 + type: Transform + - uid: 311 + components: + - pos: -2.5,5.5 + parent: 2 + type: Transform +- proto: WaterTankHighCapacity + entities: + - uid: 395 + components: + - pos: -7.5,-4.5 + parent: 2 + type: Transform +- proto: WeldingFuelTankHighCapacity + entities: + - uid: 394 + components: + - pos: -2.5,8.5 + parent: 2 + type: Transform +... diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index 1066bbb07c..13967b9df5 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -377,3 +377,14 @@ price: 150 - type: ComputerBoard prototype: ComputerMassMedia + +- type: entity + parent: BaseComputerCircuitboard + id: SensorConsoleCircuitboard + name: sensor monitoring console board + description: A computer printed circuit board for a sensor monitoring console. + components: + - type: Sprite + state: cpu_engineering + - type: ComputerBoard + prototype: ComputerSensorMonitoring diff --git a/Resources/Prototypes/Entities/Stations/test.yml b/Resources/Prototypes/Entities/Stations/test.yml new file mode 100644 index 0000000000..0f2e40537a --- /dev/null +++ b/Resources/Prototypes/Entities/Stations/test.yml @@ -0,0 +1,11 @@ +# Station prototype for cut-down test maps that don't need all the infrastructure. +- type: entity + id: TestStation + parent: + - BaseStation + - BaseStationJobsSpawning + - BaseStationRecords + - BaseStationAlertLevels + noSpawn: true + components: + - type: Transform diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 27ad39fafb..090f1e6c07 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -972,3 +972,46 @@ interfaces: - key: enum.NewsWriteUiKey.Key type: NewsWriteBoundUserInterface + +- type: entity + parent: BaseComputer + id: ComputerSensorMonitoring + name: sensor monitoring computer + description: A flexible console for monitoring all kinds of sensors. + # Putting this as "DO NOT MAP" until the performance issues are fixed. + # And it's more fleshed out. + suffix: TESTING, DO NOT MAP + components: + - type: Sprite + layers: + - map: ["computerLayerBody"] + state: computer + - map: ["computerLayerKeyboard"] + state: generic_keyboard + - map: ["computerLayerScreen"] + state: sensors + - map: ["computerLayerKeys"] + state: atmos_key + - type: PointLight + radius: 1.5 + energy: 1.6 + color: "#43ccb5" + - type: Computer + board: SensorConsoleCircuitboard + - type: SensorMonitoringConsole + - type: ActivatableUI + key: enum.SensorMonitoringConsoleUiKey.Key + - type: UserInterface + interfaces: + - key: enum.SensorMonitoringConsoleUiKey.Key + type: SensorMonitoringConsoleBoundUserInterface + - type: DeviceNetwork + deviceNetId: AtmosDevices + receiveFrequencyId: AtmosMonitor + transmitFrequencyId: AtmosMonitor + prefix: device-address-prefix-sensor-monitor + sendBroadcastAttemptEvent: true + examinableAddress: true + - type: WiredNetworkConnection + - type: DeviceList + - type: AtmosDevice diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index 13116f19ef..f60a03cfb0 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -102,6 +102,14 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_pump.ogg + - type: DeviceNetwork + deviceNetId: AtmosDevices + receiveFrequencyId: AtmosMonitor + transmitFrequencyId: AtmosMonitor + sendBroadcastAttemptEvent: true + examinableAddress: true + prefix: device-address-prefix-volume-pump + - type: WiredNetworkConnection - type: entity parent: GasBinaryBase diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index 4745a5c7eb..e50e256e9c 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -250,6 +250,14 @@ pipeDirection: South - type: Transform noRot: false + - type: DeviceNetwork + deviceNetId: AtmosDevices + receiveFrequencyId: AtmosMonitor + transmitFrequencyId: AtmosMonitor + sendBroadcastAttemptEvent: true + examinableAddress: true + - type: WiredNetworkConnection + - type: PowerSwitch - type: entity parent: BaseGasThermoMachine @@ -281,6 +289,8 @@ powerDisabled: true #starts off - type: Machine board: ThermomachineFreezerMachineCircuitBoard + - type: DeviceNetwork + prefix: device-address-prefix-freezer - type: entity parent: GasThermoMachineFreezer @@ -322,6 +332,8 @@ powerDisabled: true #starts off - type: Machine board: ThermomachineHeaterMachineCircuitBoard + - type: DeviceNetwork + prefix: device-address-prefix-heater - type: entity parent: GasThermoMachineHeater diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml new file mode 100644 index 0000000000..6fd3117112 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml @@ -0,0 +1,189 @@ +- type: entity + id: TegCenter + name: thermo-electric generator + description: A high efficiency generator that uses energy transfer between hot and cold gases to produce electricity. + parent: BaseMachinePowered + placement: + mode: SnapgridCenter + components: + - type: Transform + noRot: false + - type: Sprite + sprite: Structures/Power/Generation/teg.rsi + layers: + - state: teg + - state: teg_mid + shader: unshaded + map: [ "enum.PowerDeviceVisualLayers.Powered" ] + - state: teg-op1 + shader: unshaded + visible: false + map: [ "enum.TegVisualLayers.PowerOutput" ] + - type: GenericVisualizer + visuals: + enum.PowerDeviceVisuals.Powered: + enum.PowerDeviceVisualLayers.Powered: + True: { visible: true } + False: { visible: false } + enum.TegVisuals.PowerOutput: + enum.TegVisualLayers.PowerOutput: + 0: { visible: false } + 1: { visible: true, state: teg-op1 } + 2: { visible: true, state: teg-op2 } + 3: { visible: true, state: teg-op3 } + 4: { visible: true, state: teg-op4 } + 5: { visible: true, state: teg-op5 } + 6: { visible: true, state: teg-op6 } + 7: { visible: true, state: teg-op7 } + 8: { visible: true, state: teg-op8 } + 9: { visible: true, state: teg-op9 } + 10: { visible: true, state: teg-op10 } + 11: { visible: true, state: teg-op11 } + + - type: Anchorable + - type: Pullable + - type: NodeContainer + examinable: true + nodes: + output: + !type:CableDeviceNode + nodeGroupID: HVPower + teg: + !type:TegNodeGenerator + nodeGroupID: Teg + - type: Rotatable + + # Note that only the TEG center is an AtmosDevice. + # It fires processing on behalf of its connected circulators. + - type: AtmosDevice + - type: TegGenerator + + - type: DeviceNetwork + deviceNetId: AtmosDevices + receiveFrequencyId: AtmosMonitor + transmitFrequencyId: AtmosMonitor + prefix: device-address-prefix-teg + sendBroadcastAttemptEvent: true + examinableAddress: true + - type: WiredNetworkConnection + + - type: PowerSupplier + # Have practically irrelevant supply ramping. + # Ramping is effectively implemented by the TEG manually, + # due to how power output is handled. + supplyRampRate: 100000000 + supplyRampTolerance: 10000000000 + + - type: Appearance + - type: ApcPowerReceiver + powerLoad: 1000 + + - type: LitOnPowered + - type: PointLight + enabled: false + castShadows: false + radius: 1.5 + color: "#FFAA00" + + - type: AmbientSound + volume: -4 + range: 6 + enabled: false + sound: + path: /Audio/Ambience/Objects/vending_machine_hum.ogg + +- type: entity + id: TegCirculator + name: circulator + description: Passes gas through the thermo-electric generator to exchange heat. Has an inlet and outlet port. + parent: BaseMachine + placement: + mode: SnapgridCenter + components: + - type: Transform + noRot: false + + # visuals + - type: Sprite + sprite: Structures/Power/Generation/teg.rsi + layers: + - state: circ-0 + map: [ "enum.TegVisualLayers.CirculatorBase" ] + - state: circ-0-light + shader: unshaded + map: [ "enum.TegVisualLayers.CirculatorLight" ] + + - type: GenericVisualizer + visuals: + enum.TegVisuals.CirculatorPower: + enum.TegVisualLayers.CirculatorLight: + True: { visible: true } + False: { visible: false } + enum.TegVisuals.CirculatorSpeed: + enum.TegVisualLayers.CirculatorBase: + SpeedStill: { state: circ-0 } + SpeedSlow: { state: circ-1 } + SpeedFast: { state: circ-2 } + enum.TegVisualLayers.CirculatorLight: + SpeedStill: { state: circ-0-light } + SpeedSlow: { state: circ-1-light } + SpeedFast: { state: circ-2-light } + + - type: Appearance + - type: PointLight + enabled: false + castShadows: false + radius: 1.5 + color: "#CC00FF" + + # tags + - type: Tag + tags: + - Pipe + - Unstackable + + # basic interactions + - type: Rotatable + - type: Anchorable + - type: Pullable + + # functionality + - type: NodeContainer + nodes: + inlet: + !type:PipeNode + nodeGroupID: Pipe + pipeDirection: North + volume: 100 + outlet: + !type:PipeNode + nodeGroupID: Pipe + pipeDirection: South + volume: 100 + circulator: + !type:TegNodeCirculator + nodeGroupID: Teg + + - type: AtmosUnsafeUnanchor + - type: TegCirculator + light_color_fast: '#AA00FF' + light_color_slow: '#FF3300' + +- # Spawned by the client-side circulator examine code to indicate the inlet/outlet direction. + type: entity + id: TegCirculatorArrow + noSpawn: true + components: + - type: Sprite + sprite: Markers/teg_arrow.rsi + color: "#FFFFFFBB" + layers: + - state: arrow + offset: 0, 0.75 + - state: arrow + offset: 0, -0.75 + - type: TimedDespawn + lifetime: 2 + - type: Tag + tags: + - HideContextMenu diff --git a/Resources/Prototypes/Entities/Structures/Power/smes.yml b/Resources/Prototypes/Entities/Structures/Power/smes.yml index bdd1093bad..7ecc330a38 100644 --- a/Resources/Prototypes/Entities/Structures/Power/smes.yml +++ b/Resources/Prototypes/Entities/Structures/Power/smes.yml @@ -78,6 +78,15 @@ - type: Damageable damageContainer: Inorganic damageModifierSet: StrongMetallic + - type: BatterySensor + - type: DeviceNetwork + deviceNetId: AtmosDevices + receiveFrequencyId: AtmosMonitor + transmitFrequencyId: AtmosMonitor + prefix: device-address-prefix-smes + sendBroadcastAttemptEvent: true + examinableAddress: true + - type: WiredNetworkConnection # SMES' in use diff --git a/Resources/Prototypes/Guidebook/engineering.yml b/Resources/Prototypes/Guidebook/engineering.yml index afa89c8e9e..2c5e47c081 100644 --- a/Resources/Prototypes/Guidebook/engineering.yml +++ b/Resources/Prototypes/Guidebook/engineering.yml @@ -51,13 +51,19 @@ children: - AME - Singularity - + - TEG + - type: guideEntry id: AME name: guide-entry-ame text: "/ServerInfo/Guidebook/Engineering/AME.xml" - + - type: guideEntry id: Singularity name: guide-entry-singularity text: "/ServerInfo/Guidebook/Engineering/Singularity.xml" + +- type: guideEntry + id: TEG + name: guide-entry-teg + text: "/ServerInfo/Guidebook/Engineering/TEG.xml" diff --git a/Resources/Prototypes/Maps/debug.yml b/Resources/Prototypes/Maps/debug.yml index be23cdac99..2f475c1e57 100644 --- a/Resources/Prototypes/Maps/debug.yml +++ b/Resources/Prototypes/Maps/debug.yml @@ -31,3 +31,20 @@ - Captain availableJobs: Captain: [ -1, -1 ] + +- type: gameMap + id: TestTeg + mapName: Test TEG + mapPath: /Maps/Test/test_teg.yml + minPlayers: 0 + stations: + TEG: + stationProto: TestStation + components: + - type: StationNameSetup + mapNameTemplate: "TEG" + - type: StationJobs + overflowJobs: + - ChiefEngineer + availableJobs: + ChiefEngineer: [ -1, -1 ] diff --git a/Resources/ServerInfo/Guidebook/Engineering/TEG.xml b/Resources/ServerInfo/Guidebook/Engineering/TEG.xml new file mode 100644 index 0000000000..a2a04e03ef --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Engineering/TEG.xml @@ -0,0 +1,39 @@ + + # Thermo-electric Engine (TEG) + + The TEG generates power by exchanging heat between hot and cold gases. On station, hot gas is usually created by burning plasma, and an array of heat-exchanging pipes in space radiates away heat to make a cold side. + + The TEG relies heavily on atmospherics piping. The only truly special component about it is the generator core, the rest is all off-the-shelf atmospherics equipment. Note that while the exact layout may vary significantly depending on station, the general components and setup are usually the same. + + ## Generator + + The main generator itself is a machine made up of multiple parts: the core generator and two "circulators", in this arrangement: + + + + + + + + The circulators take in either a hot or cold gas, and pass it through the machine to exchange heat. The gas then gets output on the other end of the circulator. The generator produces the actual power and outputs it over an HV wire. + + Note that the circulators are [color=#a4885c]directional[/color]: they will only let gas through one way. You can see this direction in-game by examining the circulator itself. A pressure difference is required across the input and output, so pumps are generally provided and must be turned on. + + There is no preference for which side must be hot or cold, there need only be a difference in temperature between them. The gases in the two "loops" are never mixed, only energy is exchanged between them. The hot side will cool down, the cold side will heat up. + + ## Burn Chamber + + As I'm sure a wise person once said: the best way to make something hot is to light it on fire. Well, depending on context that may not be very wise, but luckily your engineering department has just what's needed to do it wisely after all. + + TODO: somebody fill this out once we settle on a general mapped layout for burn chambers. + + ## Coolant Array + + A whole bunch of heat-exchanger piping in space. There's not much to say here: gas goes through and cools down. + + ## The Pipes + + TODO: somebody fill this out once we settle on a general mapped layout for burn chambers. + + + diff --git a/Resources/Textures/Markers/teg_arrow.rsi/arrow.png b/Resources/Textures/Markers/teg_arrow.rsi/arrow.png new file mode 100644 index 0000000000..f0a15248cd Binary files /dev/null and b/Resources/Textures/Markers/teg_arrow.rsi/arrow.png differ diff --git a/Resources/Textures/Markers/teg_arrow.rsi/meta.json b/Resources/Textures/Markers/teg_arrow.rsi/meta.json new file mode 100644 index 0000000000..366bdec16f --- /dev/null +++ b/Resources/Textures/Markers/teg_arrow.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by PJB3005", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "arrow", + "delays": [[0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033, 0.033]] + } + ] +} diff --git a/Resources/Textures/Structures/Machines/computers.rsi/meta.json b/Resources/Textures/Structures/Machines/computers.rsi/meta.json index c43d5d7e52..b6741b195d 100644 --- a/Resources/Textures/Structures/Machines/computers.rsi/meta.json +++ b/Resources/Textures/Structures/Machines/computers.rsi/meta.json @@ -1460,6 +1460,10 @@ "name": "security_key_off", "directions": 4 }, + { + "name": "sensors", + "directions": 4 + }, { "name": "shuttle", "directions": 4, diff --git a/Resources/Textures/Structures/Machines/computers.rsi/sensors.png b/Resources/Textures/Structures/Machines/computers.rsi/sensors.png new file mode 100644 index 0000000000..8c0b6769fc Binary files /dev/null and b/Resources/Textures/Structures/Machines/computers.rsi/sensors.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-0-light.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-0-light.png new file mode 100644 index 0000000000..35515fb9a7 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-0-light.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-0.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-0.png new file mode 100644 index 0000000000..73508ad504 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-0.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-1-light.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-1-light.png new file mode 100644 index 0000000000..dbe84b6ce4 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-1-light.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-1.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-1.png new file mode 100644 index 0000000000..a86d1c040f Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-1.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-2-light.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-2-light.png new file mode 100644 index 0000000000..8b857c863c Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-2-light.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-2.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-2.png new file mode 100644 index 0000000000..7a79ba85c5 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/circ-2.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/teg.rsi/meta.json new file mode 100644 index 0000000000..f0c0b58f05 --- /dev/null +++ b/Resources/Textures/Structures/Power/Generation/teg.rsi/meta.json @@ -0,0 +1,279 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/Baystation12/Baystation12/blob/fc2196fa74492570e5abb847085afca0e53f4ea8/icons/obj/power.dmi. Modified to split light layers", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "teg", + "directions": 4 + }, + { + "name": "teg-op1", + "directions": 4 + }, + { + "name": "teg-op2", + "directions": 4 + }, + { + "name": "teg-op3", + "directions": 4 + }, + { + "name": "teg-op4", + "directions": 4 + }, + { + "name": "teg-op5", + "directions": 4 + }, + { + "name": "teg-op6", + "directions": 4 + }, + { + "name": "teg-op7", + "directions": 4 + }, + { + "name": "teg-op8", + "directions": 4 + }, + { + "name": "teg-op9", + "directions": 4 + }, + { + "name": "teg-op10", + "directions": 4 + }, + { + "name": "teg-op11", + "directions": 4 + }, + { + "name": "teg_mid", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "teg-oc00" + }, + { + "name": "teg-oc10", + "delays": [ + [ + 0.3, + 0.3 + ] + ] + }, + { + "name": "teg-oc01", + "delays": [ + [ + 0.3, + 0.3 + ] + ] + }, + { + "name": "teg-oc11", + "delays": [ + [ + 0.3, + 0.3 + ] + ] + }, + { + "name": "circ-2", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "circ-2-light", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "circ-1", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "circ-1-light", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "circ-0", + "directions": 4 + }, + { + "name": "circ-0-light", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc00.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc00.png new file mode 100644 index 0000000000..209e4faa88 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc00.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc01.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc01.png new file mode 100644 index 0000000000..f23a3eefe5 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc01.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc10.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc10.png new file mode 100644 index 0000000000..b34326da36 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc10.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc11.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc11.png new file mode 100644 index 0000000000..4e936ef8fa Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-oc11.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op1.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op1.png new file mode 100644 index 0000000000..8210dfa200 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op1.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op10.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op10.png new file mode 100644 index 0000000000..5e9d96d8d3 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op10.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op11.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op11.png new file mode 100644 index 0000000000..6270b81b2a Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op11.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op2.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op2.png new file mode 100644 index 0000000000..c3b489c869 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op2.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op3.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op3.png new file mode 100644 index 0000000000..3c2cbb32fa Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op3.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op4.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op4.png new file mode 100644 index 0000000000..d606e78d89 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op4.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op5.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op5.png new file mode 100644 index 0000000000..2d2439d1b8 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op5.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op6.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op6.png new file mode 100644 index 0000000000..b6597e5232 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op6.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op7.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op7.png new file mode 100644 index 0000000000..d02e791e28 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op7.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op8.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op8.png new file mode 100644 index 0000000000..9baf9e4408 Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op8.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op9.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op9.png new file mode 100644 index 0000000000..98ab279f8f Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg-op9.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg.png new file mode 100644 index 0000000000..bfd6cd27aa Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg.png differ diff --git a/Resources/Textures/Structures/Power/Generation/teg.rsi/teg_mid.png b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg_mid.png new file mode 100644 index 0000000000..8ac16e974f Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/teg.rsi/teg_mid.png differ diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 4ea4afc413..2e4f6bfb8a 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -502,6 +502,7 @@ public sealed class $CLASS$ : Shared$CLASS$ { True True True + True True True True