Gas tanks and masks (#2409)

Co-authored-by: a.rudenko <creadth@gmail.com>
Co-authored-by: creadth <creadth@users.noreply.github.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
This commit is contained in:
Víctor Aguilera Puerto
2020-10-27 20:53:44 +01:00
committed by GitHub
parent 329926b175
commit 870d052354
77 changed files with 1653 additions and 58 deletions

View File

@@ -190,7 +190,10 @@
"Hoe", "Hoe",
"Seed", "Seed",
"BotanySharp", "BotanySharp",
"PlantSampleTaker" "PlantSampleTaker",
"Internals",
"GasTank",
"BreathMask",
}; };
} }
} }

View File

@@ -0,0 +1,50 @@
using Content.Shared.GameObjects.Components.Atmos.GasTank;
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
namespace Content.Client.UserInterface.Atmos.GasTank
{
[UsedImplicitly]
public class GasTankBoundUserInterface
: BoundUserInterface
{
public GasTankBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) :
base(owner, uiKey)
{
}
private GasTankWindow _window;
public void SetOutputPressure(in float value)
{
SendMessage(new GasTankSetPressureMessage {Pressure = value});
}
public void ToggleInternals()
{
SendMessage(new GasTankToggleInternalsMessage());
}
protected override void Open()
{
base.Open();
_window = new GasTankWindow(this);
_window.OnClose += Close;
_window.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
_window.UpdateState((GasTankBoundUserInterfaceState) state);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_window.Close();
}
}
}

View File

