Canisters [ Continuation of clement-or #2544 ] (#2628)

* Added atmos sprites from CEV-Eris

* Moved canister sprites to appropriate dir

* Removed unnecessary sprites, edited canisters prototype

* Created Gas Canister UI and release pressure buttons

* Changed GasMixture's pressure calculation (convert liters to cube meters)

* Added relabeling Canisters

* Reverted changes on GasMixture

* Changed my name in the credits

* Added valve opening on canisters

* Change canister visual state when connected to a port

* Added nullable to SharedGasCanisterComponent

* Replaced nullable contexts

* Changed again nullable annotation context

* Moved name in the credits to correct alphabetical order

* Canisters: Fix the most blatant issues with this PR (the added project interdependencies for no reason whatsoever)

* Canisters: Stop crashes when canisters leave atmosphere

* Canisters: Gas released into no atmosphere gets transferred "into space" (deleted)

* Atmos: Nullability annotations on TileAtmosphere, explaination of the states of TileAtmosphere.Air

* Canisters: If in an airblocked tile, do NOT release gas

* Scrubbers: Fix typo leading to them not connecting properly.

* Revert manual changes to credits file (sorry!) (1/2)

This reverts commit 94f3b0e5df8d9c2fa189866a17a231920f99bdaf.

* Revert manual changes to credits file (sorry!) (2/2)

This reverts commit 1986fb094dfaa44060f08d280f36b755258d17a6.

* Canisters: Apply @Zumorica 's reviews

* Canisters: Add missing localization as suggested by PJB

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>

* Canisters: Pressure lights!

* Canisters: Light is now unshaded.

* Canisters: Now using IActivate

* Gas canisters (& air canister), now with their numbers properly calibrated (hopefully)

* Canisters: Refactor how their layers are added to be more like ApcVisualizer

* Canisters: Clean up of the tile invalidation/air release logic

* Canisters: Some gas canister window improvements

* Canisters: Clean up release pressure change button label code

Co-authored-by: Clement-O <topy72.mine@gmail.com>
Co-authored-by: Clément <clement.orlandini@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
This commit is contained in:
20kdc
2020-12-08 19:45:24 +00:00
committed by GitHub
parent 2b6964746c
commit 58af9003e7
38 changed files with 1055 additions and 21 deletions

View File

@@ -0,0 +1,115 @@
#nullable enable
using System;
using System.Diagnostics;
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using Content.Client.GameObjects.Components.Atmos;
using Content.Shared.GameObjects.Components.Atmos;
using Robust.Shared.Localization;
namespace Content.Client.GameObjects.Components.Atmos
{
/// <summary>
/// Initializes a <see cref="GasCanisterWindow"/> and updates it when new server messages are received.
/// </summary>
[UsedImplicitly]
public class GasCanisterBoundUserInterface : BoundUserInterface
{
private GasCanisterWindow? _window;
public GasCanisterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
/// <summary>
/// When a button is pressed, send a network message to the server
/// </summary>
/// <param name="button">Which button has been pressed, as an enum item</param>
private void ButtonPressed(UiButton button)
{
SendMessage(new UiButtonPressedMessage(button));
}
/// <summary>
/// When the release pressure is changed
/// </summary>
/// <param name="value">The pressure value</param>
private void ReleasePressureButtonPressed(float value)
{
SendMessage(new ReleasePressureButtonPressedMessage(value));
}
protected override void Open()
{
base.Open();
_window = new GasCanisterWindow();
_window.Title = Loc.GetString("Gas Canister");
_window.OpenCentered();
_window.OnClose += Close;
// Bind buttons OnPressed event
foreach (ReleasePressureButton btn in _window.ReleasePressureButtons)
{
btn.OnPressed += _ => ReleasePressureButtonPressed(btn.PressureChange);
}
// Bind events
_window.EditLabelBtn.OnPressed += _ => EditLabel();
_window.ToggleValve.OnPressed += _ => ToggleValve();
}
/// <summary>
/// Called when the edit label button is pressed
/// </summary>
private void EditLabel()
{
// Obligatory check because bool isn't nullable
if (_window == null) return;
if (_window.LabelInputEditable)
{
if (_window.LabelInput.Text != _window.OldLabel)
SendMessage(new CanisterLabelChangedMessage(_window.LabelInput.Text));
_window.LabelInputEditable = false;
}
else
{
_window.LabelInputEditable = true;
_window.LabelInput.HasKeyboardFocus();
}
}
private void ToggleValve()
{
SendMessage(new UiButtonPressedMessage(UiButton.ValveToggle));
}
/// <summary>
/// Update the UI state based on server-sent info
/// </summary>
/// <param name="state"></param>
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (!(state is GasCanisterBoundUserInterfaceState cast))
{
return;
}
_window?.UpdateState(cast);
}
}
}

