reagentgrinder ecs & xamlui (#4347)
* started converting grinder to ecs * finished reagentcontainer ecs refactored bui converted ui to xaml * adds handling * fixes handling * oopsie Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
This commit is contained in:
15
Content.Client/Kitchen/UI/GrinderMenu.xaml
Normal file
15
Content.Client/Kitchen/UI/GrinderMenu.xaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<SS14Window xmlns="https://spacestation14.io"
|
||||||
|
xmlns:ui="clr-namespace:Content.Client.Kitchen.UI"
|
||||||
|
Title="{Loc grinder-menu-title}" MinSize="512 256" SetSize="512 256">
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<BoxContainer Orientation="Vertical" VerticalAlignment="Center">
|
||||||
|
<Button Name="GrindButton" Text="{Loc grinder-menu-grind-button}" TextAlign="Center" MinSize="64 64"/>
|
||||||
|
<Control MinSize="0 16"/>
|
||||||
|
<Button Name="JuiceButton" Text="{Loc grinder-menu-juice-button}" TextAlign="Center" MinSize="64 64"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<Control MinSize="16 0"/>
|
||||||
|
<ui:LabelledContentBox Name="ChamberContentBox" LabelText="{Loc grinder-menu-chamber-content-box-label}" ButtonText="{Loc grinder-menu-chamber-content-box-button}" VerticalExpand="True" HorizontalExpand="True" SizeFlagsStretchRatio="2"/>
|
||||||
|
<Control MinSize="8 0"/>
|
||||||
|
<ui:LabelledContentBox Name="BeakerContentBox" LabelText="{Loc grinder-menu-beaker-content-box-label}" ButtonText="{Loc grinder-menu-beaker-content-box-button}" VerticalExpand="True" HorizontalExpand="True" SizeFlagsStretchRatio="2"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</SS14Window>
|
||||||
131
Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
Normal file
131
Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
Normal file
@@ -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<int, EntityUid> _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<Solution.ReagentQuantity>? reagents, IReadOnlyList<EntityUid> 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<SpriteComponent>().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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Content.Client/Kitchen/UI/LabelledContentBox.xaml
Normal file
7
Content.Client/Kitchen/UI/LabelledContentBox.xaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<customControls:BoxContainer Orientation="Vertical" xmlns:customControls="https://spacestation14.io">
|
||||||
|
<customControls:SplitContainer Orientation="Horizontal">
|
||||||
|
<customControls:Label Name="Label" Align="Center"/>
|
||||||
|
<customControls:Button Name="Button" TextAlign="Center"/>
|
||||||
|
</customControls:SplitContainer>
|
||||||
|
<customControls:ItemList Name="ItemList" VerticalExpand="True" HorizontalExpand="True" SelectMode="Button" SizeFlagsStretchRatio="2" MinSize="100 128"/>
|
||||||
|
</customControls:BoxContainer>
|
||||||
15
Content.Client/Kitchen/UI/LabelledContentBox.xaml.cs
Normal file
15
Content.Client/Kitchen/UI/LabelledContentBox.xaml.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Content.Shared.Kitchen.Components;
|
using Content.Shared.Kitchen.Components;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using static Content.Shared.Chemistry.Solution.Solution;
|
using static Content.Shared.Chemistry.Solution.Solution;
|
||||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
|
||||||
|
|
||||||
namespace Content.Client.Kitchen.UI
|
namespace Content.Client.Kitchen.UI
|
||||||
{
|
{
|
||||||
@@ -21,8 +14,6 @@ namespace Content.Client.Kitchen.UI
|
|||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
private GrinderMenu? _menu;
|
private GrinderMenu? _menu;
|
||||||
private readonly Dictionary<int, EntityUid> _chamberVisualContents = new();
|
|
||||||
private readonly Dictionary<int, ReagentQuantity> _beakerVisualContents = new();
|
|
||||||
|
|
||||||
public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
||||||
|
|
||||||
@@ -30,22 +21,9 @@ namespace Content.Client.Kitchen.UI
|
|||||||
{
|
{
|
||||||
base.Open();
|
base.Open();
|
||||||
|
|
||||||
_menu = new GrinderMenu(this);
|
_menu = new GrinderMenu(this, _entityManager, _prototypeManager);
|
||||||
_menu.OpenCentered();
|
_menu.OpenCentered();
|
||||||
_menu.OnClose += Close;
|
_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)
|
protected override void Dispose(bool disposing)
|
||||||
@@ -56,8 +34,6 @@ namespace Content.Client.Kitchen.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_chamberVisualContents?.Clear();
|
|
||||||
_beakerVisualContents?.Clear();
|
|
||||||
_menu?.Dispose();
|
_menu?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,243 +45,19 @@ namespace Content.Client.Kitchen.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_menu == null)
|
_menu?.UpdateState(cState);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||||
{
|
{
|
||||||
base.ReceiveMessage(message);
|
base.ReceiveMessage(message);
|
||||||
|
_menu?.HandleMessage(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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshContentsDisplay(IList<ReagentQuantity>? reagents, IReadOnlyList<EntityUid> containedSolids, bool isBeakerAttached)
|
public void StartGrinding(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage());
|
||||||
{
|
public void StartJuicing(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage());
|
||||||
//Refresh chamber contents
|
public void EjectAll(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage());
|
||||||
_chamberVisualContents.Clear();
|
public void EjectBeaker(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage());
|
||||||
|
public void EjectChamberContent(EntityUid uid) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage(uid));
|
||||||
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<SpriteComponent>().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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,8 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Content.Server.Chemistry.Components;
|
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.Interaction;
|
||||||
using Content.Shared.Kitchen.Components;
|
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.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
@@ -31,337 +16,27 @@ namespace Content.Server.Kitchen.Components
|
|||||||
/// it contained, juice an apple and get "apple juice".
|
/// it contained, juice an apple and get "apple juice".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[ComponentReference(typeof(IActivate))]
|
public class ReagentGrinderComponent : SharedReagentGrinderComponent
|
||||||
public class ReagentGrinderComponent : SharedReagentGrinderComponent, IActivate, IInteractUsing
|
|
||||||
{
|
{
|
||||||
private AudioSystem _audioSystem = default!;
|
[ViewVariables] public ContainerSlot BeakerContainer = default!;
|
||||||
[ViewVariables] private ContainerSlot _beakerContainer = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Can be null since we won't always have a beaker in the grinder.
|
/// Can be null since we won't always have a beaker in the grinder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables] private SolutionContainerComponent? _heldBeaker = default!;
|
[ViewVariables] public SolutionContainerComponent? HeldBeaker = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the things that are going to be ground or juiced.
|
/// Contains the things that are going to be ground or juiced.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Should the BoundUI be told to update?
|
|
||||||
/// </summary>
|
|
||||||
private bool _uiDirty = true;
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is the machine actively doing something and can't be used right now?
|
/// Is the machine actively doing something and can't be used right now?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool _busy = false;
|
public bool Busy;
|
||||||
|
|
||||||
//YAML serialization vars
|
//YAML serialization vars
|
||||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] private int _storageCap = 16;
|
[ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] public int StorageCap = 16;
|
||||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] private int _workTime = 3500; //3.5 seconds, completely arbitrary for now.
|
[ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] public 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<ContainerSlot>(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<Container>(Owner, $"{Name}-entityContainerContainer");
|
|
||||||
|
|
||||||
if (UserInterface != null)
|
|
||||||
{
|
|
||||||
UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
_audioSystem = EntitySystem.Get<AudioSystem>();
|
|
||||||
}
|
|
||||||
|
|
||||||
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<JuiceableComponent>()) 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
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<HandsComponent>(out var hands) || !_heldBeaker.Owner.TryGetComponent<ItemComponent>(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<bool> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isJuiceIntent">true for wanting to juice, false for wanting to grind.</param>
|
|
||||||
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<SolutionContainerComponent>(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<JuiceableComponent>(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
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
|
namespace Content.Server.Kitchen.EntitySystems
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
internal sealed class ReagentGrinderSystem : EntitySystem
|
internal sealed class ReagentGrinderSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
|
||||||
|
private Queue<ReagentGrinderComponent> _uiUpdateQueue = new ();
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<ReagentGrinderComponent, ComponentInit>(OnComponentInit);
|
||||||
|
SubscribeLocalEvent<ReagentGrinderComponent, PowerChangedEvent>((_, component, _) => EnqueueUiUpdate(component));
|
||||||
|
SubscribeLocalEvent<ReagentGrinderComponent, InteractHandEvent>(OnInteractHand);
|
||||||
|
SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(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<ContainerSlot>(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<Container>(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)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
foreach (var comp in ComponentManager.EntityQuery<ReagentGrinderComponent>(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<JuiceableComponent>()) 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()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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<HandsComponent>(out var hands) || !component.HeldBeaker.Owner.TryGetComponent<ItemComponent>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isJuiceIntent">true for wanting to juice, false for wanting to grind.</param>
|
||||||
|
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<SolutionContainerComponent>(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<JuiceableComponent>(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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]
|
[Serializable, NetSerializable]
|
||||||
public class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage
|
public class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user