diff --git a/Content.Client/Atmos/Visualizers/Components/PortableScrubberVisualsComponent.cs b/Content.Client/Atmos/Visualizers/Components/PortableScrubberVisualsComponent.cs new file mode 100644 index 0000000000..d829f6b17b --- /dev/null +++ b/Content.Client/Atmos/Visualizers/Components/PortableScrubberVisualsComponent.cs @@ -0,0 +1,23 @@ +namespace Content.Client.Atmos.Visualizers; + +/// +/// Holds 2 pairs of states. The idle/running pair controls animation, while +/// the ready / full pair controls the color of the light. +/// +[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!; +} diff --git a/Content.Client/Atmos/Visualizers/PortableScrubberVisualsSystem.cs b/Content.Client/Atmos/Visualizers/PortableScrubberVisualsSystem.cs new file mode 100644 index 0000000000..00e25097b3 --- /dev/null +++ b/Content.Client/Atmos/Visualizers/PortableScrubberVisualsSystem.cs @@ -0,0 +1,39 @@ +using Robust.Client.GameObjects; +using Content.Shared.Atmos.Visuals; +using Content.Client.Power; + +namespace Content.Client.Atmos.Visualizers +{ + /// + /// Controls client-side visuals for portable scrubbers. + /// + public sealed class PortableScrubberSystem : VisualizerSystem + { + 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 +} diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs index 081f796163..15a18baa42 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs @@ -150,18 +150,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (portNode.NodeGroup is PipeNet {NodeCount: > 1} net) { - var buffer = new GasMixture(net.Air.Volume + canister.Air.Volume); - - _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); + MixContainerWithPipeNet(canister.Air, net.Air); } ContainerManagerComponent? containerManager = null; @@ -275,5 +264,25 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems appearance.SetData(GasCanisterVisuals.TankInserted, false); } + + /// + /// Mix air from a gas container into a pipe net. + /// Useful for anything that uses connector ports. + /// + 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); + } } } diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs index b46dfa213c..6e81764367 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs @@ -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; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs index fb0f24e7a9..89521f6f41 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs @@ -86,33 +86,42 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems AtmosDeviceEnabledEvent args) => UpdateState(uid, component); private void Scrub(float timeDelta, GasVentScrubberComponent scrubber, GasMixture? tile, PipeNode outlet) + { + Scrub(timeDelta, scrubber.TransferRate, scrubber.PumpDirection, scrubber.FilterGases, tile, outlet.Air); + } + + /// + /// True if we were able to scrub, false if we were not. + /// + public bool Scrub(float timeDelta, float transferRate, ScrubberPumpDirection mode, HashSet filterGases, GasMixture? tile, GasMixture destination) { // Cannot scrub if tile is null or air-blocked. 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. - var ratio = MathF.Min(1f, timeDelta * scrubber.TransferRate / tile.Volume); + var ratio = MathF.Min(1f, timeDelta * transferRate / tile.Volume); var removed = tile.RemoveRatio(ratio); // Nothing left to remove from the tile. 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. _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) diff --git a/Content.Server/Atmos/Portable/PortableScrubberComponent.cs b/Content.Server/Atmos/Portable/PortableScrubberComponent.cs new file mode 100644 index 0000000000..ac0ffdc226 --- /dev/null +++ b/Content.Server/Atmos/Portable/PortableScrubberComponent.cs @@ -0,0 +1,48 @@ +using Content.Shared.Atmos; + +namespace Content.Server.Atmos.Portable +{ + [RegisterComponent] + public sealed class PortableScrubberComponent : Component + { + /// + /// The air inside this machine. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("gasMixture")] + public GasMixture Air { get; } = new(); + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("port")] + public string PortName { get; set; } = "port"; + + /// + /// Which gases this machine will scrub out. + /// Unlike fixed scrubbers controlled by an air alarm, + /// this can't be changed in game. + /// + [DataField("filterGases")] + public HashSet FilterGases = new() + { + Gas.CarbonDioxide, + Gas.Plasma, + Gas.Tritium, + Gas.WaterVapor, + Gas.Miasma + }; + + /// + /// Can this scrubber hold more gas? + /// + public bool Full => Air.Pressure >= MaxPressure; + + /// + /// Maximum internal pressure before it refuses to take more. + /// + [DataField("maxPressure")] + public float MaxPressure = 3000f; + [DataField("transferRate")] + public float TransferRate = 1000f; + public bool Enabled = true; + } +} diff --git a/Content.Server/Atmos/Portable/PortableScrubberSystem.cs b/Content.Server/Atmos/Portable/PortableScrubberSystem.cs new file mode 100644 index 0000000000..cc96a19b58 --- /dev/null +++ b/Content.Server/Atmos/Portable/PortableScrubberSystem.cs @@ -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(OnDeviceUpdated); + SubscribeLocalEvent(OnAnchorChanged); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(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(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); + } + } + + /// + /// If there is a port under us, let us connect with adjacent atmos pipes. + /// + 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; + } + + /// + /// Examining tells you how full it is as a %. + /// + 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))); + } + } + + /// + /// When this is destroyed, we dump out all the gas inside. + /// + 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(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(uid, out var appearance)) + return; + + appearance.SetData(PortableScrubberVisuals.IsDraining, isDraining); + } + } +} diff --git a/Content.Server/Entry/IgnoredComponents.cs b/Content.Server/Entry/IgnoredComponents.cs index daa15a1a0b..82e714c4f6 100644 --- a/Content.Server/Entry/IgnoredComponents.cs +++ b/Content.Server/Entry/IgnoredComponents.cs @@ -11,6 +11,7 @@ namespace Content.Server.Entry "MeleeWeaponArcAnimation", "EffectVisuals", "DamageStateVisuals", + "PortableScrubberVisuals", "AnimationsTest", "ItemStatus", "VehicleVisuals", diff --git a/Content.Shared/Atmos/Visuals/PortableScrubberVisuals.cs b/Content.Shared/Atmos/Visuals/PortableScrubberVisuals.cs new file mode 100644 index 0000000000..20de97da8b --- /dev/null +++ b/Content.Shared/Atmos/Visuals/PortableScrubberVisuals.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Atmos.Visuals +{ + [Serializable, NetSerializable] + /// + /// Used for the visualizer + /// + public enum PortableScrubberVisuals : byte + { + IsFull, + IsRunning, + IsDraining + } +} diff --git a/Resources/Audio/Ambience/Objects/license.txt b/Resources/Audio/Ambience/Objects/license.txt index cdac322938..67d622f7a5 100644 --- a/Resources/Audio/Ambience/Objects/license.txt +++ b/Resources/Audio/Ambience/Objects/license.txt @@ -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 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) -alarm.ogg - https://github.com/Baystation12/Baystation12/commit/41b11ef289bccfdfa2940480beb9c1e3f50c3b93, fire_alarm.ogg CC-BY-SA-3.0 \ No newline at end of file +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 diff --git a/Resources/Audio/Ambience/Objects/portable_scrubber.ogg b/Resources/Audio/Ambience/Objects/portable_scrubber.ogg new file mode 100644 index 0000000000..b49149c81c Binary files /dev/null and b/Resources/Audio/Ambience/Objects/portable_scrubber.ogg differ diff --git a/Resources/Locale/en-US/atmos/portable-scrubber.ftl b/Resources/Locale/en-US/atmos/portable-scrubber.ftl new file mode 100644 index 0000000000..c4071b4acc --- /dev/null +++ b/Resources/Locale/en-US/atmos/portable-scrubber.ftl @@ -0,0 +1 @@ +portable-scrubber-fill-level = It's at about [color=yellow]{$percent}%[/color] of its maximum internal pressure. diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml index 7a4a83ba5a..063a4adc60 100644 --- a/Resources/Prototypes/Catalog/Research/technologies.yml +++ b/Resources/Prototypes/Catalog/Research/technologies.yml @@ -265,6 +265,7 @@ - IndustrialEngineering unlockedRecipes: - ThermomachineFreezerMachineCircuitBoard + - PortableScrubberMachineCircuitBoard # Avionics Circuitry Technology Tree diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index 620f6613ce..84cf92d267 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -156,6 +156,21 @@ graph: ThermomachineBoard 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 id: CloningPodMachineCircuitboard parent: BaseMachineCircuitboard @@ -396,7 +411,7 @@ materialRequirements: Glass: 2 Cable: 2 - + - type: entity id: EmitterCircuitboard parent: BaseMachineCircuitboard diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 30ae9989cd..2851bb44fb 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -240,6 +240,7 @@ - SMESMachineCircuitboard - SubstationMachineCircuitboard - ThermomachineFreezerMachineCircuitBoard + - PortableScrubberMachineCircuitBoard - CloningPodMachineCircuitboard - MedicalScannerMachineCircuitboard - CrewMonitoringComputerCircuitboard diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml new file mode 100644 index 0000000000..367aed5c44 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml @@ -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 diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index 9eccf9ca47..8adff9b177 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -54,6 +54,16 @@ Glass: 900 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 id: MedicalScannerMachineCircuitboard icon: Objects/Misc/module.rsi/id_mod.png @@ -357,7 +367,7 @@ materials: Steel: 100 Glass: 900 - + - type: latheRecipe id: EmitterCircuitboard icon: Objects/Misc/module.rsi/id_mod.png diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/draining.png b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/draining.png new file mode 100644 index 0000000000..d69c34fe57 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/draining.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/icon-running.png b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/icon-running.png new file mode 100644 index 0000000000..c145e2993e Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/icon-running.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/icon.png b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/icon.png new file mode 100644 index 0000000000..06cf190185 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/icon.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/meta.json b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/meta.json new file mode 100644 index 0000000000..82a7dfe0cb --- /dev/null +++ b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/meta.json @@ -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" + } + ] +} diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/unlit-full.png b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/unlit-full.png new file mode 100644 index 0000000000..b688c5b73a Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/unlit-full.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/unlit.png b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/unlit.png new file mode 100644 index 0000000000..00695432c2 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/Portable/portable_scrubber.rsi/unlit.png differ