View File

@@ -0,0 +1,72 @@
using Content.Shared.GameObjects.Components.Atmos;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Atmos
{
public class GasCanisterVisualizer : AppearanceVisualizer
{
private string _stateConnected;
private string[] _statePressure = new string[] {"", "", "", ""};
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_stateConnected = node.GetNode("stateConnected").AsString();
for (int i = 0; i < _statePressure.Length; i++)
_statePressure[i] = node.GetNode("stateO" + i).AsString();
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var sprite = entity.GetComponent<ISpriteComponent>();
sprite.LayerMapSet(Layers.ConnectedToPort, sprite.AddLayerState(_stateConnected));
sprite.LayerSetVisible(Layers.ConnectedToPort, false);
sprite.LayerMapSet(Layers.PressureLight, sprite.AddLayerState(_stateConnected));
sprite.LayerSetShader(Layers.PressureLight, "unshaded");
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.Deleted)
{
return;
}
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite))
{
return;
}
// Update the visuals : Is the canister connected to a port or not
if (component.TryGetData(GasCanisterVisuals.ConnectedState, out bool isConnected))
{
sprite.LayerSetVisible(Layers.ConnectedToPort, isConnected);
}
// Update the visuals : Canister lights
if (component.TryGetData(GasCanisterVisuals.PressureState, out int pressureState))
if ((pressureState >= 0) && (pressureState < _statePressure.Length))
sprite.LayerSetState(Layers.PressureLight, _statePressure[pressureState]);
}
enum Layers
{
ConnectedToPort,
PressureLight
}
}
}

View File

