Atmos pipe rework (#3833)

* Initial

* Cleanup a bunch of things

* some changes dunno

* RequireAnchored

* a

* stuff

* more work

* Lots of progress

* delete pipe visualizer

* a

* b

* pipenet and pipenode cleanup

* Fixes

* Adds GasValve

* Adds GasMiner

* Fix stuff, maybe?

* More fixes

* Ignored components on the client

* Adds thermomachine behavior, change a bunch of stuff

* Remove Anchored

* some work, but it's shitcode

* significantly more ECS

* ECS AtmosDevices

* Cleanup

* fix appearance

* when the pipe direction is sus

* Gas tanks and canisters

* pipe anchoring and stuff

* coding is my passion

* Unsafe pipes take longer to unanchor

* turns out we're no longer using eris canisters

* Gas canister inserted tank appearance, improvements

* Work on a bunch of appearances

* Scrubber appearance

* Reorganize AtmosphereSystem.Piping into a bunch of different systems

* Appearance for vent/scrubber/pump turns off when leaving atmosphere

* ThermoMachine appearance

* Cleanup gas tanks

* Remove passive gate unused imports

* remove old canister UI functionality

* PipeNode environment air, make everything use AssumeAir instead of merging manually

* a

* Reorganize atmos to follow new structure

* ?????

* Canister UI, restructure client

* Restructure shared

* Fix build tho

* listen, at least the canister UI works entirely...

* fix build : )

* Atmos device prototypes have names and descriptions

* gas canister ui slider doesn't jitter

* trinary prototypes

* sprite for miners

* ignore components

* fix YAML

* Fix port system doing useless thing

* Fix build

* fix thinking moment

* fix build again because

* canister direction

* pipenode is a word

* GasTank Air will throw on invalid states

* fix build....

* Unhardcode volume pump thresholds

* Volume pump and filter take time into account

* Rename Join/Leave atmosphere events to AtmosDeviceEnabled/Disabled Event

* Gas tank node volume is set by initial mixtuer

* I love node container
This commit is contained in:
Vera Aguilera Puerto
2021-06-19 13:25:05 +02:00
committed by GitHub
parent cfc3f2e7fc
commit a2b737d945
250 changed files with 3964 additions and 3163 deletions

View File

@@ -2,7 +2,6 @@ using Content.Server.AI.Pathfinding.Accessible;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.Storage.Components;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interaction;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;

View File

@@ -1,4 +1,4 @@
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Alert;
using JetBrains.Annotations;
using Robust.Shared.Serialization.Manager.Attributes;

View File

@@ -1,5 +1,4 @@
using Content.Shared.Alert;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Pulling;
using Content.Shared.Pulling.Components;
using JetBrains.Annotations;

View File

@@ -1,6 +1,6 @@
#nullable enable
using System.Diagnostics.CodeAnalysis;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;

View File

@@ -1,16 +1,15 @@
#nullable enable
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
public class AirtightComponent : Component, IMapInit
@@ -96,6 +95,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return;
_currentAirBlockedDirection = (int) Rotate((AtmosDirection)_initialAirBlockedDirection, ev.NewRotation);
UpdatePosition();
}
private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle)
@@ -118,26 +118,18 @@ namespace Content.Server.GameObjects.Components.Atmos
return newAirBlockedDirs;
}
/// <inheritdoc />
public void MapInit()
{
if (Owner.Transform.Anchored)
{
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
_lastPosition = (Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
}
UpdatePosition();
}
/// <inheritdoc />
protected override void Shutdown()
{
base.Shutdown();
_airBlocked = false;
UpdatePosition(_lastPosition.Item1, _lastPosition.Item2);
InvalidatePosition(_lastPosition.Item1, _lastPosition.Item2);
if (_fixVacuum)
{
@@ -145,31 +137,31 @@ namespace Content.Server.GameObjects.Components.Atmos
}
}
public void OnTransformMove()
public void OnSnapGridMove(SnapGridPositionChangedEvent ev)
{
UpdatePosition(_lastPosition.Item1, _lastPosition.Item2);
UpdatePosition();
// Invalidate old position.
InvalidatePosition(ev.OldGrid, ev.OldPosition);
if (Owner.Transform.Anchored)
{
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
_lastPosition = (Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
}
// Update and invalidate new position.
_lastPosition = (ev.NewGrid, ev.Position);
InvalidatePosition(ev.NewGrid, ev.Position);
}
private void UpdatePosition()
{
if (Owner.Transform.Anchored)
{
if (!Owner.Transform.GridID.IsValid())
return;
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
UpdatePosition(Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
}
if (!Owner.Transform.Anchored || !Owner.Transform.GridID.IsValid())
return;
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
_lastPosition = (Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
InvalidatePosition(_lastPosition.Item1, _lastPosition.Item2);
}
private void UpdatePosition(GridId gridId, Vector2i pos)
private void InvalidatePosition(GridId gridId, Vector2i pos)
{
if (!gridId.IsValid())
return;
var gridAtmos = _atmosphereSystem.GetGridAtmosphere(gridId);
gridAtmos?.UpdateAdjacentBits(pos);

View File

@@ -1,10 +1,9 @@
#nullable enable
using Content.Server.Atmos;
using Content.Server.Temperature.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
/// <summary>
/// Represents that entity can be exposed to Atmos

View File

@@ -1,4 +1,4 @@
using Content.Shared.GameObjects.Components.Atmos;
using Content.Shared.Atmos.Visuals;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -6,7 +6,7 @@ using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
public sealed class AtmosPlaqueComponent : Component, IMapInit

View File

@@ -8,7 +8,7 @@ using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
/// <summary>
/// Barotrauma: injury because of changes in air pressure.

View File

@@ -2,7 +2,6 @@ using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.ActionBlocker;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Notification.Managers;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;

View File

@@ -4,7 +4,7 @@ using Content.Shared.Inventory;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
/// <summary>
/// Used in internals as breath tool.

View File

@@ -1,16 +1,14 @@
#nullable enable
using Robust.Shared.GameObjects;
using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Doors;
using Content.Server.Doors.Components;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Doors;
using Content.Shared.Interaction;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
/// <summary>
/// Companion component to ServerDoorComponent that handles firelock-specific behavior -- primarily prying, and not being openable on open-hand click.

View File

@@ -2,17 +2,15 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Content.Server.Alert;
using Content.Server.Atmos;
using Content.Server.Stunnable.Components;
using Content.Server.Temperature.Components;
using Content.Shared.ActionBlocker;
using Content.Shared.Alert;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.GameObjects.Components.Atmos;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Notification.Managers;
using Content.Shared.Temperature;
using Robust.Server.GameObjects;
@@ -24,7 +22,7 @@ using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
public class FlammableComponent : SharedFlammableComponent, IStartCollide, IFireAct, IInteractUsing

View File

@@ -1,16 +1,13 @@
#nullable enable
using System.Collections.Generic;
using System.Threading.Tasks;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Hands.Components;
using Content.Server.UserInterface;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.DragDrop;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Atmos;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers;
using Robust.Server.GameObjects;
using Robust.Server.Player;
@@ -20,7 +17,7 @@ using Robust.Shared.Map;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
public class GasAnalyzerComponent : SharedGasAnalyzerComponent, IAfterInteract, IDropped, IUse

View File

@@ -1,12 +1,9 @@
using Content.Server.Atmos;
using Content.Server.Interfaces;
using Content.Server.Interfaces;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
public class GasMixtureHolderComponent : Component, IGasMixtureHolder

View File

@@ -1,22 +1,22 @@
#nullable enable
#nullable disable warnings
using System;
using Content.Server.Atmos;
using Content.Server.Body.Respiratory;
using Content.Server.Explosion;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.Interfaces;
using Content.Server.NodeContainer;
using Content.Server.UserInterface;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions;
using Content.Shared.Actions.Behaviors.Item;
using Content.Shared.Actions.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Audio;
using Content.Shared.DragDrop;
using Content.Shared.Examine;
using Content.Shared.GameObjects.Components.Atmos.GasTank;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
@@ -30,12 +30,14 @@ using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public class GasTankComponent : SharedGasTankComponent, IExamine, IGasMixtureHolder, IUse, IDropped, IActivate
public class GasTankComponent : Component, IExamine, IGasMixtureHolder, IUse, IDropped, IActivate
{
public override string Name => "GasTank";
private const float MaxExplosionRange = 14f;
private const float DefaultOutputPressure = Atmospherics.OneAtmosphere;
@@ -45,7 +47,35 @@ namespace Content.Server.GameObjects.Components.Atmos
[ViewVariables] private BoundUserInterface? _userInterface;
[DataField("air")] [ViewVariables] public GasMixture? Air { get; set; } = new();
[ViewVariables]
public GasMixture Air
{
// TODO ATMOS Kill it with fire.
get
{
if (!Owner.TryGetComponent(out NodeContainerComponent nodeContainer))
throw new InvalidOperationException("Can't get tank air without a node container!");
if (!nodeContainer.TryGetNode(TankName, out PipeNode? node))
throw new InvalidOperationException($"Node container doesn't have a pipenode called {TankName}!");
return node.Air;
}
set
{
// This will throw if the node container is not found.
var nodeContainer = Owner.GetComponent<NodeContainerComponent>();
if (!nodeContainer.TryGetNode(TankName, out PipeNode? node))
throw new InvalidOperationException($"Node container doesn't have a pipenode called {TankName}!");
node.Air = value;
}
}
[DataField("air")] [ViewVariables]
public GasMixture InitialMixture { get; set; } = new();
/// <summary>
/// Distributed pressure.
@@ -88,6 +118,12 @@ namespace Content.Server.GameObjects.Components.Atmos
[DataField("tankFragmentScale")]
public float TankFragmentScale { get; set; } = 10 * Atmospherics.OneAtmosphere;
/// <summary>
/// NodeContainer node.
/// </summary>
[DataField("tank")]
public string TankName { get; set; } = "tank";
public override void Initialize()
{
base.Initialize();

View File

@@ -5,10 +5,8 @@ using System.Collections;
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.EntitySystems;
using Content.Server.GameObjects.EntitySystems.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Content.Shared.Maps;
@@ -24,7 +22,7 @@ using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
using Dependency = Robust.Shared.IoC.DependencyAttribute;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
/// <summary>
/// This is our SSAir equivalent.
@@ -36,6 +34,7 @@ namespace Content.Server.GameObjects.Components.Atmos
[Dependency] private IMapManager _mapManager = default!;
[Dependency] private ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private IServerEntityManager _serverEntityManager = default!;
[Dependency] private IGameTiming _gameTiming = default!;
public GridTileLookupSystem GridTileLookupSystem { get; private set; } = default!;
internal GasTileOverlaySystem GasTileOverlaySystem { get; private set; } = default!;
@@ -55,6 +54,8 @@ namespace Content.Server.GameObjects.Components.Atmos
[ComponentDependency] private IMapGridComponent? _mapGridComponent;
public virtual bool Simulated => true;
[ViewVariables]
public int UpdateCounter { get; private set; } = 0;
@@ -128,10 +129,10 @@ namespace Content.Server.GameObjects.Components.Atmos
private double _pipeNetLastProcess;
[ViewVariables]
private readonly HashSet<PipeNetDeviceComponent> _pipeNetDevices = new();
private readonly HashSet<AtmosDeviceComponent> _atmosDevices = new();
[ViewVariables]
private double _pipeNetDevicesLastProcess;
private double _atmosDevicesLastProcess;
[ViewVariables]
private Queue<TileAtmosphere> _currentRunTiles = new();
@@ -143,7 +144,7 @@ namespace Content.Server.GameObjects.Components.Atmos
private Queue<IPipeNet> _currentRunPipeNet = new();
[ViewVariables]
private Queue<PipeNetDeviceComponent> _currentRunPipeNetDevice = new();
private Queue<AtmosDeviceComponent> _currentRunAtmosDevices = new();
[ViewVariables]
private ProcessState _state = ProcessState.TileEqualize;
@@ -162,7 +163,7 @@ namespace Content.Server.GameObjects.Components.Atmos
Hotspots,
Superconductivity,
PipeNet,
PipeNetDevices,
AtmosDevices,
}
/// <inheritdoc />
@@ -455,14 +456,14 @@ namespace Content.Server.GameObjects.Components.Atmos
_pipeNets.Remove(pipeNet);
}
public virtual void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice)
public virtual void AddAtmosDevice(AtmosDeviceComponent atmosDevice)
{
_pipeNetDevices.Add(pipeNetDevice);
_atmosDevices.Add(atmosDevice);
}
public virtual void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice)
public virtual void RemoveAtmosDevice(AtmosDeviceComponent atmosDevice)
{
_pipeNetDevices.Remove(pipeNetDevice);
_atmosDevices.Remove(atmosDevice);
}
/// <inheritdoc />
@@ -634,10 +635,10 @@ namespace Content.Server.GameObjects.Components.Atmos
}
_paused = false;
_state = ProcessState.PipeNetDevices;
_state = ProcessState.AtmosDevices;
break;
case ProcessState.PipeNetDevices:
if (!ProcessPipeNetDevices(_paused, maxProcessTime))
case ProcessState.AtmosDevices:
if (!ProcessAtmosDevices(_paused, maxProcessTime))
{
_paused = true;
return;
@@ -857,30 +858,33 @@ namespace Content.Server.GameObjects.Components.Atmos
return true;
}
protected virtual bool ProcessPipeNetDevices(bool resumed = false, float lagCheck = 5f)
protected virtual bool ProcessAtmosDevices(bool resumed = false, float lagCheck = 5f)
{
_stopwatch.Restart();
if(!resumed)
_currentRunPipeNetDevice = new Queue<PipeNetDeviceComponent>(_pipeNetDevices);
_currentRunAtmosDevices = new Queue<AtmosDeviceComponent>(_atmosDevices);
var time = _gameTiming.CurTime;
var updateEvent = new AtmosDeviceUpdateEvent(this);
var number = 0;
while (_currentRunPipeNetDevice.Count > 0)
while (_currentRunAtmosDevices.Count > 0)
{
var device = _currentRunPipeNetDevice.Dequeue();
device.Update();
var device = _currentRunAtmosDevices.Dequeue();
Owner.EntityManager.EventBus.RaiseLocalEvent(device.Owner.Uid, updateEvent, false);
device.LastProcess = time;
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= lagCheck)
{
_pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
_atmosDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
_atmosDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}

View File

@@ -1,17 +1,21 @@
#nullable enable
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.GameObjects.Components.Atmos.Piping;
using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Content.Server.Atmos
namespace Content.Server.Atmos.Components
{
public interface IGridAtmosphereComponent : IComponent, IEnumerable<TileAtmosphere>
{
/// <summary>
/// Whether this atmosphere is simulated or not.
/// </summary>
bool Simulated { get; }
/// <summary>
/// Number of times <see cref="Update"/> has been called.
/// </summary>
@@ -173,8 +177,8 @@ namespace Content.Server.Atmos
void RemovePipeNet(IPipeNet pipeNet);
void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice);
void AddAtmosDevice(AtmosDeviceComponent atmosDevice);
void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice);
void RemoveAtmosDevice(AtmosDeviceComponent atmosDevice);
}
}

View File

@@ -13,7 +13,7 @@ using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
public class MovedByPressureComponent : Component

View File

@@ -1,11 +1,9 @@
using Content.Server.Pressure;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
public class PressureProtectionComponent : Component, IPressureProtection

View File

@@ -1,12 +1,11 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
[ComponentReference(typeof(IGridAtmosphereComponent))]

View File

@@ -1,13 +1,12 @@
#nullable enable
using System;
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.Atmos.Piping;
using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Server.GameObjects.Components.Atmos
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
[ComponentReference(typeof(IGridAtmosphereComponent))]
@@ -17,6 +16,8 @@ namespace Content.Server.GameObjects.Components.Atmos
{
public override string Name => "UnsimulatedGridAtmosphere";
public override bool Simulated => false;
public override void PryTile(Vector2i indices) { }
public override void RepopulateTiles()
@@ -63,9 +64,9 @@ namespace Content.Server.GameObjects.Components.Atmos
public override void RemovePipeNet(IPipeNet pipeNet) { }
public override void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice) { }
public override void AddAtmosDevice(AtmosDeviceComponent atmosDevice) { }
public override void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice) { }
public override void RemoveAtmosDevice(AtmosDeviceComponent atmosDevice) { }
public override void Update(float frameTime) { }
@@ -104,7 +105,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return false;
}
protected override bool ProcessPipeNetDevices(bool resumed = false, float lagCheck = 5f)
protected override bool ProcessAtmosDevices(bool resumed = false, float lagCheck = 5f)
{
return false;
}

