From 9cb7e0f6c400d2ad8229231ab8a0e7cd3b0b8276 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Thu, 11 Aug 2022 14:19:31 -0700 Subject: [PATCH] Make atmos Turing-complete (#10520) * Add pneumatic valves Pneumatic valves permit bidirectional flow between the inlet and outlet if the pressure at the control port is high enough. * Add construction recipe --- .../PressureControlledValveComponent.cs | 35 +++++++ .../PressureControlledValveSystem.cs | 96 ++++++++++++++++++ .../Piping/Atmospherics/trinary.yml | 55 ++++++++++ .../Graphs/utilities/atmos_trinary.yml | 22 ++++ .../Recipes/Construction/utilities.yml | 16 ++- .../Atmospherics/pneumaticvalve.rsi/meta.json | 19 ++++ .../Atmospherics/pneumaticvalve.rsi/off.png | Bin 0 -> 9574 bytes .../Atmospherics/pneumaticvalve.rsi/on.png | Bin 0 -> 11438 bytes 8 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 Content.Server/Atmos/Piping/Trinary/Components/PressureControlledValveComponent.cs create mode 100644 Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs create mode 100644 Resources/Textures/Structures/Piping/Atmospherics/pneumaticvalve.rsi/meta.json create mode 100644 Resources/Textures/Structures/Piping/Atmospherics/pneumaticvalve.rsi/off.png create mode 100644 Resources/Textures/Structures/Piping/Atmospherics/pneumaticvalve.rsi/on.png diff --git a/Content.Server/Atmos/Piping/Trinary/Components/PressureControlledValveComponent.cs b/Content.Server/Atmos/Piping/Trinary/Components/PressureControlledValveComponent.cs new file mode 100644 index 0000000000..5df77ed9fd --- /dev/null +++ b/Content.Server/Atmos/Piping/Trinary/Components/PressureControlledValveComponent.cs @@ -0,0 +1,35 @@ +using Content.Shared.Atmos; + +namespace Content.Server.Atmos.Piping.Trinary.Components +{ + [RegisterComponent] + public sealed class PressureControlledValveComponent : Component + { + [ViewVariables(VVAccess.ReadWrite)] + [DataField("inlet")] + public string InletName { get; set; } = "inlet"; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("control")] + public string ControlName { get; set; } = "control"; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("outlet")] + public string OutletName { get; set; } = "outlet"; + + [ViewVariables(VVAccess.ReadOnly)] + [DataField("enabled")] + public bool Enabled { get; set; } = false; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("gain")] + public float Gain { get; set; } = 10; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("threshold")] + public float Threshold { get; set; } = Atmospherics.OneAtmosphere; + + [DataField("maxTransferRate")] + public float MaxTransferRate { get; set; } = Atmospherics.MaxTransferRate; + } +} diff --git a/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs b/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs new file mode 100644 index 0000000000..d9403ba3eb --- /dev/null +++ b/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs @@ -0,0 +1,96 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Server.Atmos.Piping.Components; +using Content.Server.Atmos.Piping.Trinary.Components; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.Nodes; +using Content.Shared.Atmos.Piping; +using Content.Shared.Audio; +using JetBrains.Annotations; +using Robust.Shared.Timing; + +namespace Content.Server.Atmos.Piping.Trinary.EntitySystems +{ + [UsedImplicitly] + public sealed class PressureControlledValveSystem : EntitySystem + { + [Dependency] private IGameTiming _gameTiming = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnUpdate); + SubscribeLocalEvent(OnFilterLeaveAtmosphere); + } + + private void OnInit(EntityUid uid, PressureControlledValveComponent comp, ComponentInit args) + { + UpdateAppearance(uid, comp); + } + + private void OnUpdate(EntityUid uid, PressureControlledValveComponent comp, AtmosDeviceUpdateEvent args) + { + if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) + || !EntityManager.TryGetComponent(uid, out AtmosDeviceComponent? device) + || !nodeContainer.TryGetNode(comp.InletName, out PipeNode? inletNode) + || !nodeContainer.TryGetNode(comp.ControlName, out PipeNode? controlNode) + || !nodeContainer.TryGetNode(comp.OutletName, out PipeNode? outletNode)) + { + _ambientSoundSystem.SetAmbience(comp.Owner, false); + comp.Enabled = false; + return; + } + + // If output is higher than input, flip input/output to enable bidirectional flow. + if (outletNode.Air.Pressure > inletNode.Air.Pressure) + { + PipeNode temp = outletNode; + outletNode = inletNode; + inletNode = temp; + } + + float control = (controlNode.Air.Pressure - outletNode.Air.Pressure) - comp.Threshold; + float transferRate; + if (control < 0) + { + comp.Enabled = false; + transferRate = 0; + } + else + { + comp.Enabled = true; + transferRate = Math.Min(control * comp.Gain, comp.MaxTransferRate); + } + UpdateAppearance(uid, comp); + + // We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters. + var transferRatio = (float)(transferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inletNode.Air.Volume; + if (transferRatio <= 0) + { + _ambientSoundSystem.SetAmbience(comp.Owner, false); + return; + } + + _ambientSoundSystem.SetAmbience(comp.Owner, true); + var removed = inletNode.Air.RemoveRatio(transferRatio); + _atmosphereSystem.Merge(outletNode.Air, removed); + } + + private void OnFilterLeaveAtmosphere(EntityUid uid, PressureControlledValveComponent comp, AtmosDeviceDisabledEvent args) + { + comp.Enabled = false; + UpdateAppearance(uid, comp); + _ambientSoundSystem.SetAmbience(comp.Owner, false); + } + + private void UpdateAppearance(EntityUid uid, PressureControlledValveComponent? comp = null, AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref comp, ref appearance, false)) + return; + + appearance.SetData(FilterVisuals.Enabled, comp.Enabled); + } + } +} diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml index 9baee077fb..c02a6f4998 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml @@ -198,3 +198,58 @@ !type:PipeNode nodeGroupID: Pipe pipeDirection: North + +- type: entity + parent: GasPipeBase + id: PressureControlledValve + name: pneumatic valve + description: Valve controlled by pressure. + placement: + mode: SnapgridCenter + components: + - type: AtmosDevice + - type: SubFloorHide + blockInteractions: false + blockAmbience: false + - type: NodeContainer + nodes: + inlet: + !type:PipeNode + nodeGroupID: Pipe + pipeDirection: North + control: + !type:PipeNode + nodeGroupID: Pipe + pipeDirection: West + outlet: + !type:PipeNode + nodeGroupID: Pipe + pipeDirection: South + - type: Sprite + netsync: false + sprite: Structures/Piping/Atmospherics/pneumaticvalve.rsi + layers: + - sprite: Structures/Piping/Atmospherics/pipe.rsi + state: pipeTJunction + rotation: -90 + map: [ "enum.PipeVisualLayers.Pipe" ] + - state: off + map: [ "enum.SubfloorLayers.FirstLayer", "enabled" ] + - type: Appearance + - type: GenericVisualizer + visuals: + enum.FilterVisuals.Enabled: + enabled: + True: { state: on } + False: { state: off } + - type: PipeColorVisuals + - type: PressureControlledValve + - type: AmbientSound + enabled: false + volume: -9 + range: 5 + sound: + path: /Audio/Ambience/Objects/gas_hiss.ogg + - type: Construction + graph: GasTrinary + node: pneumaticvalve diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_trinary.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_trinary.yml index 97c03b0006..5a77ea405b 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_trinary.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_trinary.yml @@ -16,6 +16,12 @@ amount: 2 doAfter: 1 + - to: pneumaticvalve + steps: + - material: Steel + amount: 2 + doAfter: 1 + - node: filter entity: GasFilter edges: @@ -47,3 +53,19 @@ steps: - tool: Welding doAfter: 1 + + - node: pneumaticvalve + entity: PressureControlledValve + edges: + - to: start + conditions: + - !type:EntityAnchored + anchored: false + completed: + - !type:SpawnPrototype + prototype: SheetSteel1 + amount: 2 + - !type:DeleteEntity + steps: + - tool: Welding + doAfter: 1 diff --git a/Resources/Prototypes/Recipes/Construction/utilities.yml b/Resources/Prototypes/Recipes/Construction/utilities.yml index 3642fe0dea..fd93294e46 100644 --- a/Resources/Prototypes/Recipes/Construction/utilities.yml +++ b/Resources/Prototypes/Recipes/Construction/utilities.yml @@ -12,7 +12,7 @@ state: camera objectType: Structure placementMode: SnapgridCenter - + - type: construction name: telescreen id: WallmountTelescreen @@ -511,3 +511,17 @@ conditions: - !type:TileNotBlocked {} +- type: construction + id: PressureControlledValve + name: pneumatic valve + graph: GasTrinary + startNode: start + targetNode: pneumaticvalve + category: Utilities + placementMode: SnapgridCenter + canBuildInImpassable: false + icon: + sprite: Structures/Piping/Atmospherics/pneumaticvalve.rsi + state: off + conditions: + - !type:TileNotBlocked {} diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pneumaticvalve.rsi/meta.json b/Resources/Textures/Structures/Piping/Atmospherics/pneumaticvalve.rsi/meta.json new file mode 100644 index 0000000000..85649ee55a --- /dev/null +++ b/Resources/Textures/Structures/Piping/Atmospherics/pneumaticvalve.rsi/meta.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "Kevin Zheng 2022", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "off", + "directions": 4 + }, + { + "name": "on", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pneumaticvalve.rsi/off.png b/Resources/Textures/Structures/Piping/Atmospherics/pneumaticvalve.rsi/off.png new file mode 100644 index 0000000000000000000000000000000000000000..a3b4059ba4ad4490bf46b9bf6f10d0b2acb38102 GIT binary patch literal 9574 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

)DhKEE78qskNJ_9CHBtu z-Tm2Fp06JUlPjSw68k@uTV|4VhbHkQ@Hg36C+^Z_-ybIvhrIP1uiXzF{;oM@!!^ia zM~OY~G|wpbyRUx%2l&e~uq#Rlk?v2JB2`v4UzX4;&p7>DotMDD9$!LRd4#rU$Xp2e z<2sD*Z0zpluty`qne%!a^XOSDq`RgXH)_}yU5V(aZjamy^_sbF_Qo9E>{+K zj!bBjnV|Lb*QRKECqF?v^!PNq`Z=-lyJ`Ciub&%q4no&d`k4GhHD<9lpV})Gw=5P) zqrL`=l~`sv<`<-#7gV8Nf=7-w9k|JA4)F^5mrZ?!IL>;&`?^B=x(Vk96Q7}1qKt?r z?QP|Tz0xa3yCvK9{~|nWRZ({Klp9OlKV|T+^a5TcGZ~1=*|z