diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml b/Content.Client/Kitchen/UI/GrinderMenu.xaml
new file mode 100644
index 0000000000..38fd6ee197
--- /dev/null
+++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
new file mode 100644
index 0000000000..2698dab900
--- /dev/null
+++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
@@ -0,0 +1,131 @@
+using System.Collections.Generic;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Chemistry.Solution;
+using Content.Shared.Kitchen.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Localization;
+using Robust.Shared.Maths;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Kitchen.UI
+{
+ [GenerateTypedNameReferences]
+ public partial class GrinderMenu : SS14Window
+ {
+ private readonly IEntityManager _entityManager;
+ private readonly IPrototypeManager _prototypeManager ;
+ private readonly ReagentGrinderBoundUserInterface _owner;
+
+ private readonly Dictionary _chamberVisualContents = new();
+
+ public GrinderMenu(ReagentGrinderBoundUserInterface owner, IEntityManager entityManager, IPrototypeManager prototypeManager)
+ {
+ RobustXamlLoader.Load(this);
+ _entityManager = entityManager;
+ _prototypeManager = prototypeManager;
+ _owner = owner;
+ GrindButton.OnPressed += owner.StartGrinding;
+ JuiceButton.OnPressed += owner.StartJuicing;
+ ChamberContentBox.EjectButton.OnPressed += owner.EjectAll;
+ BeakerContentBox.EjectButton.OnPressed += owner.EjectBeaker;
+ ChamberContentBox.BoxContents.OnItemSelected += OnChamberBoxContentsItemSelected;
+ BeakerContentBox.BoxContents.SelectMode = ItemList.ItemListSelectMode.None;
+ }
+
+ private void OnChamberBoxContentsItemSelected(ItemList.ItemListSelectedEventArgs args)
+ {
+ _owner.EjectChamberContent(_chamberVisualContents[args.ItemIndex]);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ _chamberVisualContents.Clear();
+ GrindButton.OnPressed -= _owner.StartGrinding;
+ JuiceButton.OnPressed -= _owner.StartJuicing;
+ ChamberContentBox.EjectButton.OnPressed -= _owner.EjectAll;
+ BeakerContentBox.EjectButton.OnPressed -= _owner.EjectBeaker;
+ ChamberContentBox.BoxContents.OnItemSelected -= OnChamberBoxContentsItemSelected;
+ }
+
+ public void UpdateState(ReagentGrinderInterfaceState state)
+ {
+ BeakerContentBox.EjectButton.Disabled = !state.HasBeakerIn;
+ ChamberContentBox.EjectButton.Disabled = state.ChamberContents.Length <= 0;
+ GrindButton.Disabled = !state.CanGrind || !state.Powered;
+ JuiceButton.Disabled = !state.CanJuice || !state.Powered;
+ RefreshContentsDisplay(state.ReagentQuantities, state.ChamberContents, state.HasBeakerIn);
+ }
+
+ public void HandleMessage(BoundUserInterfaceMessage message)
+ {
+ switch (message)
+ {
+ case SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage workStarted:
+ GrindButton.Disabled = true;
+ GrindButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Grind ? Color.Green : Color.White;
+ JuiceButton.Disabled = true;
+ JuiceButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Juice ? Color.Green : Color.White;
+ BeakerContentBox.EjectButton.Disabled = true;
+ ChamberContentBox.EjectButton.Disabled = true;
+ break;
+ case SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage doneMessage:
+ GrindButton.Disabled = false;
+ JuiceButton.Disabled = false;
+ GrindButton.Modulate = Color.White;
+ JuiceButton.Modulate = Color.White;
+ BeakerContentBox.EjectButton.Disabled = false;
+ ChamberContentBox.EjectButton.Disabled = false;
+ break;
+ }
+ }
+
+ private void RefreshContentsDisplay(IList? reagents, IReadOnlyList containedSolids, bool isBeakerAttached)
+ {
+ //Refresh chamber contents
+ _chamberVisualContents.Clear();
+
+ ChamberContentBox.BoxContents.Clear();
+ foreach (var uid in containedSolids)
+ {
+ if (!_entityManager.TryGetEntity(uid, out var entity))
+ {
+ return;
+ }
+ var texture = entity.GetComponent().Icon?.Default;
+
+ var solidItem = ChamberContentBox.BoxContents.AddItem(entity.Name, texture);
+ var solidIndex = ChamberContentBox.BoxContents.IndexOf(solidItem);
+ _chamberVisualContents.Add(solidIndex, uid);
+ }
+
+ //Refresh beaker contents
+ BeakerContentBox.BoxContents.Clear();
+ //if no beaker is attached use this guard to prevent hitting a null reference.
+ if (!isBeakerAttached || reagents == null)
+ {
+ return;
+ }
+
+ //Looks like we have a beaker attached.
+ if (reagents.Count <= 0)
+ {
+ BeakerContentBox.BoxContents.AddItem(Loc.GetString("grinder-menu-beaker-content-box-is-empty"));
+ }
+ else
+ {
+ foreach (var reagent in reagents)
+ {
+ var reagentName = _prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto) ? Loc.GetString($"{reagent.Quantity} {proto.Name}") : "???";
+ BeakerContentBox.BoxContents.AddItem(reagentName);
+ }
+ }
+ }
+ }
+}
diff --git a/Content.Client/Kitchen/UI/LabelledContentBox.xaml b/Content.Client/Kitchen/UI/LabelledContentBox.xaml
new file mode 100644
index 0000000000..d69e52e2a1
--- /dev/null
+++ b/Content.Client/Kitchen/UI/LabelledContentBox.xaml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/Content.Client/Kitchen/UI/LabelledContentBox.xaml.cs b/Content.Client/Kitchen/UI/LabelledContentBox.xaml.cs
new file mode 100644
index 0000000000..b88f8fc4e3
--- /dev/null
+++ b/Content.Client/Kitchen/UI/LabelledContentBox.xaml.cs
@@ -0,0 +1,15 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.Kitchen.UI
+{
+ [GenerateTypedNameReferences]
+ public partial class LabelledContentBox : BoxContainer
+ {
+ public string? LabelText { get => Label.Text; set => Label.Text = value; }
+ public string? ButtonText { get => Button.Text; set => Button.Text = value; }
+
+ public ItemList BoxContents => ItemList;
+ public Button EjectButton => Button;
+ }
+}
diff --git a/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs b/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
index e33cae9e5e..c0398d7e6e 100644
--- a/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
+++ b/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
@@ -1,17 +1,10 @@
-using System.Collections.Generic;
-using Content.Shared.Chemistry.Reagent;
using Content.Shared.Kitchen.Components;
using Robust.Client.GameObjects;
-using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using static Content.Shared.Chemistry.Solution.Solution;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Kitchen.UI
{
@@ -21,8 +14,6 @@ namespace Content.Client.Kitchen.UI
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private GrinderMenu? _menu;
- private readonly Dictionary _chamberVisualContents = new();
- private readonly Dictionary _beakerVisualContents = new();
public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
@@ -30,22 +21,9 @@ namespace Content.Client.Kitchen.UI
{
base.Open();
- _menu = new GrinderMenu(this);
+ _menu = new GrinderMenu(this, _entityManager, _prototypeManager);
_menu.OpenCentered();
_menu.OnClose += Close;
- _menu.GrindButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage());
- _menu.JuiceButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage());
- _menu.ChamberContentBox.EjectButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage());
- _menu.BeakerContentBox.EjectButton.OnPressed += args => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage());
- _menu.ChamberContentBox.BoxContents.OnItemSelected += args =>
- {
- SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage(_chamberVisualContents[args.ItemIndex]));
- };
-
- _menu.BeakerContentBox.BoxContents.OnItemSelected += args =>
- {
- SendMessage(new SharedReagentGrinderComponent.ReagentGrinderVaporizeReagentIndexedMessage(_beakerVisualContents[args.ItemIndex]));
- };
}
protected override void Dispose(bool disposing)
@@ -56,8 +34,6 @@ namespace Content.Client.Kitchen.UI
return;
}
- _chamberVisualContents?.Clear();
- _beakerVisualContents?.Clear();
_menu?.Dispose();
}
@@ -69,243 +45,19 @@ namespace Content.Client.Kitchen.UI
return;
}
- if (_menu == null)
- {
- return;
- }
-
- _menu.BeakerContentBox.EjectButton.Disabled = !cState.HasBeakerIn;
- _menu.ChamberContentBox.EjectButton.Disabled = cState.ChamberContents.Length <= 0;
- _menu.GrindButton.Disabled = !cState.CanGrind || !cState.Powered;
- _menu.JuiceButton.Disabled = !cState.CanJuice || !cState.Powered;
- RefreshContentsDisplay(cState.ReagentQuantities, cState.ChamberContents, cState.HasBeakerIn);
+ _menu?.UpdateState(cState);
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
base.ReceiveMessage(message);
-
- if (_menu == null)
- {
- return;
- }
-
- switch (message)
- {
- case SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage workStarted:
- _menu.GrindButton.Disabled = true;
- _menu.GrindButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Grind ? Color.Green : Color.White;
- _menu.JuiceButton.Disabled = true;
- _menu.JuiceButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Juice ? Color.Green : Color.White;
- _menu.BeakerContentBox.EjectButton.Disabled = true;
- _menu.ChamberContentBox.EjectButton.Disabled = true;
- break;
- case SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage doneMessage:
- _menu.GrindButton.Disabled = false;
- _menu.JuiceButton.Disabled = false;
- _menu.GrindButton.Modulate = Color.White;
- _menu.JuiceButton.Modulate = Color.White;
- _menu.BeakerContentBox.EjectButton.Disabled = false;
- _menu.ChamberContentBox.EjectButton.Disabled = false;
- break;
- }
+ _menu?.HandleMessage(message);
}
- private void RefreshContentsDisplay(IList? reagents, IReadOnlyList containedSolids, bool isBeakerAttached)
- {
- //Refresh chamber contents
- _chamberVisualContents.Clear();
-
- if (_menu == null)
- {
- return;
- }
-
- _menu.ChamberContentBox.BoxContents.Clear();
- foreach (var uid in containedSolids)
- {
- if (!_entityManager.TryGetEntity(uid, out var entity))
- {
- return;
- }
- var texture = entity.GetComponent().Icon?.Default;
-
- var solidItem = _menu.ChamberContentBox.BoxContents.AddItem(entity.Name, texture);
- var solidIndex = _menu.ChamberContentBox.BoxContents.IndexOf(solidItem);
- _chamberVisualContents.Add(solidIndex, uid);
- }
-
- //Refresh beaker contents
- _beakerVisualContents.Clear();
- _menu.BeakerContentBox.BoxContents.Clear();
- //if no beaker is attached use this guard to prevent hitting a null reference.
- if (!isBeakerAttached || reagents == null)
- {
- return;
- }
-
- //Looks like we have a beaker attached.
- if (reagents.Count <= 0)
- {
- _menu.BeakerContentBox.BoxContents.AddItem(Loc.GetString("grinder-menu-beaker-content-box-is-empty"));
- }
- else
- {
- for (var i = 0; i < reagents.Count; i++)
- {
- var goodIndex = _prototypeManager.TryIndex(reagents[i].ReagentId, out ReagentPrototype? proto);
- var reagentName = goodIndex ? Loc.GetString($"{reagents[i].Quantity} {proto!.Name}") : "???";
- var reagentAdded = _menu.BeakerContentBox.BoxContents.AddItem(reagentName);
- var reagentIndex = _menu.BeakerContentBox.BoxContents.IndexOf(reagentAdded);
- _beakerVisualContents.Add(reagentIndex, reagents[i]);
- }
- }
- }
-
- public class GrinderMenu : SS14Window
- {
- /*The contents of the chamber and beaker will both be vertical scroll rectangles.
- * Will have a vsplit to split the g/j buttons from the contents menu.
- * |--------------------------------\
- * | | Chamber [E] Beaker [E] |
- * | [G] | | | | | |
- * | | | | | | |
- * | | | | | | |
- * | [J] | |-----| |-----| |
- * | | |
- * \---------------------------------/
- *
- */
-
- private ReagentGrinderBoundUserInterface Owner { get; set; }
-
- //We'll need 4 buttons, grind, juice, eject beaker, eject the chamber contents.
- //The other 2 are referenced in the Open function.
- public Button GrindButton { get; }
- public Button JuiceButton { get; }
-
- public LabelledContentBox ChamberContentBox { get; }
- public LabelledContentBox BeakerContentBox { get; }
-
- public sealed class LabelledContentBox : VBoxContainer
- {
- public string? LabelText { get; set; }
-
- public ItemList BoxContents { get; set; }
-
- public Button EjectButton { get; set; }
-
- private Label _label;
-
- public LabelledContentBox(string labelText, string buttonText)
- {
- _label = new Label
- {
- Text = labelText,
- Align = Label.AlignMode.Center,
- };
-
- EjectButton = new Button
- {
- Text = buttonText,
- TextAlign = Label.AlignMode.Center,
- };
-
- var vSplit = new HSplitContainer
- {
- Children =
- {
- _label,
- EjectButton
- }
- };
-
- AddChild(vSplit);
- BoxContents = new ItemList
- {
- VerticalExpand = true,
- HorizontalExpand = true,
- SelectMode = ItemList.ItemListSelectMode.Button,
- SizeFlagsStretchRatio = 2,
- MinSize = (100, 128)
- };
- AddChild(BoxContents);
- }
- }
-
- public GrinderMenu(ReagentGrinderBoundUserInterface owner)
- {
- SetSize = MinSize = (512, 256);
- Owner = owner;
- Title = Loc.GetString("grinder-menu-title");
-
- var hSplit = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal
- };
-
- var vBoxGrindJuiceButtonPanel = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- VerticalAlignment = VAlignment.Center
- };
-
- GrindButton = new Button
- {
- Text = Loc.GetString("grinder-menu-grind-button"),
- TextAlign = Label.AlignMode.Center,
- MinSize = (64, 64)
- };
-
- JuiceButton = new Button
- {
- Text = Loc.GetString("grinder-menu-juice-button"),
- TextAlign = Label.AlignMode.Center,
- MinSize = (64, 64)
- };
-
- vBoxGrindJuiceButtonPanel.AddChild(GrindButton);
- //inner button padding
- vBoxGrindJuiceButtonPanel.AddChild(new Control
- {
- MinSize = (0, 16),
- });
- vBoxGrindJuiceButtonPanel.AddChild(JuiceButton);
-
- ChamberContentBox = new LabelledContentBox(Loc.GetString("grinder-menu-chamber-content-box-label"), Loc.GetString("grinder-menu-chamber-content-box-button"))
- {
- //Modulate = Color.Red,
- VerticalExpand = true,
- HorizontalExpand = true,
- SizeFlagsStretchRatio = 2,
-
- };
-
- BeakerContentBox = new LabelledContentBox(Loc.GetString("grinder-menu-beaker-content-box-label"), Loc.GetString("grinder-menu-beaker-content-box-button"))
- {
- //Modulate = Color.Blue,
- VerticalExpand = true,
- HorizontalExpand = true,
- SizeFlagsStretchRatio = 2,
- };
-
- hSplit.AddChild(vBoxGrindJuiceButtonPanel);
-
- //Padding between the g/j buttons panel and the itemlist boxes panel.
- hSplit.AddChild(new Control
- {
- MinSize = (16, 0),
- });
- hSplit.AddChild(ChamberContentBox);
-
- //Padding between the two itemlists.
- hSplit.AddChild(new Control
- {
- MinSize = (8, 0),
- });
- hSplit.AddChild(BeakerContentBox);
- Contents.AddChild(hSplit);
- }
- }
+ public void StartGrinding(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage());
+ public void StartJuicing(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage());
+ public void EjectAll(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage());
+ public void EjectBeaker(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage());
+ public void EjectChamberContent(EntityUid uid) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage(uid));
}
}
diff --git a/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs b/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs
index 5811d7f650..d277d53d09 100644
--- a/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs
+++ b/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs
@@ -1,23 +1,8 @@
-using System;
-using System.Linq;
-using System.Threading.Tasks;
using Content.Server.Chemistry.Components;
-using Content.Server.Hands.Components;
-using Content.Server.Items;
-using Content.Server.Power.Components;
-using Content.Server.UserInterface;
-using Content.Shared.Chemistry.Solution;
using Content.Shared.Interaction;
using Content.Shared.Kitchen.Components;
-using Content.Shared.Notification.Managers;
-using Content.Shared.Random.Helpers;
-using Content.Shared.Tag;
-using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
-using Robust.Shared.Localization;
-using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
@@ -31,337 +16,27 @@ namespace Content.Server.Kitchen.Components
/// it contained, juice an apple and get "apple juice".
///
[RegisterComponent]
- [ComponentReference(typeof(IActivate))]
- public class ReagentGrinderComponent : SharedReagentGrinderComponent, IActivate, IInteractUsing
+ public class ReagentGrinderComponent : SharedReagentGrinderComponent
{
- private AudioSystem _audioSystem = default!;
- [ViewVariables] private ContainerSlot _beakerContainer = default!;
+ [ViewVariables] public ContainerSlot BeakerContainer = default!;
///
/// Can be null since we won't always have a beaker in the grinder.
///
- [ViewVariables] private SolutionContainerComponent? _heldBeaker = default!;
+ [ViewVariables] public SolutionContainerComponent? HeldBeaker = default!;
///
/// Contains the things that are going to be ground or juiced.
///
- [ViewVariables] private Container _chamber = default!;
+ [ViewVariables] public Container Chamber = default!;
- [ViewVariables] private bool ChamberEmpty => _chamber.ContainedEntities.Count <= 0;
- [ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
- [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ReagentGrinderUiKey.Key);
-
- private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered;
-
- ///
- /// Should the BoundUI be told to update?
- ///
- private bool _uiDirty = true;
///
/// Is the machine actively doing something and can't be used right now?
///
- private bool _busy = false;
+ public bool Busy;
//YAML serialization vars
- [ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] private int _storageCap = 16;
- [ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] private int _workTime = 3500; //3.5 seconds, completely arbitrary for now.
-
- protected override void Initialize()
- {
- base.Initialize();
- //A slot for the beaker where the grounds/juices will go.
- _beakerContainer =
- ContainerHelpers.EnsureContainer(Owner, $"{Name}-reagentContainerContainer");
-
- //A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user.
- _chamber =
- ContainerHelpers.EnsureContainer(Owner, $"{Name}-entityContainerContainer");
-
- if (UserInterface != null)
- {
- UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
- }
-
- _audioSystem = EntitySystem.Get();
- }
-
- public override void HandleMessage(ComponentMessage message, IComponent? component)
- {
- base.HandleMessage(message, component);
- switch (message)
- {
- case PowerChangedMessage powerChanged:
- OnPowerStateChanged(powerChanged);
- break;
- }
- }
-
- protected override void OnRemove()
- {
- base.OnRemove();
- if (UserInterface != null)
- {
- UserInterface.OnReceiveMessage -= UserInterfaceOnReceiveMessage;
- }
- }
-
- private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage message)
- {
- if(_busy)
- {
- return;
- }
-
- switch(message.Message)
- {
- case ReagentGrinderGrindStartMessage msg:
- if (!Powered) break;
- ClickSound();
- DoWork(message.Session.AttachedEntity!, GrinderProgram.Grind);
- break;
-
- case ReagentGrinderJuiceStartMessage msg:
- if (!Powered) break;
- ClickSound();
- DoWork(message.Session.AttachedEntity!, GrinderProgram.Juice);
- break;
-
- case ReagentGrinderEjectChamberAllMessage msg:
- if(!ChamberEmpty)
- {
- ClickSound();
- for (var i = _chamber.ContainedEntities.Count - 1; i >= 0; i--)
- {
- EjectSolid(_chamber.ContainedEntities.ElementAt(i).Uid);
- }
- }
- break;
-
- case ReagentGrinderEjectChamberContentMessage msg:
- if (!ChamberEmpty)
- {
- EjectSolid(msg.EntityID);
- ClickSound();
- _uiDirty = true;
- }
- break;
-
- case ReagentGrinderEjectBeakerMessage msg:
- ClickSound();
- EjectBeaker(message.Session.AttachedEntity);
- //EjectBeaker will dirty the UI for us, we don't have to do it explicitly here.
- break;
- }
- }
-
- private void OnPowerStateChanged(PowerChangedMessage e)
- {
- _uiDirty = true;
- }
-
- private void ClickSound()
- {
- SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
- }
-
- private void SetAppearance()
- {
- if (Owner.TryGetComponent(out AppearanceComponent? appearance))
- {
- appearance.SetData(ReagentGrinderVisualState.BeakerAttached, HasBeaker);
- }
- }
-
- public void OnUpdate()
- {
- if(_uiDirty)
- {
- UpdateInterface();
- _uiDirty = false;
- }
- }
-
- // This doesn't check for UI dirtiness so handle that when calling this.
- private void UpdateInterface()
- {
- bool canJuice = false;
- bool canGrind = false;
- if (HasBeaker)
- {
- foreach (var entity in _chamber.ContainedEntities)
- {
- if (!canJuice && entity.HasComponent()) canJuice = true;
- if (!canGrind && entity.HasTag("Grindable")) canGrind = true;
- if (canJuice && canGrind) break;
- }
- }
-
- UserInterface?.SetState(new ReagentGrinderInterfaceState
- (
- _busy,
- HasBeaker,
- Powered,
- canJuice,
- canGrind,
- _chamber.ContainedEntities.Select(item => item.Uid).ToArray(),
- //Remember the beaker can be null!
- _heldBeaker?.Solution.Contents.ToArray()
- ));
- _uiDirty = false;
- }
-
- private void EjectSolid(EntityUid entityID)
- {
- if (_busy)
- return;
-
- if (Owner.EntityManager.TryGetEntity(entityID, out var entity))
- {
- _chamber.Remove(entity);
-
- //Give the ejected entity a tiny bit of offset so each one is apparent in case of a big stack,
- //but (hopefully) not enough to clip it through a solid (wall).
- entity.RandomOffset(0.4f);
- }
- _uiDirty = true;
- }
-
- ///
- /// Tries to eject whatever is in the beaker slot. Puts the item in the user's hands or failing that on top
- /// of the grinder.
- ///
- private void EjectBeaker(IEntity? user)
- {
- if (!HasBeaker || _heldBeaker == null || _busy)
- return;
-
- var beaker = _beakerContainer.ContainedEntity;
- if(beaker is null)
- return;
-
- _beakerContainer.Remove(beaker);
-
- if (user == null || !user.TryGetComponent(out var hands) || !_heldBeaker.Owner.TryGetComponent(out var item))
- return;
- hands.PutInHandOrDrop(item);
-
- _heldBeaker = null;
- _uiDirty = true;
- SetAppearance();
- }
-
- void IActivate.Activate(ActivateEventArgs eventArgs)
- {
- if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
- {
- return;
- }
- _uiDirty = true;
- UserInterface?.Toggle(actor.PlayerSession);
- }
-
- async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
- {
- if (!eventArgs.User.TryGetComponent(out IHandsComponent? hands))
- {
- Owner.PopupMessage(eventArgs.User, Loc.GetString("reagent-grinder-component-interact-using-no-hands"));
- return true;
- }
-
- IEntity heldEnt = eventArgs.Using;
-
- //First, check if user is trying to insert a beaker.
- //No promise it will be a beaker right now, but whatever.
- //Maybe this should whitelist "beaker" in the prototype id of heldEnt?
- if(heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser))
- {
- _beakerContainer.Insert(heldEnt);
- _heldBeaker = beaker;
- _uiDirty = true;
- //We are done, return. Insert the beaker and exit!
- SetAppearance();
- ClickSound();
- return true;
- }
-
- //Next, see if the user is trying to insert something they want to be ground/juiced.
- if(!heldEnt.HasTag("Grindable") && !heldEnt.TryGetComponent(out JuiceableComponent? juice))
- {
- //Entity did NOT pass the whitelist for grind/juice.
- //Wouldn't want the clown grinding up the Captain's ID card now would you?
- //Why am I asking you? You're biased.
- return false;
- }
-
- //Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once.
- //Maybe I should have done that for the microwave too?
- if (_chamber.ContainedEntities.Count >= _storageCap)
- {
- return false;
- }
-
- if (!_chamber.Insert(heldEnt))
- return false;
-
- _uiDirty = true;
- return true;
- }
-
- ///
- /// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
- ///
- /// true for wanting to juice, false for wanting to grind.
- private async void DoWork(IEntity user, GrinderProgram program)
- {
- //Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go?
- if(!Powered || _busy || ChamberEmpty || !HasBeaker || _heldBeaker == null)
- {
- return;
- }
-
- _busy = true;
-
- UserInterface?.SendMessage(new ReagentGrinderWorkStartedMessage(program));
- switch (program)
- {
- case GrinderProgram.Grind:
- SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/blender.ogg", Owner, AudioParams.Default);
- //Get each item inside the chamber and get the reagents it contains. Transfer those reagents to the beaker, given we have one in.
- Owner.SpawnTimer(_workTime, (Action) (() =>
- {
- foreach (var item in _chamber.ContainedEntities.ToList())
- {
- if (!item.HasTag("Grindable")) continue;
- if (!item.TryGetComponent(out var solution)) continue;
- if (_heldBeaker.CurrentVolume + solution.CurrentVolume > _heldBeaker.MaxVolume) continue;
- _heldBeaker.TryAddSolution(solution.Solution);
- solution.RemoveAllSolution();
- item.Delete();
- }
-
- _busy = false;
- _uiDirty = true;
- UserInterface?.SendMessage(new ReagentGrinderWorkCompleteMessage());
- }));
- break;
-
- case GrinderProgram.Juice:
- SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/juicer.ogg", Owner, AudioParams.Default);
- Owner.SpawnTimer(_workTime, (Action) (() =>
- {
- foreach (var item in _chamber.ContainedEntities.ToList())
- {
- if (!item.TryGetComponent(out var juiceMe)) continue;
- if (_heldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume > _heldBeaker.MaxVolume) continue;
- _heldBeaker.TryAddSolution(juiceMe.JuiceResultSolution);
- item.Delete();
- }
- UserInterface?.SendMessage(new ReagentGrinderWorkCompleteMessage());
- _busy = false;
- _uiDirty = true;
- }));
- break;
- }
- }
+ [ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] public int StorageCap = 16;
+ [ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] public int WorkTime = 3500; //3.5 seconds, completely arbitrary for now.
}
}
diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs
index 95f3dbe5f8..c58c5b32ee 100644
--- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs
+++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs
@@ -1,19 +1,314 @@
-using Content.Server.Kitchen.Components;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Content.Server.Chemistry.Components;
+using Content.Server.Hands.Components;
+using Content.Server.Items;
+using Content.Server.Kitchen.Components;
+using Content.Server.Power.Components;
+using Content.Server.UserInterface;
+using Content.Shared.Chemistry.Solution;
+using Content.Shared.Interaction;
+using Content.Shared.Kitchen.Components;
+using Content.Shared.Notification.Managers;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Tag;
using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+using Robust.Shared.Audio;
+using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+using Robust.Shared.Player;
+using Robust.Shared.Utility;
namespace Content.Server.Kitchen.EntitySystems
{
[UsedImplicitly]
internal sealed class ReagentGrinderSystem : EntitySystem
{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+
+ private Queue _uiUpdateQueue = new ();
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnComponentInit);
+ SubscribeLocalEvent((_, component, _) => EnqueueUiUpdate(component));
+ SubscribeLocalEvent(OnInteractHand);
+ SubscribeLocalEvent(OnInteractUsing);
+ }
+
+ private void OnInteractUsing(EntityUid uid, ReagentGrinderComponent component, InteractUsingEvent args)
+ {
+ if(args.Handled) return;
+
+ if (!args.User.TryGetComponent(out IHandsComponent? hands))
+ {
+ component.Owner.PopupMessage(args.User, Loc.GetString("reagent-grinder-component-interact-using-no-hands"));
+ args.Handled = true;
+ return;
+ }
+
+ IEntity heldEnt = args.Used;
+
+ //First, check if user is trying to insert a beaker.
+ //No promise it will be a beaker right now, but whatever.
+ //Maybe this should whitelist "beaker" in the prototype id of heldEnt?
+ if(heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser))
+ {
+ component.BeakerContainer.Insert(heldEnt);
+ component.HeldBeaker = beaker;
+ EnqueueUiUpdate(component);
+ //We are done, return. Insert the beaker and exit!
+ if (component.Owner.TryGetComponent(out AppearanceComponent? appearance))
+ {
+ appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached, component.BeakerContainer.ContainedEntity != null);
+ }
+ ClickSound(component);
+ args.Handled = true;
+ return;
+ }
+
+ //Next, see if the user is trying to insert something they want to be ground/juiced.
+ if(!heldEnt.HasTag("Grindable") && !heldEnt.TryGetComponent(out JuiceableComponent? juice))
+ {
+ //Entity did NOT pass the whitelist for grind/juice.
+ //Wouldn't want the clown grinding up the Captain's ID card now would you?
+ //Why am I asking you? You're biased.
+ return;
+ }
+
+ //Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once.
+ //Maybe I should have done that for the microwave too?
+ if (component.Chamber.ContainedEntities.Count >= component.StorageCap)
+ {
+ return;
+ }
+
+ if (!component.Chamber.Insert(heldEnt))
+ {
+ return;
+ }
+
+ EnqueueUiUpdate(component);
+ args.Handled = true;
+ }
+
+ private void OnInteractHand(EntityUid uid, ReagentGrinderComponent component, InteractHandEvent args)
+ {
+ if (args.Handled) return;
+
+ if (!args.User.TryGetComponent(out ActorComponent? actor))
+ {
+ return;
+ }
+ EnqueueUiUpdate(component);
+ component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.Toggle(actor.PlayerSession);
+ args.Handled = true;
+ }
+
+ private void EnqueueUiUpdate(ReagentGrinderComponent component)
+ {
+ if(!_uiUpdateQueue.Contains(component)) _uiUpdateQueue.Enqueue(component);
+ }
+
+ private void OnComponentInit(EntityUid uid, ReagentGrinderComponent component, ComponentInit args)
+ {
+ EnqueueUiUpdate(component);
+
+ //A slot for the beaker where the grounds/juices will go.
+ component.BeakerContainer =
+ ContainerHelpers.EnsureContainer(component.Owner, $"{component.Name}-reagentContainerContainer");
+
+ //A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user.
+ component.Chamber =
+ ContainerHelpers.EnsureContainer(component.Owner, $"{component.Name}-entityContainerContainer");
+
+ var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key);
+ if (bui != null)
+ {
+ bui.OnReceiveMessage += msg => OnUIMessageReceived(uid, component, msg);
+ }
+ }
+
+ private void OnUIMessageReceived(EntityUid uid, ReagentGrinderComponent component,
+ ServerBoundUserInterfaceMessage message)
+ {
+ if(component.Busy)
+ {
+ return;
+ }
+
+ switch(message.Message)
+ {
+ case SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage msg:
+ if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered) break;
+ ClickSound(component);
+ DoWork(component, message.Session.AttachedEntity!, SharedReagentGrinderComponent.GrinderProgram.Grind);
+ break;
+
+ case SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage msg:
+ if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver2) || !receiver2.Powered) break;
+ ClickSound(component);
+ DoWork(component, message.Session.AttachedEntity!, SharedReagentGrinderComponent.GrinderProgram.Juice);
+ break;
+
+ case SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage msg:
+ if(component.Chamber.ContainedEntities.Count > 0)
+ {
+ ClickSound(component);
+ for (var i = component.Chamber.ContainedEntities.Count - 1; i >= 0; i--)
+ {
+ var entity = component.Chamber.ContainedEntities[i];
+ component.Chamber.Remove(entity);
+ entity.RandomOffset(0.4f);
+ }
+ EnqueueUiUpdate(component);
+ }
+ break;
+
+ case SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage msg:
+ if (component.Chamber.ContainedEntities.TryFirstOrDefault(x => x.Uid == msg.EntityID, out var ent))
+ {
+ component.Chamber.Remove(ent);
+ ent.RandomOffset(0.4f);
+ EnqueueUiUpdate(component);
+ ClickSound(component);
+ }
+ break;
+
+ case SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage msg:
+ ClickSound(component);
+ EjectBeaker(component, message.Session.AttachedEntity);
+ EnqueueUiUpdate(component);
+ break;
+ }
+ }
+
public override void Update(float frameTime)
{
base.Update(frameTime);
- foreach (var comp in ComponentManager.EntityQuery(true))
+
+ while (_uiUpdateQueue.TryDequeue(out var comp))
{
- comp.OnUpdate();
+ bool canJuice = false;
+ bool canGrind = false;
+ if (comp.BeakerContainer.ContainedEntity != null)
+ {
+ foreach (var entity in comp.Chamber.ContainedEntities)
+ {
+ if (!canJuice && entity.HasComponent()) canJuice = true;
+ if (!canGrind && entity.HasTag("Grindable")) canGrind = true;
+ if (canJuice && canGrind) break;
+ }
+ }
+
+ comp.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.SetState(new ReagentGrinderInterfaceState
+ (
+ comp.Busy,
+ comp.BeakerContainer.ContainedEntity != null,
+ comp.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) && receiver.Powered,
+ canJuice,
+ canGrind,
+ comp.Chamber.ContainedEntities.Select(item => item.Uid).ToArray(),
+ //Remember the beaker can be null!
+ comp.HeldBeaker?.Solution.Contents.ToArray()
+ ));
}
}
+
+ ///
+ /// Tries to eject whatever is in the beaker slot. Puts the item in the user's hands or failing that on top
+ /// of the grinder.
+ ///
+ private void EjectBeaker(ReagentGrinderComponent component, IEntity? user)
+ {
+ if (component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null || component.Busy)
+ return;
+
+ var beaker = component.BeakerContainer.ContainedEntity;
+ if(beaker is null)
+ return;
+
+ component.BeakerContainer.Remove(beaker);
+
+ if (user == null || !user.TryGetComponent(out var hands) || !component.HeldBeaker.Owner.TryGetComponent(out var item))
+ return;
+ hands.PutInHandOrDrop(item);
+
+ component.HeldBeaker = null;
+ EnqueueUiUpdate(component);
+ if (component.Owner.TryGetComponent(out AppearanceComponent? appearance))
+ {
+ appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached, component.BeakerContainer.ContainedEntity != null);
+ }
+ }
+
+ ///
+ /// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
+ ///
+ /// true for wanting to juice, false for wanting to grind.
+ private void DoWork(ReagentGrinderComponent component, IEntity user, SharedReagentGrinderComponent.GrinderProgram program)
+ {
+ //Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go?
+ if(!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered || component.Busy || component.Chamber.ContainedEntities.Count <= 0 || component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null)
+ {
+ return;
+ }
+
+ component.Busy = true;
+
+ var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key);
+ bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage(program));
+ switch (program)
+ {
+ case SharedReagentGrinderComponent.GrinderProgram.Grind:
+ SoundSystem.Play(Filter.Pvs(component.Owner), "/Audio/Machines/blender.ogg", component.Owner, AudioParams.Default);
+ //Get each item inside the chamber and get the reagents it contains. Transfer those reagents to the beaker, given we have one in.
+ component.Owner.SpawnTimer(component.WorkTime, (Action) (() =>
+ {
+ foreach (var item in component.Chamber.ContainedEntities.ToList())
+ {
+ if (!item.HasTag("Grindable")) continue;
+ if (!item.TryGetComponent(out var solution)) continue;
+ if (component.HeldBeaker.CurrentVolume + solution.CurrentVolume > component.HeldBeaker.MaxVolume) continue;
+ component.HeldBeaker.TryAddSolution(solution.Solution);
+ solution.RemoveAllSolution();
+ item.Delete();
+ }
+
+ component.Busy = false;
+ EnqueueUiUpdate(component);
+ bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage());
+ }));
+ break;
+
+ case SharedReagentGrinderComponent.GrinderProgram.Juice:
+ SoundSystem.Play(Filter.Pvs(component.Owner), "/Audio/Machines/juicer.ogg", component.Owner, AudioParams.Default);
+ component.Owner.SpawnTimer(component.WorkTime, (Action) (() =>
+ {
+ foreach (var item in component.Chamber.ContainedEntities.ToList())
+ {
+ if (!item.TryGetComponent(out var juiceMe)) continue;
+ if (component.HeldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume > component.HeldBeaker.MaxVolume) continue;
+ component.HeldBeaker.TryAddSolution(juiceMe.JuiceResultSolution);
+ item.Delete();
+ }
+ bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage());
+ component.Busy = false;
+ EnqueueUiUpdate(component);
+ }));
+ break;
+ }
+ }
+
+ private void ClickSound(ReagentGrinderComponent component)
+ {
+ SoundSystem.Play(Filter.Pvs(component.Owner), "/Audio/Machines/machine_switch.ogg", component.Owner, AudioParams.Default.WithVolume(-2f));
+ }
}
}
diff --git a/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs b/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs
index ca1eae6923..0effbe15db 100644
--- a/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs
+++ b/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs
@@ -51,16 +51,6 @@ namespace Content.Shared.Kitchen.Components
}
}
- [Serializable, NetSerializable]
- public class ReagentGrinderVaporizeReagentIndexedMessage : BoundUserInterfaceMessage
- {
- public Solution.ReagentQuantity ReagentQuantity;
- public ReagentGrinderVaporizeReagentIndexedMessage(Solution.ReagentQuantity reagentQuantity)
- {
- ReagentQuantity = reagentQuantity;
- }
- }
-
[Serializable, NetSerializable]
public class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage
{