View File

@@ -0,0 +1,26 @@
using Content.Server.Atmos.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos.EntitySystems
{
[UsedImplicitly]
public class AirtightSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<AirtightComponent, SnapGridPositionChangedEvent>(OnAirtightPositionChanged);
SubscribeLocalEvent<AirtightComponent, RotateEvent>(OnAirtightRotated);
}
private void OnAirtightPositionChanged(EntityUid uid, AirtightComponent component, SnapGridPositionChangedEvent args)
{
component.OnSnapGridMove(args);
}
private void OnAirtightRotated(EntityUid uid, AirtightComponent airtight, RotateEvent ev)
{
airtight.RotateEvent(ev);
}
}
}

View File

@@ -1,11 +1,9 @@
#nullable enable
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos;
using Content.Shared;
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.CCVar;
using Content.Shared.GameObjects.EntitySystems.Atmos;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.Configuration;
@@ -15,7 +13,7 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Content.Server.GameObjects.EntitySystems.Atmos
namespace Content.Server.Atmos.EntitySystems
{
[UsedImplicitly]
public sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem

View File

@@ -0,0 +1,24 @@
using Content.Shared.CCVar;
namespace Content.Server.Atmos.EntitySystems
{
public partial class AtmosphereSystem
{
public bool SpaceWind { get; private set; }
public bool MonstermosEqualization { get; private set; }
public bool Superconduction { get; private set; }
public bool ExcitedGroupsSpaceIsAllConsuming { get; private set; }
public float AtmosMaxProcessTime { get; private set; }
public float AtmosTickRate { get; private set; }
private void InitializeCVars()
{
_cfg.OnValueChanged(CCVars.SpaceWind, value => SpaceWind = value, true);
_cfg.OnValueChanged(CCVars.MonstermosEqualization, value => MonstermosEqualization = value, true);
_cfg.OnValueChanged(CCVars.Superconduction, value => Superconduction = value, true);
_cfg.OnValueChanged(CCVars.AtmosMaxProcessTime, value => AtmosMaxProcessTime = value, true);
_cfg.OnValueChanged(CCVars.AtmosTickRate, value => AtmosTickRate = value, true);
_cfg.OnValueChanged(CCVars.ExcitedGroupsSpaceIsAllConsuming, value => ExcitedGroupsSpaceIsAllConsuming = value, true);
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.Atmos.Reactions;
using Content.Shared.Atmos;
using Robust.Shared.Maths;
namespace Content.Server.Atmos.EntitySystems
{
public partial class AtmosphereSystem
{
private GasReactionPrototype[] _gasReactions = Array.Empty<GasReactionPrototype>();
private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases];
/// <summary>
/// List of gas reactions ordered by priority.
/// </summary>
public IEnumerable<GasReactionPrototype> GasReactions => _gasReactions!;
public float[] GasSpecificHeats => _gasSpecificHeats;
private void InitializeGases()
{
_gasReactions = _protoMan.EnumeratePrototypes<GasReactionPrototype>().ToArray();
Array.Sort(_gasReactions, (a, b) => b.Priority.CompareTo(a.Priority));
Array.Resize(ref _gasSpecificHeats, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4));
for (var i = 0; i < GasPrototypes.Length; i++)
{
_gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat;
}
}
}
}

View File

@@ -1,14 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.Atmos;
using Content.Server.Atmos.Reactions;
using Content.Server.GameObjects.Components.Atmos;
using Content.Shared;
using Content.Shared.Atmos;
using Content.Shared.CCVar;
using Content.Shared.GameObjects.EntitySystems.Atmos;
using System.Diagnostics.CodeAnalysis;
using Content.Server.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
@@ -16,31 +9,23 @@ using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.GameObjects.EntitySystems
namespace Content.Server.Atmos.EntitySystems
{
[UsedImplicitly]
public class AtmosphereSystem : SharedAtmosphereSystem
public partial class AtmosphereSystem : SharedAtmosphereSystem
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPauseManager _pauseManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private GasReactionPrototype[] _gasReactions = Array.Empty<GasReactionPrototype>();
private GridTileLookupSystem? _gridTileLookup = null;
/// <summary>
/// List of gas reactions ordered by priority.
/// </summary>
public IEnumerable<GasReactionPrototype> GasReactions => _gasReactions!;
private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases];
public float[] GasSpecificHeats => _gasSpecificHeats;
private const float ExposedUpdateDelay = 1f;
private float _exposedTimer = 0f;
public GridTileLookupSystem GridTileLookupSystem => _gridTileLookup ??= Get<GridTileLookupSystem>();
@@ -48,71 +33,16 @@ namespace Content.Server.GameObjects.EntitySystems
{
base.Initialize();
_gasReactions = _protoMan.EnumeratePrototypes<GasReactionPrototype>().ToArray();
Array.Sort(_gasReactions, (a, b) => b.Priority.CompareTo(a.Priority));
InitializeGases();
InitializeCVars();
#region Events
// Map events.
_mapManager.MapCreated += OnMapCreated;
_mapManager.TileChanged += OnTileChanged;
Array.Resize(ref _gasSpecificHeats, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4));
for (var i = 0; i < GasPrototypes.Length; i++)
{
_gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat;
}
// Required for airtight components.
SubscribeLocalEvent<RotateEvent>(RotateEvent);
SubscribeLocalEvent<AirtightComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
_cfg.OnValueChanged(CCVars.SpaceWind, OnSpaceWindChanged, true);
_cfg.OnValueChanged(CCVars.MonstermosEqualization, OnMonstermosEqualizationChanged, true);
_cfg.OnValueChanged(CCVars.Superconduction, OnSuperconductionChanged, true);
_cfg.OnValueChanged(CCVars.AtmosMaxProcessTime, OnAtmosMaxProcessTimeChanged, true);
_cfg.OnValueChanged(CCVars.AtmosTickRate, OnAtmosTickRateChanged, true);
_cfg.OnValueChanged(CCVars.ExcitedGroupsSpaceIsAllConsuming, OnExcitedGroupsSpaceIsAllConsumingChanged, true);
}
private static void HandleSnapGridMove(EntityUid uid, AirtightComponent component, SnapGridPositionChangedEvent args)
{
component.OnTransformMove();
}
public bool SpaceWind { get; private set; }
public bool MonstermosEqualization { get; private set; }
public bool Superconduction { get; private set; }
public bool ExcitedGroupsSpaceIsAllConsuming { get; private set; }
public float AtmosMaxProcessTime { get; private set; }
public float AtmosTickRate { get; private set; }
private void OnExcitedGroupsSpaceIsAllConsumingChanged(bool obj)
{
ExcitedGroupsSpaceIsAllConsuming = obj;
}
private void OnAtmosTickRateChanged(float obj)
{
AtmosTickRate = obj;
}
private void OnAtmosMaxProcessTimeChanged(float obj)
{
AtmosMaxProcessTime = obj;
}
private void OnMonstermosEqualizationChanged(bool obj)
{
MonstermosEqualization = obj;
}
private void OnSuperconductionChanged(bool obj)
{
Superconduction = obj;
}
private void OnSpaceWindChanged(bool obj)
{
SpaceWind = obj;
#endregion
}
public override void Shutdown()
@@ -120,16 +50,35 @@ namespace Content.Server.GameObjects.EntitySystems
base.Shutdown();
_mapManager.MapCreated -= OnMapCreated;
_mapManager.TileChanged -= OnTileChanged;
}
private void RotateEvent(RotateEvent ev)
private void OnTileChanged(object? sender, TileChangedEventArgs eventArgs)
{
if (ev.Sender.TryGetComponent(out AirtightComponent? airtight))
// When a tile changes, we want to update it only if it's gone from
// space -> not space or vice versa. So if the old tile is the
// same as the new tile in terms of space-ness, ignore the change
if (eventArgs.NewTile.IsSpace() == eventArgs.OldTile.IsSpace())
{
airtight.RotateEvent(ev);
return;
}
GetGridAtmosphere(eventArgs.NewTile.GridIndex)?.Invalidate(eventArgs.NewTile.GridIndices);
}
private void OnMapCreated(object? sender, MapEventArgs e)
{
if (e.Map == MapId.Nullspace)
return;
var map = _mapManager.GetMapEntity(e.Map);
if (!map.HasComponent<IGridAtmosphereComponent>())
map.AddComponent<SpaceGridAtmosphereComponent>();
}
#region Helper Methods
public IGridAtmosphereComponent? GetGridAtmosphere(GridId gridId)
{
if (!gridId.IsValid())
@@ -165,41 +114,61 @@ namespace Content.Server.GameObjects.EntitySystems
return _mapManager.GetMapEntity(coordinates.MapId).GetComponent<IGridAtmosphereComponent>();
}
/// <summary>
/// Unlike <see cref="GetGridAtmosphere"/>, this doesn't return space grid when not found.
/// </summary>
public bool TryGetSimulatedGridAtmosphere(MapCoordinates coordinates, [NotNullWhen(true)] out IGridAtmosphereComponent? atmosphere)
{
if (coordinates.MapId == MapId.Nullspace)
{
atmosphere = null;
return false;
}
if (_mapManager.TryFindGridAt(coordinates, out var mapGrid)
&& ComponentManager.TryGetComponent(mapGrid.GridEntityId, out IGridAtmosphereComponent? atmosGrid)
&& atmosGrid.Simulated)
{
atmosphere = atmosGrid;
return true;
}
if (_mapManager.GetMapEntity(coordinates.MapId).TryGetComponent(out IGridAtmosphereComponent? atmosMap)
&& atmosMap.Simulated)
{
atmosphere = atmosMap;
return true;
}
atmosphere = null;
return false;
}
#endregion
public override void Update(float frameTime)
{
base.Update(frameTime);
_exposedTimer += frameTime;
foreach (var (mapGridComponent, gridAtmosphereComponent) in EntityManager.ComponentManager.EntityQuery<IMapGridComponent, IGridAtmosphereComponent>(true))
{
if (_pauseManager.IsGridPaused(mapGridComponent.GridIndex)) continue;
gridAtmosphereComponent.Update(frameTime);
}
}
private void OnTileChanged(object? sender, TileChangedEventArgs eventArgs)
{
// When a tile changes, we want to update it only if it's gone from
// space -> not space or vice versa. So if the old tile is the
// same as the new tile in terms of space-ness, ignore the change
if (eventArgs.NewTile.IsSpace() == eventArgs.OldTile.IsSpace())
if (_exposedTimer >= ExposedUpdateDelay)
{
return;
foreach (var exposed in EntityManager.ComponentManager.EntityQuery<AtmosExposedComponent>(true))
{
var tile = exposed.Owner.Transform.Coordinates.GetTileAtmosphere();
if (tile == null) continue;
exposed.Update(tile, _exposedTimer);
}
_exposedTimer = 0;
}
GetGridAtmosphere(eventArgs.NewTile.GridPosition(_mapManager))?.Invalidate(eventArgs.NewTile.GridIndices);
}
private void OnMapCreated(object? sender, MapEventArgs e)
{
if (e.Map == MapId.Nullspace)
return;
var map = _mapManager.GetMapEntity(e.Map);
if (!map.HasComponent<IGridAtmosphereComponent>())
map.AddComponent<SpaceGridAtmosphereComponent>();
}
}
}

