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 BarryNorfolk
parent 4e75d99f8f
commit 6f9c3bedcb
7 changed files with 161 additions and 46 deletions

View File

@ -44,6 +44,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

@ -83,10 +83,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-container-label'}" /> <!-- DeltaV-->
<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.InputContainerInfo?.CurrentVolume.Int() ?? 0}u"; // DeltaV
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 ?? "";
}
@ -230,6 +255,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())
{
@ -411,6 +438,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

@ -27,5 +27,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)
@ -76,8 +77,8 @@ namespace Content.Server.Chemistry.EntitySystems
var bufferCurrentVolume = bufferSolution.Volume;
var state = new ChemMasterBoundUserInterfaceState(
chemMaster.SortingType, BuildInputContainerInfo(inputContainer), BuildOutputContainerInfo(outputContainer), // DeltaV - removed mode
bufferReagents, bufferCurrentVolume, chemMaster.PillType, chemMaster.PillDosageLimit, updateLabel);
/*chemMaster.Mode,*/ chemMaster.SortingType, BuildInputContainerInfo(inputContainer), BuildOutputContainerInfo(outputContainer), // DeltaV - Removed discarding
bufferReagents, bufferCurrentVolume, chemMaster.PillType, chemMaster.PillDosageLimit, updateLabel, chemMaster.DrawSource);
_userInterfaceSystem.SetUiState(owner, ChemMasterUiKey.Key, state);
}
@ -124,6 +125,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);
@ -173,9 +185,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++)
@ -184,7 +196,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;
@ -221,7 +236,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);
@ -235,45 +250,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;
/* DeltaV - use beaker slot instead of buffer
if (!_solutionContainerSystem.TryGetSolution(chemMaster.Owner, SharedChemMaster.BufferSolutionName, out _, out var solution))
{
return false;
}
*/
// Begin DeltaV Additions - Use beaker slot instead of buffer
var container = _itemSlotsSystem.GetItemOrNull(chemMaster, SharedChemMaster.InputSlotName);
if (container is null ||
!_solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSoln, out var solution))
{
return false;
}
// End DeltaV Additions
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);
_solutionContainerSystem.UpdateChemicals(containerSoln.Value); // DeltaV
if (soln.HasValue)
_solutionContainerSystem.UpdateChemicals(soln.Value);
return true;
}

View File

@ -72,6 +72,12 @@ namespace Content.Shared.Chemistry
}
}
[Serializable, NetSerializable]
public sealed class ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource drawSource) : BoundUserInterfaceMessage
{
public readonly ChemMasterDrawSource DrawSource = drawSource;
}
/* DeltaV - removed discarding
public enum ChemMasterMode
{
@ -105,6 +111,12 @@ namespace Content.Shared.Chemistry
All,
}
public enum ChemMasterDrawSource
{
Internal,
External,
}
public static class ChemMasterReagentAmountToFixedPoint
{
public static FixedPoint2 GetFixedPoint(this ChemMasterReagentAmount amount)
@ -174,11 +186,13 @@ namespace Content.Shared.Chemistry
public readonly bool UpdateLabel;
public readonly ChemMasterDrawSource DrawSource;
public ChemMasterBoundUserInterfaceState(
// DeltaV - removed discarding
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;
@ -189,6 +203,7 @@ namespace Content.Shared.Chemistry
SelectedPillType = selectedPillType;
PillDosageLimit = pillDosageLimit;
UpdateLabel = updateLabel;
DrawSource = drawSource;
}
}

View File

@ -31,3 +31,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