@@ -0,0 +1,235 @@
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.GameObjects.Components.Atmos.GasTank;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
namespace Content.Client.UserInterface.Atmos.GasTank
{
public class GasTankWindow
: BaseWindow
{
private GasTankBoundUserInterface _owner;
private readonly Label _lblName;
private readonly VBoxContainer _topContainer;
private readonly Control _contentContainer;
private readonly IResourceCache _resourceCache = default!;
private readonly RichTextLabel _lblPressure;
private readonly FloatSpinBox _spbPressure;
private readonly RichTextLabel _lblInternals;
private readonly Button _btnInternals;
public GasTankWindow(GasTankBoundUserInterface owner)
{
TextureButton btnClose;
_resourceCache = IoCManager.Resolve<IResourceCache>();
_owner = owner;
var rootContainer = new LayoutContainer {Name = "GasTankRoot"};
AddChild(rootContainer);
MouseFilter = MouseFilterMode.Stop;
var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
{
Texture = panelTex,
Modulate = Color.FromHex("#25252A"),
};
back.SetPatchMargin(StyleBox.Margin.All, 10);
var topPanel = new PanelContainer
{
PanelOverride = back,
MouseFilter = MouseFilterMode.Pass
};
var bottomWrap = new LayoutContainer
{
Name = "BottomWrap"
};
rootContainer.AddChild(topPanel);
rootContainer.AddChild(bottomWrap);
LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide);
LayoutContainer.SetMarginBottom(topPanel, -85);
LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide);
LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both);
var topContainerWrap = new VBoxContainer
{
Children =
{
(_topContainer = new VBoxContainer()),
new Control {CustomMinimumSize = (0, 110)}
}
};
rootContainer.AddChild(topContainerWrap);
LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide);
var font = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
var topRow = new MarginContainer
{
MarginLeftOverride = 4,
MarginTopOverride = 2,
MarginRightOverride = 12,
MarginBottomOverride = 2,
Children =
{
new HBoxContainer
{
Children =
{
(_lblName = new Label
{
Text = Loc.GetString("Gas Tank"),
FontOverride = font,
FontColorOverride = StyleNano.NanoGold,
SizeFlagsVertical = SizeFlags.ShrinkCenter
}),
new Control
{
CustomMinimumSize = (20, 0),
SizeFlagsHorizontal = SizeFlags.Expand
},
(btnClose = new TextureButton
{
StyleClasses = {SS14Window.StyleClassWindowCloseButton},
SizeFlagsVertical = SizeFlags.ShrinkCenter
})
}
}
}
};
var middle = new PanelContainer
{
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#202025")},
Children =
{
new MarginContainer
{
MarginLeftOverride = 8,
MarginRightOverride = 8,
MarginTopOverride = 4,
MarginBottomOverride = 4,
Children =
{
(_contentContainer = new VBoxContainer())
}
}
}
};
_topContainer.AddChild(topRow);
_topContainer.AddChild(new PanelContainer
{
CustomMinimumSize = (0, 2),
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
});
_topContainer.AddChild(middle);
_topContainer.AddChild(new PanelContainer
{
CustomMinimumSize = (0, 2),
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
});
_lblPressure = new RichTextLabel();
_contentContainer.AddChild(_lblPressure);
//internals
_lblInternals = new RichTextLabel
{CustomMinimumSize = (200, 0), SizeFlagsVertical = SizeFlags.ShrinkCenter};
_btnInternals = new Button {Text = Loc.GetString("Toggle")};
_contentContainer.AddChild(
new MarginContainer
{
MarginTopOverride = 7,
Children =
{
new HBoxContainer
{
Children = {_lblInternals, _btnInternals}
}
}
});
// Separator
_contentContainer.AddChild(new Control
{
CustomMinimumSize = new Vector2(0, 10)
});
_contentContainer.AddChild(new Label
{
Text = Loc.GetString("Output Pressure"),
Align = Label.AlignMode.Center
});
_spbPressure = new FloatSpinBox {IsValid = f => f >= 0 || f <= 3000};
_contentContainer.AddChild(
new MarginContainer
{
MarginRightOverride = 25,
MarginLeftOverride = 25,
MarginBottomOverride = 7,
Children =
{
_spbPressure
}
}
);
// Handlers
_spbPressure.OnValueChanged += args =>
{
_owner.SetOutputPressure(args.Value);
};
_btnInternals.OnPressed += args =>
{
_owner.ToggleInternals();
};
btnClose.OnPressed += _ => Close();
}
public void UpdateState(GasTankBoundUserInterfaceState state)
{
_lblPressure.SetMarkup(Loc.GetString("Pressure: {0:0.##} kPa", state.TankPressure));
_btnInternals.Disabled = !state.CanConnectInternals;
_lblInternals.SetMarkup(Loc.GetString("Internals: [color={0}]{1}[/color]",
state.InternalsConnected ? "green" : "red",
state.InternalsConnected ? "Connected" : "Disconnected"));
if (state.OutputPressure.HasValue)
{
_spbPressure.Value = state.OutputPressure.Value;
}
}
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
{
return DragMode.Move;
}
protected override bool HasPoint(Vector2 point)
{
return false;
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Atmos; using Content.Server.Atmos;
@@ -46,7 +47,7 @@ namespace Content.IntegrationTests.Tests.Body
var originalOxygen = 2; var originalOxygen = 2;
var originalNitrogen = 8; var originalNitrogen = 8;
var breathedPercentage = Atmospherics.BreathPercentage; var breathedPercentage = Atmospherics.BreathVolume / gas.Volume;
gas.AdjustMoles(Gas.Oxygen, originalOxygen); gas.AdjustMoles(Gas.Oxygen, originalOxygen);
gas.AdjustMoles(Gas.Nitrogen, originalNitrogen); gas.AdjustMoles(Gas.Nitrogen, originalNitrogen);
@@ -76,7 +77,7 @@ namespace Content.IntegrationTests.Tests.Body
lung.Exhale(1, gas); lung.Exhale(1, gas);
var lungOxygenAfterExhale = lung.Air.GetMoles(Gas.Oxygen); var lungOxygenAfterExhale = lung.Air.GetMoles(Gas.Oxygen);
var exhaledOxygen = lungOxygenBeforeExhale - lungOxygenAfterExhale; var exhaledOxygen = Math.Abs(lungOxygenBeforeExhale - lungOxygenAfterExhale);
// Not completely empty // Not completely empty
Assert.Positive(lung.Air.Gases.Sum()); Assert.Positive(lung.Air.Gases.Sum());

View File

@@ -65,6 +65,38 @@ namespace Content.Server.Atmos
} }
} }
/// <summary>
/// Heat capacity ratio of gas mixture
/// </summary>
[ViewVariables]
public float HeatCapacityRatio
{
get
{
var delimiterSum = 0f;
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
delimiterSum += _moles[i] / (_atmosphereSystem.GetGas(i).HeatCapacityRatio - 1);
}
return 1 + TotalMoles / delimiterSum;
}
}
public float MolarMass
{
get
{
var molarMass = 0f;
var totalMoles = TotalMoles;
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
molarMass += _atmosphereSystem.GetGas(i).MolarMass * (_moles[i] / totalMoles);
}
return molarMass;
}
}
[ViewVariables] [ViewVariables]
public float HeatCapacityArchived public float HeatCapacityArchived
{ {
@@ -136,14 +168,15 @@ namespace Content.Server.Atmos
public GasMixture(AtmosphereSystem? atmosphereSystem) public GasMixture(AtmosphereSystem? atmosphereSystem)
{ {
_atmosphereSystem = atmosphereSystem ?? EntitySystem.Get<AtmosphereSystem>(); _atmosphereSystem = atmosphereSystem ?? EntitySystem.Get<AtmosphereSystem>();
_moles = new float[_atmosphereSystem.Gases.Count()];
_molesArchived = new float[_moles.Length];
} }
public GasMixture(float volume, AtmosphereSystem? atmosphereSystem = null) public GasMixture(float volume, AtmosphereSystem? atmosphereSystem = null): this(atmosphereSystem)
{ {
if (volume < 0) if (volume < 0)
volume = 0; volume = 0;
Volume = volume; Volume = volume;
_atmosphereSystem = atmosphereSystem ?? EntitySystem.Get<AtmosphereSystem>();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -1127,6 +1127,10 @@ namespace Content.Server.Atmos
UpdateVisuals(); UpdateVisuals();
if (!Excited)
{
_gridAtmosphereComponent.AddActiveTile(this);
}
return true; return true;
} }

View File

@@ -0,0 +1,70 @@
#nullable enable
using Content.Server.GameObjects.Components.Body.Respiratory;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.Interfaces.GameObjects.Components;
using Npgsql.TypeHandlers;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Atmos
{
/// <summary>
/// Used in internals as breath tool.
/// </summary>
[RegisterComponent]
public class BreathToolComponent : Component, IEquipped, IUnequipped
{
/// <summary>
/// Tool is functional only in allowed slots
/// </summary>
private EquipmentSlotDefines.SlotFlags _allowedSlots;
public override string Name => "BreathMask";
public bool IsFunctional { get; private set; }
public IEntity? ConnectedInternalsEntity { get; private set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _allowedSlots, "allowedSlots", EquipmentSlotDefines.SlotFlags.MASK);
}
protected override void Shutdown()
{
base.Shutdown();
DisconnectInternals();
}
public void Equipped(EquippedEventArgs eventArgs)
{
if ((EquipmentSlotDefines.SlotMasks[eventArgs.Slot] & _allowedSlots) != _allowedSlots) return;
IsFunctional = true;
if (eventArgs.User.TryGetComponent(out InternalsComponent? internals))
{
ConnectedInternalsEntity = eventArgs.User;
internals.ConnectBreathTool(Owner);
}
}
public void Unequipped(UnequippedEventArgs eventArgs)
{
DisconnectInternals();
}
public void DisconnectInternals()
{
var old = ConnectedInternalsEntity;
ConnectedInternalsEntity = null;
if (old != null && old.TryGetComponent<InternalsComponent>(out var internalsComponent))
{
internalsComponent.DisconnectBreathTool();
}
IsFunctional = false;
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Server.Atmos; using Content.Server.Atmos;
using Content.Server.Interfaces;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -6,23 +7,19 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos namespace Content.Server.GameObjects.Components.Atmos
{ {
[RegisterComponent] [RegisterComponent]
public class GasMixtureHolderComponent : Component public class GasMixtureHolderComponent : Component, IGasMixtureHolder
{ {
public override string Name => "GasMixtureHolder"; public override string Name => "GasMixtureHolder";
[ViewVariables] public GasMixture GasMixture { get; set; } [ViewVariables] public GasMixture Air { get; set; }
public override void ExposeData(ObjectSerializer serializer) public override void ExposeData(ObjectSerializer serializer)
{ {
base.ExposeData(serializer); base.ExposeData(serializer);
GasMixture = new GasMixture(); Air = new GasMixture();
serializer.DataReadWriteFunction( serializer.DataField(this, x => x.Air, "air", new GasMixture());
"volume",
0f,
vol => GasMixture.Volume = vol,
() => GasMixture.Volume);
} }
} }
} }

View File

@@ -1,10 +1,356 @@
using Robust.Shared.GameObjects; #nullable enable
using System;
using Content.Server.Atmos;
using Content.Server.Explosions;
using Content.Server.GameObjects.Components.Body.Respiratory;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.Atmos;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Atmos.GasTank;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.EntitySystemMessages;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos namespace Content.Server.GameObjects.Components.Atmos
{ {
[RegisterComponent] [RegisterComponent]
public class GasTankComponent : Component [ComponentReference(typeof(IActivate))]
public class GasTankComponent : SharedGasTankComponent, IExamine, IGasMixtureHolder, IUse, IDropped, IActivate
{ {
public override string Name => "GasTank"; private const float MaxExplosionRange = 14f;
private const float DefaultOutputPressure = Atmospherics.OneAtmosphere;
private float _pressureResistance;
private int _integrity = 3;
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables] private BoundUserInterface? _userInterface;
[ViewVariables] public GasMixture? Air { get; set; }
/// <summary>
/// Distributed pressure.
/// </summary>
[ViewVariables] public float OutputPressure { get; private set; }
/// <summary>
/// Tank is connected to internals.
/// </summary>
[ViewVariables] public bool IsConnected { get; set; }
/// <summary>
/// Represents that tank is functional and can be connected to internals.
/// </summary>
public bool IsFunctional => GetInternalsComponent() != null;
/// <summary>
/// Pressure at which tanks start leaking.
/// </summary>
public float TankLeakPressure { get; set; } = 30 * Atmospherics.OneAtmosphere;
/// <summary>
/// Pressure at which tank spills all contents into atmosphere.
/// </summary>
public float TankRupturePressure { get; set; } = 40 * Atmospherics.OneAtmosphere;
/// <summary>
/// Base 3x3 explosion.
/// </summary>
public float TankFragmentPressure { get; set; } = 50 * Atmospherics.OneAtmosphere;
/// <summary>
/// Increases explosion for each scale kPa above threshold.
/// </summary>
public float TankFragmentScale { get; set; } = 10 * Atmospherics.OneAtmosphere;
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetUIOrNull(SharedGasTankUiKey.Key);
if (_userInterface != null)
{
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
public void OpenInterface(IPlayerSession session)
{
_userInterface?.Open(session);
UpdateUserInterface(true);
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.Air, "air", new GasMixture());
serializer.DataField(this, x => x.OutputPressure, "outputPressure", DefaultOutputPressure);
serializer.DataField(this, x => x.TankLeakPressure, "tankLeakPressure", 30 * Atmospherics.OneAtmosphere);
serializer.DataField(this, x => x.TankRupturePressure, "tankRupturePressure", 40 * Atmospherics.OneAtmosphere);
serializer.DataField(this, x => x.TankFragmentPressure, "tankFragmentPressure", 50 * Atmospherics.OneAtmosphere);
serializer.DataField(this, x => x.TankFragmentScale, "tankFragmentScale", 10 * Atmospherics.OneAtmosphere);
serializer.DataField(ref _pressureResistance, "pressureResistance", Atmospherics.OneAtmosphere * 5f);
}
public void Examine(FormattedMessage message, bool inDetailsRange)
{
message.AddMarkup(Loc.GetString("Pressure: [color=orange]{0}[/color] kPa.\n",
Math.Round(Air?.Pressure ?? 0)));
if (IsConnected)
{
message.AddMarkup(Loc.GetString("Connected to external component"));
}
}
protected override void Shutdown()
{
base.Shutdown();
DisconnectFromInternals();
}
public void Update()
{
Air?.React(this);
CheckStatus();
UpdateUserInterface();
}
public GasMixture? RemoveAir(float amount)
{
var gas = Air?.Remove(amount);
CheckStatus();
return gas;
}
public GasMixture RemoveAirVolume(float volume)
{
if (Air == null)
return new GasMixture(volume);
var tankPressure = Air.Pressure;
if (tankPressure < OutputPressure)
{
OutputPressure = tankPressure;
UpdateUserInterface();
}
var molesNeeded = OutputPressure * volume / (Atmospherics.R * Air.Temperature);
var air = RemoveAir(molesNeeded);
if (air != null)
air.Volume = volume;
else
return new GasMixture(volume);
return air;
}
public bool UseEntity(UseEntityEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false;
OpenInterface(actor.playerSession);
return true;
}
public void Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return;
OpenInterface(actor.playerSession);
}
public void ConnectToInternals()
{
if (IsConnected || !IsFunctional) return;
var internals = GetInternalsComponent();
if (internals == null) return;
IsConnected = internals.TryConnectTank(Owner);
UpdateUserInterface();
}
public void DisconnectFromInternals(IEntity? owner = null)
{
if (!IsConnected) return;
IsConnected = false;
GetInternalsComponent(owner)?.DisconnectTank();
UpdateUserInterface();
}
private void UpdateUserInterface(bool initialUpdate = false)
{
_userInterface?.SetState(
new GasTankBoundUserInterfaceState
{
TankPressure = Air?.Pressure ?? 0,
OutputPressure = initialUpdate ? OutputPressure : (float?) null,
InternalsConnected = IsConnected,
CanConnectInternals = IsFunctional && GetInternalsComponent() != null
});
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
switch (message.Message)
{
case GasTankSetPressureMessage msg:
OutputPressure = msg.Pressure;
break;
case GasTankToggleInternalsMessage _:
ToggleInternals();
break;
}
}
private void ToggleInternals()
{
if (IsConnected)
{
DisconnectFromInternals();
return;
}
ConnectToInternals();
}
private InternalsComponent? GetInternalsComponent(IEntity? owner = null)
{
if (owner != null) return owner.GetComponentOrNull<InternalsComponent>();
return ContainerHelpers.TryGetContainer(Owner, out var container)
? container.Owner.GetComponentOrNull<InternalsComponent>()
: null;
}
public void AssumeAir(GasMixture giver)
{
Air?.Merge(giver);
CheckStatus();
}
private void CheckStatus()
{
if (Air == null)
return;
var pressure = Air.Pressure;
if (pressure > TankFragmentPressure)
{
// Give the gas a chance to build up more pressure.
for (var i = 0; i < 3; i++)
{
Air.React(this);
}
pressure = Air.Pressure;
var range = (pressure - TankFragmentPressure) / TankFragmentScale;
// Let's cap the explosion, yeah?
if (range > MaxExplosionRange)
{
range = MaxExplosionRange;
}
Owner.SpawnExplosion((int) (range * 0.25f), (int) (range * 0.5f), (int) (range * 1.5f), 1);
Owner.Delete();
return;
}
if (pressure > TankRupturePressure)
{
if (_integrity <= 0)
{
var tileAtmos = Owner.Transform.Coordinates.GetTileAtmosphere();
tileAtmos?.AssumeAir(Air);
EntitySystem.Get<AudioSystem>().PlayAtCoords("Audio/Effects/spray.ogg", Owner.Transform.Coordinates,
AudioHelpers.WithVariation(0.125f));
Owner.Delete();
return;
}
_integrity--;
return;
}
if (pressure > TankLeakPressure)
{
if (_integrity <= 0)
{
var tileAtmos = Owner.Transform.Coordinates.GetTileAtmosphere();
if (tileAtmos == null)
return;
var leakedGas = Air.RemoveRatio(0.25f);
tileAtmos.AssumeAir(leakedGas);
}
else
{
_integrity--;
}
return;
}
if (_integrity < 3)
_integrity++;
}
/// <summary>
/// Open interaction window
/// </summary>
[Verb]
private sealed class ControlVerb : Verb<GasTankComponent>
{
public override bool RequireInteractionRange => true;
protected override void GetData(IEntity user, GasTankComponent component, VerbData data)
{
data.Visibility = VerbVisibility.Invisible;
if (!user.HasComponent<IActorComponent>())
{
return;
}
data.Visibility = VerbVisibility.Visible;
data.Text = "Open Control Panel";
}
protected override void Activate(IEntity user, GasTankComponent component)
{
if (!user.TryGetComponent<IActorComponent>(out var actor))
{
return;
}
component.OpenInterface(actor.playerSession);
}
}
public void Dropped(DroppedEventArgs eventArgs)
{
DisconnectFromInternals(eventArgs.User);
}
} }
} }

View File

@@ -2,7 +2,9 @@
using System; using System;
using System.Linq; using System.Linq;
using Content.Server.Atmos; using Content.Server.Atmos;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.GameObjects.Components.Body.Circulatory; using Content.Server.GameObjects.Components.Body.Circulatory;
using Content.Server.GameObjects.Components.Body.Respiratory;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.GameObjects.Components.Body.Behavior; using Content.Shared.GameObjects.Components.Body.Behavior;
@@ -10,6 +12,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Timing; using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -147,6 +150,16 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
public override void Inhale(float frameTime) public override void Inhale(float frameTime)
{ {
if (Body != null && Body.Owner.TryGetComponent(out InternalsComponent? internals)
&& internals.BreathToolEntity != null && internals.GasTankEntity != null
&& internals.BreathToolEntity.TryGetComponent(out BreathToolComponent? breathTool)
&& breathTool.IsFunctional && internals.GasTankEntity.TryGetComponent(out GasTankComponent? gasTank)
&& gasTank.Air != null)
{
Inhale(frameTime, gasTank.RemoveAirVolume(Atmospherics.BreathVolume));
return;
}
if (!Owner.Transform.Coordinates.TryGetTileAir(out var tileAir)) if (!Owner.Transform.Coordinates.TryGetTileAir(out var tileAir))
{ {
return; return;
@@ -157,8 +170,7 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
public void Inhale(float frameTime, GasMixture from) public void Inhale(float frameTime, GasMixture from)
{ {
var ratio = Atmospherics.BreathPercentage * frameTime; var ratio = (Atmospherics.BreathVolume / from.Volume) * frameTime;
Transfer(from, Air, ratio); Transfer(from, Air, ratio);
ToBloodstream(Air); ToBloodstream(Air);

View File

@@ -0,0 +1,63 @@
#nullable enable
using Content.Server.GameObjects.Components.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body.Respiratory
{
[RegisterComponent]
public class InternalsComponent : Component
{
public override string Name => "Internals";
[ViewVariables] public IEntity? GasTankEntity { get; set; }
[ViewVariables] public IEntity? BreathToolEntity { get; set; }
public void DisconnectBreathTool()
{
var old = BreathToolEntity;
BreathToolEntity = null;
if (old != null && old.TryGetComponent(out BreathToolComponent? breathTool) )
{
breathTool.DisconnectInternals();
DisconnectTank();
}
}
public void ConnectBreathTool(IEntity toolEntity)
{
if (BreathToolEntity != null && BreathToolEntity.TryGetComponent(out BreathToolComponent? tool))
{
tool.DisconnectInternals();
}
BreathToolEntity = toolEntity;
}
public void DisconnectTank()
{
if (GasTankEntity != null && GasTankEntity.TryGetComponent(out GasTankComponent? tank))
{
tank.DisconnectFromInternals(Owner);
}
GasTankEntity = null;
}
public bool TryConnectTank(IEntity tankEntity)
{
if (BreathToolEntity == null)
return false;
if (GasTankEntity != null && GasTankEntity.TryGetComponent(out GasTankComponent? tank))
{
tank.DisconnectFromInternals(Owner);
}
GasTankEntity = tankEntity;
return true;
}
}
}

View File

@@ -245,7 +245,7 @@ namespace Content.Server.GameObjects.Components.GUI
return false; return false;
} }
public bool Drop(string slot, EntityCoordinates coords, bool doMobChecks = true) public bool Drop(string slot, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true)
{ {
var hand = GetHand(slot); var hand = GetHand(slot);
if (!CanDrop(slot, doMobChecks) || hand?.Entity == null) if (!CanDrop(slot, doMobChecks) || hand?.Entity == null)
@@ -260,7 +260,7 @@ namespace Content.Server.GameObjects.Components.GUI
return false; return false;
} }
if (!DroppedInteraction(item, false)) if (doDropInteraction && !DroppedInteraction(item, false))
return false; return false;
item.RemovedFromSlot(); item.RemovedFromSlot();
@@ -282,7 +282,7 @@ namespace Content.Server.GameObjects.Components.GUI
return true; return true;
} }
public bool Drop(IEntity entity, EntityCoordinates coords, bool doMobChecks = true) public bool Drop(IEntity entity, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true)
{ {
if (entity == null) if (entity == null)
{ {
@@ -294,15 +294,15 @@ namespace Content.Server.GameObjects.Components.GUI
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity)); throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
} }
return Drop(slot, coords, doMobChecks); return Drop(slot, coords, doMobChecks, doDropInteraction);
} }
public bool Drop(string slot, bool mobChecks = true) public bool Drop(string slot, bool mobChecks = true, bool doDropInteraction = true)
{ {
return Drop(slot, Owner.Transform.Coordinates, mobChecks); return Drop(slot, Owner.Transform.Coordinates, mobChecks, doDropInteraction);
} }
public bool Drop(IEntity entity, bool mobChecks = true) public bool Drop(IEntity entity, bool mobChecks = true, bool doDropInteraction = true)
{ {
if (entity == null) if (entity == null)
{ {
@@ -314,10 +314,10 @@ namespace Content.Server.GameObjects.Components.GUI
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity)); throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
} }
return Drop(slot, Owner.Transform.Coordinates, mobChecks); return Drop(slot, Owner.Transform.Coordinates, mobChecks, doDropInteraction);
} }
public bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true) public bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true)
{ {
if (slot == null) if (slot == null)
{ {
@@ -352,7 +352,7 @@ namespace Content.Server.GameObjects.Components.GUI
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
if (!DroppedInteraction(item, doMobChecks)) if (doDropInteraction && !DroppedInteraction(item, doMobChecks))
return false; return false;
item.RemovedFromSlot(); item.RemovedFromSlot();
@@ -368,7 +368,7 @@ namespace Content.Server.GameObjects.Components.GUI
return true; return true;
} }
public bool Drop(IEntity entity, BaseContainer targetContainer, bool doMobChecks = true) public bool Drop(IEntity entity, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true)
{ {
if (entity == null) if (entity == null)
{ {
@@ -380,7 +380,7 @@ namespace Content.Server.GameObjects.Components.GUI
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity)); throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
} }
return Drop(slot, targetContainer, doMobChecks); return Drop(slot, targetContainer, doMobChecks, doDropInteraction);
} }
/// <summary> /// <summary>