View File

@@ -1,11 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Interaction;
using Content.Server.GameObjects.Components;
using Content.Shared.GameTicking;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using JetBrains.Annotations;
namespace Content.Server.GameObjects.EntitySystems

View File

@@ -1,8 +1,8 @@
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.EntitySystems
namespace Content.Server.Atmos.EntitySystems
{
[UsedImplicitly]
public class GasAnalyzerSystem : EntitySystem

View File

@@ -1,8 +1,8 @@
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.EntitySystems
namespace Content.Server.Atmos.EntitySystems
{
[UsedImplicitly]
public class GasTankSystem : EntitySystem

View File

@@ -3,11 +3,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Content.Server.GameObjects.Components.Atmos;
using Content.Shared;
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.CCVar;
using Content.Shared.GameObjects.EntitySystems.Atmos;
using Content.Shared.GameTicking;
using JetBrains.Annotations;
using Robust.Server.Player;
@@ -15,14 +14,14 @@ using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
// ReSharper disable once RedundantUsingDirective
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
// ReSharper disable once RedundantUsingDirective
using Dependency = Robust.Shared.IoC.DependencyAttribute;
namespace Content.Server.GameObjects.EntitySystems.Atmos
namespace Content.Server.Atmos.EntitySystems
{
[UsedImplicitly]
internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem, IResettingEntitySystem

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Robust.Shared.ViewVariables;

View File

@@ -3,8 +3,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Reactions;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
@@ -154,8 +154,7 @@ namespace Content.Server.Atmos
var combinedHeatCapacity = HeatCapacity + giver.HeatCapacity;
if (combinedHeatCapacity > 0f)
{
Temperature =
(giver.Temperature * giver.HeatCapacity + Temperature * HeatCapacity) / combinedHeatCapacity;
Temperature = (giver.Temperature * giver.HeatCapacity + Temperature * HeatCapacity) / combinedHeatCapacity;
}
}
@@ -600,5 +599,18 @@ namespace Content.Server.Atmos
};
return newMixture;
}
public void ScrubInto(GasMixture destination, IReadOnlyCollection<Gas> filterGases)
{
var buffer = new GasMixture(Volume){Temperature = Temperature};
foreach (var gas in filterGases)
{
buffer.AdjustMoles(gas, GetMoles(gas));
SetMoles(gas, 0f);
}
destination.Merge(buffer);
}
}
}

View File

@@ -0,0 +1,52 @@
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Binary.Components
{
[RegisterComponent]
public class GasCanisterComponent : Component
{
public override string Name => "GasCanister";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("port")]
public string PortName { get; set; } = "port";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("tank")]
public string TankName { get; set; } = "tank";
/// <summary>
/// Container name for the gas tank holder.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("container")]
public string ContainerName { get; set; } = "GasCanisterTankHolder";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("gasMixture")]
public GasMixture InitialMixture { get; } = new();
/// <summary>
/// Stores the last pressure the tank had, for appearance-updating purposes.
/// </summary>
[ViewVariables]
public float LastPressure { get; set; } = 0f;
/// <summary>
/// Minimum release pressure possible for the release valve.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("minReleasePressure")]
public float MinReleasePressure { get; set; } = Atmospherics.OneAtmosphere / 10;
/// <summary>
/// Maximum release pressure possible for the release valve.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
[DataField("maxReleasePressure")]
public float MaxReleasePressure { get; set; } = Atmospherics.OneAtmosphere * 10;
}
}

View File

@@ -0,0 +1,35 @@
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Binary.Components
{
[RegisterComponent]
public class GasPassiveGateComponent : Component
{
public override string Name => "GasPassiveGate";
[DataField("enabled")]
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled { get; set; } = true;
/// <summary>
/// This is the minimum difference needed to overcome the friction in the mechanism.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("frictionDifference")]
public float FrictionPressureDifference { get; set; } = 10f;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("inlet")]
public string InletName { get; set; } = "inlet";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("outlet")]
public string OutletName { get; set; } = "outlet";
[ViewVariables(VVAccess.ReadWrite)]
public float TargetPressure { get; set; } = Atmospherics.OneAtmosphere;
}
}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Binary.Components
{
[RegisterComponent]
public class GasPortComponent : Component
{
public override string Name => "GasPort";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("pipe")]
public string PipeName { get; set; } = "connected";
[ViewVariables(VVAccess.ReadOnly)]
public GasMixture Buffer { get; } = new();
}
}

View File

@@ -0,0 +1,27 @@
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Binary.Components
{
[RegisterComponent]
public class GasPressurePumpComponent : Component
{
public override string Name => "GasPressurePump";
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("inlet")]
public string InletName { get; set; } = "inlet";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("outlet")]
public string OutletName { get; set; } = "outlet";
[ViewVariables(VVAccess.ReadWrite)]
public float TargetPressure { get; set; } = Atmospherics.OneAtmosphere;
}
}

View File

