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>
This commit is contained in:
OnyxTheBrave
2025-12-30 15:34:54 -06:00
committed by GitHub
parent d61ecd3d50
commit 3cc79c223a
7 changed files with 160 additions and 34 deletions

View File

@@ -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++)
{

View File

@@ -79,10 +79,13 @@
<!-- Packaging -->
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'chem-master-window-packaging-text'}" />
<Label Text="{Loc 'chem-master-output-source'}" StyleClasses="LabelSecondaryColor" Margin="0 0 5 0"/>
<Button MinSize="80 0" Name="OutputBufferDraw" Access="Public" Text="{Loc 'chem-master-output-buffer-draw'}" ToggleMode="True" StyleClasses="OpenRight" />
<Button MinSize="80 0" Name="OutputBeakerDraw" Access="Public" Text="{Loc 'chem-master-output-beaker-draw'}" ToggleMode="True" StyleClasses="OpenLeft" />
<Control HorizontalExpand="True"/>
<Label Text="{Loc 'chem-master-window-buffer-label'}" />
<Label Name="BufferCurrentVolume" StyleClasses="LabelWeak" />
<!-- Output Draw Source -->
<Label Name="DrawSource"/>
<Label Name="BufferCurrentVolume" StyleClasses="LabelSecondaryColor" />
</BoxContainer>
<!-- Wrap the packaging info-->

View File

@@ -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);
}
/// <summary>
/// Generate a product label based on reagents in the buffer.
/// Generate a product label based on reagents in the buffer or beaker.
/// </summary>
/// <param name="state">State data sent by the server.</param>
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

View File

@@ -26,5 +26,11 @@ namespace Content.Server.Chemistry.Components
[DataField("clickSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
/// <summary>
/// Which source the chem master should draw from when making pills/bottles.
/// </summary>
[DataField]
public ChemMasterDrawSource DrawSource = ChemMasterDrawSource.Internal;
}
}

View File

@@ -57,6 +57,7 @@ namespace Content.Server.Chemistry.EntitySystems
SubscribeLocalEvent<ChemMasterComponent, ChemMasterReagentAmountButtonMessage>(OnReagentButtonMessage);
SubscribeLocalEvent<ChemMasterComponent, ChemMasterCreatePillsMessage>(OnCreatePillsMessage);
SubscribeLocalEvent<ChemMasterComponent, ChemMasterOutputToBottleMessage>(OnOutputToBottleMessage);
SubscribeLocalEvent<ChemMasterComponent, ChemMasterOutputDrawSourceMessage>(OnSetDrawSourceMessage);
}
private void SubscribeUpdateUiState<T>(Entity<ChemMasterComponent> 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<ChemMasterComponent> 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<ChemMasterComponent> 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<ChemMasterComponent> 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<SolutionComponent>? 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;
}

View File

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

View File

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