From: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Date: Thu, 15 Jan 2026 19:45:20 +0000 (+0100)
Subject: Add Paper Centrifuge (#42040)
X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=c7e8bbbf873deb9e341facf73c443b0b39936f6f;p=space-station-14.git
Add Paper Centrifuge (#42040)
* init
* sound
* sprite, half functional construction
* proper recipe
* oops
* loop sound
* inhands
* review
* review squared
---
diff --git a/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs b/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs
index 25fda25607..247b203e21 100644
--- a/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs
+++ b/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs
@@ -1,4 +1,5 @@
using Content.Shared.DoAfter;
+using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
@@ -20,17 +21,34 @@ public sealed partial class ReactionMixerComponent : Component
[DataField, AutoNetworkedField]
public LocId MixMessage = "default-mixing-success";
+ ///
+ /// The sound to play when mixing.
+ ///
+ [DataField]
+ public SoundSpecifier? MixingSound;
+
///
/// Defines if interacting is enough to mix with this component.
///
[DataField, AutoNetworkedField]
- public bool MixOnInteract = true;
+ public ReactionMixerType MixerType = ReactionMixerType.Machine;
///
/// How long it takes to mix with this.
///
[DataField, AutoNetworkedField]
public TimeSpan TimeToMix = TimeSpan.Zero;
+
+ // Used to cancel the played sound.
+ public EntityUid? AudioStream;
+}
+
+[Serializable, NetSerializable]
+public enum ReactionMixerType
+{
+ None, // Mixing is handled by its own system.
+ Machine, // Mixing is handled via interaction.
+ Handheld // Mixing is handled via using in hand
}
[ByRefEvent]
diff --git a/Content.Shared/Chemistry/Reaction/ReactionMixerSystem.cs b/Content.Shared/Chemistry/Reaction/ReactionMixerSystem.cs
index 4bc878a42f..030c8b890c 100644
--- a/Content.Shared/Chemistry/Reaction/ReactionMixerSystem.cs
+++ b/Content.Shared/Chemistry/Reaction/ReactionMixerSystem.cs
@@ -4,7 +4,10 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Network;
namespace Content.Shared.Chemistry.Reaction;
@@ -13,24 +16,64 @@ public sealed partial class ReactionMixerSystem : EntitySystem
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly INetManager _net = default!;
public override void Initialize()
{
base.Initialize();
+ SubscribeLocalEvent(OnUseInHand);
SubscribeLocalEvent(OnAfterInteract, before: [typeof(IngestionSystem)]);
SubscribeLocalEvent(OnShake);
SubscribeLocalEvent(OnDoAfter);
}
+ private void OnUseInHand(Entity ent, ref UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (ent.Comp.MixerType != ReactionMixerType.Handheld)
+ return;
+
+ args.Handled = true;
+
+ if (!CanMix(ent.AsNullable(), ent))
+ return;
+
+ if (_net.IsServer) // Cannot cancel predicted audio.
+ ent.Comp.AudioStream = _audio.PlayPvs(ent.Comp.MixingSound, ent)?.Entity;
+
+ var doAfterArgs = new DoAfterArgs(EntityManager,
+ args.User,
+ ent.Comp.TimeToMix,
+ new ReactionMixDoAfterEvent(),
+ ent,
+ ent,
+ ent)
+ {
+ NeedHand = true,
+ BreakOnDamage = true,
+ BreakOnDropItem = true,
+ BreakOnHandChange = true,
+ BreakOnMove = true
+ };
+
+ _doAfter.TryStartDoAfter(doAfterArgs);
+ }
+
private void OnAfterInteract(Entity ent, ref AfterInteractEvent args)
{
- if (!args.Target.HasValue || !args.CanReach || !ent.Comp.MixOnInteract)
+ if (!args.Target.HasValue || !args.CanReach || ent.Comp.MixerType != ReactionMixerType.Machine)
return;
if (!CanMix(ent.AsNullable(), args.Target.Value))
return;
+ if (_net.IsServer) // Cannot cancel predicted audio.
+ ent.Comp.AudioStream = _audio.PlayPvs(ent.Comp.MixingSound, ent)?.Entity;
+
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, ent.Comp.TimeToMix, new ReactionMixDoAfterEvent(), ent, args.Target.Value, ent);
_doAfter.TryStartDoAfter(doAfterArgs);
@@ -39,6 +82,11 @@ public sealed partial class ReactionMixerSystem : EntitySystem
private void OnDoAfter(Entity ent, ref ReactionMixDoAfterEvent args)
{
+ ent.Comp.AudioStream = _audio.Stop(ent.Comp.AudioStream);
+
+ if (args.Cancelled)
+ return;
+
if (args.Target == null)
return;
@@ -46,8 +94,7 @@ public sealed partial class ReactionMixerSystem : EntitySystem
return;
_popup.PopupClient(
- Loc.GetString(
- ent.Comp.MixMessage,
+ Loc.GetString(ent.Comp.MixMessage,
("mixed", Identity.Entity(args.Target.Value, EntityManager)),
("mixer", Identity.Entity(ent.Owner, EntityManager))),
args.User,
@@ -69,14 +116,18 @@ public sealed partial class ReactionMixerSystem : EntitySystem
if (!Resolve(ent, ref ent.Comp, false)) // The used entity needs the component to be able to mix a solution
return false;
+ if (!_solutionContainer.TryGetMixableSolution(target, out _, out var mixableSolution))
+ return false;
+
+ // Can't mix nothing.
+ if (mixableSolution.Volume <= 0)
+ return false;
+
var mixAttemptEvent = new MixingAttemptEvent(ent);
RaiseLocalEvent(ent, ref mixAttemptEvent);
if (mixAttemptEvent.Cancelled)
return false;
- if (!_solutionContainer.TryGetMixableSolution(target, out _, out _))
- return false;
-
return true;
}
diff --git a/Resources/Audio/Items/Medical/attributions.yml b/Resources/Audio/Items/Medical/attributions.yml
index 09c4dcd0f5..23023781c7 100644
--- a/Resources/Audio/Items/Medical/attributions.yml
+++ b/Resources/Audio/Items/Medical/attributions.yml
@@ -3,6 +3,11 @@
copyright: "Taken from FM Synthesis on freesound.org"
source: "https://freesound.org/people/FreqMan/sounds/32683/"
+- files: ["paper_centrifuge.ogg"]
+ license: "CC-BY-4.0"
+ copyright: "Taken from temawas on freesound. Cut, converted to .ogg"
+ source: "https://freesound.org/people/temawas/sounds/179248/"
+
- files: ["jet_injector.ogg"]
license: "CC-BY-3.0"
copyright: "Orginal audio by EminYILDIRIM -- https://freesound.org/s/548009/ -- License: Attribution 4.0, 2imitk -- https://freesound.org/s/279044/ -- License: Attribution 3.0 and brunoboselli -- https://freesound.org/s/457294/ -- License: Creative Commons 0 -- https://freesound.org/s/460586/ -- License: Attribution NonCommercial 4.0, modified by Princess-Cheeseballs (GitHub)"
diff --git a/Resources/Audio/Items/Medical/paper_centrifuge.ogg b/Resources/Audio/Items/Medical/paper_centrifuge.ogg
new file mode 100644
index 0000000000..829512fbfd
Binary files /dev/null and b/Resources/Audio/Items/Medical/paper_centrifuge.ogg differ
diff --git a/Resources/Locale/en-US/chemistry/components/mixing-component.ftl b/Resources/Locale/en-US/chemistry/components/mixing-component.ftl
index c434246fab..b6ca5226da 100644
--- a/Resources/Locale/en-US/chemistry/components/mixing-component.ftl
+++ b/Resources/Locale/en-US/chemistry/components/mixing-component.ftl
@@ -14,4 +14,5 @@ mixing-verb-shake = shake
default-mixing-success = You mix the {$mixed} with the {$mixer}
bible-mixing-success = You bless the {$mixed} with the {$mixer}
spoon-mixing-success = You stir the {$mixed} with the {$mixer}
+handheld-centrifuge-success = You seperate chemicals in the {$mixed}
diff --git a/Resources/Locale/en-US/recipes/tags.ftl b/Resources/Locale/en-US/recipes/tags.ftl
index 8d739eef6f..db636df773 100644
--- a/Resources/Locale/en-US/recipes/tags.ftl
+++ b/Resources/Locale/en-US/recipes/tags.ftl
@@ -73,6 +73,7 @@ construction-graph-tag-apron = an apron
construction-graph-tag-utility-belt = a utility belt
soil-construction-graph-any-mushroom = any mushroom
construction-graph-tag-mop-basic = mop
+construction-graph-tag-paper = office paper
construction-graph-tag-core-pinpointer-piece = piece of core pinpointer
# toys
@@ -153,3 +154,6 @@ construction-graph-tag-spationaut-hardsuit = spationaut hardsuit
# clothing
construction-graph-tag-backpack = backpack
+
+# chemistry
+construction-graph-tag-centrifuge-compatible = centrifugable container
diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml
index 0d3907c50f..2a44c451ab 100644
--- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml
+++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml
@@ -131,7 +131,7 @@
Steel: 50
- type: Shakeable
- type: ReactionMixer
- mixOnInteract: false
+ mixerType: None
reactionTypes:
- Shake
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chemistry/paper_centrifuge.yml b/Resources/Prototypes/Entities/Objects/Specific/Chemistry/paper_centrifuge.yml
new file mode 100644
index 0000000000..aaeeb17bf8
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Specific/Chemistry/paper_centrifuge.yml
@@ -0,0 +1,60 @@
+- type: entity
+ abstract: true
+ parent: BaseItem
+ id: BaseHandheldMixer
+ components:
+ - type: SolutionContainerManager
+ solutions:
+ mixer:
+ maxVol: 10
+ - type: MixableSolution
+ solution: mixer
+ - type: RefillableSolution
+ solution: mixer
+ - type: DrainableSolution
+ solution: mixer
+ - type: ExaminableSolution
+ solution: mixer
+ exactVolume: true
+ - type: DrawableSolution
+ solution: mixer
+ - type: InjectableSolution
+ solution: mixer
+ - type: Spillable
+ solution: mixer
+ - type: SolutionTransfer
+ - type: SolutionItemStatus
+ solution: mixer
+ - type: Appearance
+ - type: DnaSubstanceTrace
+ - type: ReactionMixer
+
+- type: entity
+ parent: BaseHandheldMixer
+ id: HandheldMixerPaperCentrifuge
+ name: paper centrifuge
+ description: A small portable makeshift centrifuge. Works by rotating the paper sheets when its cords are pulled.
+ components:
+ - type: Sprite
+ sprite: Objects/Specific/Chemistry/paper_centrifuge.rsi
+ layers:
+ - state: icon
+ - state: fill-1
+ map: [ "enum.SolutionContainerLayers.Fill" ]
+ visible: false
+ - type: SolutionContainerVisuals
+ maxFillLevels: 4
+ fillBaseName: fill-
+ - type: Construction
+ graph: MakeshiftCentrifuge
+ node: makeshiftCentrifuge
+ - type: ReactionMixer
+ reactionTypes:
+ - Centrifuge
+ mixerType: Handheld
+ mixMessage: handheld-centrifuge-success
+ timeToMix: 10
+ mixingSound: !type:SoundPathSpecifier
+ path: /Audio/Items/Medical/paper_centrifuge.ogg
+ params:
+ loop: true
diff --git a/Resources/Prototypes/Recipes/Construction/tools.yml b/Resources/Prototypes/Recipes/Construction/tools.yml
index ed83abfc1a..6b89f3687a 100644
--- a/Resources/Prototypes/Recipes/Construction/tools.yml
+++ b/Resources/Prototypes/Recipes/Construction/tools.yml
@@ -45,3 +45,11 @@
targetNode: random_gate
category: construction-category-tools
objectType: Item
+
+- type: construction
+ id: MakeshiftCentrifuge
+ graph: MakeshiftCentrifuge
+ startNode: start
+ targetNode: makeshiftCentrifuge
+ category: construction-category-tools
+ objectType: Item
diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/paper_centrifuge.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/paper_centrifuge.yml
new file mode 100644
index 0000000000..46c040f881
--- /dev/null
+++ b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/paper_centrifuge.yml
@@ -0,0 +1,39 @@
+# Mortar
+- type: constructionGraph
+ id: MakeshiftCentrifuge
+ start: start
+ graph:
+ - node: start
+ edges:
+ - to: makeshiftCentrifuge
+ steps:
+ - material: MetalRod
+ amount: 2
+ - material: Cable
+ amount: 15
+ doAfter: 2
+ - tag: CentrifugeCompatible
+ icon:
+ sprite: Objects/Specific/Chemistry/vial.rsi
+ state: vial
+ name: construction-graph-tag-centrifuge-compatible
+ doAfter: 2
+ - tag: CentrifugeCompatible
+ icon:
+ sprite: Objects/Specific/Chemistry/vial.rsi
+ state: vial
+ name: construction-graph-tag-centrifuge-compatible
+ doAfter: 2
+ - tag: Paper
+ icon:
+ sprite: Objects/Misc/bureaucracy.rsi
+ state: paper
+ name: construction-graph-tag-paper
+ - tag: Paper
+ icon:
+ sprite: Objects/Misc/bureaucracy.rsi
+ state: paper
+ name: construction-graph-tag-paper
+
+ - node: makeshiftCentrifuge
+ entity: HandheldMixerPaperCentrifuge
diff --git a/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-1.png b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-1.png
new file mode 100644
index 0000000000..d32accae69
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-1.png differ
diff --git a/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-2.png b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-2.png
new file mode 100644
index 0000000000..108a3e79b3
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-2.png differ
diff --git a/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-3.png b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-3.png
new file mode 100644
index 0000000000..ee4a1f0e4e
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-3.png differ
diff --git a/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-4.png b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-4.png
new file mode 100644
index 0000000000..df4f4b6cbc
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/fill-4.png differ
diff --git a/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/icon.png b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/icon.png
new file mode 100644
index 0000000000..ff66cd1048
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/icon.png differ
diff --git a/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/inhand-left.png
new file mode 100644
index 0000000000..5ae3bcfebf
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/inhand-left.png differ
diff --git a/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/inhand-right.png b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/inhand-right.png
new file mode 100644
index 0000000000..5ae3bcfebf
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/inhand-right.png differ
diff --git a/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/meta.json b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/meta.json
new file mode 100644
index 0000000000..a839622821
--- /dev/null
+++ b/Resources/Textures/Objects/Specific/Chemistry/paper_centrifuge.rsi/meta.json
@@ -0,0 +1,34 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Sprites created by DispenserG0inUp(Discord)",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "fill-1"
+ },
+ {
+ "name": "fill-2"
+ },
+ {
+ "name": "fill-3"
+ },
+ {
+ "name": "fill-4"
+ },
+ {
+ "name": "inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "inhand-right",
+ "directions": 4
+ }
+ ]
+}