@@ -0,0 +1,55 @@
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.ActionBlocker;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Binary.Components
{
// TODO ATMOS: Make ECS.
[ComponentReference(typeof(IActivate))]
[RegisterComponent]
public class GasValveComponent : Component, IActivate
{
public override string Name => "GasValve";
[ViewVariables]
[DataField("open")]
private bool _open = true;
[DataField("pipe")]
[ViewVariables(VVAccess.ReadWrite)]
private string _pipeName = "pipe";
protected override void Startup()
{
base.Startup();
Set();
}
private void Set()
{
if (Owner.TryGetComponent(out NodeContainerComponent? nodeContainer)
&& nodeContainer.TryGetNode(_pipeName, out PipeNode? pipe))
{
pipe.ConnectionsEnabled = _open;
}
}
private void Toggle()
{
_open = !_open;
Set();
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if(eventArgs.InRangeUnobstructed() && EntitySystem.Get<ActionBlockerSystem>().CanInteract(eventArgs.User))
Toggle();
}
}
}

View File

@@ -0,0 +1,42 @@
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Binary.Components
{
[RegisterComponent]
public class GasVolumePumpComponent : Component
{
public override string Name => "GasVolumePump";
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool Overclocked { get; set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("inlet")]
public string InletName { get; set; } = "inlet";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("outlet")]
public string OutletName { get; set; } = "outlet";
[ViewVariables(VVAccess.ReadWrite)]
public float TransferRate { get; set; } = Atmospherics.MaxTransferRate;
[DataField("leakRatio")]
public float LeakRatio { get; set; } = 0.1f;
[DataField("lowerThreshold")]
public float LowerThreshold { get; set; } = 0.01f;
[DataField("higherThreshold")]
public float HigherThreshold { get; set; } = 9000f;
[DataField("overclockThreshold")]
public float OverclockThreshold { get; set; } = 1000f;
}
}

View File

@@ -0,0 +1,247 @@
using System;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.Piping.Binary.Components;
using Content.Server.Atmos.Piping.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.Hands.Components;
using Content.Server.NodeContainer;
using Content.Server.UserInterface;
using Content.Shared.ActionBlocker;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
{
[UsedImplicitly]
public class GasCanisterSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasCanisterComponent, ComponentStartup>(OnCanisterStartup);
SubscribeLocalEvent<GasCanisterComponent, AtmosDeviceUpdateEvent>(OnCanisterUpdated);
SubscribeLocalEvent<GasCanisterComponent, ActivateInWorldEvent>(OnCanisterActivate);
SubscribeLocalEvent<GasCanisterComponent, InteractHandEvent>(OnCanisterInteractHand);
SubscribeLocalEvent<GasCanisterComponent, InteractUsingEvent>(OnCanisterInteractUsing);
SubscribeLocalEvent<GasCanisterComponent, EntInsertedIntoContainerMessage>(OnCanisterContainerInserted);
SubscribeLocalEvent<GasCanisterComponent, EntRemovedFromContainerMessage>(OnCanisterContainerRemoved);
}
private void OnCanisterStartup(EntityUid uid, GasCanisterComponent canister, ComponentStartup args)
{
// TODO ATMOS: Don't use Owner to get the UI.
if(canister.Owner.GetUIOrNull(GasCanisterUiKey.Key) is {} ui)
ui.OnReceiveMessage += msg => OnCanisterUIMessage(uid, canister, msg);
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(canister.PortName, out PipeNode? portNode))
return;
// Create a pipenet if we don't have one already.
portNode.TryAssignGroupIfNeeded();
portNode.Air.Merge(canister.InitialMixture);
portNode.Air.Temperature = canister.InitialMixture.Temperature;
portNode.Volume = canister.InitialMixture.Volume;
}
private void DirtyUI(EntityUid uid)
{
if (!ComponentManager.TryGetComponent(uid, out IMetaDataComponent? metadata)
|| !ComponentManager.TryGetComponent(uid, out GasCanisterComponent? canister)
|| !ComponentManager.TryGetComponent(uid, out GasPassiveGateComponent? passiveGate)
|| !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|| !nodeContainer.TryGetNode(canister.PortName, out PipeNode? portNode)
|| !nodeContainer.TryGetNode(canister.TankName, out PipeNode? tankNode)
|| !ComponentManager.TryGetComponent(uid, out ServerUserInterfaceComponent? userInterfaceComponent)
|| !userInterfaceComponent.TryGetBoundUserInterface(GasCanisterUiKey.Key, out var ui))
return;
string? tankLabel = null;
var tankPressure = 0f;
if (ComponentManager.TryGetComponent(uid, out ContainerManagerComponent? containerManager) && containerManager.TryGetContainer(canister.ContainerName, out var tankContainer) && tankContainer.ContainedEntities.Count > 0)
{
var tank = tankContainer.ContainedEntities[0];
tankLabel = tank.Name;
tankPressure = tankNode.Air.Pressure;
}
ui.SetState(new GasCanisterBoundUserInterfaceState(metadata.EntityName, portNode.Air.Pressure,
portNode.NodeGroup.Nodes.Count > 1, tankLabel, tankPressure,
passiveGate.TargetPressure, passiveGate.Enabled,
canister.MinReleasePressure, canister.MaxReleasePressure));
}
private void OnCanisterUIMessage(EntityUid uid, GasCanisterComponent canister, ServerBoundUserInterfaceMessage msg)
{
if (msg.Session.AttachedEntity is not {} entity
|| !Get<ActionBlockerSystem>().CanInteract(entity)
|| !Get<ActionBlockerSystem>().CanUse(entity))
return;
if (!ComponentManager.TryGetComponent(uid, out GasPassiveGateComponent? passiveGate)
|| !ComponentManager.TryGetComponent(uid, out ContainerManagerComponent? containerManager)
|| !containerManager.TryGetContainer(canister.ContainerName, out var container))
return;
switch (msg.Message)
{
case GasCanisterHoldingTankEjectMessage:
if (container.ContainedEntities.Count == 0)
break;
container.Remove(container.ContainedEntities[0]);
break;
case GasCanisterChangeReleasePressureMessage changeReleasePressure:
var pressure = Math.Clamp(changeReleasePressure.Pressure, canister.MinReleasePressure, canister.MaxReleasePressure);
passiveGate.TargetPressure = pressure;
DirtyUI(uid);
break;
case GasCanisterChangeReleaseValveMessage changeReleaseValve:
passiveGate.Enabled = changeReleaseValve.Valve;
DirtyUI(uid);
break;
}
}
private void OnCanisterUpdated(EntityUid uid, GasCanisterComponent canister, AtmosDeviceUpdateEvent args)
{
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|| !ComponentManager.TryGetComponent(uid, out AppearanceComponent? appearance))
return;
if (!nodeContainer.TryGetNode(canister.PortName, out PipeNode? portNode))
return;
DirtyUI(uid);
// Nothing to do here.
if (MathHelper.CloseTo(portNode.Air.Pressure, canister.LastPressure))
return;
canister.LastPressure = portNode.Air.Pressure;
if (portNode.Air.Pressure < 10)
{
appearance.SetData(GasCanisterVisuals.PressureState, 0);
}
else if (portNode.Air.Pressure < Atmospherics.OneAtmosphere)
{
appearance.SetData(GasCanisterVisuals.PressureState, 1);
}
else if (portNode.Air.Pressure < (15 * Atmospherics.OneAtmosphere))
{
appearance.SetData(GasCanisterVisuals.PressureState, 2);
}
else
{
appearance.SetData(GasCanisterVisuals.PressureState, 3);
}
}
private void OnCanisterActivate(EntityUid uid, GasCanisterComponent component, ActivateInWorldEvent args)
{
if (!args.User.TryGetComponent(out ActorComponent? actor))
return;
component.Owner.GetUIOrNull(GasCanisterUiKey.Key)?.Open(actor.PlayerSession);
args.Handled = true;
}
private void OnCanisterInteractHand(EntityUid uid, GasCanisterComponent component, InteractHandEvent args)
{
if (!args.User.TryGetComponent(out ActorComponent? actor))
return;
component.Owner.GetUIOrNull(GasCanisterUiKey.Key)?.Open(actor.PlayerSession);
args.Handled = true;
}
private void OnCanisterInteractUsing(EntityUid uid, GasCanisterComponent component, InteractUsingEvent args)
{
var canister = EntityManager.GetEntity(uid);
var container = canister.EnsureContainer<ContainerSlot>(component.ContainerName);
// Container full.
if (container.ContainedEntity != null)
return;
// Check the used item is valid...
if (!args.Used.TryGetComponent(out GasTankComponent? _)
|| !args.Used.TryGetComponent(out NodeContainerComponent? _))
return;
// Check the user has hands.
if (!args.User.TryGetComponent(out HandsComponent? hands))
return;
if (!args.User.InRangeUnobstructed(canister, SharedInteractionSystem.InteractionRange, popup: true))
return;
if (!hands.Drop(args.Used, canister.Transform.Coordinates))
return;
if (!container.Insert(args.Used))
return;
args.Handled = true;
}
private void OnCanisterContainerInserted(EntityUid uid, GasCanisterComponent component, EntInsertedIntoContainerMessage args)
{
if (args.Container.ID != component.ContainerName)
return;
DirtyUI(uid);
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|| !nodeContainer.TryGetNode(component.TankName, out PipeNode? tankNode))
return;
tankNode.EnvironmentalAir = false;
tankNode.ConnectToContainedEntities = true;
tankNode.NodeGroup.RemakeGroup();
if (!ComponentManager.TryGetComponent(uid, out AppearanceComponent? appearance))
return;
appearance.SetData(GasCanisterVisuals.TankInserted, true);
}
private void OnCanisterContainerRemoved(EntityUid uid, GasCanisterComponent component, EntRemovedFromContainerMessage args)
{
if (args.Container.ID != component.ContainerName)
return;
DirtyUI(uid);
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|| !nodeContainer.TryGetNode(component.TankName, out PipeNode? tankNode))
return;
tankNode.NodeGroup.RemakeGroup();
tankNode.ConnectToContainedEntities = false;
tankNode.EnvironmentalAir = true;
if (!ComponentManager.TryGetComponent(uid, out AppearanceComponent? appearance))
return;
appearance.SetData(GasCanisterVisuals.TankInserted, false);
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using Content.Server.Atmos.Piping.Binary.Components;
using Content.Server.Atmos.Piping.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
{
[UsedImplicitly]
public class GasPassiveGateSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasPassiveGateComponent, AtmosDeviceUpdateEvent>(OnPassiveGateUpdated);
}
private void OnPassiveGateUpdated(EntityUid uid, GasPassiveGateComponent gate, AtmosDeviceUpdateEvent args)
{
if (!gate.Enabled)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(gate.InletName, out PipeNode? inlet)
|| !nodeContainer.TryGetNode(gate.OutletName, out PipeNode? outlet))
return;
var outputStartingPressure = outlet.Air.Pressure;
var inputStartingPressure = inlet.Air.Pressure;
if (outputStartingPressure >= MathF.Min(gate.TargetPressure, inputStartingPressure - gate.FrictionPressureDifference))
return; // No need to pump gas, target reached or input pressure too low.
if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0)
{
// We calculate the necessary moles to transfer using our good ol' friend PV=nRT.
var pressureDelta = MathF.Min(gate.TargetPressure - outputStartingPressure, (inputStartingPressure - outputStartingPressure)/2);
// We can't have a pressure delta that would cause outlet pressure > inlet pressure.
var transferMoles = pressureDelta * outlet.Air.Volume / (inlet.Air.Temperature * Atmospherics.R);
// Actually transfer the gas.
outlet.AssumeAir(inlet.Air.Remove(transferMoles));
}
}
}
}

