Portable scrubbers (#9417)
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
namespace Content.Client.Atmos.Visualizers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Holds 2 pairs of states. The idle/running pair controls animation, while
|
||||||
|
/// the ready / full pair controls the color of the light.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class PortableScrubberVisualsComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("idleState", required: true)]
|
||||||
|
public string IdleState = default!;
|
||||||
|
|
||||||
|
[DataField("runningState", required: true)]
|
||||||
|
public string RunningState = default!;
|
||||||
|
|
||||||
|
/// Powered and not full
|
||||||
|
[DataField("readyState", required: true)]
|
||||||
|
public string ReadyState = default!;
|
||||||
|
|
||||||
|
/// Powered and full
|
||||||
|
[DataField("fullState", required: true)]
|
||||||
|
public string FullState = default!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Content.Shared.Atmos.Visuals;
|
||||||
|
using Content.Client.Power;
|
||||||
|
|
||||||
|
namespace Content.Client.Atmos.Visualizers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Controls client-side visuals for portable scrubbers.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PortableScrubberSystem : VisualizerSystem<PortableScrubberVisualsComponent>
|
||||||
|
{
|
||||||
|
protected override void OnAppearanceChange(EntityUid uid, PortableScrubberVisualsComponent component, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
if (args.Sprite == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.Component.TryGetData(PortableScrubberVisuals.IsFull, out bool isFull)
|
||||||
|
&& args.Component.TryGetData(PortableScrubberVisuals.IsRunning, out bool isRunning))
|
||||||
|
{
|
||||||
|
var runningState = isRunning ? component.RunningState : component.IdleState;
|
||||||
|
args.Sprite.LayerSetState(PortableScrubberVisualLayers.IsRunning, runningState);
|
||||||
|
|
||||||
|
var fullState = isFull ? component.FullState : component.ReadyState;
|
||||||
|
args.Sprite.LayerSetState(PowerDeviceVisualLayers.Powered, fullState);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Component.TryGetData(PortableScrubberVisuals.IsDraining, out bool isDraining))
|
||||||
|
{
|
||||||
|
args.Sprite.LayerSetVisible(PortableScrubberVisualLayers.IsDraining, isDraining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public enum PortableScrubberVisualLayers : byte
|
||||||
|
{
|
||||||
|
IsRunning,
|
||||||
|
|
||||||
|
IsDraining
|
||||||
|
}
|
||||||
@@ -150,18 +150,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
|||||||
|
|
||||||
if (portNode.NodeGroup is PipeNet {NodeCount: > 1} net)
|
if (portNode.NodeGroup is PipeNet {NodeCount: > 1} net)
|
||||||
{
|
{
|
||||||
var buffer = new GasMixture(net.Air.Volume + canister.Air.Volume);
|
MixContainerWithPipeNet(canister.Air, net.Air);
|
||||||
|
|
||||||
_atmosphereSystem.Merge(buffer, net.Air);
|
|
||||||
_atmosphereSystem.Merge(buffer, canister.Air);
|
|
||||||
|
|
||||||
net.Air.Clear();
|
|
||||||
_atmosphereSystem.Merge(net.Air, buffer);
|
|
||||||
net.Air.Multiply(net.Air.Volume / buffer.Volume);
|
|
||||||
|
|
||||||
canister.Air.Clear();
|
|
||||||
_atmosphereSystem.Merge(canister.Air, buffer);
|
|
||||||
canister.Air.Multiply(canister.Air.Volume / buffer.Volume);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContainerManagerComponent? containerManager = null;
|
ContainerManagerComponent? containerManager = null;
|
||||||
@@ -275,5 +264,25 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
|||||||
|
|
||||||
appearance.SetData(GasCanisterVisuals.TankInserted, false);
|
appearance.SetData(GasCanisterVisuals.TankInserted, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix air from a gas container into a pipe net.
|
||||||
|
/// Useful for anything that uses connector ports.
|
||||||
|
/// </summary>
|
||||||
|
public void MixContainerWithPipeNet(GasMixture containerAir, GasMixture pipeNetAir)
|
||||||
|
{
|
||||||
|
var buffer = new GasMixture(pipeNetAir.Volume + containerAir.Volume);
|
||||||
|
|
||||||
|
_atmosphereSystem.Merge(buffer, pipeNetAir);
|
||||||
|
_atmosphereSystem.Merge(buffer, containerAir);
|
||||||
|
|
||||||
|
pipeNetAir.Clear();
|
||||||
|
_atmosphereSystem.Merge(pipeNetAir, buffer);
|
||||||
|
pipeNetAir.Multiply(pipeNetAir.Volume / buffer.Volume);
|
||||||
|
|
||||||
|
containerAir.Clear();
|
||||||
|
_atmosphereSystem.Merge(containerAir, buffer);
|
||||||
|
containerAir.Multiply(containerAir.Volume / buffer.Volume);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool FindGasPortIn(EntityUid? gridId, EntityCoordinates coordinates, [NotNullWhen(true)] out GasPortComponent? port)
|
public bool FindGasPortIn(EntityUid? gridId, EntityCoordinates coordinates, [NotNullWhen(true)] out GasPortComponent? port)
|
||||||
{
|
{
|
||||||
port = null;
|
port = null;
|
||||||
|
|
||||||
|
|||||||
@@ -86,33 +86,42 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
|||||||
AtmosDeviceEnabledEvent args) => UpdateState(uid, component);
|
AtmosDeviceEnabledEvent args) => UpdateState(uid, component);
|
||||||
|
|
||||||
private void Scrub(float timeDelta, GasVentScrubberComponent scrubber, GasMixture? tile, PipeNode outlet)
|
private void Scrub(float timeDelta, GasVentScrubberComponent scrubber, GasMixture? tile, PipeNode outlet)
|
||||||
|
{
|
||||||
|
Scrub(timeDelta, scrubber.TransferRate, scrubber.PumpDirection, scrubber.FilterGases, tile, outlet.Air);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if we were able to scrub, false if we were not.
|
||||||
|
/// </summary>
|
||||||
|
public bool Scrub(float timeDelta, float transferRate, ScrubberPumpDirection mode, HashSet<Gas> filterGases, GasMixture? tile, GasMixture destination)
|
||||||
{
|
{
|
||||||
// Cannot scrub if tile is null or air-blocked.
|
// Cannot scrub if tile is null or air-blocked.
|
||||||
if (tile == null
|
if (tile == null
|
||||||
|| outlet.Air.Pressure >= 50 * Atmospherics.OneAtmosphere) // Cannot scrub if pressure too high.
|
|| destination.Pressure >= 50 * Atmospherics.OneAtmosphere) // Cannot scrub if pressure too high.
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take a gas sample.
|
// Take a gas sample.
|
||||||
var ratio = MathF.Min(1f, timeDelta * scrubber.TransferRate / tile.Volume);
|
var ratio = MathF.Min(1f, timeDelta * transferRate / tile.Volume);
|
||||||
var removed = tile.RemoveRatio(ratio);
|
var removed = tile.RemoveRatio(ratio);
|
||||||
|
|
||||||
// Nothing left to remove from the tile.
|
// Nothing left to remove from the tile.
|
||||||
if (MathHelper.CloseToPercent(removed.TotalMoles, 0f))
|
if (MathHelper.CloseToPercent(removed.TotalMoles, 0f))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
if (scrubber.PumpDirection == ScrubberPumpDirection.Scrubbing)
|
if (mode == ScrubberPumpDirection.Scrubbing)
|
||||||
{
|
{
|
||||||
_atmosphereSystem.ScrubInto(removed, outlet.Air, scrubber.FilterGases);
|
_atmosphereSystem.ScrubInto(removed, destination, filterGases);
|
||||||
|
|
||||||
// Remix the gases.
|
// Remix the gases.
|
||||||
_atmosphereSystem.Merge(tile, removed);
|
_atmosphereSystem.Merge(tile, removed);
|
||||||
}
|
}
|
||||||
else if (scrubber.PumpDirection == ScrubberPumpDirection.Siphoning)
|
else if (mode == ScrubberPumpDirection.Siphoning)
|
||||||
{
|
{
|
||||||
_atmosphereSystem.Merge(outlet.Air, removed);
|
_atmosphereSystem.Merge(destination, removed);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAtmosAlarm(EntityUid uid, GasVentScrubberComponent component, AtmosMonitorAlarmEvent args)
|
private void OnAtmosAlarm(EntityUid uid, GasVentScrubberComponent component, AtmosMonitorAlarmEvent args)
|
||||||
|
|||||||
48
Content.Server/Atmos/Portable/PortableScrubberComponent.cs
Normal file
48
Content.Server/Atmos/Portable/PortableScrubberComponent.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using Content.Shared.Atmos;
|
||||||
|
|
||||||
|
namespace Content.Server.Atmos.Portable
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class PortableScrubberComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The air inside this machine.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("gasMixture")]
|
||||||
|
public GasMixture Air { get; } = new();
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("port")]
|
||||||
|
public string PortName { get; set; } = "port";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Which gases this machine will scrub out.
|
||||||
|
/// Unlike fixed scrubbers controlled by an air alarm,
|
||||||
|
/// this can't be changed in game.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("filterGases")]
|
||||||
|
public HashSet<Gas> FilterGases = new()
|
||||||
|
{
|
||||||
|
Gas.CarbonDioxide,
|
||||||
|
Gas.Plasma,
|
||||||
|
Gas.Tritium,
|
||||||
|
Gas.WaterVapor,
|
||||||
|
Gas.Miasma
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can this scrubber hold more gas?
|
||||||
|
/// </summary>
|
||||||
|
public bool Full => Air.Pressure >= MaxPressure;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum internal pressure before it refuses to take more.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maxPressure")]
|
||||||
|
public float MaxPressure = 3000f;
|
||||||
|
[DataField("transferRate")]
|
||||||
|
public float TransferRate = 1000f;
|
||||||
|
public bool Enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
162
Content.Server/Atmos/Portable/PortableScrubberSystem.cs
Normal file
162
Content.Server/Atmos/Portable/PortableScrubberSystem.cs
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
using Content.Server.Atmos.Piping.Unary.EntitySystems;
|
||||||
|
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||||
|
using Content.Shared.Atmos.Visuals;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Destructible;
|
||||||
|
using Content.Server.Atmos.Piping.Components;
|
||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Server.NodeContainer;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Content.Server.NodeContainer.Nodes;
|
||||||
|
using Content.Server.NodeContainer.NodeGroups;
|
||||||
|
using Content.Server.Audio;
|
||||||
|
using Content.Server.Administration.Logs;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace Content.Server.Atmos.Portable
|
||||||
|
{
|
||||||
|
public sealed class PortableScrubberSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly GasVentScrubberSystem _scrubberSystem = default!;
|
||||||
|
[Dependency] private readonly GasCanisterSystem _canisterSystem = default!;
|
||||||
|
[Dependency] private readonly GasPortableSystem _gasPortableSystem = default!;
|
||||||
|
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||||
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] private readonly AmbientSoundSystem _ambientSound = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<PortableScrubberComponent, AtmosDeviceUpdateEvent>(OnDeviceUpdated);
|
||||||
|
SubscribeLocalEvent<PortableScrubberComponent, AnchorStateChangedEvent>(OnAnchorChanged);
|
||||||
|
SubscribeLocalEvent<PortableScrubberComponent, PowerChangedEvent>(OnPowerChanged);
|
||||||
|
SubscribeLocalEvent<PortableScrubberComponent, ExaminedEvent>(OnExamined);
|
||||||
|
SubscribeLocalEvent<PortableScrubberComponent, DestructionEventArgs>(OnDestroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDeviceUpdated(EntityUid uid, PortableScrubberComponent component, AtmosDeviceUpdateEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp(uid, out AtmosDeviceComponent? device))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var timeDelta = (float) (_gameTiming.CurTime - device.LastProcess).TotalSeconds;
|
||||||
|
|
||||||
|
if (!component.Enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/// If we are on top of a connector port, empty into it.
|
||||||
|
if (TryComp<NodeContainerComponent>(uid, out var nodeContainer)
|
||||||
|
&& nodeContainer.TryGetNode(component.PortName, out PortablePipeNode? portableNode)
|
||||||
|
&& portableNode.ConnectionsEnabled)
|
||||||
|
{
|
||||||
|
_atmosphereSystem.React(component.Air, portableNode);
|
||||||
|
if (portableNode.NodeGroup is PipeNet {NodeCount: > 1} net)
|
||||||
|
_canisterSystem.MixContainerWithPipeNet(component.Air, net.Air);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.Full)
|
||||||
|
{
|
||||||
|
UpdateAppearance(uid, true, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var xform = Transform(uid);
|
||||||
|
|
||||||
|
if (xform.GridUid == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var position = _transformSystem.GetGridOrMapTilePosition(uid, xform);
|
||||||
|
|
||||||
|
var environment = _atmosphereSystem.GetTileMixture(xform.GridUid, xform.MapUid, position, true);
|
||||||
|
|
||||||
|
var running = Scrub(timeDelta, component, environment);
|
||||||
|
|
||||||
|
UpdateAppearance(uid, false, running);
|
||||||
|
/// We scrub once to see if we can and set the animation
|
||||||
|
if (!running)
|
||||||
|
return;
|
||||||
|
/// widenet
|
||||||
|
foreach (var adjacent in _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true))
|
||||||
|
{
|
||||||
|
Scrub(timeDelta, component, environment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If there is a port under us, let us connect with adjacent atmos pipes.
|
||||||
|
/// </summary>
|
||||||
|
private void OnAnchorChanged(EntityUid uid, PortableScrubberComponent component, ref AnchorStateChangedEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp(uid, out NodeContainerComponent? nodeContainer))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!nodeContainer.TryGetNode(component.PortName, out PipeNode? portableNode))
|
||||||
|
return;
|
||||||
|
|
||||||
|
portableNode.ConnectionsEnabled = (args.Anchored && _gasPortableSystem.FindGasPortIn(Transform(uid).GridUid, Transform(uid).Coordinates, out _));
|
||||||
|
|
||||||
|
UpdateDrainingAppearance(uid, portableNode.ConnectionsEnabled);
|
||||||
|
}
|
||||||
|
private void OnPowerChanged(EntityUid uid, PortableScrubberComponent component, PowerChangedEvent args)
|
||||||
|
{
|
||||||
|
UpdateAppearance(uid, component.Full, args.Powered);
|
||||||
|
component.Enabled = args.Powered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Examining tells you how full it is as a %.
|
||||||
|
/// </summary>
|
||||||
|
private void OnExamined(EntityUid uid, PortableScrubberComponent component, ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (args.IsInDetailsRange)
|
||||||
|
{
|
||||||
|
var percentage = Math.Round(((component.Air.Pressure) / component.MaxPressure) * 100);
|
||||||
|
args.PushMarkup(Loc.GetString("portable-scrubber-fill-level", ("percent", percentage)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When this is destroyed, we dump out all the gas inside.
|
||||||
|
/// </summary>
|
||||||
|
private void OnDestroyed(EntityUid uid, PortableScrubberComponent component, DestructionEventArgs args)
|
||||||
|
{
|
||||||
|
var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
|
||||||
|
|
||||||
|
if (environment != null)
|
||||||
|
_atmosphereSystem.Merge(environment, component.Air);
|
||||||
|
|
||||||
|
_adminLogger.Add(LogType.CanisterPurged, LogImpact.Medium, $"Portable scrubber {ToPrettyString(uid):canister} purged its contents of {component.Air:gas} into the environment.");
|
||||||
|
component.Air.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Scrub(float timeDelta, PortableScrubberComponent scrubber, GasMixture? tile)
|
||||||
|
{
|
||||||
|
return _scrubberSystem.Scrub(timeDelta, scrubber.TransferRate, ScrubberPumpDirection.Scrubbing, scrubber.FilterGases, tile, scrubber.Air);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance(EntityUid uid, bool isFull, bool isRunning)
|
||||||
|
{
|
||||||
|
if (!TryComp<AppearanceComponent>(uid, out var appearance))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ambientSound.SetAmbience(uid, isRunning);
|
||||||
|
|
||||||
|
appearance.SetData(PortableScrubberVisuals.IsFull, isFull);
|
||||||
|
appearance.SetData(PortableScrubberVisuals.IsRunning, isRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDrainingAppearance(EntityUid uid, bool isDraining)
|
||||||
|
{
|
||||||
|
if (!TryComp<AppearanceComponent>(uid, out var appearance))
|
||||||
|
return;
|
||||||
|
|
||||||
|
appearance.SetData(PortableScrubberVisuals.IsDraining, isDraining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ namespace Content.Server.Entry
|
|||||||
"MeleeWeaponArcAnimation",
|
"MeleeWeaponArcAnimation",
|
||||||
"EffectVisuals",
|
"EffectVisuals",
|
||||||
"DamageStateVisuals",
|
"DamageStateVisuals",
|
||||||
|
"PortableScrubberVisuals",
|
||||||
"AnimationsTest",
|
"AnimationsTest",
|
||||||
"ItemStatus",
|
"ItemStatus",
|
||||||
"VehicleVisuals",
|
"VehicleVisuals",
|
||||||
|
|||||||
15
Content.Shared/Atmos/Visuals/PortableScrubberVisuals.cs
Normal file
15
Content.Shared/Atmos/Visuals/PortableScrubberVisuals.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Atmos.Visuals
|
||||||
|
{
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
/// <summary>
|
||||||
|
/// Used for the visualizer
|
||||||
|
/// </summary>
|
||||||
|
public enum PortableScrubberVisuals : byte
|
||||||
|
{
|
||||||
|
IsFull,
|
||||||
|
IsRunning,
|
||||||
|
IsDraining
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,4 +5,5 @@ gas_vent - https://freesound.org/people/kyles/sounds/453642/ - CC0-1.0
|
|||||||
flowing_water_open - https://freesound.org/people/sterferny/sounds/382322/ - CC0-1.0
|
flowing_water_open - https://freesound.org/people/sterferny/sounds/382322/ - CC0-1.0
|
||||||
server_fans - https://freesound.org/people/DeVern/sounds/610761/ - CC-BY-3.0
|
server_fans - https://freesound.org/people/DeVern/sounds/610761/ - CC-BY-3.0
|
||||||
drain.ogg - https://freesound.org/people/PhreaKsAccount/sounds/46266/ - CC-BY-3.0 (by PhreaKsAccount)
|
drain.ogg - https://freesound.org/people/PhreaKsAccount/sounds/46266/ - CC-BY-3.0 (by PhreaKsAccount)
|
||||||
alarm.ogg - https://github.com/Baystation12/Baystation12/commit/41b11ef289bccfdfa2940480beb9c1e3f50c3b93, fire_alarm.ogg CC-BY-SA-3.0
|
portable_scrubber.ogg - https://freesound.org/people/Beethovenboy/sounds/384335/ - CC0 (by Beethovenboy)
|
||||||
|
alarm.ogg - https://github.com/Baystation12/Baystation12/commit/41b11ef289bccfdfa2940480beb9c1e3f50c3b93, fire_alarm.ogg CC-BY-SA-3.0
|
||||||
|
|||||||
BIN
Resources/Audio/Ambience/Objects/portable_scrubber.ogg
Normal file
BIN
Resources/Audio/Ambience/Objects/portable_scrubber.ogg
Normal file
Binary file not shown.
1
Resources/Locale/en-US/atmos/portable-scrubber.ftl
Normal file
1
Resources/Locale/en-US/atmos/portable-scrubber.ftl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
portable-scrubber-fill-level = It's at about [color=yellow]{$percent}%[/color] of its maximum internal pressure.
|
||||||
@@ -265,6 +265,7 @@
|
|||||||
- IndustrialEngineering
|
- IndustrialEngineering
|
||||||
unlockedRecipes:
|
unlockedRecipes:
|
||||||
- ThermomachineFreezerMachineCircuitBoard
|
- ThermomachineFreezerMachineCircuitBoard
|
||||||
|
- PortableScrubberMachineCircuitBoard
|
||||||
|
|
||||||
# Avionics Circuitry Technology Tree
|
# Avionics Circuitry Technology Tree
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,21 @@
|
|||||||
graph: ThermomachineBoard
|
graph: ThermomachineBoard
|
||||||
node: heater
|
node: heater
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: PortableScrubberMachineCircuitBoard
|
||||||
|
parent: BaseMachineCircuitboard
|
||||||
|
name: portable scrubber machine board
|
||||||
|
description: A PCB for a portable scrubber.
|
||||||
|
components:
|
||||||
|
- type: MachineBoard
|
||||||
|
prototype: PortableScrubber
|
||||||
|
requirements:
|
||||||
|
MatterBin: 3
|
||||||
|
Laser: 2
|
||||||
|
ScanningModule: 1
|
||||||
|
materialRequirements:
|
||||||
|
Cable: 5
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: CloningPodMachineCircuitboard
|
id: CloningPodMachineCircuitboard
|
||||||
parent: BaseMachineCircuitboard
|
parent: BaseMachineCircuitboard
|
||||||
@@ -396,7 +411,7 @@
|
|||||||
materialRequirements:
|
materialRequirements:
|
||||||
Glass: 2
|
Glass: 2
|
||||||
Cable: 2
|
Cable: 2
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: EmitterCircuitboard
|
id: EmitterCircuitboard
|
||||||
parent: BaseMachineCircuitboard
|
parent: BaseMachineCircuitboard
|
||||||
|
|||||||
@@ -240,6 +240,7 @@
|
|||||||
- SMESMachineCircuitboard
|
- SMESMachineCircuitboard
|
||||||
- SubstationMachineCircuitboard
|
- SubstationMachineCircuitboard
|
||||||
- ThermomachineFreezerMachineCircuitBoard
|
- ThermomachineFreezerMachineCircuitBoard
|
||||||
|
- PortableScrubberMachineCircuitBoard
|
||||||
- CloningPodMachineCircuitboard
|
- CloningPodMachineCircuitboard
|
||||||
- MedicalScannerMachineCircuitboard
|
- MedicalScannerMachineCircuitboard
|
||||||
- CrewMonitoringComputerCircuitboard
|
- CrewMonitoringComputerCircuitboard
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
- type: entity
|
||||||
|
id: PortableScrubber
|
||||||
|
parent: BaseStructureDynamic
|
||||||
|
name: portable scrubber
|
||||||
|
description: It scrubs, portably!
|
||||||
|
components:
|
||||||
|
- type: Transform
|
||||||
|
noRot: true
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Physics
|
||||||
|
bodyType: Dynamic
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeCircle
|
||||||
|
radius: 0.4
|
||||||
|
mass: 50
|
||||||
|
mask:
|
||||||
|
- MachineMask
|
||||||
|
layer:
|
||||||
|
- MachineLayer
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
sprite: Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi
|
||||||
|
layers:
|
||||||
|
- state: icon
|
||||||
|
map: ["enum.PortableScrubberVisualLayers.IsRunning"]
|
||||||
|
- state: unlit
|
||||||
|
shader: unshaded
|
||||||
|
map: ["enum.PowerDeviceVisualLayers.Powered"]
|
||||||
|
- state: draining
|
||||||
|
shader: unshaded
|
||||||
|
visible: false
|
||||||
|
map: ["enum.PortableScrubberVisualLayers.IsDraining"]
|
||||||
|
- type: Pullable
|
||||||
|
- type: AtmosDevice
|
||||||
|
joinSystem: true
|
||||||
|
- type: PortableScrubber
|
||||||
|
gasMixture:
|
||||||
|
volume: 1250
|
||||||
|
- type: NodeContainer
|
||||||
|
nodes:
|
||||||
|
port:
|
||||||
|
!type:PortablePipeNode
|
||||||
|
nodeGroupID: Pipe
|
||||||
|
rotationsEnabled: false
|
||||||
|
volume: 1
|
||||||
|
- type: ApcPowerReceiver
|
||||||
|
powerLoad: 2000
|
||||||
|
- type: ExtensionCableReceiver
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: PowerDeviceVisualizer
|
||||||
|
- type: PortableScrubberVisuals
|
||||||
|
idleState: icon
|
||||||
|
runningState: icon-running
|
||||||
|
readyState: unlit
|
||||||
|
fullState: unlit-full
|
||||||
|
- type: AmbientSound
|
||||||
|
enabled: false
|
||||||
|
volume: -5
|
||||||
|
range: 5
|
||||||
|
sound:
|
||||||
|
path: /Audio/Ambience/Objects/portable_scrubber.ogg
|
||||||
|
- type: Machine
|
||||||
|
board: PortableScrubberMachineCircuitBoard
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Inorganic
|
||||||
|
damageModifierSet: Metallic
|
||||||
|
- type: Destructible
|
||||||
|
thresholds:
|
||||||
|
- trigger:
|
||||||
|
!type:DamageTrigger
|
||||||
|
damage: 300
|
||||||
|
behaviors:
|
||||||
|
- !type:PlaySoundBehavior
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/metalbreak.ogg
|
||||||
|
- !type:SpawnEntitiesBehavior
|
||||||
|
spawn:
|
||||||
|
SheetSteel1:
|
||||||
|
min: 1
|
||||||
|
max: 3
|
||||||
|
SheetGlass1:
|
||||||
|
min: 1
|
||||||
|
max: 3
|
||||||
|
- !type:DoActsBehavior
|
||||||
|
acts: [ "Destruction" ]
|
||||||
|
- type: CollideOnAnchor
|
||||||
|
enable: true
|
||||||
@@ -54,6 +54,16 @@
|
|||||||
Glass: 900
|
Glass: 900
|
||||||
Gold: 50
|
Gold: 50
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: PortableScrubberMachineCircuitBoard
|
||||||
|
icon: Objects/Misc/module.rsi/id_mod.png
|
||||||
|
result: PortableScrubberMachineCircuitBoard
|
||||||
|
completetime: 4
|
||||||
|
materials:
|
||||||
|
Steel: 150
|
||||||
|
Glass: 900
|
||||||
|
Gold: 50
|
||||||
|
|
||||||
- type: latheRecipe
|
- type: latheRecipe
|
||||||
id: MedicalScannerMachineCircuitboard
|
id: MedicalScannerMachineCircuitboard
|
||||||
icon: Objects/Misc/module.rsi/id_mod.png
|
icon: Objects/Misc/module.rsi/id_mod.png
|
||||||
@@ -357,7 +367,7 @@
|
|||||||
materials:
|
materials:
|
||||||
Steel: 100
|
Steel: 100
|
||||||
Glass: 900
|
Glass: 900
|
||||||
|
|
||||||
- type: latheRecipe
|
- type: latheRecipe
|
||||||
id: EmitterCircuitboard
|
id: EmitterCircuitboard
|
||||||
icon: Objects/Misc/module.rsi/id_mod.png
|
icon: Objects/Misc/module.rsi/id_mod.png
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a, and modified a bit by Rane",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "icon-running",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unlit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unlit-full"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "draining"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 705 B |
Binary file not shown.
|
After Width: | Height: | Size: 706 B |
Reference in New Issue
Block a user