diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 28545a1909..5d3cca4938 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -160,6 +160,9 @@ "Metabolism", "AiFactionTag", "PressureProtection", + "DebugPump", + "DebugVent", + "DebugSiphon", }; } } diff --git a/Content.Server/Atmos/IGridAtmosphereComponent.cs b/Content.Server/Atmos/IGridAtmosphereComponent.cs index 5518881767..416445b1a9 100644 --- a/Content.Server/Atmos/IGridAtmosphereComponent.cs +++ b/Content.Server/Atmos/IGridAtmosphereComponent.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using Content.Server.GameObjects.Components.Atmos.Piping; +using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Map; @@ -146,5 +148,13 @@ namespace Content.Server.Atmos float GetVolumeForCells(int cellCount); void Update(float frameTime); + + void AddPipeNet(IPipeNet pipeNet); + + void RemovePipeNet(IPipeNet pipeNet); + + void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice); + + void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice); } } diff --git a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs index 6af07c6eba..87346d632b 100644 --- a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Content.Server.Atmos; +using Content.Server.GameObjects.Components.Atmos.Piping; +using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Shared.Atmos; using Content.Shared.Maps; using Robust.Server.Interfaces.GameObjects; @@ -73,6 +75,22 @@ namespace Content.Server.GameObjects.Components.Atmos [ViewVariables] private HashSet _highPressureDelta = new HashSet(1000); + [ViewVariables] + private readonly List _pipeNets = new List(); + + /// + /// Index of most recently updated . + /// + private int _pipeNetIndex = 0; + + [ViewVariables] + private readonly List _pipeNetDevices = new List(); + + /// + /// Index of most recently updated . + /// + private int _deviceIndex = 0; + [ViewVariables] private ProcessState _state = ProcessState.TileEqualize; @@ -84,6 +102,8 @@ namespace Content.Server.GameObjects.Components.Atmos HighPressureDelta, Hotspots, Superconductivity, + PipeNet, + PipeNetDevices, } /// @@ -296,6 +316,28 @@ namespace Content.Server.GameObjects.Components.Atmos _excitedGroups.Remove(excitedGroup); } + public void AddPipeNet(IPipeNet pipeNet) + { + _pipeNets.Add(pipeNet); + } + + public void RemovePipeNet(IPipeNet pipeNet) + { + _pipeNets.Remove(pipeNet); + _deviceIndex = 0; + } + + public void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice) + { + _pipeNetDevices.Add(pipeNetDevice); + } + + public void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice) + { + _pipeNetDevices.Remove(pipeNetDevice); + _deviceIndex = 0; + } + /// public TileAtmosphere? GetTile(GridCoordinates coordinates) { @@ -401,6 +443,14 @@ namespace Content.Server.GameObjects.Components.Atmos break; case ProcessState.Superconductivity: ProcessSuperconductivity(); + _state = ProcessState.PipeNet; + break; + case ProcessState.PipeNet: + ProcessPipeNets(); + _state = ProcessState.PipeNetDevices; + break; + case ProcessState.PipeNetDevices: + ProcessPipeNetDevices(); _state = ProcessState.TileEqualize; break; } @@ -520,6 +570,45 @@ namespace Content.Server.GameObjects.Components.Atmos } } + private void ProcessPipeNets() + { + _stopwatch.Restart(); + + var number = 0; + var pipeNets = _pipeNets.ToArray(); + var netCount = pipeNets.Count(); + for ( ; _pipeNetIndex < netCount; _pipeNetIndex++) + { + pipeNets[_pipeNetIndex].Update(); + + if (number++ < LagCheckIterations) continue; + number = 0; + // Process the rest next time. + if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) + return; + } + _pipeNetIndex = 0; + } + + private void ProcessPipeNetDevices() + { + _stopwatch.Restart(); + + var number = 0; + var pipeNetDevices = _pipeNetDevices.ToArray(); + var deviceCount = pipeNetDevices.Count(); + for ( ; _deviceIndex < deviceCount; _deviceIndex++) + { + pipeNetDevices[_deviceIndex].Update(); + + if (number++ < LagCheckIterations) continue; + number = 0; + // Process the rest next time. + if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) + return; + } + _deviceIndex = 0; + } private AirtightComponent? GetObstructingComponent(MapIndices indices) { if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default; diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/PipeNetDeviceComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/PipeNetDeviceComponent.cs new file mode 100644 index 0000000000..e9494d059d --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/PipeNetDeviceComponent.cs @@ -0,0 +1,50 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Log; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Adds itself to a to be updated by. + /// TODO: Make compatible with unanchoring/anchoring. Currently assumes that the Owner does not move. + /// + public abstract class PipeNetDeviceComponent : Component + { + public abstract void Update(); + + protected IGridAtmosphereComponent JoinedGridAtmos { get; private set; } + + public override void Initialize() + { + base.Initialize(); + JoinGridAtmos(); + } + + public override void OnRemove() + { + base.OnRemove(); + LeaveGridAtmos(); + } + + private void JoinGridAtmos() + { + var gridAtmos = EntitySystem.Get() + .GetGridAtmosphere(Owner.Transform.GridID); + if (gridAtmos == null) + { + Logger.Error($"{nameof(PipeNetDeviceComponent)} on entity {Owner.Uid} could not find an {nameof(IGridAtmosphereComponent)}."); + return; + } + JoinedGridAtmos = gridAtmos; + JoinedGridAtmos.AddPipeNetDevice(this); + } + + private void LeaveGridAtmos() + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + JoinedGridAtmos = null; + } + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs new file mode 100644 index 0000000000..0099549e84 --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs @@ -0,0 +1,68 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; +using Robust.Shared.Log; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Transfer gas from one to another. + /// + public abstract class BasePumpComponent : PipeNetDeviceComponent + { + /// + /// Needs to be same as that of a on this entity. + /// + [ViewVariables] + private PipeDirection _inletDirection; + + /// + /// Needs to be same as that of a on this entity. + /// + [ViewVariables] + private PipeDirection _outletDirection; + + [ViewVariables] + private PipeNode _inletPipe; + + [ViewVariables] + private PipeNode _outletPipe; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _inletDirection, "inletDirection", PipeDirection.None); + serializer.DataField(ref _outletDirection, "outletDirection", PipeDirection.None); + } + + public override void Initialize() + { + base.Initialize(); + if (!Owner.TryGetComponent(out var container)) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BasePumpComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}."); + return; + } + var pipeNodes = container.Nodes.OfType(); + _inletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _inletDirection).FirstOrDefault(); + _outletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _outletDirection).FirstOrDefault(); + if (_inletPipe == null | _outletPipe == null) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BasePumpComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + return; + } + } + + public override void Update() + { + PumpGas(_inletPipe.Air, _outletPipe.Air); + } + + protected abstract void PumpGas(GasMixture inletGas, GasMixture outletGas); + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/DebugPumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/DebugPumpComponent.cs new file mode 100644 index 0000000000..dfddf21cab --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/DebugPumpComponent.cs @@ -0,0 +1,21 @@ +using Content.Server.Atmos; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Placeholder example of pump functionality. + /// + [RegisterComponent] + [ComponentReference(typeof(BasePumpComponent))] + public class DebugPumpComponent : BasePumpComponent + { + public override string Name => "DebugPump"; + + protected override void PumpGas(GasMixture inletGas, GasMixture outletGas) + { + outletGas.Merge(inletGas); + inletGas.Clear(); + } + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs new file mode 100644 index 0000000000..16d04a20fb --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs @@ -0,0 +1,52 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; +using Content.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Log; +using Robust.Shared.ViewVariables; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Transfers gas from the tile it is on to a . + /// + public abstract class BaseSiphonComponent : PipeNetDeviceComponent + { + [ViewVariables] + private PipeNode _scrubberOutlet; + + private AtmosphereSystem _atmosSystem; + + public override void Initialize() + { + base.Initialize(); + _atmosSystem = EntitySystem.Get(); + if (!Owner.TryGetComponent(out var container)) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BaseSiphonComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}."); + return; + } + _scrubberOutlet = container.Nodes.OfType().FirstOrDefault(); + if (_scrubberOutlet == null) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BaseSiphonComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + return; + } + } + + public override void Update() + { + var tileAtmos = AtmosHelpers.GetTileAtmosphere(Owner.Transform.GridPosition); + if (tileAtmos == null) + return; + ScrubGas(tileAtmos.Air, _scrubberOutlet.Air); + _atmosSystem.GetGridAtmosphere(Owner.Transform.GridID).Invalidate(tileAtmos.GridIndices); + } + + protected abstract void ScrubGas(GasMixture inletGas, GasMixture outletGas); + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/DebugSiphonComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/DebugSiphonComponent.cs new file mode 100644 index 0000000000..6aee5812b6 --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/DebugSiphonComponent.cs @@ -0,0 +1,21 @@ +using Content.Server.Atmos; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Placeholder example of scrubber functionality. + /// + [RegisterComponent] + [ComponentReference(typeof(BaseSiphonComponent))] + public class DebugSiphonComponent : BaseSiphonComponent + { + public override string Name => "DebugSiphon"; + + protected override void ScrubGas(GasMixture inletGas, GasMixture outletGas) + { + outletGas.Merge(inletGas); + inletGas.Clear(); + } + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs new file mode 100644 index 0000000000..334d647712 --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs @@ -0,0 +1,54 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; +using Content.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Log; +using Robust.Shared.ViewVariables; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Transfers gas from a to the tile it is on. + /// + public abstract class BaseVentComponent : PipeNetDeviceComponent + { + [ViewVariables] + private PipeNode _ventInlet; + + private AtmosphereSystem _atmosSystem; + + + + public override void Initialize() + { + base.Initialize(); + _atmosSystem = EntitySystem.Get(); + if (!Owner.TryGetComponent(out var container)) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BaseVentComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}."); + return; + } + _ventInlet = container.Nodes.OfType().FirstOrDefault(); + if (_ventInlet == null) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BaseVentComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + return; + } + } + + public override void Update() + { + var tileAtmos = AtmosHelpers.GetTileAtmosphere(Owner.Transform.GridPosition); + if (tileAtmos == null) + return; + VentGas(_ventInlet.Air, tileAtmos.Air); + _atmosSystem.GetGridAtmosphere(Owner.Transform.GridID).Invalidate(tileAtmos.GridIndices); + } + + protected abstract void VentGas(GasMixture inletGas, GasMixture outletGas); + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/DebugVentComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/DebugVentComponent.cs new file mode 100644 index 0000000000..211cbd440c --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/DebugVentComponent.cs @@ -0,0 +1,21 @@ +using Content.Server.Atmos; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Placeholder example of vent functionality. + /// + [RegisterComponent] + [ComponentReference(typeof(BaseVentComponent))] + public class DebugVentComponent : BaseVentComponent + { + public override string Name => "DebugVent"; + + protected override void VentGas(GasMixture inletGas, GasMixture outletGas) + { + outletGas.Merge(inletGas); + inletGas.Clear(); + } + } +} diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs index b019cbf4ea..e57ce1db5d 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Robust.Shared.IoC; +using Robust.Shared.Map; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups @@ -13,6 +14,8 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups { IReadOnlyList Nodes { get; } + void Initialize(Node sourceNode); + void AddNode(Node node); void RemoveNode(Node node); @@ -34,6 +37,13 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups public static readonly INodeGroup NullGroup = new NullNodeGroup(); + protected GridId GridId { get; private set;} + + public virtual void Initialize(Node sourceNode) + { + GridId = sourceNode.Owner.Transform.GridID; + } + public void AddNode(Node node) { _nodes.Add(node); @@ -54,6 +64,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups newGroup.CombineGroup(this); return; } + OnGivingNodesForCombine(newGroup); foreach (var node in Nodes) { node.NodeGroup = newGroup; @@ -70,23 +81,31 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups { node.ClearNodeGroup(); } + var newGroups = new List(); foreach (var node in Nodes) { if (node.TryAssignGroupIfNeeded()) { node.SpreadGroup(); + newGroups.Add(node.NodeGroup); } } + AfterRemake(newGroups); } protected virtual void OnAddNode(Node node) { } protected virtual void OnRemoveNode(Node node) { } + protected virtual void OnGivingNodesForCombine(INodeGroup newGroup) { } + + protected virtual void AfterRemake(IEnumerable newGroups) { } + private class NullNodeGroup : INodeGroup { public IReadOnlyList Nodes => _nodes; private readonly List _nodes = new List(); + public void Initialize(Node sourceNode) { } public void AddNode(Node node) { } public void CombineGroup(INodeGroup newGroup) { } public void RemoveNode(Node node) { } diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs new file mode 100644 index 0000000000..75f5f147b1 --- /dev/null +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs @@ -0,0 +1,100 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; +using Content.Server.GameObjects.EntitySystems; +using Content.Server.Interfaces; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.ViewVariables; +using System.Collections.Generic; + +namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups +{ + public interface IPipeNet : IGasMixtureHolder + { + /// + /// Causes gas in the PipeNet to react. + /// + void Update(); + } + + [NodeGroup(NodeGroupID.Pipe)] + public class PipeNet : BaseNodeGroup, IPipeNet + { + [ViewVariables] + public GasMixture Air { get; set; } = new GasMixture(); + + public static readonly IPipeNet NullNet = new NullPipeNet(); + + [ViewVariables] + private readonly List _pipes = new List(); + + [ViewVariables] + private IGridAtmosphereComponent _gridAtmos; + + public override void Initialize(Node sourceNode) + { + base.Initialize(sourceNode); + _gridAtmos = EntitySystem.Get() + .GetGridAtmosphere(GridId); + _gridAtmos?.AddPipeNet(this); + } + + public void Update() + { + Air.React(this); + } + + protected override void OnAddNode(Node node) + { + if (!(node is PipeNode pipeNode)) + return; + _pipes.Add(pipeNode); + pipeNode.JoinPipeNet(this); + Air.Volume += pipeNode.Volume; + Air.Merge(pipeNode.LocalAir); + pipeNode.LocalAir.Clear(); + } + + protected override void OnRemoveNode(Node node) + { + RemoveFromGridAtmos(); + if (!(node is PipeNode pipeNode)) + return; + var pipeAir = pipeNode.LocalAir; + pipeAir.Merge(Air); + pipeAir.Multiply(pipeNode.Volume / Air.Volume); + _pipes.Remove(pipeNode); + } + + protected override void OnGivingNodesForCombine(INodeGroup newGroup) + { + if (!(newGroup is IPipeNet newPipeNet)) + return; + newPipeNet.Air.Merge(Air); + Air.Clear(); + } + + protected override void AfterRemake(IEnumerable newGroups) + { + foreach (var newGroup in newGroups) + { + if (!(newGroup is IPipeNet newPipeNet)) + continue; + newPipeNet.Air.Merge(Air); + var newPipeNetGas = newPipeNet.Air; + newPipeNetGas.Multiply(newPipeNetGas.Volume / Air.Volume); + } + RemoveFromGridAtmos(); + } + + private void RemoveFromGridAtmos() + { + _gridAtmos.RemovePipeNet(this); + } + + private class NullPipeNet : IPipeNet + { + GasMixture IGasMixtureHolder.Air { get; set; } = new GasMixture(); + public void Update() { } + } + } +} diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs index 630437f04e..2133f67134 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs @@ -1,8 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Reflection; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.IoC; +using System; +using System.Collections.Generic; +using System.Reflection; namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups { @@ -17,7 +18,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups /// /// Returns a new instance. /// - INodeGroup MakeNodeGroup(NodeGroupID nodeGroupType); + INodeGroup MakeNodeGroup(Node sourceNode); } public class NodeGroupFactory : INodeGroupFactory @@ -29,7 +30,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups public void Initialize() { - var nodeGroupTypes = _reflectionManager.GetAllChildren(); + var nodeGroupTypes = _reflectionManager.GetAllChildren(); foreach (var nodeGroupType in nodeGroupTypes) { var att = nodeGroupType.GetCustomAttribute(); @@ -43,13 +44,15 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups } } - public INodeGroup MakeNodeGroup(NodeGroupID nodeGroupType) + public INodeGroup MakeNodeGroup(Node sourceNode) { - if (_groupTypes.TryGetValue(nodeGroupType, out var type)) + if (_groupTypes.TryGetValue(sourceNode.NodeGroupID, out var type)) { - return _typeFactory.CreateInstance(type); + var nodeGroup = _typeFactory.CreateInstance(type); + nodeGroup.Initialize(sourceNode); + return nodeGroup; } - throw new ArgumentException($"{nodeGroupType} did not have an associated {nameof(INodeGroup)}."); + throw new ArgumentException($"{sourceNode.NodeGroupID} did not have an associated {nameof(INodeGroup)}."); } } @@ -59,5 +62,6 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups HVPower, MVPower, Apc, + Pipe, } } diff --git a/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs b/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs index 291d681df4..02c6ef3cf7 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs @@ -53,7 +53,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes serializer.DataField(this, x => NodeGroupID, "nodeGroupID", NodeGroupID.Default); } - public void Initialize(IEntity owner) + public virtual void Initialize(IEntity owner) { Owner = owner; _nodeGroupFactory = IoCManager.Resolve(); @@ -143,7 +143,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes private INodeGroup MakeNewGroup() { - return _nodeGroupFactory.MakeNodeGroup(NodeGroupID); + return _nodeGroupFactory.MakeNodeGroup(this); } private void AnchorUpdate() diff --git a/Content.Server/GameObjects/Components/NodeContainer/Nodes/PipeNode.cs b/Content.Server/GameObjects/Components/NodeContainer/Nodes/PipeNode.cs new file mode 100644 index 0000000000..b35d16280f --- /dev/null +++ b/Content.Server/GameObjects/Components/NodeContainer/Nodes/PipeNode.cs @@ -0,0 +1,171 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; +using Content.Server.Interfaces; +using Content.Shared.Atmos; +using Robust.Shared.GameObjects.Components.Transform; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Content.Server.GameObjects.Components.NodeContainer.Nodes +{ + /// + /// Connects with other s whose + /// correctly correspond. + /// + public class PipeNode : Node, IGasMixtureHolder + { + [ViewVariables] + public PipeDirection PipeDirection => _pipeDirection; + private PipeDirection _pipeDirection; + + [ViewVariables] + private IPipeNet _pipeNet = PipeNet.NullNet; + + [ViewVariables] + private bool _needsPipeNet = true; + + /// + /// The gases in this pipe. + /// + [ViewVariables] + public GasMixture Air + { + get => _needsPipeNet ? LocalAir : _pipeNet.Air; + set + { + if (_needsPipeNet) + LocalAir = value; + else + _pipeNet.Air = value; + } + } + + /// + /// Stores gas in this pipe when disconnected from a . + /// Only for usage by s. + /// + [ViewVariables] + public GasMixture LocalAir { get; set; } + + [ViewVariables] + public float Volume { get; private set; } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _pipeDirection, "pipeDirection", PipeDirection.None); + serializer.DataField(this, x => Volume, "volume", 10); + } + + public override void Initialize(IEntity owner) + { + base.Initialize(owner); + LocalAir = new GasMixture(Volume); + } + + public void JoinPipeNet(IPipeNet pipeNet) + { + _pipeNet = pipeNet; + _needsPipeNet = false; + } + + public void ClearPipeNet() + { + _pipeNet = PipeNet.NullNet; + _needsPipeNet = true; + } + + protected override IEnumerable GetReachableNodes() + { + foreach (CardinalDirection direction in Enum.GetValues(typeof(CardinalDirection))) + { + PipeDirectionFromCardinal(direction, out var ownNeededConnection, out var theirNeededConnection); + if ((_pipeDirection & ownNeededConnection) == PipeDirection.None) + { + continue; + } + var pipeNodesInDirection = Owner.GetComponent() + .GetInDir((Direction) direction) + .Select(entity => entity.TryGetComponent(out var container) ? container : null) + .Where(container => container != null) + .SelectMany(container => container.Nodes) + .OfType() + .Where(pipeNode => (pipeNode._pipeDirection & theirNeededConnection) != PipeDirection.None); + foreach (var pipeNode in pipeNodesInDirection) + { + yield return pipeNode; + } + } + } + + private void PipeDirectionFromCardinal(CardinalDirection direction, out PipeDirection sameDir, out PipeDirection oppDir) + { + switch (direction) + { + case CardinalDirection.North: + sameDir = PipeDirection.North; + oppDir = PipeDirection.South; + break; + case CardinalDirection.South: + sameDir = PipeDirection.South; + oppDir = PipeDirection.North; + break; + case CardinalDirection.East: + sameDir = PipeDirection.East; + oppDir = PipeDirection.West; + break; + case CardinalDirection.West: + sameDir = PipeDirection.West; + oppDir = PipeDirection.East; + break; + default: + throw new ArgumentException("Invalid Direction."); + } + } + + private enum CardinalDirection + { + North = Direction.North, + South = Direction.South, + East = Direction.East, + West = Direction.West, + } + } + + public enum PipeDirection + { + None = 0, + + //Half of a pipe in a direction + North = 1 << 0, + South = 1 << 1, + West = 1 << 2, + East = 1 << 3, + + //Straight pipes + Longitudinal = North | South, + Lateral = West | East, + + //Bends + NWBend = North | West, + NEBend = North | East, + SWBend = South | West, + SEBend = South | East, + + //T-Junctions + TNorth = North | Lateral, + TSouth = South | Lateral, + TWest = West | Longitudinal, + TEast = East | Longitudinal, + + //Four way + FourWay = North | South | East | West, + + All = -1, + } +} diff --git a/Resources/Prototypes/Entities/Constructible/Ground/pipes.yml b/Resources/Prototypes/Entities/Constructible/Ground/pipes.yml new file mode 100644 index 0000000000..5dcbab768c --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Ground/pipes.yml @@ -0,0 +1,43 @@ +- type: entity + abstract: true + id: PipeBase + placement: + mode: SnapgridCenter + components: + - type: Clickable + - type: InteractionOutline + - type: Collidable + - type: SnapGrid + offset: Center + - type: Icon + texture: Constructible/Power/eightdirwire.png + - type: Sprite + sprite: Constructible/Power/hv_cable.rsi + - type: Destructible + thresholdvalue: 100 + +- type: entity + parent: PipeBase + id: FourwayPipe + name: Fourway Pipe + components: + - type: Sprite + state: hvcable_15 + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: FourWay + +- type: entity + parent: PipeBase + id: LongitudinalPipe + name: Longitudinal Pipe + components: + - type: Sprite + state: hvcable_3 + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: Longitudinal diff --git a/Resources/Prototypes/Entities/Constructible/Ground/pumps.yml b/Resources/Prototypes/Entities/Constructible/Ground/pumps.yml new file mode 100644 index 0000000000..e3741e762e --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Ground/pumps.yml @@ -0,0 +1,36 @@ +- type: entity + abstract: true + id: PumpBase + placement: + mode: SnapgridCenter + components: + - type: Clickable + - type: InteractionOutline + - type: Collidable + - type: SnapGrid + offset: Center + - type: Icon + texture: Constructible/Power/eightdirwire.png + - type: Sprite + sprite: Constructible/Power/mv_cable.rsi + - type: Destructible + thresholdvalue: 100 + +- type: entity + parent: PumpBase + id: NorthFromSouthPipePump + name: North from south pipe pump + components: + - type: Sprite + state: mvcable_3 + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: North + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: South + - type: DebugPump + outletDirection: North + inletDirection: South diff --git a/Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml b/Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml new file mode 100644 index 0000000000..2372c3e65d --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml @@ -0,0 +1,30 @@ +- type: entity + abstract: true + id: ScrubberBase + placement: + mode: SnapgridCenter + components: + - type: Clickable + - type: InteractionOutline + - type: Collidable + - type: SnapGrid + offset: Center + - type: Icon + texture: Constructible/Power/eightdirwire.png + - type: Sprite + texture: Constructible/Power/eightdirwire.png + - type: Destructible + thresholdvalue: 100 + +- type: entity + parent: ScrubberBase + id: FromSouthScrubber + name: From South Scrubber + components: + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroID: Pipe + pipeDirection: South + - type: DebugSiphon + scrubberOutletDirection: South diff --git a/Resources/Prototypes/Entities/Constructible/Ground/vents.yml b/Resources/Prototypes/Entities/Constructible/Ground/vents.yml new file mode 100644 index 0000000000..2940ec1b08 --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Ground/vents.yml @@ -0,0 +1,30 @@ +- type: entity + abstract: true + id: VentBase + placement: + mode: SnapgridCenter + components: + - type: Clickable + - type: InteractionOutline + - type: Collidable + - type: SnapGrid + offset: Center + - type: Icon + texture: Constructible/Power/eightdirwire.png + - type: Sprite + texture: Constructible/Power/eightdirwire.png + - type: Destructible + thresholdvalue: 100 + +- type: entity + parent: VentBase + id: FromSouthVent + name: From South Vent + components: + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: South + - type: DebugVent + ventInletDirection: South