View File

@@ -0,0 +1,67 @@
using Content.Server.Atmos.Piping.Binary.Components;
using Content.Server.Atmos.Piping.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
{
[UsedImplicitly]
public class GasPressurePumpSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceUpdateEvent>(OnPumpUpdated);
SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceDisabledEvent>(OnPumpLeaveAtmosphere);
}
private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, AtmosDeviceUpdateEvent args)
{
var appearance = pump.Owner.GetComponentOrNull<AppearanceComponent>();
appearance?.SetData(PressurePumpVisuals.Enabled, false);
if (!pump.Enabled)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(pump.InletName, out PipeNode? inlet)
|| !nodeContainer.TryGetNode(pump.OutletName, out PipeNode? outlet))
return;
var outputStartingPressure = outlet.Air.Pressure;
if (MathHelper.CloseTo(pump.TargetPressure, outputStartingPressure))
return; // No need to pump gas if target has been reached.
if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0)
{
appearance?.SetData(PressurePumpVisuals.Enabled, true);
// We calculate the necessary moles to transfer using our good ol' friend PV=nRT.
var pressureDelta = pump.TargetPressure - outputStartingPressure;
var transferMoles = pressureDelta * outlet.Air.Volume / inlet.Air.Temperature * Atmospherics.R;
var removed = inlet.Air.Remove(transferMoles);
outlet.AssumeAir(removed);
}
}
private void OnPumpLeaveAtmosphere(EntityUid uid, GasPressurePumpComponent component, AtmosDeviceDisabledEvent args)
{
if (ComponentManager.TryGetComponent(uid, out AppearanceComponent? appearance))
{
appearance.SetData(PressurePumpVisuals.Enabled, false);
}
}
}
}

View File

@@ -0,0 +1,70 @@
using Content.Server.Atmos.Piping.Binary.Components;
using Content.Server.Atmos.Piping.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
{
[UsedImplicitly]
public class GasVolumePumpSystem : EntitySystem
{
[Dependency] private IGameTiming _gameTiming = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasVolumePumpComponent, AtmosDeviceUpdateEvent>(OnVolumePumpUpdated);
}
private void OnVolumePumpUpdated(EntityUid uid, GasVolumePumpComponent pump, AtmosDeviceUpdateEvent args)
{
if (!pump.Enabled)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!ComponentManager.TryGetComponent(uid, out AtmosDeviceComponent? device))
return;
if (!nodeContainer.TryGetNode(pump.InletName, out PipeNode? inlet)
|| !nodeContainer.TryGetNode(pump.OutletName, out PipeNode? outlet))
return;
var inputStartingPressure = inlet.Air.Pressure;
var outputStartingPressure = outlet.Air.Pressure;
// Pump mechanism won't do anything if the pressure is too high/too low unless you overclock it.
if ((inputStartingPressure < pump.LowerThreshold) || (outputStartingPressure > pump.HigherThreshold) && !pump.Overclocked)
return;
// Overclocked pumps can only force gas a certain amount.
if ((outputStartingPressure - inputStartingPressure > pump.OverclockThreshold) && pump.Overclocked)
return;
// We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters.
var transferRatio = (float)(pump.TransferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inlet.Air.Volume;
var removed = inlet.Air.RemoveRatio(transferRatio);
// Some of the gas from the mixture leaks when overclocked.
if (pump.Overclocked)
{
var tile = args.Atmosphere.GetTile(pump.Owner.Transform.Coordinates);
if (tile != null)
{
var leaked = removed.RemoveRatio(pump.LeakRatio);
tile.AssumeAir(leaked);
}
}
outlet.AssumeAir(removed);
}
}
}

View File

@@ -0,0 +1,61 @@
#nullable enable
using System;
using Content.Server.Atmos.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Components
{
/// <summary>
/// Adds itself to a <see cref="IGridAtmosphereComponent"/> to be updated by.
/// </summary>
[RegisterComponent]
public class AtmosDeviceComponent : Component
{
public override string Name => "AtmosDevice";
/// <summary>
/// Whether this device requires being anchored to join an atmosphere.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("requireAnchored")]
public bool RequireAnchored { get; private set; } = true;
public IGridAtmosphereComponent? Atmosphere { get; set; }
[ViewVariables]
public TimeSpan LastProcess { get; set; } = TimeSpan.Zero;
}
public abstract class BaseAtmosDeviceEvent : EntityEventArgs
{
public IGridAtmosphereComponent Atmosphere { get; }
public BaseAtmosDeviceEvent(IGridAtmosphereComponent atmosphere)
{
Atmosphere = atmosphere;
}
}
public sealed class AtmosDeviceUpdateEvent : BaseAtmosDeviceEvent
{
public AtmosDeviceUpdateEvent(IGridAtmosphereComponent atmosphere) : base(atmosphere)
{
}
}
public sealed class AtmosDeviceEnabledEvent : BaseAtmosDeviceEvent
{
public AtmosDeviceEnabledEvent(IGridAtmosphereComponent atmosphere) : base(atmosphere)
{
}
}
public sealed class AtmosDeviceDisabledEvent : BaseAtmosDeviceEvent
{
public AtmosDeviceDisabledEvent(IGridAtmosphereComponent atmosphere) : base(atmosphere)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Components
{
[RegisterComponent]
public class AtmosUnsafeUnanchorComponent : Component
{
public override string Name => "AtmosUnsafeUnanchor";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("enabled")]
public bool Enabled { get; set; } = true;
}
}

View File

@@ -0,0 +1,92 @@
using System;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
namespace Content.Server.Atmos.Piping.EntitySystems
{
[UsedImplicitly]
public class AtmosDeviceSystem : EntitySystem
{
[Dependency] private IGameTiming _gameTiming = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AtmosDeviceComponent, ComponentInit>(OnDeviceInitialize);
SubscribeLocalEvent<AtmosDeviceComponent, ComponentShutdown>(OnDeviceShutdown);
SubscribeLocalEvent<AtmosDeviceComponent, PhysicsBodyTypeChangedEvent>(OnDeviceBodyTypeChanged);
SubscribeLocalEvent<AtmosDeviceComponent, EntParentChangedMessage>(OnDeviceParentChanged);
}
private bool CanJoinAtmosphere(AtmosDeviceComponent component)
{
return !component.RequireAnchored || !component.Owner.TryGetComponent(out PhysicsComponent? physics) || physics.BodyType == BodyType.Static;
}
public void JoinAtmosphere(AtmosDeviceComponent component)
{
if (!CanJoinAtmosphere(component))
return;
// We try to get a valid, simulated atmosphere.
if (!Get<AtmosphereSystem>().TryGetSimulatedGridAtmosphere(component.Owner.Transform.MapPosition, out var atmosphere))
return;
component.LastProcess = _gameTiming.CurTime;
component.Atmosphere = atmosphere;
atmosphere.AddAtmosDevice(component);
RaiseLocalEvent(component.Owner.Uid, new AtmosDeviceEnabledEvent(atmosphere), false);
}
public void LeaveAtmosphere(AtmosDeviceComponent component)
{
var atmosphere = component.Atmosphere;
atmosphere?.RemoveAtmosDevice(component);
component.Atmosphere = null;
component.LastProcess = TimeSpan.Zero;
if(atmosphere != null)
RaiseLocalEvent(component.Owner.Uid, new AtmosDeviceDisabledEvent(atmosphere), false);
}
public void RejoinAtmosphere(AtmosDeviceComponent component)
{
LeaveAtmosphere(component);
JoinAtmosphere(component);
}
private void OnDeviceInitialize(EntityUid uid, AtmosDeviceComponent component, ComponentInit args)
{
JoinAtmosphere(component);
}
private void OnDeviceShutdown(EntityUid uid, AtmosDeviceComponent component, ComponentShutdown args)
{
LeaveAtmosphere(component);
}
private void OnDeviceBodyTypeChanged(EntityUid uid, AtmosDeviceComponent component, PhysicsBodyTypeChangedEvent args)
{
// Do nothing if the component doesn't require being anchored to function.
if (!component.RequireAnchored)
return;
if (args.New == BodyType.Static)
JoinAtmosphere(component);
else
LeaveAtmosphere(component);
}
private void OnDeviceParentChanged(EntityUid uid, AtmosDeviceComponent component, EntParentChangedMessage args)
{
RejoinAtmosphere(component);
}
}
}

View File

@@ -0,0 +1,80 @@
using Content.Server.Anchor;
using Content.Server.Atmos.Piping.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos;
using Content.Shared.Notification.Managers;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
namespace Content.Server.Atmos.Piping.EntitySystems
{
[UsedImplicitly]
public class AtmosUnsafeUnanchorSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<AtmosUnsafeUnanchorComponent, BeforeUnanchoredEvent>(OnBeforeUnanchored);
SubscribeLocalEvent<AtmosUnsafeUnanchorComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
}
private void OnUnanchorAttempt(EntityUid uid, AtmosUnsafeUnanchorComponent component, UnanchorAttemptEvent args)
{
if (!component.Enabled || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodes))
return;
if (!component.Owner.Transform.Coordinates.TryGetTileAir(out var environment, EntityManager))
return;
foreach (var node in nodes.Nodes.Values)
{
if (node is not PipeNode pipe) continue;
if ((pipe.Air.Pressure - environment.Pressure) > 2 * Atmospherics.OneAtmosphere)
{
args.Delay += 1.5f;
args.User?.PopupMessageCursor(Loc.GetString("comp-atmos-unsafe-unanchor-warning"));
return; // Show the warning only once.
}
}
}
private void OnBeforeUnanchored(EntityUid uid, AtmosUnsafeUnanchorComponent component, BeforeUnanchoredEvent args)
{
if (!component.Enabled || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodes))
return;
if (!component.Owner.Transform.Coordinates.TryGetTileAtmosphere(out var environment))
environment = null;
var environmentPressure = environment?.Air?.Pressure ?? 0f;
var environmentVolume = environment?.Air?.Volume ?? Atmospherics.CellVolume;
var environmentTemperature = environment?.Air?.Volume ?? Atmospherics.TCMB;
var lost = 0f;
var timesLost = 0;
foreach (var node in nodes.Nodes.Values)
{
if (node is not PipeNode pipe) continue;
var difference = pipe.Air.Pressure - environmentPressure;
lost += difference * environmentVolume / (environmentTemperature * Atmospherics.R);
timesLost++;
}
var sharedLoss = lost / timesLost;
var buffer = new GasMixture();
foreach (var node in nodes.Nodes.Values)
{
if (node is not PipeNode pipe) continue;
buffer.Merge(pipe.Air.Remove(sharedLoss));
}
environment?.AssumeAir(buffer);
}
}
}

