diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 3570c3b374..a618a37aa6 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -235,7 +235,7 @@ namespace Content.Client "SliceableFood", "DamageOtherOnHit", "DamageOnLand", - "Recyclable" + "GasFilter" }; } } diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/GasFilterComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/GasFilterComponent.cs new file mode 100644 index 0000000000..70bbc7bbb6 --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/GasFilterComponent.cs @@ -0,0 +1,179 @@ +#nullable enable +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; +using Content.Shared.Atmos; +using Content.Shared.GameObjects.Components.Atmos; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.ComponentDependencies; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Log; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using System; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Atmos.Piping.Filters +{ + [RegisterComponent] + public class GasFilterComponent : Component + { + public override string Name => "GasFilter"; + + /// + /// If the filter is currently filtering. + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool FilterEnabled + { + get => _filterEnabled; + set + { + _filterEnabled = value; + UpdateAppearance(); + } + } + private bool _filterEnabled; + + [ViewVariables(VVAccess.ReadWrite)] + public Gas GasToFilter + { + get => _gasToFilter; + set + { + _gasToFilter = value; + UpdateAppearance(); + } + } + private Gas _gasToFilter; + + [ViewVariables(VVAccess.ReadWrite)] + public int VolumeFilterRate + { + get => _volumeFilterRate; + set => _volumeFilterRate = Math.Clamp(value, 0, MaxVolumeFilterRate); + } + private int _volumeFilterRate; + + [ViewVariables(VVAccess.ReadWrite)] + public int MaxVolumeFilterRate + { + get => _maxVolumeFilterRate; + set => Math.Max(value, 0); + } + private int _maxVolumeFilterRate; + + [ViewVariables] + private PipeDirection _initialInletDirection; + + /// + /// The direction the filtered-out gas goes. + /// + [ViewVariables] + private PipeDirection _initialFilterOutletDirection; + + /// + /// The direction the rest of the gas goes. + /// + [ViewVariables] + private PipeDirection _initialOutletDirection; + + [ViewVariables] + private PipeNode? _inletPipe; + + [ViewVariables] + private PipeNode? _filterOutletPipe; + + [ViewVariables] + private PipeNode? _outletPipe; + + [ComponentDependency] + private readonly AppearanceComponent? _appearance = default; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _volumeFilterRate, "startingVolumePumpRate", 0); + serializer.DataField(ref _maxVolumeFilterRate, "maxVolumePumpRate", 100); + serializer.DataField(ref _gasToFilter, "gasToFilter", Gas.Phoron); + serializer.DataField(ref _initialInletDirection, "inletDirection", PipeDirection.None); + serializer.DataField(ref _initialFilterOutletDirection, "filterOutletDirection", PipeDirection.None); + serializer.DataField(ref _initialOutletDirection, "outletDirection", PipeDirection.None); + } + + public override void Initialize() + { + base.Initialize(); + Owner.EnsureComponent(); + SetPipes(); + UpdateAppearance(); + } + + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + switch (message) + { + case PipeNetUpdateMessage: + Update(); + break; + } + } + + public void Update() + { + if (!FilterEnabled) + return; + + if (_inletPipe == null || _inletPipe.Air == null || + _filterOutletPipe == null || _filterOutletPipe.Air == null || + _outletPipe == null || _outletPipe.Air == null) + return; + + FilterGas(_inletPipe.Air, _filterOutletPipe.Air, _outletPipe.Air); + } + + private void FilterGas(GasMixture inletGas, GasMixture filterOutletGas, GasMixture outletGas) + { + var volumeRatio = Math.Clamp(VolumeFilterRate / inletGas.Volume, 0, 1); + var gas = inletGas.RemoveRatio(volumeRatio); + + var molesToSeperate = gas.GetMoles(GasToFilter); + gas.SetMoles(GasToFilter, 0); + filterOutletGas.AdjustMoles(GasToFilter, molesToSeperate); + + outletGas.Merge(gas); + } + + private void UpdateAppearance() + { + _appearance?.SetData(FilterVisuals.VisualState, new FilterVisualState()); + } + + private void SetPipes() + { + _inletPipe = null; + _filterOutletPipe = null; + _outletPipe = null; + + if (!Owner.TryGetComponent(out var container)) + { + Logger.Error($"{typeof(GasFilterComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}."); + return; + } + + var pipeNodes = container.Nodes.OfType(); + + _inletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _initialInletDirection).FirstOrDefault(); + _filterOutletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _initialFilterOutletDirection).FirstOrDefault(); + _outletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _initialOutletDirection).FirstOrDefault(); + + if (_inletPipe == null || _filterOutletPipe == null || _outletPipe == null) + { + Logger.Error($"{typeof(GasFilterComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + return; + } + } + } +} diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 76a7a45056..d87ac132cd 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -1,4 +1,6 @@ -using Robust.Shared.Maths; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; +using System; namespace Content.Shared.Atmos { @@ -246,6 +248,7 @@ namespace Content.Shared.Atmos /// /// Gases to Ids. Keep these updated with the prototypes! /// + [Serializable, NetSerializable] public enum Gas : sbyte { Oxygen = 0, diff --git a/Content.Shared/GameObjects/Components/Atmos/SharedFilterComponent.cs b/Content.Shared/GameObjects/Components/Atmos/SharedFilterComponent.cs new file mode 100644 index 0000000000..8308d30cbb --- /dev/null +++ b/Content.Shared/GameObjects/Components/Atmos/SharedFilterComponent.cs @@ -0,0 +1,17 @@ +using System; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Atmos +{ + [Serializable, NetSerializable] + public enum FilterVisuals + { + VisualState + } + + [Serializable, NetSerializable] + public class FilterVisualState + { + + } +} diff --git a/Resources/Prototypes/Entities/Constructible/Ground/gasfilters.yml b/Resources/Prototypes/Entities/Constructible/Ground/gasfilters.yml new file mode 100644 index 0000000000..fc84458bdc --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Ground/gasfilters.yml @@ -0,0 +1,50 @@ +- type: entity + abstract: true + id: GasFilterBase + placement: + mode: SnapgridCenter + components: + - type: Clickable + - type: InteractionOutline + - type: Physics + - type: SnapGrid + offset: Center + - type: Sprite + sprite: Constructible/Atmos/pipeitems.rsi + state: scrubber + - type: Damageable + resistances: metallicResistances + - type: Destructible + thresholds: + 100: + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] + +- type: entity + parent: GasFilterBase + id: GasFilter + name: Gas Filter + description: It filters gases. + components: + - type: Sprite + sprite: Constructible/Atmos/pipe.rsi + state: pipeTJunction2 + - type: Icon + sprite: Constructible/Atmos/pipe.rsi + state: pipeTJunction2 + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: South + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: East + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: North + - type: GasFilter + inletDirection: South + filterOutletDirection: East + outletDirection: North