Add Paper Centrifuge (#42040)

* init

* sound

* sprite, half functional construction

* proper recipe

* oops

* loop sound

* inhands

* review

* review squared
This commit is contained in:
ScarKy0
2026-01-15 20:45:20 +01:00
committed by GitHub
parent 84ca0ebe9c
commit c7e8bbbf87
18 changed files with 228 additions and 8 deletions

View File

@@ -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";
/// <summary>
/// The sound to play when mixing.
/// </summary>
[DataField]
public SoundSpecifier? MixingSound;
/// <summary>
/// Defines if interacting is enough to mix with this component.
/// </summary>
[DataField, AutoNetworkedField]
public bool MixOnInteract = true;
public ReactionMixerType MixerType = ReactionMixerType.Machine;
/// <summary>
/// How long it takes to mix with this.
/// </summary>
[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]

View File

@@ -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<ReactionMixerComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<ReactionMixerComponent, AfterInteractEvent>(OnAfterInteract, before: [typeof(IngestionSystem)]);
SubscribeLocalEvent<ReactionMixerComponent, ShakeEvent>(OnShake);
SubscribeLocalEvent<ReactionMixerComponent, ReactionMixDoAfterEvent>(OnDoAfter);
}
private void OnUseInHand(Entity<ReactionMixerComponent> 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<ReactionMixerComponent> 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<ReactionMixerComponent> 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;
}

View File

@@ -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)"

Binary file not shown.

View File

@@ -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}

View File

@@ -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

View File

@@ -131,7 +131,7 @@
Steel: 50
- type: Shakeable
- type: ReactionMixer
mixOnInteract: false
mixerType: None
reactionTypes:
- Shake

View File

@@ -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

View File

@@ -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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

View File

@@ -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
}
]
}