View File

@@ -0,0 +1,37 @@
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Other.Components
{
[RegisterComponent]
public class GasMinerComponent : Component
{
public override string Name => "GasMiner";
public bool Enabled { get; set; } = true;
public bool Broken { get; set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("maxExternalAmount")]
public float MaxExternalAmount { get; set; } = float.PositiveInfinity;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("maxExternalPressure")]
public float MaxExternalPressure { get; set; } = 6500f;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("spawnGas")]
public Gas SpawnGas { get; set; } = Gas.Invalid;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("spawnTemperature")]
public float SpawnTemperature { get; set; } = Atmospherics.T20C;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("spawnAmount")]
public float SpawnAmount { get; set; } = Atmospherics.MolesCellStandard * 20f;
}
}

View File

@@ -0,0 +1,71 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.Other.Components;
using Content.Shared.Atmos;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos.Piping.Other.EntitySystems
{
[UsedImplicitly]
public class GasMinerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasMinerComponent, AtmosDeviceUpdateEvent>(OnMinerUpdated);
}
private void OnMinerUpdated(EntityUid uid, GasMinerComponent miner, AtmosDeviceUpdateEvent args)
{
if (!CheckMinerOperation(args.Atmosphere, miner, out var tile) || !miner.Enabled || miner.SpawnGas <= Gas.Invalid || miner.SpawnAmount <= 0f)
return;
// Time to mine some gas.
var merger = new GasMixture(1) { Temperature = miner.SpawnTemperature };
merger.SetMoles(miner.SpawnGas, miner.SpawnAmount);
tile.AssumeAir(merger);
}
private bool CheckMinerOperation(IGridAtmosphereComponent atmosphere, GasMinerComponent miner, [NotNullWhen(true)] out TileAtmosphere? tile)
{
tile = atmosphere.GetTile(miner.Owner.Transform.Coordinates)!;
// Space.
if (atmosphere.IsSpace(tile.GridIndices))
{
miner.Broken = true;
return false;
}
// Airblocked location.
if (tile.Air == null)
{
miner.Broken = true;
return false;
}
// External pressure above threshold.
if (!float.IsInfinity(miner.MaxExternalPressure) &&
tile.Air.Pressure > miner.MaxExternalPressure - miner.SpawnAmount * miner.SpawnTemperature * Atmospherics.R / tile.Air.Volume)
{
miner.Broken = true;
return false;
}
// External gas amount above threshold.
if (!float.IsInfinity(miner.MaxExternalAmount) && tile.Air.TotalMoles > miner.MaxExternalAmount)
{
miner.Broken = true;
return false;
}
miner.Broken = false;
return true;
}
}
}

View File

@@ -0,0 +1,35 @@
#nullable enable
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Trinary.Components
{
[RegisterComponent]
public class GasFilterComponent : Component
{
public override string Name => "GasFilter";
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("inlet")]
public string InletName { get; set; } = "inlet";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("filter")]
public string FilterName { get; set; } = "filter";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("outlet")]
public string OutletName { get; set; } = "outlet";
[ViewVariables(VVAccess.ReadWrite)]
public float TransferRate { get; set; } = Atmospherics.MaxTransferRate;
[ViewVariables(VVAccess.ReadWrite)]
public Gas? FilteredGas { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Trinary.Components
{
[RegisterComponent]
public class GasMixerComponent : Component
{
public override string Name => "GasMixer";
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled = true;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("inletOne")]
public string InletOneName = "inletOne";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("inletTwo")]
public string InletTwoName = "inletTwo";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("outlet")]
public string OutletName = "outlet";
[ViewVariables(VVAccess.ReadWrite)]
public float TargetPressure = Atmospherics.OneAtmosphere;
[ViewVariables(VVAccess.ReadWrite)]
public float InletOneConcentration = 0.5f;
[ViewVariables(VVAccess.ReadWrite)]
public float InletTwoConcentration = 0.5f;
}
}

View File

@@ -0,0 +1,66 @@
using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.Trinary.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
{
[UsedImplicitly]
public class GasFilterSystem : EntitySystem
{
[Dependency] private IGameTiming _gameTiming = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasFilterComponent, AtmosDeviceUpdateEvent>(OnFilterUpdated);
}
private void OnFilterUpdated(EntityUid uid, GasFilterComponent filter, AtmosDeviceUpdateEvent args)
{
if (!filter.Enabled)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!ComponentManager.TryGetComponent(uid, out AtmosDeviceComponent? device))
return;
if (!nodeContainer.TryGetNode(filter.InletName, out PipeNode? inletNode)
|| !nodeContainer.TryGetNode(filter.FilterName, out PipeNode? filterNode)
|| !nodeContainer.TryGetNode(filter.OutletName, out PipeNode? outletNode))
return;
if (outletNode.Air.Pressure >= Atmospherics.MaxOutputPressure)
return; // No need to transfer if target is full.
// We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters.
var transferRatio = (float)(filter.TransferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inletNode.Air.Volume;
if (transferRatio <= 0)
return;
var removed = inletNode.Air.RemoveRatio(transferRatio);
if (filter.FilteredGas.HasValue)
{
var filteredOut = new GasMixture() {Temperature = removed.Temperature};
filteredOut.SetMoles(filter.FilteredGas.Value, removed.GetMoles(filter.FilteredGas.Value));
removed.SetMoles(filter.FilteredGas.Value, 0f);
var target = filterNode.Air.Pressure < Atmospherics.MaxOutputPressure ? filterNode : inletNode;
target.AssumeAir(filteredOut);
}
outletNode.AssumeAir(removed);
}
}
}

View File

@@ -0,0 +1,95 @@
using System;
using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.Trinary.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
{
[UsedImplicitly]
public class GasMixerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasMixerComponent, AtmosDeviceUpdateEvent>(OnMixerUpdated);
}
private void OnMixerUpdated(EntityUid uid, GasMixerComponent mixer, AtmosDeviceUpdateEvent args)
{
// TODO ATMOS: Cache total moles since it's expensive.
if (!mixer.Enabled)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(mixer.InletOneName, out PipeNode? inletOne)
|| !nodeContainer.TryGetNode(mixer.InletTwoName, out PipeNode? inletTwo)
|| !nodeContainer.TryGetNode(mixer.OutletName, out PipeNode? outlet))
return;
var outputStartingPressure = outlet.Air.Pressure;
if (outputStartingPressure >= mixer.TargetPressure)
return; // Target reached, no need to mix.
var generalTransfer = (mixer.TargetPressure - outputStartingPressure) * outlet.Air.Volume / Atmospherics.R;
var transferMolesOne = inletOne.Air.Temperature > 0 ? mixer.InletOneConcentration * generalTransfer / inletOne.Air.Temperature : 0f;
var transferMolesTwo = inletTwo.Air.Temperature > 0 ? mixer.InletTwoConcentration * generalTransfer / inletTwo.Air.Temperature : 0f;
if (mixer.InletTwoConcentration <= 0f)
{
if (inletOne.Air.Temperature <= 0f)
return;
transferMolesOne = MathF.Min(transferMolesOne, inletOne.Air.TotalMoles);
transferMolesTwo = 0f;
}
else if (mixer.InletOneConcentration <= 0)
{
if (inletTwo.Air.Temperature <= 0f)
return;
transferMolesOne = 0f;
transferMolesTwo = MathF.Min(transferMolesTwo, inletTwo.Air.TotalMoles);
}
else
{
if (inletOne.Air.Temperature <= 0f || inletTwo.Air.Temperature <= 0f)
return;
if (transferMolesOne <= 0 || transferMolesTwo <= 0)
return;
if (inletOne.Air.TotalMoles < transferMolesOne || inletTwo.Air.TotalMoles < transferMolesTwo)
{
var ratio = MathF.Min(inletOne.Air.TotalMoles / transferMolesOne, inletTwo.Air.TotalMoles / transferMolesTwo);
transferMolesOne *= ratio;
transferMolesTwo *= ratio;
}
}
// Actually transfer the gas now.
if (transferMolesOne > 0f)
{
var removed = inletOne.Air.Remove(transferMolesOne);
outlet.AssumeAir(removed);
}
if (transferMolesTwo > 0f)
{
var removed = inletTwo.Air.Remove(transferMolesTwo);
outlet.AssumeAir(removed);
}
}
}
}

View File

