From 3cc79c223a243317446578982d2a4367773e5ce8 Mon Sep 17 00:00:00 2001
From: OnyxTheBrave <131422822+OnyxTheBrave@users.noreply.github.com>
Date: Tue, 30 Dec 2025 15:34:54 -0600
Subject: [PATCH] Chemmaster Pill Source (#40121)
* Buttons and basic internal data
* The buttons DO something
* it works?!!
* I hate predictions
* 5000 monkeys on typewritters
* who let the monkeys code?
* Localizations
* waiter, more commits please
* Not going insane (this is a lie)
* last one I SWEAR
* Some improvements ported from Moff
* clean it up a little
* one more cleanup
* The chemmaster is not a mime
* Fix my mistakes + address the other review
* Point to what chemmaster is broken, and why it's broken
* ChemMasterComponent changes
* Margin for packaging source
---------
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
---
.../UI/ChemMasterBoundUserInterface.cs | 4 +
.../Chemistry/UI/ChemMasterWindow.xaml | 9 +-
.../Chemistry/UI/ChemMasterWindow.xaml.cs | 49 +++++++--
.../Components/ChemMasterComponent.cs | 6 ++
.../EntitySystems/ChemMasterSystem.cs | 102 ++++++++++++++----
Content.Shared/Chemistry/SharedChemMaster.cs | 17 ++-
.../components/chem-master-component.ftl | 7 ++
7 files changed, 160 insertions(+), 34 deletions(-)
diff --git a/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs b/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs
index a669a8da4c..d0a9f5644c 100644
--- a/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs
+++ b/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs
@@ -48,6 +48,10 @@ namespace Content.Client.Chemistry.UI
(uint) _window.BottleDosage.Value, _window.LabelLine));
_window.BufferSortButton.OnPressed += _ => SendMessage(
new ChemMasterSortingTypeCycleMessage());
+ _window.OutputBufferDraw.OnPressed += _ => SendMessage(
+ new ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource.Internal));
+ _window.OutputBeakerDraw.OnPressed += _ => SendMessage(
+ new ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource.External));
for (uint i = 0; i < _window.PillTypeButtons.Length; i++)
{
diff --git a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml
index 5bc640ab44..cc2a199172 100644
--- a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml
+++ b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml
@@ -79,10 +79,13 @@
-
+
+
+
-
-
+
+
+
diff --git a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs
index d610f34b29..a8aab1d35f 100644
--- a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs
+++ b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs
@@ -150,7 +150,17 @@ namespace Content.Client.Chemistry.UI
// Ensure the Panel Info is updated, including UI elements for Buffer Volume, Output Container and so on
UpdatePanelInfo(castState);
- BufferCurrentVolume.Text = $" {castState.BufferCurrentVolume?.Int() ?? 0}u";
+ switch (castState.DrawSource)
+ {
+ case ChemMasterDrawSource.Internal:
+ SetBufferText(castState.BufferCurrentVolume, "chem-master-output-buffer-draw");
+ break;
+ case ChemMasterDrawSource.External:
+ SetBufferText(castState.InputContainerInfo?.CurrentVolume, "chem-master-output-beaker-draw");
+ break;
+ default:
+ throw new($"Chemmaster {castState.OutputContainerInfo} draw source is not set");
+ }
InputEjectButton.Disabled = castState.InputContainerInfo is null;
OutputEjectButton.Disabled = castState.OutputContainerInfo is null;
@@ -168,9 +178,14 @@ namespace Content.Client.Chemistry.UI
var holdsReagents = output?.Reagents != null;
var pillNumberMax = holdsReagents ? 0 : remainingCapacity;
var bottleAmountMax = holdsReagents ? remainingCapacity : 0;
- var bufferVolume = castState.BufferCurrentVolume?.Int() ?? 0;
+ var outputVolume = castState.DrawSource switch
+ {
+ ChemMasterDrawSource.Internal => castState.BufferCurrentVolume?.Int() ?? 0,
+ ChemMasterDrawSource.External => castState.InputContainerInfo?.CurrentVolume.Int() ?? 0,
+ _ => 0,
+ };
- PillDosage.Value = (int)Math.Min(bufferVolume, castState.PillDosageLimit);
+ PillDosage.Value = (int)Math.Min(outputVolume, castState.PillDosageLimit);
PillTypeButtons[castState.SelectedPillType].Pressed = true;
@@ -186,25 +201,35 @@ namespace Content.Client.Chemistry.UI
// Avoid division by zero
if (PillDosage.Value > 0)
{
- PillNumber.Value = Math.Min(bufferVolume / PillDosage.Value, pillNumberMax);
+ PillNumber.Value = Math.Min(outputVolume / PillDosage.Value, pillNumberMax);
}
else
{
PillNumber.Value = 0;
}
- BottleDosage.Value = Math.Min(bottleAmountMax, bufferVolume);
+ BottleDosage.Value = Math.Min(bottleAmountMax, outputVolume);
}
///
- /// Generate a product label based on reagents in the buffer.
+ /// Generate a product label based on reagents in the buffer or beaker.
///
/// State data sent by the server.
private string GenerateLabel(ChemMasterBoundUserInterfaceState state)
{
- if (state.BufferCurrentVolume == 0)
+ if (
+ state.BufferCurrentVolume == 0 && state.DrawSource == ChemMasterDrawSource.Internal ||
+ state.InputContainerInfo?.CurrentVolume == 0 && state.DrawSource == ChemMasterDrawSource.External ||
+ state.InputContainerInfo?.Reagents == null
+ )
return "";
- var reagent = state.BufferReagents.OrderBy(r => r.Quantity).First().Reagent;
+ var reagent = (state.DrawSource switch
+ {
+ ChemMasterDrawSource.Internal => state.BufferReagents,
+ ChemMasterDrawSource.External => state.InputContainerInfo.Reagents ?? [],
+ _ => throw new($"Chemmaster {state.OutputContainerInfo} draw source is not set"),
+ }).MinBy(r => r.Quantity)
+ .Reagent;
_prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto);
return proto?.LocalizedName ?? "";
}
@@ -233,6 +258,8 @@ namespace Content.Client.Chemistry.UI
_ => Loc.GetString("chem-master-window-sort-type-none")
};
+ OutputBufferDraw.Pressed = state.DrawSource == ChemMasterDrawSource.Internal;
+ OutputBeakerDraw.Pressed = state.DrawSource == ChemMasterDrawSource.External;
if (!state.BufferReagents.Any())
{
@@ -414,6 +441,12 @@ namespace Content.Client.Chemistry.UI
get => LabelLineEdit.Text;
set => LabelLineEdit.Text = value;
}
+
+ private void SetBufferText(FixedPoint2? volume, string text)
+ {
+ BufferCurrentVolume.Text = $" {volume ?? FixedPoint2.Zero}u";
+ DrawSource.Text = Loc.GetString(text);
+ }
}
public sealed class ReagentButton : Button
diff --git a/Content.Server/Chemistry/Components/ChemMasterComponent.cs b/Content.Server/Chemistry/Components/ChemMasterComponent.cs
index 0309d07ed9..398fddae0c 100644
--- a/Content.Server/Chemistry/Components/ChemMasterComponent.cs
+++ b/Content.Server/Chemistry/Components/ChemMasterComponent.cs
@@ -26,5 +26,11 @@ namespace Content.Server.Chemistry.Components
[DataField("clickSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
+
+ ///
+ /// Which source the chem master should draw from when making pills/bottles.
+ ///
+ [DataField]
+ public ChemMasterDrawSource DrawSource = ChemMasterDrawSource.Internal;
}
}
diff --git a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs
index 64350cbda1..7252a59f60 100644
--- a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs
+++ b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs
@@ -57,6 +57,7 @@ namespace Content.Server.Chemistry.EntitySystems
SubscribeLocalEvent(OnReagentButtonMessage);
SubscribeLocalEvent(OnCreatePillsMessage);
SubscribeLocalEvent(OnOutputToBottleMessage);
+ SubscribeLocalEvent(OnSetDrawSourceMessage);
}
private void SubscribeUpdateUiState(Entity ent, ref T ev)
@@ -77,7 +78,7 @@ namespace Content.Server.Chemistry.EntitySystems
var state = new ChemMasterBoundUserInterfaceState(
chemMaster.Mode, chemMaster.SortingType, BuildInputContainerInfo(inputContainer), BuildOutputContainerInfo(outputContainer),
- bufferReagents, bufferCurrentVolume, chemMaster.PillType, chemMaster.PillDosageLimit, updateLabel);
+ bufferReagents, bufferCurrentVolume, chemMaster.PillType, chemMaster.PillDosageLimit, updateLabel, chemMaster.DrawSource);
_userInterfaceSystem.SetUiState(owner, ChemMasterUiKey.Key, state);
}
@@ -135,6 +136,17 @@ namespace Content.Server.Chemistry.EntitySystems
ClickSound(chemMaster);
}
+ private void OnSetDrawSourceMessage(Entity chemMaster, ref ChemMasterOutputDrawSourceMessage message)
+ {
+ //Ensure draw source is valid, either from the internal buffer or the inserted beaker
+ if (!Enum.IsDefined(message.DrawSource))
+ return;
+
+ chemMaster.Comp.DrawSource = message.DrawSource;
+ UpdateUiState(chemMaster);
+ ClickSound(chemMaster);
+ }
+
private void TransferReagents(Entity chemMaster, ReagentId id, FixedPoint2 amount, bool fromBuffer)
{
var container = _itemSlotsSystem.GetItemOrNull(chemMaster, SharedChemMaster.InputSlotName);
@@ -208,9 +220,9 @@ namespace Content.Server.Chemistry.EntitySystems
return;
var needed = message.Dosage * message.Number;
- if (!WithdrawFromBuffer(chemMaster, needed, user, out var withdrawal))
- return;
+ if (!WithdrawFromSource(chemMaster, needed, user, out var withdrawal))
+ return;
_labelSystem.Label(container, message.Label);
for (var i = 0; i < message.Number; i++)
@@ -219,7 +231,10 @@ namespace Content.Server.Chemistry.EntitySystems
_storageSystem.Insert(container, item, out _, user: user, storage);
_labelSystem.Label(item, message.Label);
- _solutionContainerSystem.EnsureSolutionEntity(item, SharedChemMaster.PillSolutionName,out var itemSolution ,message.Dosage);
+ _solutionContainerSystem.EnsureSolutionEntity(item,
+ SharedChemMaster.PillSolutionName,
+ out var itemSolution,
+ message.Dosage);
if (!itemSolution.HasValue)
return;
@@ -256,7 +271,7 @@ namespace Content.Server.Chemistry.EntitySystems
if (message.Label.Length > SharedChemMaster.LabelMaxLength)
return;
- if (!WithdrawFromBuffer(chemMaster, message.Dosage, user, out var withdrawal))
+ if (!WithdrawFromSource(chemMaster, message.Dosage, user, out var withdrawal))
return;
_labelSystem.Label(container, message.Label);
@@ -270,34 +285,77 @@ namespace Content.Server.Chemistry.EntitySystems
ClickSound(chemMaster);
}
- private bool WithdrawFromBuffer(
+ private bool WithdrawFromSource(
Entity chemMaster,
- FixedPoint2 neededVolume, EntityUid? user,
+ FixedPoint2 neededVolume,
+ EntityUid? user,
[NotNullWhen(returnValue: true)] out Solution? outputSolution)
{
outputSolution = null;
- if (!_solutionContainerSystem.TryGetSolution(chemMaster.Owner, SharedChemMaster.BufferSolutionName, out _, out var solution))
- {
- return false;
- }
+ Solution? solution;
+ Entity? soln = null;
- if (solution.Volume == 0)
+ switch (chemMaster.Comp.DrawSource)
{
- if (user.HasValue)
- _popupSystem.PopupCursor(Loc.GetString("chem-master-window-buffer-empty-text"), user.Value);
- return false;
- }
+ case ChemMasterDrawSource.Internal:
+ if (!_solutionContainerSystem.TryGetSolution(chemMaster.Owner, SharedChemMaster.BufferSolutionName, out _, out solution))
+ return false;
- // ReSharper disable once InvertIf
- if (neededVolume > solution.Volume)
- {
- if (user.HasValue)
- _popupSystem.PopupCursor(Loc.GetString("chem-master-window-buffer-low-text"), user.Value);
- return false;
+ if (solution.Volume == 0)
+ {
+ if (user is { } uid)
+ _popupSystem.PopupCursor(Loc.GetString("chem-master-window-buffer-empty-text"), uid);
+
+ return false;
+ }
+ if (neededVolume > solution.Volume)
+ {
+ if (user is { } uid)
+ _popupSystem.PopupCursor(Loc.GetString("chem-master-window-buffer-low-text"), uid);
+
+ return false;
+ }
+
+ break;
+
+ case ChemMasterDrawSource.External:
+ if (_itemSlotsSystem.GetItemOrNull(chemMaster, SharedChemMaster.InputSlotName) is not {} container)
+ {
+ if (user.HasValue)
+ _popupSystem.PopupCursor(Loc.GetString("chem-master-window-no-beaker-text"), user.Value);
+ return false;
+ }
+
+ if (!_solutionContainerSystem.TryGetFitsInDispenser(container, out soln, out solution))
+ return false;
+
+ if (solution.Volume == 0)
+ {
+ if (user is { } uid)
+ _popupSystem.PopupCursor(Loc.GetString("chem-master-window-beaker-empty-text"), uid);
+
+ return false;
+ }
+ if (neededVolume > solution.Volume)
+ {
+ if (user is { } uid)
+ _popupSystem.PopupCursor(Loc.GetString("chem-master-window-beaker-low-text"), uid);
+
+ return false;
+ }
+
+ break;
+
+ default:
+ return false;
}
outputSolution = solution.SplitSolution(neededVolume);
+
+ if (soln.HasValue)
+ _solutionContainerSystem.UpdateChemicals(soln.Value);
+
return true;
}
diff --git a/Content.Shared/Chemistry/SharedChemMaster.cs b/Content.Shared/Chemistry/SharedChemMaster.cs
index cabee465ea..8a71cc49f6 100644
--- a/Content.Shared/Chemistry/SharedChemMaster.cs
+++ b/Content.Shared/Chemistry/SharedChemMaster.cs
@@ -83,6 +83,12 @@ namespace Content.Shared.Chemistry
}
}
+ [Serializable, NetSerializable]
+ public sealed class ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource drawSource) : BoundUserInterfaceMessage
+ {
+ public readonly ChemMasterDrawSource DrawSource = drawSource;
+ }
+
public enum ChemMasterMode
{
Transfer,
@@ -115,6 +121,12 @@ namespace Content.Shared.Chemistry
All,
}
+ public enum ChemMasterDrawSource
+ {
+ Internal,
+ External,
+ }
+
public static class ChemMasterReagentAmountToFixedPoint
{
public static FixedPoint2 GetFixedPoint(this ChemMasterReagentAmount amount)
@@ -184,10 +196,12 @@ namespace Content.Shared.Chemistry
public readonly bool UpdateLabel;
+ public readonly ChemMasterDrawSource DrawSource;
+
public ChemMasterBoundUserInterfaceState(
ChemMasterMode mode, ChemMasterSortingType sortingType, ContainerInfo? inputContainerInfo, ContainerInfo? outputContainerInfo,
IReadOnlyList bufferReagents, FixedPoint2 bufferCurrentVolume,
- uint selectedPillType, uint pillDosageLimit, bool updateLabel)
+ uint selectedPillType, uint pillDosageLimit, bool updateLabel, ChemMasterDrawSource drawSource)
{
InputContainerInfo = inputContainerInfo;
OutputContainerInfo = outputContainerInfo;
@@ -198,6 +212,7 @@ namespace Content.Shared.Chemistry
SelectedPillType = selectedPillType;
PillDosageLimit = pillDosageLimit;
UpdateLabel = updateLabel;
+ DrawSource = drawSource;
}
}
diff --git a/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl b/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl
index c000811e77..0860e8347b 100644
--- a/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl
+++ b/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl
@@ -33,3 +33,10 @@ chem-master-window-sort-type-none = Sort by: Oldest First
chem-master-window-sort-type-alphabetical = Sort by: Alphabetical
chem-master-window-sort-type-quantity = Sort by: Quantity
chem-master-window-sort-type-latest = Sort by: Recent First
+chem-master-output-buffer-draw = Buffer
+chem-master-output-beaker-draw = Beaker
+chem-master-window-no-beaker-text = No beaker loaded
+chem-master-window-beaker-empty-text = Beaker Empty
+chem-master-window-beaker-low-text = Not enough solution in beaker
+chem-master-output-source = Packaging source:
+chem-master-no-source = No Source
--
2.52.0