View File

@@ -156,6 +156,13 @@ namespace Content.Server.GameObjects.Components.GUI
{ {
return GetSlotItem<ItemComponent>(slot); return GetSlotItem<ItemComponent>(slot);
} }
public IEnumerable<T> LookupItems<T>() where T: Component
{
return _slotContainers.Values.SelectMany(x => x.ContainedEntities.Select(e => e.GetComponentOrNull<T>()))
.Where(x => x != null);
}
public T GetSlotItem<T>(Slots slot) where T : ItemComponent public T GetSlotItem<T>(Slots slot) where T : ItemComponent
{ {
if (!_slotContainers.ContainsKey(slot)) if (!_slotContainers.ContainsKey(slot))
@@ -435,7 +442,7 @@ namespace Content.Server.GameObjects.Components.GUI
var activeHand = hands.GetActiveHand; var activeHand = hands.GetActiveHand;
if (activeHand != null && activeHand.Owner.TryGetComponent(out ItemComponent clothing)) if (activeHand != null && activeHand.Owner.TryGetComponent(out ItemComponent clothing))
{ {
hands.Drop(hands.ActiveHand); hands.Drop(hands.ActiveHand, doDropInteraction:false);
if (!Equip(msg.Inventoryslot, clothing, true, out var reason)) if (!Equip(msg.Inventoryslot, clothing, true, out var reason))
{ {
hands.PutInHand(clothing); hands.PutInHand(clothing);

View File

@@ -58,12 +58,12 @@ namespace Content.Server.GameObjects.Components.Items.Storage
} }
} }
public void Equipped(EquippedEventArgs eventArgs) public virtual void Equipped(EquippedEventArgs eventArgs)
{ {
EquippedToSlot(); EquippedToSlot();
} }
public void Unequipped(UnequippedEventArgs eventArgs) public virtual void Unequipped(UnequippedEventArgs eventArgs)
{ {
RemovedFromSlot(); RemovedFromSlot();
} }

View File

@@ -0,0 +1,32 @@
using System;
using Content.Server.GameObjects.Components.Atmos;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.EntitySystemMessages;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
namespace Content.Server.GameObjects.EntitySystems
{
[UsedImplicitly]
public class GasTankSystem : EntitySystem
{
private float _timer = 0f;
private const float Interval = 0.5f;
public override void Update(float frameTime)
{
base.Update(frameTime);
_timer += frameTime;
if (_timer < Interval) return;
_timer = 0f;
foreach (var gasTank in EntityManager.ComponentManager.EntityQuery<GasTankComponent>())
{
gasTank.Update();
}
}
}
}

View File

@@ -109,14 +109,16 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items
/// </summary> /// </summary>
/// <param name="slot">The slot of which to drop to drop the item.</param> /// <param name="slot">The slot of which to drop to drop the item.</param>
/// <param name="mobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param> /// <param name="mobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
/// <returns>True on success, false if something blocked the drop.</returns> /// <returns>True on success, false if something blocked the drop.</returns>
bool Drop(string slot, bool mobChecks = true); bool Drop(string slot, bool mobChecks = true, bool doDropInteraction = true);
/// <summary> /// <summary>
/// Drops an item held by one of our hand slots to the same position as our owning entity. /// Drops an item held by one of our hand slots to the same position as our owning entity.
/// </summary> /// </summary>
/// <param name="entity">The item to drop.</param> /// <param name="entity">The item to drop.</param>
/// <param name="mobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param> /// <param name="mobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
/// <returns>True on success, false if something blocked the drop.</returns> /// <returns>True on success, false if something blocked the drop.</returns>
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// Thrown if <see cref="entity"/> is null. /// Thrown if <see cref="entity"/> is null.
@@ -124,7 +126,7 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// Thrown if <see cref="entity"/> is not actually held in any hand. /// Thrown if <see cref="entity"/> is not actually held in any hand.
/// </exception> /// </exception>
bool Drop(IEntity entity, bool mobChecks = true); bool Drop(IEntity entity, bool mobChecks = true, bool doDropInteraction = true);
/// <summary> /// <summary>
/// Drops the item in a slot. /// Drops the item in a slot.
@@ -132,8 +134,9 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items
/// <param name="slot">The slot to drop the item from.</param> /// <param name="slot">The slot to drop the item from.</param>
/// <param name="coords"></param> /// <param name="coords"></param>
/// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param> /// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
/// <returns>True if an item was dropped, false otherwise.</returns> /// <returns>True if an item was dropped, false otherwise.</returns>
bool Drop(string slot, EntityCoordinates coords, bool doMobChecks = true); bool Drop(string slot, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true);
/// <summary> /// <summary>
/// Drop the specified entity in our hands to a certain position. /// Drop the specified entity in our hands to a certain position.
@@ -145,6 +148,7 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items
/// <param name="entity">The entity to drop, must be held in one of the hands.</param> /// <param name="entity">The entity to drop, must be held in one of the hands.</param>
/// <param name="coords">The coordinates to drop the entity at.</param> /// <param name="coords">The coordinates to drop the entity at.</param>
/// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param> /// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
/// <returns> /// <returns>
/// True if the drop succeeded, /// True if the drop succeeded,
/// false if it failed (due to failing to eject from our hand slot, etc...) /// false if it failed (due to failing to eject from our hand slot, etc...)
@@ -155,7 +159,7 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// Thrown if <see cref="entity"/> is not actually held in any hand. /// Thrown if <see cref="entity"/> is not actually held in any hand.
/// </exception> /// </exception>
bool Drop(IEntity entity, EntityCoordinates coords, bool doMobChecks = true); bool Drop(IEntity entity, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true);
/// <summary> /// <summary>
/// Drop the item contained in a slot into another container. /// Drop the item contained in a slot into another container.
@@ -163,13 +167,14 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items
/// <param name="slot">The slot of which to drop the entity.</param> /// <param name="slot">The slot of which to drop the entity.</param>
/// <param name="targetContainer">The container to drop into.</param> /// <param name="targetContainer">The container to drop into.</param>
/// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop(IEntity)"/> for the mob or not.</param> /// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop(IEntity)"/> for the mob or not.</param>
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
/// <returns>True on success, false if something was blocked (insertion or removal).</returns> /// <returns>True on success, false if something was blocked (insertion or removal).</returns>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// Thrown if dry-run checks reported OK to remove and insert, /// Thrown if dry-run checks reported OK to remove and insert,
/// but practical remove or insert returned false anyways. /// but practical remove or insert returned false anyways.
/// This is an edge-case that is currently unhandled. /// This is an edge-case that is currently unhandled.
/// </exception> /// </exception>
bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true); bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true);
/// <summary> /// <summary>
/// Drops an item in one of the hands into a container. /// Drops an item in one of the hands into a container.
@@ -177,6 +182,7 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items
/// <param name="entity">The item to drop.</param> /// <param name="entity">The item to drop.</param>
/// <param name="targetContainer">The container to drop into.</param> /// <param name="targetContainer">The container to drop into.</param>
/// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param> /// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
/// <returns>True on success, false if something was blocked (insertion or removal).</returns> /// <returns>True on success, false if something was blocked (insertion or removal).</returns>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// Thrown if dry-run checks reported OK to remove and insert, /// Thrown if dry-run checks reported OK to remove and insert,
@@ -189,7 +195,7 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// Thrown if <see cref="entity"/> is not actually held in any hand. /// Thrown if <see cref="entity"/> is not actually held in any hand.
/// </exception> /// </exception>
bool Drop(IEntity entity, BaseContainer targetContainer, bool doMobChecks = true); bool Drop(IEntity entity, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true);
/// <summary> /// <summary>
/// Checks whether the item in the specified hand can be dropped. /// Checks whether the item in the specified hand can be dropped.