@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.GameObjects.Components.Disposal;
using Robust.Client.Graphics.Drawing;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Content.Client.GameObjects.Components.Atmos;
using Content.Shared.GameObjects.Components.Atmos;
namespace Content.Client.GameObjects.Components.Atmos
{
/// <summary>
/// Client-side UI used to control a <see cref="SharedGasCanisterComponent"/>
/// </summary>
public class GasCanisterWindow : SS14Window
{
private readonly Label _pressure;
private readonly Label _releasePressure;
public readonly CheckButton ToggleValve;
public readonly LineEdit LabelInput;
public readonly Button EditLabelBtn;
public string OldLabel { get; set; } = "";
public bool LabelInputEditable {
get => LabelInput.Editable;
set {
LabelInput.Editable = value;
EditLabelBtn.Text = value ? Loc.GetString("OK") : Loc.GetString("Edit");
}
}
public List<ReleasePressureButton> ReleasePressureButtons { get; private set; }
protected override Vector2? CustomSize => (300, 200);
public GasCanisterWindow()
{
HBoxContainer releasePressureButtons;
Contents.AddChild(new VBoxContainer
{
Children =
{
new VBoxContainer
{
Children =
{
new HBoxContainer()
{
Children =
{
new Label(){ Text = Loc.GetString("Label") },
(LabelInput = new LineEdit() { Text = Name, Editable = false,
CustomMinimumSize = new Vector2(200, 30)}),
(EditLabelBtn = new Button()),
}
},
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Pressure:")},
(_pressure = new Label())
}
},
new VBoxContainer()
{
Children =
{
new HBoxContainer()
{
Children =
{
new Label() {Text = Loc.GetString("Release pressure:")},
(_releasePressure = new Label())
}
},
(releasePressureButtons = new HBoxContainer()
{
Children =
{
new ReleasePressureButton() {PressureChange = -50},
new ReleasePressureButton() {PressureChange = -10},
new ReleasePressureButton() {PressureChange = -1},
new ReleasePressureButton() {PressureChange = -0.1f},
new ReleasePressureButton() {PressureChange = 0.1f},
new ReleasePressureButton() {PressureChange = 1},
new ReleasePressureButton() {PressureChange = 10},
new ReleasePressureButton() {PressureChange = 50}
}
})
}
},
new HBoxContainer()
{
Children =
{
new Label { Text = Loc.GetString("Valve") },
(ToggleValve = new CheckButton() { Text = Loc.GetString("Open") })
}
}
},
}
}
});
// Create the release pressure buttons list
ReleasePressureButtons = new List<ReleasePressureButton>();
foreach (var control in releasePressureButtons.Children.ToList())
{
var btn = (ReleasePressureButton) control;
ReleasePressureButtons.Add(btn);
}
// Reset the editable label
LabelInputEditable = false;
}
/// <summary>
/// Update the UI based on <see cref="GasCanisterBoundUserInterfaceState"/>
/// </summary>
/// <param name="state">The state the UI should reflect</param>
public void UpdateState(GasCanisterBoundUserInterfaceState state)
{
_pressure.Text = Loc.GetString("{0}kPa", state.Volume);
_releasePressure.Text = Loc.GetString("{0}kPa", state.ReleasePressure);
// Update the canister label
OldLabel = LabelInput.Text;
LabelInput.Text = state.Label;
Title = state.Label;
// Reset the editable label
LabelInputEditable = false;
ToggleValve.Pressed = state.ValveOpened;
}
}
/// <summary>
/// Special button class which stores a numerical value and has it as a label
/// </summary>
public class ReleasePressureButton : Button
{
public float PressureChange
{
get { return _pressureChange; }
set
{
_pressureChange = value;
Text = (value >= 0) ? ("+" + value) : value.ToString();
}
}
private float _pressureChange;
public ReleasePressureButton() : base() {}
}
}

View File