@@ -0,0 +1,26 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Unary.Components
{
[RegisterComponent]
public class GasOutletInjectorComponent : Component
{
public override string Name => "GasOutletInjector";
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool Injecting { get; set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
public float VolumeRate { get; set; } = 50f;
[DataField("inlet")]
public string InletName { get; set; } = "pipe";
// TODO ATMOS: Inject method.
}
}

View File

@@ -0,0 +1,14 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Atmos.Piping.Unary.Components
{
[RegisterComponent]
public class GasPassiveVentComponent : Component
{
public override string Name => "GasPassiveVent";
[DataField("inlet")]
public string InletName = "pipe";
}
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Unary.Components
{
[RegisterComponent]
public class GasPortableComponent : Component
{
public override string Name => "GasPortable";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("port")]
public string PortName { get; set; } = "port";
}
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using Content.Server.Construction;
using Content.Server.Construction.Components;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Unary.Components
{
[RegisterComponent]
public class GasThermoMachineComponent : Component, IRefreshParts, ISerializationHooks
{
public override string Name => "GasThermoMachine";
[DataField("inlet")]
public string InletName { get; set; } = "pipe";
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public float HeatCapacity { get; set; } = 0;
[ViewVariables(VVAccess.ReadWrite)]
public float TargetTemperature { get; set; } = Atmospherics.T20C;
[DataField("mode")]
[ViewVariables(VVAccess.ReadWrite)]
public ThermoMachineMode Mode { get; set; } = ThermoMachineMode.Freezer;
[DataField("minTemperature")]
[ViewVariables(VVAccess.ReadWrite)]
public float MinTemperature { get; set; } = Atmospherics.T20C;
[DataField("maxTemperature")]
[ViewVariables(VVAccess.ReadWrite)]
public float MaxTemperature { get; set; } = Atmospherics.T20C;
public float InitialMinTemperature { get; private set; }
public float InitialMaxTemperature { get; private set; }
void IRefreshParts.RefreshParts(IEnumerable<MachinePartComponent> parts)
{
var matterBinRating = 0;
var laserRating = 0;
foreach (var part in parts)
{
switch (part.PartType)
{
case MachinePart.MatterBin:
matterBinRating += part.Rating;
break;
case MachinePart.Laser:
laserRating += part.Rating;
break;
}
}
HeatCapacity = 5000 * MathF.Pow((matterBinRating - 1), 2);
switch (Mode)
{
// 573.15K with stock parts.
case ThermoMachineMode.Heater:
MaxTemperature = Atmospherics.T20C + (InitialMaxTemperature * laserRating);
break;
// 73.15K with stock parts.
case ThermoMachineMode.Freezer:
MinTemperature = MathF.Max(Atmospherics.T0C - InitialMinTemperature + laserRating * 15f, Atmospherics.TCMB);
break;
}
}
void ISerializationHooks.AfterDeserialization()
{
InitialMinTemperature = MinTemperature;
InitialMaxTemperature = MaxTemperature;
}
}
public enum ThermoMachineMode : byte
{
Freezer = 0,
Heater = 1,
}
}

View File

@@ -0,0 +1,50 @@
using System;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Unary.Components
{
[RegisterComponent]
public class GasVentPumpComponent : Component
{
public override string Name => "GasVentPump";
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool Welded { get; set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("inlet")]
public string InletName { get; set; } = "pipe";
[ViewVariables(VVAccess.ReadWrite)]
public VentPumpDirection PumpDirection { get; set; } = VentPumpDirection.Releasing;
[ViewVariables(VVAccess.ReadWrite)]
public VentPressureBound PressureChecks { get; set; } = VentPressureBound.ExternalBound;
[ViewVariables(VVAccess.ReadWrite)]
public float ExternalPressureBound { get; set; } = Atmospherics.OneAtmosphere;
[ViewVariables(VVAccess.ReadWrite)]
public float InternalPressureBound { get; set; } = 0f;
}
public enum VentPumpDirection : sbyte
{
Siphoning = 0,
Releasing = 1,
}
[Flags]
public enum VentPressureBound : sbyte
{
NoBound = 0,
InternalBound = 1,
ExternalBound = 2,
}
}

View File

@@ -0,0 +1,45 @@
using System.Collections.Generic;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos.Piping.Unary.Components
{
[RegisterComponent]
public class GasVentScrubberComponent : Component
{
public override string Name => "GasVentScrubber";
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool Welded { get; set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("outlet")]
public string OutletName { get; set; } = "pipe";
[ViewVariables]
public readonly HashSet<Gas> FilterGases = new()
{
Gas.CarbonDioxide
};
[ViewVariables(VVAccess.ReadWrite)]
public ScrubberPumpDirection PumpDirection { get; set; } = ScrubberPumpDirection.Scrubbing;
[ViewVariables(VVAccess.ReadWrite)]
public float VolumeRate { get; set; } = 200f;
[ViewVariables(VVAccess.ReadWrite)]
public bool WideNet { get; set; } = false;
}
public enum ScrubberPumpDirection : sbyte
{
Siphoning = 0,
Scrubbing = 1,
}
}

View File

@@ -0,0 +1,50 @@
using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{
[UsedImplicitly]
public class GasOutletInjectorSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasOutletInjectorComponent, AtmosDeviceUpdateEvent>(OnOutletInjectorUpdated);
}
private void OnOutletInjectorUpdated(EntityUid uid, GasOutletInjectorComponent injector, AtmosDeviceUpdateEvent args)
{
injector.Injecting = false;
if (!injector.Enabled)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(injector.InletName, out PipeNode? inlet))
return;
var environment = args.Atmosphere.GetTile(injector.Owner.Transform.Coordinates)!;
if (environment.Air == null)
return;
if (inlet.Air.Temperature > 0)
{
var transferMoles = inlet.Air.Pressure * injector.VolumeRate / (inlet.Air.Temperature * Atmospherics.R);
var removed = inlet.Air.Remove(transferMoles);
environment.AssumeAir(removed);
environment.Invalidate();
}
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{
[UsedImplicitly]
public class GasPassiveVentSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasPassiveVentComponent, AtmosDeviceUpdateEvent>(OnPassiveVentUpdated);
}
private void OnPassiveVentUpdated(EntityUid uid, GasPassiveVentComponent vent, AtmosDeviceUpdateEvent args)
{
var environment = args.Atmosphere.GetTile(vent.Owner.Transform.Coordinates)!;
if (environment.Air == null)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(vent.InletName, out PipeNode? inlet))
return;
var environmentPressure = environment.Air.Pressure;
var pressureDelta = MathF.Abs(environmentPressure - inlet.Air.Pressure);
if ((environment.Air.Temperature > 0 || inlet.Air.Temperature > 0) && pressureDelta > 0.5f)
{
if (environmentPressure < inlet.Air.Pressure)
{
var airTemperature = environment.Temperature > 0 ? environment.Temperature : inlet.Air.Temperature;
var transferMoles = pressureDelta * environment.Air.Volume / (airTemperature * Atmospherics.R);
var removed = inlet.Air.Remove(transferMoles);
environment.AssumeAir(removed);
}
else
{
var airTemperature = inlet.Air.Temperature > 0 ? inlet.Air.Temperature : environment.Temperature;
var outputVolume = inlet.Air.Volume;
var transferMoles = (pressureDelta * outputVolume) / (airTemperature * Atmospherics.R);
transferMoles = MathF.Min(transferMoles, environment.Air.TotalMoles * inlet.Air.Volume / environment.Air.Volume);
var removed = environment.Air.Remove(transferMoles);
inlet.AssumeAir(removed);
environment.Invalidate();
}
}
}
}
}

View File

@@ -0,0 +1,92 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Anchor;
using Content.Server.Atmos.Piping.Binary.Components;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos.Piping.Unary.Components;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{
[UsedImplicitly]
public class GasPortableSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasPortableComponent, AnchorAttemptEvent>(OnPortableAnchorAttempt);
SubscribeLocalEvent<GasPortableComponent, AnchoredEvent>(OnPortableAnchored);
SubscribeLocalEvent<GasPortableComponent, UnanchoredEvent>(OnPortableUnanchored);
}
private void OnPortableAnchorAttempt(EntityUid uid, GasPortableComponent component, AnchorAttemptEvent args)
{
if (!ComponentManager.TryGetComponent(uid, out ITransformComponent? transform))
return;
// If we can't find any ports, cancel the anchoring.
if(!FindGasPortIn(transform.GridID, transform.Coordinates, out _))
args.Cancel();
}
private void OnPortableAnchored(EntityUid uid, GasPortableComponent portable, AnchoredEvent args)
{
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(portable.PortName, out PipeNode? portableNode))
return;
portableNode.ConnectionsEnabled = true;
if (ComponentManager.TryGetComponent(uid, out AppearanceComponent? appearance))
{
appearance.SetData(GasPortableVisuals.ConnectedState, true);
}
}
private void OnPortableUnanchored(EntityUid uid, GasPortableComponent portable, UnanchoredEvent args)
{
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(portable.PortName, out PipeNode? portableNode))
return;
portableNode.ConnectionsEnabled = false;
if (ComponentManager.TryGetComponent(uid, out AppearanceComponent? appearance))
{
appearance.SetData(GasPortableVisuals.ConnectedState, false);
}
}
private bool FindGasPortIn(GridId gridId, EntityCoordinates coordinates, [NotNullWhen(true)] out GasPortComponent? port)
{
port = null;
if (!gridId.IsValid())
return false;
var grid = _mapManager.GetGrid(gridId);
foreach (var entityUid in grid.GetLocal(coordinates))
{
if (ComponentManager.TryGetComponent<GasPortComponent>(entityUid, out port))
{
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,35 @@
using Content.Server.Atmos.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{
[UsedImplicitly]
public class GasTankSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasTankComponent, ComponentStartup>(OnTankStartup);
}
private void OnTankStartup(EntityUid uid, GasTankComponent tank, ComponentStartup args)
{
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(tank.TankName, out PipeNode? tankNode))
return;
// Create a pipenet if we don't have one already.
tankNode.TryAssignGroupIfNeeded();
tankNode.AssumeAir(tank.InitialMixture);
tankNode.Volume = tank.InitialMixture.Volume;
tankNode.Air.Volume = tank.InitialMixture.Volume;
tankNode.Air.Temperature = tank.InitialMixture.Temperature;
}
}
}

View File

@@ -0,0 +1,59 @@
using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos.Piping;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{
[UsedImplicitly]
public class GasThermoMachineSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasThermoMachineComponent, AtmosDeviceUpdateEvent>(OnThermoMachineUpdated);
SubscribeLocalEvent<GasThermoMachineComponent, AtmosDeviceDisabledEvent>(OnThermoMachineLeaveAtmosphere);
}
private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, AtmosDeviceUpdateEvent args)
{
var appearance = thermoMachine.Owner.GetComponentOrNull<AppearanceComponent>();
appearance?.SetData(ThermoMachineVisuals.Enabled, false);
if (!thermoMachine.Enabled)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(thermoMachine.InletName, out PipeNode? inlet))
return;
var airHeatCapacity = inlet.Air.HeatCapacity;
var combinedHeatCapacity = airHeatCapacity + thermoMachine.HeatCapacity;
var oldTemperature = inlet.Air.Temperature;
if (combinedHeatCapacity > 0)
{
appearance?.SetData(ThermoMachineVisuals.Enabled, true);
var combinedEnergy = thermoMachine.HeatCapacity * thermoMachine.TargetTemperature + airHeatCapacity * inlet.Air.Temperature;
inlet.Air.Temperature = combinedEnergy / combinedHeatCapacity;
}
// TODO ATMOS: Active power usage.
}
private void OnThermoMachineLeaveAtmosphere(EntityUid uid, GasThermoMachineComponent component, AtmosDeviceDisabledEvent args)
{
if (ComponentManager.TryGetComponent(uid, out AppearanceComponent? appearance))
{
appearance.SetData(ThermoMachineVisuals.Enabled, false);
}
}
}
}

View File