View File

@@ -5,5 +5,20 @@ namespace Content.Server.Interfaces
public interface IGasMixtureHolder public interface IGasMixtureHolder
{ {
public GasMixture Air { get; set; } public GasMixture Air { get; set; }
public void AssumeAir(GasMixture giver)
{
Air.Merge(giver);
}
public GasMixture RemoveAir(float amount)
{
return Air.Remove(amount);
}
public GasMixture RemoveAirVolume(float ratio)
{
return Air.RemoveRatio(ratio);
}
} }
} }

View File

@@ -19,6 +19,17 @@ namespace Content.Shared.Atmos
/// </summary> /// </summary>
public float SpecificHeat { get; private set; } public float SpecificHeat { get; private set; }
/// <summary>
/// Heat capacity ratio for gas
/// </summary>
public float HeatCapacityRatio { get; private set; }
/// <summary>
/// Molar mass of gas
/// </summary>
public float MolarMass { get; set; }
/// <summary> /// <summary>
/// Minimum amount of moles for this gas to be visible. /// Minimum amount of moles for this gas to be visible.
/// </summary> /// </summary>
@@ -49,6 +60,7 @@ namespace Content.Shared.Atmos
/// </summary> /// </summary>
public string OverlayPath { get; private set; } public string OverlayPath { get; private set; }
public string Color { get; private set; } public string Color { get; private set; }
public void LoadFrom(YamlMappingNode mapping) public void LoadFrom(YamlMappingNode mapping)
@@ -59,6 +71,8 @@ namespace Content.Shared.Atmos
serializer.DataField(this, x => Name, "name", string.Empty); serializer.DataField(this, x => Name, "name", string.Empty);
serializer.DataField(this, x => OverlayPath, "overlayPath", string.Empty); serializer.DataField(this, x => OverlayPath, "overlayPath", string.Empty);
serializer.DataField(this, x => SpecificHeat, "specificHeat", 0f); serializer.DataField(this, x => SpecificHeat, "specificHeat", 0f);
serializer.DataField(this, x => HeatCapacityRatio, "heatCapacityRatio", 1.4f);
serializer.DataField(this, x => MolarMass, "molarMass", 1f);
serializer.DataField(this, x => GasMolesVisible, "gasMolesVisible", 0.25f); serializer.DataField(this, x => GasMolesVisible, "gasMolesVisible", 0.25f);
serializer.DataField(this, x => GasOverlayTexture, "gasOverlayTexture", string.Empty); serializer.DataField(this, x => GasOverlayTexture, "gasOverlayTexture", string.Empty);
serializer.DataField(this, x => GasOverlaySprite, "gasOverlaySprite", string.Empty); serializer.DataField(this, x => GasOverlaySprite, "gasOverlaySprite", string.Empty);