@@ -452,15 +452,16 @@ namespace Content.Server.Atmos
/// <summary>
/// Releases gas from this mixture to the output mixture.
/// If the output mixture is null, then this is being released into space.
/// It can't transfer air to a mixture with higher pressure.
/// </summary>
/// <param name="outputAir"></param>
/// <param name="targetPressure"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ReleaseGasTo(GasMixture outputAir, float targetPressure)
public bool ReleaseGasTo(GasMixture? outputAir, float targetPressure)
{
var outputStartingPressure = outputAir.Pressure;
var outputStartingPressure = outputAir?.Pressure ?? 0;
var inputStartingPressure = Pressure;
if (outputStartingPressure >= MathF.Min(targetPressure, inputStartingPressure - 10))
@@ -472,11 +473,11 @@ namespace Content.Server.Atmos
// We calculate the necessary moles to transfer with the ideal gas law.
var pressureDelta = MathF.Min(targetPressure - outputStartingPressure, (inputStartingPressure - outputStartingPressure) / 2f);
var transferMoles = pressureDelta * outputAir.Volume / (Temperature * Atmospherics.R);
var transferMoles = pressureDelta * (outputAir?.Volume ?? Atmospherics.CellVolume) / (Temperature * Atmospherics.R);
// And now we transfer the gas.
var removed = Remove(transferMoles);
outputAir.Merge(removed);
outputAir?.Merge(removed);
return true;

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable annotations
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -49,7 +50,7 @@ namespace Content.Server.Atmos
private static int _soundCooldown;
[ViewVariables]
public TileAtmosphere PressureSpecificTarget { get; set; }
public TileAtmosphere? PressureSpecificTarget { get; set; }
[ViewVariables]
public float PressureDifference { get; set; }
@@ -103,8 +104,12 @@ namespace Content.Server.Atmos
[ViewVariables]
public ExcitedGroup ExcitedGroup { get; set; }
/// <summary>
/// The air in this tile. If null, this tile is completely airblocked.
/// This can be immutable if the tile is spaced.
/// </summary>
[ViewVariables]
public GasMixture Air { get; set; }
public GasMixture? Air { get; set; }
[ViewVariables, UsedImplicitly]
private int _blockedAirflow => (int)BlockedAirflow;

View File

@@ -1,4 +1,6 @@
using Content.Server.Atmos;
#nullable enable
using System;
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.Atmos.Piping;
using Content.Server.Interfaces;
using Robust.Shared.GameObjects;
@@ -6,36 +8,83 @@ using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System;
using System.Linq;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Atmos;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Atmos;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
namespace Content.Server.GameObjects.Components.Atmos
{
/// <summary>
/// Component that manages gas mixtures temperature, pressure and output.
/// </summary>
[RegisterComponent]
public class GasCanisterComponent : Component, IGasMixtureHolder
[ComponentReference(typeof(IActivate))]
public class GasCanisterComponent : Component, IGasMixtureHolder, IActivate
{
public override string Name => "GasCanister";
[ViewVariables]
public GasMixture Air { get; set; }
private const int MaxLabelLength = 32;
[ViewVariables(VVAccess.ReadWrite)]
public string Label { get; set; } = "Gas Canister";
[ViewVariables(VVAccess.ReadWrite)]
public bool ValveOpened { get; set; } = false;
/// <summary>
/// What <see cref="GasMixture"/> the canister contains.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public GasMixture Air { get; set; } = default!;
[ViewVariables]
public bool Anchored => !Owner.TryGetComponent<IPhysicsComponent>(out var physics) || physics.Anchored;
/// <summary>
/// The floor connector port that the canister is attached to.
/// </summary>
[ViewVariables]
public GasCanisterPortComponent ConnectedPort { get; private set; }
public GasCanisterPortComponent? ConnectedPort { get; private set; }
[ViewVariables]
public bool ConnectedToPort => ConnectedPort != null;
private const float DefaultVolume = 10;
[ViewVariables(VVAccess.ReadWrite)] public float ReleasePressure { get; set; }
/// <summary>
/// The user interface bound to the canister.
/// </summary>
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(SharedGasCanisterComponent.GasCanisterUiKey.Key);
/// <summary>
/// Stores the last ui state after it's been casted into <see cref="GasCanisterBoundUserInterface"/>
/// </summary>
private GasCanisterBoundUserInterfaceState? _lastUiState;
private AppearanceComponent? _appearance;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => Air, "gasMixture", new GasMixture(DefaultVolume));
}
public override void Initialize()
{
base.Initialize();
@@ -44,8 +93,21 @@ namespace Content.Server.GameObjects.Components.Atmos
AnchorUpdate();
physics.AnchoredChanged += AnchorUpdate;
}
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
// Init some variables
Label = Owner.Name;
Owner.TryGetComponent(out _appearance);
UpdateUserInterface();
UpdateAppearance();
}
#region Connector port methods
public override void OnRemove()
{
base.OnRemove();
@@ -53,23 +115,27 @@ namespace Content.Server.GameObjects.Components.Atmos
{
physics.AnchoredChanged -= AnchorUpdate;
}
if (UserInterface != null)
{
UserInterface.OnReceiveMessage -= OnUiReceiveMessage;
}
DisconnectFromPort();
}
public void TryConnectToPort()
{
if (!Owner.TryGetComponent<SnapGridComponent>(out var snapGrid)) return;
var port = snapGrid.GetLocal()
.Select(entity => entity.TryGetComponent<GasCanisterPortComponent>(out var port) ? port : null)
.Where(port => port != null)
.Where(port => !port.ConnectedToCanister)
.Where(port => !port!.ConnectedToCanister)
.FirstOrDefault();
if (port == null) return;
ConnectedPort = port;
ConnectedPort.ConnectGasCanister(this);
}
public void DisconnectFromPort()
{
ConnectedPort?.DisconnectGasCanister();
@@ -86,6 +152,181 @@ namespace Content.Server.GameObjects.Components.Atmos
{
DisconnectFromPort();
}
UpdateAppearance();
}
#endregion
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
return;
UserInterface?.Open(actor.playerSession);
}
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
if (!PlayerCanUse(obj.Session.AttachedEntity))
{
return;
}
// If the label has been changed by a client
if (obj.Message is CanisterLabelChangedMessage canLabelMessage)
{
var newLabel = canLabelMessage.NewLabel;
if (newLabel.Length > MaxLabelLength)
newLabel = newLabel.Substring(0, MaxLabelLength);
Label = newLabel;
Owner.Name = Label;
UpdateUserInterface();
return;
}
// If the release pressure has been adjusted by the client on the gas canister
if (obj.Message is ReleasePressureButtonPressedMessage rPMessage)
{
ReleasePressure += rPMessage.ReleasePressure;
ReleasePressure = Math.Clamp(ReleasePressure, 0, 1000);
ReleasePressure = MathF.Round(ReleasePressure, 2);
UpdateUserInterface();
return;
}
if (obj.Message is UiButtonPressedMessage btnPressedMessage)
{
switch (btnPressedMessage.Button)
{
case UiButton.ValveToggle:
ToggleValve();
break;
}
}
}
/// <summary>
/// Update the user interface if relevant
/// </summary>
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
if (_lastUiState != null && _lastUiState.Equals(state))
{
return;
}
_lastUiState = state;
UserInterface?.SetState(state);
}
/// <summary>
/// Update the canister's sprite
/// </summary>
private void UpdateAppearance()
{
_appearance?.SetData(GasCanisterVisuals.ConnectedState, ConnectedToPort);
// The Eris canisters are being used, so best to use the Eris light logic unless someone else has a better idea.
// https://github.com/discordia-space/CEV-Eris/blob/fdd6ee7012f46838a6711adb1737cd90c48ae448/code/game/machinery/atmoalter/canister.dm#L129
if (Air.Pressure < 10)
{
_appearance?.SetData(GasCanisterVisuals.PressureState, 0);
}
else if (Air.Pressure < Atmospherics.OneAtmosphere)
{
_appearance?.SetData(GasCanisterVisuals.PressureState, 1);
}
else if (Air.Pressure < (15 * Atmospherics.OneAtmosphere))
{
_appearance?.SetData(GasCanisterVisuals.PressureState, 2);
}
else
{
_appearance?.SetData(GasCanisterVisuals.PressureState, 3);
}
}
/// <summary>
/// Get the current interface state from server data
/// </summary>
/// <returns>The state</returns>
private GasCanisterBoundUserInterfaceState GetUserInterfaceState()
{
// We round the pressure for ease of reading
return new GasCanisterBoundUserInterfaceState(Label,
MathF.Round(Air.Pressure, 2),
ReleasePressure,
ValveOpened);
}
public void AirWasUpdated()
{
UpdateUserInterface();
UpdateAppearance();
}
#region Check methods
private bool PlayerCanUse(IEntity? player)
{
if (player == null)
{
return false;
}
if (!ActionBlockerSystem.CanInteract(player) ||
!ActionBlockerSystem.CanUse(player))
{
return false;
}
return true;
}
#endregion
/// <summary>
/// Called when the canister's valve is toggled
/// </summary>
private void ToggleValve()
{
ValveOpened = !ValveOpened;
UpdateUserInterface();
}
/// <summary>
/// Called every frame
/// </summary>
/// <param name="frameTime"></param>
public void Update(in float frameTime)
{
if (ValveOpened)
{
var tileAtmosphere = Owner.Transform.Coordinates.GetTileAtmosphere();
if (tileAtmosphere != null)
{
// If tileAtmosphere.Air is null, then we're airblocked, so DON'T release
if (tileAtmosphere.Air != null)
{
Air.ReleaseGasTo(tileAtmosphere.Air, ReleasePressure);
tileAtmosphere.Invalidate();
}
}
else
{
Air.ReleaseGasTo(null, ReleasePressure);
}
AirWasUpdated();
}
}
}
}