@@ -0,0 +1,101 @@
using System;
using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Visuals;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{
[UsedImplicitly]
public class GasVentPumpSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasVentPumpComponent, AtmosDeviceUpdateEvent>(OnGasVentPumpUpdated);
SubscribeLocalEvent<GasVentPumpComponent, AtmosDeviceDisabledEvent>(OnGasVentPumpLeaveAtmosphere);
}
private void OnGasVentPumpUpdated(EntityUid uid, GasVentPumpComponent vent, AtmosDeviceUpdateEvent args)
{
var appearance = vent.Owner.GetComponentOrNull<AppearanceComponent>();
if (vent.Welded)
{
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Welded);
return;
}
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off);
if (!vent.Enabled)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(vent.InletName, out PipeNode? pipe))
return;
var environment = args.Atmosphere.GetTile(vent.Owner.Transform.Coordinates)!;
// We're in an air-blocked tile... Do nothing.
if (environment.Air == null)
return;
if (vent.PumpDirection == VentPumpDirection.Releasing)
{
appearance?.SetData(VentPumpVisuals.State, VentPumpState.Out);
var pressureDelta = 10000f;
if ((vent.PressureChecks & VentPressureBound.ExternalBound) != 0)
pressureDelta = MathF.Min(pressureDelta, vent.ExternalPressureBound - environment.Air.Pressure);
if ((vent.PressureChecks & VentPressureBound.InternalBound) != 0)
pressureDelta = MathF.Min(pressureDelta, pipe.Air.Pressure - vent.InternalPressureBound);
if (pressureDelta > 0 && pipe.Air.Temperature > 0)
{
var transferMoles = pressureDelta * environment.Air.Volume / (pipe.Air.Temperature * Atmospherics.R);
environment.AssumeAir(pipe.Air.Remove(transferMoles));
}
}
else if (vent.PumpDirection == VentPumpDirection.Siphoning && environment.Air.Pressure > 0)
{
appearance?.SetData(VentPumpVisuals.State, VentPumpState.In);
var ourMultiplier = pipe.Air.Volume / (environment.Air.Temperature * Atmospherics.R);
var molesDelta = 10000f * ourMultiplier;
if ((vent.PressureChecks & VentPressureBound.ExternalBound) != 0)
molesDelta = MathF.Min(molesDelta,
(environment.Air.Pressure - vent.ExternalPressureBound) * environment.Air.Volume /
(environment.Air.Temperature * Atmospherics.R));
if ((vent.PressureChecks & VentPressureBound.InternalBound) != 0)
molesDelta = MathF.Min(molesDelta, (vent.InternalPressureBound - pipe.Air.Pressure) * ourMultiplier);
if (molesDelta > 0)
{
var removed = environment.Air.Remove(molesDelta);
pipe.AssumeAir(removed);
environment.Invalidate();
}
}
}
private void OnGasVentPumpLeaveAtmosphere(EntityUid uid, GasVentPumpComponent component, AtmosDeviceDisabledEvent args)
{
if (ComponentManager.TryGetComponent(uid, out AppearanceComponent? appearance))
{
appearance.SetData(VentPumpVisuals.State, VentPumpState.Off);
}
}
}
}

View File

@@ -0,0 +1,108 @@
using System;
using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.NodeContainer;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Unary.Visuals;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{
[UsedImplicitly]
public class GasVentScrubberSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasVentScrubberComponent, AtmosDeviceUpdateEvent>(OnVentScrubberUpdated);
SubscribeLocalEvent<GasVentScrubberComponent, AtmosDeviceDisabledEvent>(OnVentScrubberLeaveAtmosphere);
}
private void OnVentScrubberUpdated(EntityUid uid, GasVentScrubberComponent scrubber, AtmosDeviceUpdateEvent args)
{
var appearance = scrubber.Owner.GetComponentOrNull<AppearanceComponent>();
if (scrubber.Welded)
{
appearance?.SetData(ScrubberVisuals.State, ScrubberState.Welded);
return;
}
appearance?.SetData(ScrubberVisuals.State, ScrubberState.Off);
if (!scrubber.Enabled)
return;
if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
return;
if (!nodeContainer.TryGetNode(scrubber.OutletName, out PipeNode? outlet))
return;
var environment = args.Atmosphere.GetTile(scrubber.Owner.Transform.Coordinates)!;
Scrub(scrubber, appearance, environment, outlet);
if (!scrubber.WideNet) return;
// Scrub adjacent tiles too.
foreach (var adjacent in environment.AdjacentTiles)
{
// Pass null appearance, we don't need to set it there.
Scrub(scrubber, null, adjacent, outlet);
}
}
private void OnVentScrubberLeaveAtmosphere(EntityUid uid, GasVentScrubberComponent component, AtmosDeviceDisabledEvent args)
{
if (ComponentManager.TryGetComponent(uid, out AppearanceComponent? appearance))
{
appearance.SetData(ScrubberVisuals.State, ScrubberState.Off);
}
}
private void Scrub(GasVentScrubberComponent scrubber, AppearanceComponent? appearance, TileAtmosphere? tile, PipeNode outlet)
{
// Cannot scrub if tile is null or air-blocked.
if (tile?.Air == null)
return;
// Cannot scrub if pressure too high.
if (outlet.Air.Pressure >= 50 * Atmospherics.OneAtmosphere)
return;
if (scrubber.PumpDirection == ScrubberPumpDirection.Scrubbing)
{
appearance?.SetData(ScrubberVisuals.State, scrubber.WideNet ? ScrubberState.WideScrub : ScrubberState.Scrub);
var transferMoles = MathF.Min(1f, (scrubber.VolumeRate / tile.Air.Volume) * tile.Air.TotalMoles);
// Take a gas sample.
var removed = tile.Air.Remove(transferMoles);
// Nothing left to remove from the tile.
if (MathHelper.CloseTo(removed.TotalMoles, 0f))
return;
removed.ScrubInto(outlet.Air, scrubber.FilterGases);
// Remix the gases.
tile.AssumeAir(removed);
}
else if (scrubber.PumpDirection == ScrubberPumpDirection.Siphoning)
{
appearance?.SetData(ScrubberVisuals.State, ScrubberState.Siphon);
var transferMoles = tile.Air.TotalMoles * (scrubber.VolumeRate / tile.Air.Volume);
var removed = tile.Air.Remove(transferMoles);
outlet.AssumeAir(removed);
tile.Invalidate();
}
}
}
}

View File

@@ -1,7 +1,6 @@
#nullable enable
using Content.Server.Fluids.Components;
using Content.Server.Interfaces;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Maps;

View File

@@ -4,9 +4,9 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.Reactions;
using Content.Server.Coordinates.Helpers;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Interfaces;
using Content.Shared.Atmos;
using Content.Shared.Audio;
@@ -72,6 +72,8 @@ namespace Content.Server.Atmos
[ViewVariables]
private readonly TileAtmosphere[] _adjacentTiles = new TileAtmosphere[Atmospherics.Directions];
public IReadOnlyList<TileAtmosphere> AdjacentTiles => _adjacentTiles;
private AtmosDirection _adjacentBits = AtmosDirection.Invalid;
[ViewVariables, UsedImplicitly]

View File

@@ -1,9 +1,9 @@
#nullable enable
using System;
using Content.Server.Atmos;
using Content.Server.Atmos.Components;
using Content.Server.Body.Circulatory;
using Content.Server.Body.Respiratory;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Notification;
using Content.Shared.Atmos;
using Content.Shared.Body.Components;

View File

@@ -1,5 +1,5 @@
#nullable enable
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;

View File

@@ -12,7 +12,6 @@ using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Notification.Managers;
using Content.Shared.Random.Helpers;
using Content.Shared.Verbs;

View File

@@ -4,7 +4,6 @@ using Content.Server.Weapon.Melee;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;

View File

@@ -8,7 +8,6 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;

View File

@@ -6,7 +6,6 @@ using Content.Shared.Body.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;

View File

@@ -13,7 +13,6 @@ using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Notification.Managers;
using Content.Shared.Verbs;
using JetBrains.Annotations;

View File

@@ -1,8 +1,8 @@
#nullable enable
using System;
using System.Linq;
using Content.Server.Atmos.Components;
using Content.Server.Coordinates.Helpers;
using Content.Server.GameObjects.Components.Atmos;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;

View File

@@ -4,7 +4,6 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using JetBrains.Annotations;

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Atmos;
using Content.Shared.Chemistry;
using Content.Server.Atmos.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using JetBrains.Annotations;

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Atmos;
using Content.Shared.Chemistry;
using Content.Server.Atmos.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using JetBrains.Annotations;

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using JetBrains.Annotations;

View File

@@ -5,7 +5,6 @@ using Content.Shared.Clothing;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.NetIDs;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers;
using Robust.Shared.GameObjects;
using Robust.Shared.Players;

View File

@@ -1,6 +1,6 @@
#nullable enable
using Content.Server.Alert;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Server.Inventory.Components;
using Content.Server.Items;
using Content.Shared.ActionBlocker;
@@ -10,7 +10,6 @@ using Content.Shared.Actions.Components;
using Content.Shared.Alert;
using Content.Shared.Clothing;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Verbs;
using JetBrains.Annotations;

View File

@@ -1,7 +1,6 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;

View File

@@ -1,6 +1,6 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Administration;
using Content.Shared.Atmos;
using Robust.Shared.Console;

View File

@@ -1,7 +1,6 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;

View File

@@ -1,7 +1,7 @@
#nullable enable
using System;
using Content.Server.Administration;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Administration;
using Content.Shared.Atmos;
using Robust.Server.Player;

View File

@@ -1,7 +1,6 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Administration;
using Content.Shared.Atmos;
using Robust.Shared.Console;

View File

@@ -1,7 +1,7 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Administration;
using Content.Shared.Atmos;
using Robust.Shared.Console;

View File

@@ -1,6 +1,6 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;

View File

@@ -1,6 +1,6 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;

View File

@@ -1,6 +1,6 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Administration;
using Content.Shared.Atmos;
using Robust.Shared.Console;

View File

@@ -1,6 +1,6 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Shared.Administration;
using Content.Shared.Atmos;
using Robust.Shared.Console;

View File

@@ -1,6 +1,6 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.GameObjects.EntitySystems.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Administration;
using Robust.Server.Player;
using Robust.Shared.Console;

View File

@@ -1,13 +1,9 @@
#nullable enable
using System;
using System.Threading.Tasks;
using Content.Server.Stack;
using Content.Shared.Construction;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Stacks;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Construction.Completions

View File

@@ -1,11 +1,8 @@
#nullable enable
using System;
using System.Threading.Tasks;
using Content.Server.Stack;
using Content.Shared.Construction;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Prototypes;
using Content.Shared.Stacks;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;

View File

@@ -121,6 +121,7 @@ namespace Content.Server.Construction.Components
public void MapInit()
{
CreateBoardAndStockParts();
RefreshParts();
}
}
}

View File

@@ -26,7 +26,7 @@ namespace Content.Server.Conveyor
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("angle")]
private Angle _angle;
private Angle _angle = Angle.Zero;
public float Speed => _speed;

View File

@@ -1,7 +1,7 @@
#nullable enable
using System.Collections.Generic;
using Content.Server.Atmos.Components;
using System.Linq;
using Content.Server.GameObjects.Components.Atmos;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Resistances;

View File

@@ -1,4 +1,4 @@
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Atmos.Components;
using Content.Server.Nutrition.Components;
using Content.Server.Stunnable.Components;
using Content.Shared.Damage.Components;

View File

@@ -6,9 +6,9 @@ using System.Threading;
using System.Threading.Tasks;
using Content.Server.Anchor;
using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Disposal.Tube.Components;
using Content.Server.DoAfter;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Hands.Components;
using Content.Server.Interfaces;
using Content.Server.Power.Components;
@@ -19,7 +19,6 @@ using Content.Shared.Disposal.Components;
using Content.Shared.DragDrop;
using Content.Shared.Interaction;
using Content.Shared.Movement;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers;
using Content.Shared.Throwing;
using Content.Shared.Verbs;

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