View File

@@ -0,0 +1,16 @@
using System;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Atmos.GasTank
{
[Serializable, NetSerializable]
public class GasTankBoundUserInterfaceState : BoundUserInterfaceState
{
public float TankPressure { get; set; }
public float? OutputPressure { get; set; }
public bool InternalsConnected { get; set; }
public bool CanConnectInternals { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using System;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Atmos.GasTank
{
[Serializable, NetSerializable]
public class GasTankSetPressureMessage : BoundUserInterfaceMessage
{
public float Pressure { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
using System;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Atmos.GasTank
{
[Serializable, NetSerializable]
public class GasTankToggleInternalsMessage : BoundUserInterfaceMessage
{
}
}

View File

@@ -0,0 +1,10 @@
using Robust.Shared.GameObjects;
namespace Content.Shared.GameObjects.Components.Atmos.GasTank
{
public class SharedGasTankComponent : Component
{
public override string Name => "GasTank";
public override uint? NetID => ContentNetIDs.GAS_TANK;
}
}

View File

@@ -0,0 +1,11 @@
using System;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Atmos.GasTank
{
[Serializable, NetSerializable]
public enum SharedGasTankUiKey
{
Key
}
}

View File

@@ -82,6 +82,8 @@
public const uint PLACEABLE_SURFACE = 1076; public const uint PLACEABLE_SURFACE = 1076;
public const uint STORABLE = 1077; public const uint STORABLE = 1077;
public const uint PULLABLE = 1078; public const uint PULLABLE = 1078;
public const uint GAS_TANK = 1079;
// Net IDs for integration tests. // Net IDs for integration tests.
public const uint PREDICTION_TEST = 10001; public const uint PREDICTION_TEST = 10001;

View File

@@ -2,24 +2,32 @@
id: 0 id: 0
name: Oxygen name: Oxygen
specificHeat: 20 specificHeat: 20
heatCapacityRatio: 1.4
molarMass: 32
color: 2887E8 color: 2887E8
- type: gas - type: gas
id: 1 id: 1
name: Nitrogen name: Nitrogen
specificHeat: 30 specificHeat: 30
heatCapacityRatio: 1.4
molarMass: 28
color: DA1010 color: DA1010
- type: gas - type: gas
id: 2 id: 2
name: Carbon Dioxide name: Carbon Dioxide
specificHeat: 30 specificHeat: 30
heatCapacityRatio: 1.3
molarMass: 44
color: 4e4e4e color: 4e4e4e
- type: gas - type: gas
id: 3 id: 3
name: Phoron name: Phoron
specificHeat: 200 specificHeat: 200
heatCapacityRatio: 1.7
molarMass: 120 #creadth: making it very heavy (x4 of oxygen), idk what the proper value should be
gasOverlaySprite: /Textures/Effects/atmospherics.rsi gasOverlaySprite: /Textures/Effects/atmospherics.rsi
gasOverlayState: phoron gasOverlayState: phoron
color: FF3300 color: FF3300
@@ -28,6 +36,8 @@
id: 4 id: 4
name: Tritium name: Tritium
specificHeat: 10 specificHeat: 10
heatCapacityRatio: 1.3
molarMass: 6
gasOverlaySprite: /Textures/Effects/atmospherics.rsi gasOverlaySprite: /Textures/Effects/atmospherics.rsi
gasOverlayState: tritium gasOverlayState: tritium
color: 13FF4B color: 13FF4B
@@ -36,6 +46,8 @@
id: 5 id: 5
name: Water Vapor name: Water Vapor
specificHeat: 40 specificHeat: 40
heatCapacityRatio: 1.33
molarMass: 18
gasOverlaySprite: /Textures/Effects/atmospherics.rsi gasOverlaySprite: /Textures/Effects/atmospherics.rsi
gasOverlayState: water_vapor gasOverlayState: water_vapor
color: bffffd color: bffffd

View File

@@ -62,15 +62,10 @@
prob: 0.4 prob: 0.4
- name: BreathMaskClothing - name: BreathMaskClothing
prob: 0.25 prob: 0.25
# TODO: uncomment when we actually have these items. - name: EmergencyOxygenTankFilled
#- name: TankOxygenSmallFilled prob: 0.4
# prob: 0.4s - name: OxygenTankFilled
#- name: TankOxygenSmallFilled prob: 0.2
# prob: 0.25
#- name: MedkitOxygenFilled
# prob: 0.25
#- name: TankOxygenFilled
# prob: 0.2
- type: entity - type: entity
id: LockerBoozeFilled id: LockerBoozeFilled
@@ -291,6 +286,13 @@
id: LockerFireFilled id: LockerFireFilled
parent: LockerFire parent: LockerFire
suffix: Filled suffix: Filled
components:
- type: StorageFill
contents:
- name: RedOxygenTankFilled
prob: 0.6
- name: OuterclothingFiresuit
prob: 0.75
- type: entity - type: entity
id: WardrobePajama id: WardrobePajama

View File

@@ -0,0 +1,314 @@
- type: entity
parent: BaseItem
abstract: true
id: GasTankBase
name: Gas Tank
description: It's a gas tank. It contains gas.
components:
- type: Sprite
sprite: Objects/Tanks/generic.rsi
state: icon
- type: UserInterface
interfaces:
- key: enum.SharedGasTankUiKey.Key
type: GasTankBoundUserInterface
- type: Clothing
sprite: Objects/Tanks/generic.rsi
QuickEquip: false
Slots:
- Back
- Belt
- type: GasTank
- type: entity
id: OxygenTank
parent: GasTankBase
name: oxygen tank
description: A tank of oxygen.
suffix: Empty
components:
- type: Sprite
sprite: Objects/Tanks/oxygen.rsi
state: icon
- type: GasTank
outputPressure: 21.27825
air:
volume: 70
temperature: 293.15
- type: Clothing
sprite: Objects/Tanks/oxygen.rsi
Slots:
- Back
- Belt
- type: entity
id: OxygenTankFilled
parent: OxygenTank
name: oxygen tank
description: A tank of oxygen.
suffix: Filled
components:
- type: GasTank
outputPressure: 21.27825
air:
volume: 70
moles:
- 22.6293856 # oxygen
temperature: 293.15
- type: entity
id: YellowOxygenTank
parent: OxygenTank
name: oxygen tank
description: A tank of oxygen. This one is in yellow.
suffix: Empty
components:
- type: Sprite
sprite: Objects/Tanks/yellow.rsi
state: icon
- type: Clothing
sprite: Objects/Tanks/yellow.rsi
Slots:
- Back
- Belt
- type: entity
id: YellowOxygenTankFilled
parent: OxygenTankFilled
name: oxygen tank
description: A tank of oxygen. This one is in yellow.
suffix: Filled
components:
- type: Sprite
sprite: Objects/Tanks/yellow.rsi
state: icon
- type: Clothing
sprite: Objects/Tanks/yellow.rsi
Slots:
- Back
- Belt
- type: entity
id: RedOxygenTank
parent: OxygenTank
name: oxygen tank
description: A tank of oxygen. This one is in yellow.
suffix: Empty
components:
- type: Sprite
sprite: Objects/Tanks/red.rsi
state: icon
- type: Clothing
sprite: Objects/Tanks/red.rsi
Slots:
- Back
- Belt
- type: entity
id: RedOxygenTankFilled
parent: OxygenTankFilled
name: oxygen tank
description: A tank of oxygen. This one is in yellow.
suffix: Filled
components:
- type: Sprite
sprite: Objects/Tanks/red.rsi
state: icon
- type: Clothing
sprite: Objects/Tanks/red.rsi
Slots:
- Back
- Belt
- type: entity
id: EmergencyOxygenTank
parent: OxygenTank
name: emergency oxygen tank
description: Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it.
suffix: Empty
components:
- type: Sprite
sprite: Objects/Tanks/emergency.rsi
state: icon
- type: GasTank
outputPressure: 21.27825
air:
volume: 2
temperature: 293.15
- type: Clothing
sprite: Objects/Tanks/emergency.rsi
Slots:
- Back
- Pocket
- Belt
- type: entity
id: EmergencyOxygenTankFilled
parent: EmergencyOxygenTank
name: emergency oxygen tank
description: Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it.
suffix: Filled
components:
- type: GasTank
outputPressure: 21.27825
air:
volume: 2
moles:
- 0.323460326 # oxygen
temperature: 293.15
- type: entity
id: ExtendedEmergencyOxygenTank
parent: EmergencyOxygenTank
name: extended-capacity emergency oxygen tank
description: Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it.
suffix: Empty
components:
- type: Sprite
sprite: Objects/Tanks/emergency_yellow.rsi
state: icon
- type: GasTank
outputPressure: 21.27825
air:
volume: 6
temperature: 293.15
- type: Clothing
sprite: Objects/Tanks/emergency_yellow.rsi
Slots:
- Back
- Pocket
- Belt
- type: entity
id: ExtendedEmergencyOxygenTankFilled
parent: ExtendedEmergencyOxygenTank
name: extended-capacity emergency oxygen tank
description: Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it.
suffix: Filled
components:
- type: GasTank
outputPressure: 21.27825
air:
volume: 6
moles:
- 0.969830813 # oxygen
temperature: 293.15
- type: entity
id: DoubleEmergencyOxygenTank
parent: ExtendedEmergencyOxygenTank
name: double emergency oxygen tank
description: Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it.
suffix: Empty
components:
- type: Sprite
sprite: Objects/Tanks/emergency_double.rsi
state: icon
- type: GasTank
outputPressure: 21.27825
air:
volume: 10
temperature: 293.15
- type: Clothing
sprite: Objects/Tanks/emergency_double.rsi
Slots:
- Back
- Pocket
- Belt
- type: entity
id: DoubleEmergencyOxygenTankFilled
parent: DoubleEmergencyOxygenTank
name: double emergency oxygen tank
description: Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it.
suffix: Filled
components:
- type: GasTank
outputPressure: 21.27825
air:
volume: 10
moles:
- 1.61721219 # oxygen
temperature: 293.15
- type: entity
id: AirTank
parent: GasTankBase
name: air tank
description: Mixed anyone?
suffix: Empty
components:
- type: Sprite
sprite: Objects/Tanks/generic.rsi
state: icon
- type: GasTank
outputPressure: 101.325
air:
volume: 70
temperature: 293.15
- type: Clothing
sprite: Objects/Tanks/generic.rsi
Slots:
- Back
- Belt
- type: entity
id: AirTankFilled
parent: GasTankBase
name: air tank
description: Mixed anyone?
suffix: Filled
components:
- type: Sprite
sprite: Objects/Tanks/generic.rsi
state: icon
- type: GasTank
outputPressure: 101.325
air:
volume: 70
moles:
- 4.75217098 # oxygen
- 17.8772147 # nitrogen
temperature: 293.15
- type: Clothing
sprite: Objects/Tanks/generic.rsi
Slots:
- Back
- Belt
- type: entity
id: PhoronTank
parent: GasTankBase
name: phoron tank
suffix: Empty
description: "Contains dangerous phoron. Do not inhale. Warning: extremely flammable."
components:
- type: Sprite
sprite: Objects/Tanks/phoron.rsi
state: icon
- type: GasTank
outputPressure: 101.325
air:
volume: 70
temperature: 293.15
- type: Clothing
sprite: Objects/Tanks/phoron.rsi
Slots: [] # no straps
- type: entity
id: PhoronTankFilled
parent: PhoronTank
name: phoron tank
suffix: Filled
description: "Contains dangerous phoron. Do not inhale. Warning: extremely flammable."
components:
- type: GasTank
outputPressure: 101.325
air:
volume: 70
moles:
- 0
- 0
- 0
- 11.3146928 # phoron
temperature: 293.15

View File

@@ -15,7 +15,7 @@
- type: Sprite - type: Sprite
sprite: Clothing/Masks/mask_gasalt.rsi sprite: Clothing/Masks/mask_gasalt.rsi
state: icon state: icon
- type: BreathMask
- type: Clothing - type: Clothing
sprite: Clothing/Masks/mask_gasalt.rsi sprite: Clothing/Masks/mask_gasalt.rsi
@@ -28,7 +28,7 @@
- type: Sprite - type: Sprite
sprite: Clothing/Masks/mask_gas.rsi sprite: Clothing/Masks/mask_gas.rsi
state: icon state: icon
- type: BreathMask
- type: Clothing - type: Clothing
sprite: Clothing/Masks/mask_gas.rsi sprite: Clothing/Masks/mask_gas.rsi
@@ -41,7 +41,7 @@
- type: Sprite - type: Sprite
sprite: Clothing/Masks/mask_breath.rsi sprite: Clothing/Masks/mask_breath.rsi
state: icon state: icon
- type: BreathMask
- type: Clothing - type: Clothing
sprite: Clothing/Masks/mask_breath.rsi sprite: Clothing/Masks/mask_breath.rsi
@@ -54,7 +54,7 @@
- type: Sprite - type: Sprite
sprite: Clothing/Masks/mask_clown.rsi sprite: Clothing/Masks/mask_clown.rsi
state: icon state: icon
- type: BreathMask
- type: Clothing - type: Clothing
sprite: Clothing/Masks/mask_clown.rsi sprite: Clothing/Masks/mask_clown.rsi
@@ -67,7 +67,7 @@
- type: Sprite - type: Sprite
sprite: Clothing/Masks/mask_joy.rsi sprite: Clothing/Masks/mask_joy.rsi
state: icon state: icon
- type: BreathMask
- type: Clothing - type: Clothing
sprite: Clothing/Masks/mask_joy.rsi sprite: Clothing/Masks/mask_joy.rsi
@@ -80,7 +80,7 @@
- type: Sprite - type: Sprite
sprite: Clothing/Masks/mask_mime.rsi sprite: Clothing/Masks/mask_mime.rsi
state: icon state: icon
- type: BreathMask
- type: Clothing - type: Clothing
sprite: Clothing/Masks/mask_mime.rsi sprite: Clothing/Masks/mask_mime.rsi
@@ -93,6 +93,6 @@
- type: Sprite - type: Sprite
sprite: Clothing/Masks/mask_sterile.rsi sprite: Clothing/Masks/mask_sterile.rsi
state: icon state: icon
- type: BreathMask
- type: Clothing - type: Clothing
sprite: Clothing/Masks/mask_sterile.rsi sprite: Clothing/Masks/mask_sterile.rsi

View File

@@ -150,7 +150,9 @@
components: components:
- type: Sprite - type: Sprite
sprite: Clothing/OuterClothing/firesuit.rsi sprite: Clothing/OuterClothing/firesuit.rsi
- type: PressureProtection
highPressureMultiplier: 0.85
lowPressureMultiplier: 25
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/firesuit.rsi sprite: Clothing/OuterClothing/firesuit.rsi

View File

@@ -160,6 +160,7 @@
producesGases: producesGases:
Oxygen: 0.00045572916 Oxygen: 0.00045572916
CarbonDioxide: 0.00015190972 CarbonDioxide: 0.00015190972
- type: Internals
- type: MobStateManager - type: MobStateManager
- type: HeatResistance - type: HeatResistance
- type: Appearance - type: Appearance

View File

@@ -91,7 +91,7 @@
- type: StorageFill - type: StorageFill
contents: contents:
- name: BreathMaskClothing - name: BreathMaskClothing
#- name: O2 Canister - name: EmergencyOxygenTankFilled
#- name: Injector #- name: Injector
- type: Sprite - type: Sprite
layers: layers:

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

View File

@@ -0,0 +1,27 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/tgstation/tgstation at commit e1142f20f5e4661cb6845cfcf2dd69f864d67432",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon",
"directions": 1
},
{
"name": "equipped-BACKPACK",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

View File

@@ -0,0 +1,38 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/tgstation/tgstation at commit e1142f20f5e4661cb6845cfcf2dd69f864d67432",
"states": [
{
"name": "icon",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "inhand-left",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "inhand-right",
"directions": 1,
"delays": [
[
1.0
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

View File

@@ -0,0 +1,38 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/tgstation/tgstation at commit e1142f20f5e4661cb6845cfcf2dd69f864d67432",
"states": [
{
"name": "icon",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "inhand-left",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "inhand-right",
"directions": 1,
"delays": [
[
1.0
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

View File

@@ -0,0 +1,38 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/tgstation/tgstation at commit e1142f20f5e4661cb6845cfcf2dd69f864d67432",
"states": [
{
"name": "icon",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "inhand-left",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "inhand-right",
"directions": 1,
"delays": [
[
1.0
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

View File

@@ -0,0 +1,27 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon",
"directions": 1
},
{
"name": "equipped-BACKPACK",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

View File

@@ -0,0 +1,27 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon",
"directions": 1
},
{
"name": "equipped-BACKPACK",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

View File

@@ -0,0 +1,38 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/tgstation/tgstation at commit e1142f20f5e4661cb6845cfcf2dd69f864d67432",
"states": [
{
"name": "icon",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "inhand-left",
"directions": 1,
"delays": [
[
1.0
]
]
},
{
"name": "inhand-right",
"directions": 1,
"delays": [
[
1.0
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

View File

@@ -0,0 +1,27 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon",
"directions": 1
},
{
"name": "equipped-BACKPACK",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

View File

@@ -0,0 +1,27 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/tgstation/tgstation at commit e1142f20f5e4661cb6845cfcf2dd69f864d67432",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon",
"directions": 1
},
{
"name": "equipped-BACKPACK",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

View File

@@ -0,0 +1,27 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/tgstation/tgstation at commit e1142f20f5e4661cb6845cfcf2dd69f864d67432",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon",
"directions": 1
},
{
"name": "equipped-BACKPACK",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}