View File

@@ -283,6 +283,7 @@ namespace Content.Server.GameObjects.Components.Atmos
{
var tile = GetTile(indices);
if (tile?.GridIndex != _gridId) return;
// includeAirBlocked is false, therefore all tiles in this have Air != null.
var adjacent = GetAdjacentTiles(indices);
tile.Air = new GasMixture(GetVolumeForCells(1), AtmosphereSystem){Temperature = Atmospherics.T20C};
Tiles[indices] = tile;
@@ -291,7 +292,7 @@ namespace Content.Server.GameObjects.Components.Atmos
foreach (var (_, adj) in adjacent)
{
var mix = adj.Air.RemoveRatio(ratio);
var mix = adj.Air!.RemoveRatio(ratio);
tile.Air.Merge(mix);
adj.Air.Merge(mix);
}

View File

@@ -62,6 +62,7 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping
public override void Update()
{
ConnectedCanister?.Air.Share(_gasPort.Air, 1);
ConnectedCanister?.AirWasUpdated();
}
public void ConnectGasCanister(GasCanisterComponent gasCanister)

View File

@@ -414,7 +414,6 @@ namespace Content.Server.GameObjects.Components.Disposal
case UiButton.Power:
TogglePower();
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
break;
default:
throw new ArgumentOutOfRangeException();

View File

@@ -351,7 +351,8 @@ namespace Content.Server.GameObjects.Components.Doors
foreach (var (_, adjacent) in gridAtmosphere.GetAdjacentTiles(tileAtmos.GridIndices))
{
var moles = adjacent.Air.TotalMoles;
// includeAirBlocked remains false, and therefore Air must be present
var moles = adjacent.Air!.TotalMoles;
if (moles < minMoles)
minMoles = moles;
if (moles > maxMoles)

View File

@@ -0,0 +1,19 @@
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.GameObjects.Components.Recycling;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
internal sealed class GasCanisterSystem : EntitySystem
{
public override void Update(float frameTime)
{
foreach (var component in ComponentManager.EntityQuery<GasCanisterComponent>())
{
component.Update(frameTime);
}
}
}
}

View File

@@ -0,0 +1,123 @@
#nullable enable annotations
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Atmos
{
public class SharedGasCanisterComponent : Component
{
public override string Name => "GasCanister";
/// <summary>
/// Key representing which <see cref="BoundUserInterface"/> is currently open.
/// Useful when there are multiple UI for an object. Here it's future-proofing only.
/// </summary>
[Serializable, NetSerializable]
public enum GasCanisterUiKey
{
Key,
}
}
#region Enums
/// <summary>
/// Enum representing a UI button.
/// </summary>
[Serializable, NetSerializable]
public enum UiButton
{
ValveToggle
}
/// <summary>
/// Used in <see cref="GasCanisterVisualizer"/> to determine which visuals to update.
/// </summary>
[Serializable, NetSerializable]
public enum GasCanisterVisuals
{
ConnectedState,
PressureState
}
#endregion
/// <summary>
/// Represents a <see cref="GasCanisterComponent"/> state that can be sent to the client
/// </summary>
[Serializable, NetSerializable]
public class GasCanisterBoundUserInterfaceState : BoundUserInterfaceState
{
public readonly string Label;
public readonly float Volume;
public readonly float ReleasePressure;
public readonly bool ValveOpened;
public GasCanisterBoundUserInterfaceState(string newLabel, float volume, float releasePressure, bool valveOpened)
{
Label = newLabel;
Volume = volume;
ReleasePressure = releasePressure;
ValveOpened = valveOpened;
}
public bool Equals(GasCanisterBoundUserInterfaceState? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Label == other.Label &&
Volume.Equals(other.Volume) &&
ReleasePressure.Equals(other.ReleasePressure) &&
ValveOpened == other.ValveOpened;
}
}
#region NetMessages
/// <summary>
/// Message sent from the client to the server when a gas canister button is pressed
/// </summary>
[Serializable, NetSerializable]
public class UiButtonPressedMessage : BoundUserInterfaceMessage
{
public readonly UiButton Button;
public UiButtonPressedMessage(UiButton button)
{
Button = button;
}
}
/// <summary>
/// Message sent when the release pressure is changed client side
/// </summary>
[Serializable, NetSerializable]
public class ReleasePressureButtonPressedMessage : BoundUserInterfaceMessage
{
public readonly float ReleasePressure;
public ReleasePressureButtonPressedMessage(float val) : base()
{
ReleasePressure = val;
}
}
/// <summary>
/// Message sent when the canister label has been changed
/// </summary>
[Serializable, NetSerializable]
public class CanisterLabelChangedMessage : BoundUserInterfaceMessage
{
public readonly string NewLabel;
public CanisterLabelChangedMessage(string newLabel) : base()
{
NewLabel = newLabel;
}
}
#endregion
}

View File

@@ -18,12 +18,101 @@
- type: GasCanister
- type: Anchorable
- type: Pullable
- type: UserInterface
- type: Appearance
- type: entity
parent: GasCanisterBase
id: GasCanister
name: Gas Canister
description: A canister that can contain any type of gas. It can be attached to connector ports using a wrench.
components:
- type: Sprite
sprite: "Constructible/Power/apc.rsi"
state: apc0
netsync: false
sprite: Constructible/Atmos/canister.rsi
state: grey
- type: Appearance
visuals:
- type: GasCanisterVisualizer
stateConnected: can-connector
stateO0: can-o0
stateO1: can-o1
stateO2: can-o2
stateO3: can-o3
- type: UserInterface
interfaces:
- key: enum.GasCanisterUiKey.Key
type: GasCanisterBoundUserInterface
- type: Physics
mass: 25
anchored: false
shapes:
- !type:PhysShapeAabb
bounds: "-0.5,-0.25,0.5,0.25"
mask:
- Impassable
- MobImpassable
- VaultImpassable
- SmallImpassable
layer:
- Opaque
- MobImpassable
- VaultImpassable
- SmallImpassable
# Filled canisters, contain 1871.71051 moles each
- type: entity
parent: GasCanister
id: AirCanister
name: Air Canister
components:
- type: Sprite
sprite: Constructible/Atmos/canister.rsi
state: grey
- type: GasCanister
gasMixture:
volume: 1000
moles:
- 393.0592071 # oxygen 21%
- 1478.6513029 # nitrogen 79%
temperature: 293.15
- type: entity
parent: GasCanister
id: OxygenCanister
name: Oxygen Canister
components:
- type: Sprite
sprite: Constructible/Atmos/canister.rsi
state: blue
- type: GasCanister
gasMixture:
volume: 1000
moles:
- 1871.71051 # oxygen
temperature: 293.15
- type: entity
parent: GasCanister
id: PhoronCanister
name: Phoron Canister
components:
- type: Sprite
sprite: Constructible/Atmos/canister.rsi
state: orange
- type: GasCanister
gasMixture:
volume: 1000
moles:
- 0 # oxygen
- 0 # nitrogen
- 0 # carbon dioxide
- 1871.71051 # phoron
temperature: 293.15

View File

@@ -34,7 +34,7 @@
- type: NodeContainer
nodes:
- !type:PipeNode
nodeGroID: Pipe
nodeGroupID: Pipe
pipeDirection: East
- type: PressureSiphon
scrubberOutletDirection: East

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

View File

@@ -0,0 +1,201 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/discordia-space/CEV-Eris",
"states": [
{
"name": "black",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "black-1",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "blue",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "blue-1",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "can-connector",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "can-o0",
"directions": 1,
"delays": [
[
0.2,
0.2
]
]
},
{
"name": "can-o1",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "can-o2",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "can-o3",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "can-oa1",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "can-open",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "grey",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "grey-1",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "orange",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "orange-1",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "red",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "red-1",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "redws",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "redws-1",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "yellow",
"directions": 1,
"delays": [
[
1
]
]
},
{
"name": "yellow-1",
"directions": 1,
"delays": [
[
1
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 957 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B