mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
Predict reagent grinder (#42815)
* prediction!!! * fixes * fixes and add debug logs * fix prediction and cleanup * raise event on client + comp access * reapply power state changes * Update Content.Client/Kitchen/EntitySystems/ReagentGrinderSystem.cs Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> * Update Content.Shared/Kitchen/Components/ReagentGrinderComponent.cs Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> * comments and prefilled grinder protos * t --------- Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Co-authored-by: ScarKy0 <scarky0@onet.eu>
This commit is contained in:
parent
651b70f13a
commit
2736c458c4
14 changed files with 697 additions and 640 deletions
|
|
@ -116,7 +116,7 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
|||
}
|
||||
|
||||
|
||||
if (extractableComponent.GrindableSolution is { } grindableSolutionId &&
|
||||
if (extractableComponent.GrindableSolutionName is { } grindableSolutionId &&
|
||||
entProto.TryGetComponent<SolutionContainerManagerComponent>(out var manager, EntityManager.ComponentFactory) &&
|
||||
_solutionContainer.TryGetSolution(manager, grindableSolutionId, out var grindableSolution))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,28 @@
|
|||
using Content.Shared.Kitchen;
|
||||
using Content.Shared.Kitchen.Components;
|
||||
using Content.Shared.Kitchen.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Kitchen.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class ReagentGrinderSystem : SharedReagentGrinderSystem;
|
||||
public sealed class ReagentGrinderSystem : SharedReagentGrinderSystem
|
||||
{
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _userInterface = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
|
||||
}
|
||||
|
||||
private void OnAfterAutoHandleState(Entity<ReagentGrinderComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateUi(ent);
|
||||
}
|
||||
|
||||
public override void UpdateUi(EntityUid uid)
|
||||
{
|
||||
if (_userInterface.TryGetOpenUi(uid, ReagentGrinderUiKey.Key, out var bui))
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<BoxContainer Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" VerticalAlignment="Top" Margin="8" MinWidth="100">
|
||||
<Label Text="{Loc grinder-menu-auto-label}" HorizontalAlignment="Center"/>
|
||||
<Button Name="AutoModeButton" Text="{Loc grinder-menu-auto-button}" TextAlign="Center" MinSize="64 48" Margin="0 0 0 16" />
|
||||
<Button Name="AutoModeButton" Text="{Loc grinder-menu-auto-button-off}" TextAlign="Center" MinSize="64 48" Margin="0 0 0 16" />
|
||||
<Label Text="{Loc grinder-menu-manual-label}" HorizontalAlignment="Center"/>
|
||||
<Button Name="GrindButton" Text="{Loc grinder-menu-grind-button}" TextAlign="Center" MinSize="64 48" Margin="0 0 0 16" />
|
||||
<Button Name="JuiceButton" Text="{Loc grinder-menu-juice-button}" TextAlign="Center" MinSize="64 48" />
|
||||
|
|
|
|||
|
|
@ -1,136 +1,127 @@
|
|||
using System.Linq;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Kitchen;
|
||||
using Content.Shared.Kitchen.Components;
|
||||
using Content.Shared.Kitchen.EntitySystems;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Kitchen.UI
|
||||
namespace Content.Client.Kitchen.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GrinderMenu : FancyWindow
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GrinderMenu : FancyWindow
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
private readonly ItemSlotsSystem _slots = default!;
|
||||
private readonly SharedPowerReceiverSystem _power = default!;
|
||||
private readonly SharedReagentGrinderSystem _grinder = default!;
|
||||
private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
|
||||
private readonly Dictionary<int, EntityUid> _chamberVisualContents = new();
|
||||
private EntityUid _owner;
|
||||
|
||||
public event Action? OnToggleAuto;
|
||||
public event Action? OnGrind;
|
||||
public event Action? OnJuice;
|
||||
public event Action? OnEjectAll;
|
||||
public event Action? OnEjectBeaker;
|
||||
public event Action<EntityUid>? OnEjectChamber;
|
||||
|
||||
public GrinderMenu()
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_slots = _entityManager.System<ItemSlotsSystem>();
|
||||
_power = _entityManager.System<SharedPowerReceiverSystem>();
|
||||
_grinder = _entityManager.System<SharedReagentGrinderSystem>();
|
||||
_solutionContainer = _entityManager.System<SharedSolutionContainerSystem>();
|
||||
|
||||
private readonly Dictionary<int, EntityUid> _chamberVisualContents = new();
|
||||
AutoModeButton.OnPressed += _ => OnToggleAuto?.Invoke();
|
||||
GrindButton.OnPressed += _ => OnGrind?.Invoke();
|
||||
JuiceButton.OnPressed += _ => OnJuice?.Invoke();
|
||||
ChamberContentBox.EjectButton.OnPressed += _ => OnEjectAll?.Invoke();
|
||||
BeakerContentBox.EjectButton.OnPressed += _ => OnEjectBeaker?.Invoke();
|
||||
ChamberContentBox.BoxContents.OnItemSelected += OnChamberBoxContentsItemSelected;
|
||||
BeakerContentBox.BoxContents.SelectMode = ItemList.ItemListSelectMode.None;
|
||||
}
|
||||
|
||||
public event Action? OnToggleAuto;
|
||||
public event Action? OnGrind;
|
||||
public event Action? OnJuice;
|
||||
public event Action? OnEjectAll;
|
||||
public event Action? OnEjectBeaker;
|
||||
public event Action<EntityUid>? OnEjectChamber;
|
||||
private void OnChamberBoxContentsItemSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
OnEjectChamber?.Invoke(_chamberVisualContents[args.ItemIndex]);
|
||||
}
|
||||
|
||||
public GrinderMenu()
|
||||
public void SetEntity(EntityUid owner)
|
||||
{
|
||||
_owner = owner;
|
||||
UpdateUi();
|
||||
}
|
||||
|
||||
public void UpdateUi()
|
||||
{
|
||||
if (!_entityManager.TryGetComponent<ReagentGrinderComponent>(_owner, out var grinderComp))
|
||||
return;
|
||||
|
||||
var active = _grinder.IsActive((_owner, grinderComp));
|
||||
var beaker = _slots.GetItemOrNull(_owner, ReagentGrinderComponent.BeakerSlotId);
|
||||
var powered = _power.IsPowered(_owner);
|
||||
var hasInput = grinderComp.InputContainer.ContainedEntities.Any();
|
||||
var canGrind = hasInput && grinderComp.InputContainer.ContainedEntities.All(x => _grinder.CanGrind(x));
|
||||
var canJuice = hasInput && grinderComp.InputContainer.ContainedEntities.All(x => _grinder.CanJuice(x));
|
||||
|
||||
BeakerContentBox.EjectButton.Disabled = active || !beaker.HasValue;
|
||||
ChamberContentBox.EjectButton.Disabled = active || !hasInput;
|
||||
GrindButton.Disabled = active || !canGrind || !powered || !beaker.HasValue;
|
||||
JuiceButton.Disabled = active || !canJuice || !powered || !beaker.HasValue;
|
||||
GrindButton.Modulate = grinderComp.Program == GrinderProgram.Grind ? Color.Green : Color.White;
|
||||
JuiceButton.Modulate = grinderComp.Program == GrinderProgram.Juice ? Color.Green : Color.White;
|
||||
|
||||
AutoModeButton.Text = grinderComp.AutoMode switch
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
AutoModeButton.OnPressed += _ => OnToggleAuto?.Invoke();
|
||||
GrindButton.OnPressed += _ => OnGrind?.Invoke();
|
||||
JuiceButton.OnPressed += _ => OnJuice?.Invoke();
|
||||
ChamberContentBox.EjectButton.OnPressed += _ => OnEjectAll?.Invoke();
|
||||
BeakerContentBox.EjectButton.OnPressed += _ => OnEjectBeaker?.Invoke();
|
||||
ChamberContentBox.BoxContents.OnItemSelected += OnChamberBoxContentsItemSelected;
|
||||
BeakerContentBox.BoxContents.SelectMode = ItemList.ItemListSelectMode.None;
|
||||
GrinderAutoMode.Grind => Loc.GetString("grinder-menu-grind-button"),
|
||||
GrinderAutoMode.Juice => Loc.GetString("grinder-menu-juice-button"),
|
||||
_ => Loc.GetString("grinder-menu-auto-button-off"),
|
||||
};
|
||||
|
||||
//Refresh chamber contents
|
||||
_chamberVisualContents.Clear();
|
||||
|
||||
ChamberContentBox.BoxContents.Clear();
|
||||
foreach (var entity in grinderComp.InputContainer.ContainedEntities)
|
||||
{
|
||||
// TODO: Replace with SpriteView
|
||||
var texture = _entityManager.GetComponent<SpriteComponent>(entity).Icon?.Default;
|
||||
var solidItem = ChamberContentBox.BoxContents.AddItem(_entityManager.GetComponent<MetaDataComponent>(entity).EntityName, texture);
|
||||
var solidIndex = ChamberContentBox.BoxContents.IndexOf(solidItem);
|
||||
_chamberVisualContents.Add(solidIndex, entity);
|
||||
}
|
||||
|
||||
private void OnChamberBoxContentsItemSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
//Refresh beaker contents
|
||||
BeakerContentBox.BoxContents.Clear();
|
||||
//if no beaker is attached use this guard to prevent hitting a null reference.
|
||||
if (!beaker.HasValue || !_solutionContainer.TryGetFitsInDispenser(beaker.Value, out _, out var reagents))
|
||||
return;
|
||||
|
||||
//Looks like we have a beaker attached.
|
||||
if (!reagents.Any())
|
||||
{
|
||||
OnEjectChamber?.Invoke(_chamberVisualContents[args.ItemIndex]);
|
||||
BeakerContentBox.BoxContents.AddItem(Loc.GetString("grinder-menu-beaker-content-box-is-empty"));
|
||||
}
|
||||
|
||||
public void UpdateState(ReagentGrinderInterfaceState state)
|
||||
else
|
||||
{
|
||||
BeakerContentBox.EjectButton.Disabled = !state.HasBeakerIn;
|
||||
ChamberContentBox.EjectButton.Disabled = state.ChamberContents.Length <= 0;
|
||||
GrindButton.Disabled = !state.CanGrind || !state.Powered;
|
||||
JuiceButton.Disabled = !state.CanJuice || !state.Powered;
|
||||
|
||||
switch (state.AutoMode)
|
||||
foreach (var (reagent, quantity) in reagents)
|
||||
{
|
||||
case GrinderAutoMode.Grind:
|
||||
AutoModeButton.Text = Loc.GetString("grinder-menu-grind-button");
|
||||
break;
|
||||
case GrinderAutoMode.Juice:
|
||||
AutoModeButton.Text = Loc.GetString("grinder-menu-juice-button");
|
||||
break;
|
||||
default:
|
||||
AutoModeButton.Text = Loc.GetString("grinder-menu-auto-button-off");
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO move this to a component state and ensure the net ids.
|
||||
RefreshContentsDisplay(state.ReagentQuantities, _entityManager.GetEntityArray(state.ChamberContents), state.HasBeakerIn);
|
||||
}
|
||||
|
||||
public void HandleMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case ReagentGrinderWorkStartedMessage workStarted:
|
||||
GrindButton.Disabled = true;
|
||||
GrindButton.Modulate = workStarted.GrinderProgram == GrinderProgram.Grind ? Color.Green : Color.White;
|
||||
JuiceButton.Disabled = true;
|
||||
JuiceButton.Modulate = workStarted.GrinderProgram == GrinderProgram.Juice ? Color.Green : Color.White;
|
||||
BeakerContentBox.EjectButton.Disabled = true;
|
||||
ChamberContentBox.EjectButton.Disabled = true;
|
||||
break;
|
||||
case ReagentGrinderWorkCompleteMessage:
|
||||
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<ReagentQuantity>? reagents, IReadOnlyList<EntityUid> containedSolids, bool isBeakerAttached)
|
||||
{
|
||||
//Refresh chamber contents
|
||||
_chamberVisualContents.Clear();
|
||||
|
||||
ChamberContentBox.BoxContents.Clear();
|
||||
foreach (var entity in containedSolids)
|
||||
{
|
||||
if (!_entityManager.EntityExists(entity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var texture = _entityManager.GetComponent<SpriteComponent>(entity).Icon?.Default;
|
||||
|
||||
var solidItem = ChamberContentBox.BoxContents.AddItem(_entityManager.GetComponent<MetaDataComponent>(entity).EntityName, texture);
|
||||
var solidIndex = ChamberContentBox.BoxContents.IndexOf(solidItem);
|
||||
_chamberVisualContents.Add(solidIndex, entity);
|
||||
}
|
||||
|
||||
//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, quantity) in reagents)
|
||||
{
|
||||
var reagentName = _prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto)
|
||||
? Loc.GetString($"{quantity} {proto.LocalizedName}")
|
||||
: "???";
|
||||
BeakerContentBox.BoxContents.AddItem(reagentName);
|
||||
}
|
||||
var reagentName = _prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto)
|
||||
? $"{quantity} {proto.LocalizedName}"
|
||||
: "???";
|
||||
BeakerContentBox.BoxContents.AddItem(reagentName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,77 +1,63 @@
|
|||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Kitchen;
|
||||
using Robust.Client.GameObjects;
|
||||
using Content.Shared.Kitchen.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Kitchen.UI
|
||||
namespace Content.Client.Kitchen.UI;
|
||||
|
||||
public sealed class ReagentGrinderBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
public sealed class ReagentGrinderBoundUserInterface : BoundUserInterface
|
||||
[ViewVariables]
|
||||
private GrinderMenu? _menu;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
[ViewVariables]
|
||||
private GrinderMenu? _menu;
|
||||
base.Open();
|
||||
|
||||
public ReagentGrinderBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
_menu = this.CreateWindow<GrinderMenu>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.OnToggleAuto += ToggleAutoMode;
|
||||
_menu.OnGrind += StartGrinding;
|
||||
_menu.OnJuice += StartJuicing;
|
||||
_menu.OnEjectAll += EjectAll;
|
||||
_menu.OnEjectBeaker += EjectBeaker;
|
||||
_menu.OnEjectChamber += EjectChamberContent;
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
_menu = this.CreateWindow<GrinderMenu>();
|
||||
_menu.OnToggleAuto += ToggleAutoMode;
|
||||
_menu.OnGrind += StartGrinding;
|
||||
_menu.OnJuice += StartJuicing;
|
||||
_menu.OnEjectAll += EjectAll;
|
||||
_menu.OnEjectBeaker += EjectBeaker;
|
||||
_menu.OnEjectChamber += EjectChamberContent;
|
||||
}
|
||||
_menu?.UpdateUi();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (state is not ReagentGrinderInterfaceState cState)
|
||||
return;
|
||||
public void ToggleAutoMode()
|
||||
{
|
||||
SendPredictedMessage(new ReagentGrinderToggleAutoModeMessage());
|
||||
}
|
||||
|
||||
_menu?.UpdateState(cState);
|
||||
}
|
||||
public void StartGrinding()
|
||||
{
|
||||
SendPredictedMessage(new ReagentGrinderStartMessage(GrinderProgram.Grind));
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
base.ReceiveMessage(message);
|
||||
_menu?.HandleMessage(message);
|
||||
}
|
||||
public void StartJuicing()
|
||||
{
|
||||
SendPredictedMessage(new ReagentGrinderStartMessage(GrinderProgram.Juice));
|
||||
}
|
||||
|
||||
public void ToggleAutoMode()
|
||||
{
|
||||
SendMessage(new ReagentGrinderToggleAutoModeMessage());
|
||||
}
|
||||
public void EjectAll()
|
||||
{
|
||||
SendPredictedMessage(new ReagentGrinderEjectChamberAllMessage());
|
||||
}
|
||||
|
||||
public void StartGrinding()
|
||||
{
|
||||
SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Grind));
|
||||
}
|
||||
public void EjectBeaker()
|
||||
{
|
||||
SendPredictedMessage(new ItemSlotButtonPressedEvent(ReagentGrinderComponent.BeakerSlotId));
|
||||
}
|
||||
|
||||
public void StartJuicing()
|
||||
{
|
||||
SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Juice));
|
||||
}
|
||||
|
||||
public void EjectAll()
|
||||
{
|
||||
SendMessage(new ReagentGrinderEjectChamberAllMessage());
|
||||
}
|
||||
|
||||
public void EjectBeaker()
|
||||
{
|
||||
SendMessage(new ItemSlotButtonPressedEvent(SharedReagentGrinder.BeakerSlotId));
|
||||
}
|
||||
|
||||
public void EjectChamberContent(EntityUid uid)
|
||||
{
|
||||
SendMessage(new ReagentGrinderEjectChamberContentMessage(EntMan.GetNetEntity(uid)));
|
||||
}
|
||||
public void EjectChamberContent(EntityUid uid)
|
||||
{
|
||||
SendPredictedMessage(new ReagentGrinderEjectChamberContentMessage(EntMan.GetNetEntity(uid)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,322 +1,5 @@
|
|||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Kitchen;
|
||||
using Content.Shared.Kitchen.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Stacks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
using Content.Server.Jittering;
|
||||
using Content.Shared.Jittering;
|
||||
using Content.Shared.Kitchen.EntitySystems;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
|
||||
namespace Content.Server.Kitchen.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class ReagentGrinderSystem : SharedReagentGrinderSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainersSystem = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly StackSystem _stackSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedDestructibleSystem _destructible = default!;
|
||||
[Dependency] private readonly RandomHelperSystem _randomHelper = default!;
|
||||
[Dependency] private readonly JitteringSystem _jitter = default!;
|
||||
[Dependency] private readonly SharedPowerStateSystem _powerState = default!;
|
||||
namespace Content.Server.Kitchen.EntitySystems;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ActiveReagentGrinderComponent, ComponentStartup>(OnActiveGrinderStart);
|
||||
SubscribeLocalEvent<ActiveReagentGrinderComponent, ComponentRemove>(OnActiveGrinderRemove);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ComponentStartup>((uid, _, _) => UpdateUiState(uid));
|
||||
SubscribeLocalEvent((EntityUid uid, ReagentGrinderComponent _, ref PowerChangedEvent _) => UpdateUiState(uid));
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, EntRemovedFromContainerMessage>(OnContainerModified);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ContainerIsRemovingAttemptEvent>(OnEntRemoveAttempt);
|
||||
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderToggleAutoModeMessage>(OnToggleAutoModeMessage);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderStartMessage>(OnStartMessage);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberAllMessage>(OnEjectChamberAllMessage);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberContentMessage>(OnEjectChamberContentMessage);
|
||||
}
|
||||
|
||||
private void OnToggleAutoModeMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderToggleAutoModeMessage message)
|
||||
{
|
||||
entity.Comp.AutoMode = (GrinderAutoMode) (((byte) entity.Comp.AutoMode + 1) % Enum.GetValues(typeof(GrinderAutoMode)).Length);
|
||||
|
||||
Dirty(entity);
|
||||
|
||||
UpdateUiState(entity);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<ActiveReagentGrinderComponent, ReagentGrinderComponent>();
|
||||
while (query.MoveNext(out var uid, out var active, out var reagentGrinder))
|
||||
{
|
||||
if (active.EndTime > _timing.CurTime)
|
||||
continue;
|
||||
|
||||
reagentGrinder.AudioStream = _audioSystem.Stop(reagentGrinder.AudioStream);
|
||||
RemCompDeferred<ActiveReagentGrinderComponent>(uid);
|
||||
|
||||
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
|
||||
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
|
||||
if (outputContainer is null || !_solutionContainersSystem.TryGetFitsInDispenser(outputContainer.Value, out var containerSoln, out var containerSolution))
|
||||
continue;
|
||||
|
||||
foreach (var item in inputContainer.ContainedEntities.ToList())
|
||||
{
|
||||
var solution = GetGrinderSolution(item, active.Program);
|
||||
|
||||
if (solution is null)
|
||||
continue;
|
||||
|
||||
if (TryComp<StackComponent>(item, out var stack))
|
||||
{
|
||||
var totalVolume = solution.Volume * stack.Count;
|
||||
if (totalVolume <= 0)
|
||||
continue;
|
||||
|
||||
// Maximum number of items we can process in the stack without going over AvailableVolume
|
||||
// We add a small tolerance, because floats are inaccurate.
|
||||
var fitsCount = (int) (stack.Count * FixedPoint2.Min(containerSolution.AvailableVolume / totalVolume + 0.01, 1));
|
||||
if (fitsCount <= 0)
|
||||
continue;
|
||||
|
||||
// Make a copy of the solution to scale
|
||||
// Otherwise we'll actually change the volume of the remaining stack too
|
||||
var scaledSolution = new Solution(solution);
|
||||
scaledSolution.ScaleSolution(fitsCount);
|
||||
solution = scaledSolution;
|
||||
|
||||
_stackSystem.ReduceCount((item, stack), fitsCount); // Setting to 0 will QueueDel
|
||||
}
|
||||
else
|
||||
{
|
||||
if (solution.Volume > containerSolution.AvailableVolume)
|
||||
continue;
|
||||
|
||||
_destructible.DestroyEntity(item);
|
||||
}
|
||||
|
||||
_solutionContainersSystem.TryAddSolution(containerSoln.Value, solution);
|
||||
}
|
||||
|
||||
_userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key,
|
||||
new ReagentGrinderWorkCompleteMessage());
|
||||
|
||||
UpdateUiState(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActiveGrinderStart(Entity<ActiveReagentGrinderComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
_jitter.AddJitter(ent, -10, 100);
|
||||
|
||||
// Not all grinders need power.
|
||||
_powerState.TrySetWorkingState(ent.Owner, true);
|
||||
}
|
||||
|
||||
private void OnActiveGrinderRemove(Entity<ActiveReagentGrinderComponent> ent, ref ComponentRemove args)
|
||||
{
|
||||
RemComp<JitteringComponent>(ent);
|
||||
_powerState.TrySetWorkingState(ent.Owner, false);
|
||||
}
|
||||
|
||||
private void OnEntRemoveAttempt(Entity<ReagentGrinderComponent> entity, ref ContainerIsRemovingAttemptEvent args)
|
||||
{
|
||||
if (HasComp<ActiveReagentGrinderComponent>(entity))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnContainerModified(EntityUid uid, ReagentGrinderComponent reagentGrinder, ContainerModifiedMessage args)
|
||||
{
|
||||
UpdateUiState(uid);
|
||||
|
||||
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
|
||||
_appearanceSystem.SetData(uid, ReagentGrinderVisualState.BeakerAttached, outputContainer.HasValue);
|
||||
|
||||
if (reagentGrinder.AutoMode != GrinderAutoMode.Off && !HasComp<ActiveReagentGrinderComponent>(uid) && this.IsPowered(uid, EntityManager))
|
||||
{
|
||||
var program = reagentGrinder.AutoMode == GrinderAutoMode.Grind ? GrinderProgram.Grind : GrinderProgram.Juice;
|
||||
DoWork(uid, reagentGrinder, program);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInteractUsing(Entity<ReagentGrinderComponent> entity, ref InteractUsingEvent args)
|
||||
{
|
||||
var heldEnt = args.Used;
|
||||
var inputContainer = _containerSystem.EnsureContainer<Container>(entity.Owner, SharedReagentGrinder.InputContainerId);
|
||||
|
||||
if (!HasComp<ExtractableComponent>(heldEnt))
|
||||
{
|
||||
if (!HasComp<FitsInDispenserComponent>(heldEnt))
|
||||
{
|
||||
// This is ugly but we can't use whitelistFailPopup because there are 2 containers with different whitelists.
|
||||
_popupSystem.PopupEntity(Loc.GetString("reagent-grinder-component-cannot-put-entity-message"), entity.Owner, args.User);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (args.Handled)
|
||||
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 (inputContainer.ContainedEntities.Count >= entity.Comp.StorageMaxEntities)
|
||||
return;
|
||||
|
||||
if (!_containerSystem.Insert(heldEnt, inputContainer))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void UpdateUiState(EntityUid uid)
|
||||
{
|
||||
ReagentGrinderComponent? grinderComp = null;
|
||||
if (!Resolve(uid, ref grinderComp))
|
||||
return;
|
||||
|
||||
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
|
||||
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
|
||||
Solution? containerSolution = null;
|
||||
var isBusy = HasComp<ActiveReagentGrinderComponent>(uid);
|
||||
var canJuice = false;
|
||||
var canGrind = false;
|
||||
|
||||
if (outputContainer is not null
|
||||
&& _solutionContainersSystem.TryGetFitsInDispenser(outputContainer.Value, out _, out containerSolution)
|
||||
&& inputContainer.ContainedEntities.Count > 0)
|
||||
{
|
||||
canGrind = inputContainer.ContainedEntities.All(x => CanGrind(x));
|
||||
canJuice = inputContainer.ContainedEntities.All(x => CanJuice(x));
|
||||
}
|
||||
|
||||
var state = new ReagentGrinderInterfaceState(
|
||||
isBusy,
|
||||
outputContainer.HasValue,
|
||||
this.IsPowered(uid, EntityManager),
|
||||
canJuice,
|
||||
canGrind,
|
||||
grinderComp.AutoMode,
|
||||
GetNetEntityArray(inputContainer.ContainedEntities.ToArray()),
|
||||
containerSolution?.Contents.ToArray()
|
||||
);
|
||||
_userInterfaceSystem.SetUiState(uid, ReagentGrinderUiKey.Key, state);
|
||||
}
|
||||
|
||||
private void OnStartMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderStartMessage message)
|
||||
{
|
||||
if (!this.IsPowered(entity.Owner, EntityManager) || HasComp<ActiveReagentGrinderComponent>(entity))
|
||||
return;
|
||||
|
||||
DoWork(entity.Owner, entity.Comp, message.Program);
|
||||
}
|
||||
|
||||
private void OnEjectChamberAllMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderEjectChamberAllMessage message)
|
||||
{
|
||||
var inputContainer = _containerSystem.EnsureContainer<Container>(entity.Owner, SharedReagentGrinder.InputContainerId);
|
||||
|
||||
if (HasComp<ActiveReagentGrinderComponent>(entity) || inputContainer.ContainedEntities.Count <= 0)
|
||||
return;
|
||||
|
||||
ClickSound(entity);
|
||||
foreach (var toEject in inputContainer.ContainedEntities.ToList())
|
||||
{
|
||||
_containerSystem.Remove(toEject, inputContainer);
|
||||
_randomHelper.RandomOffset(toEject, 0.4f);
|
||||
}
|
||||
UpdateUiState(entity);
|
||||
}
|
||||
|
||||
private void OnEjectChamberContentMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderEjectChamberContentMessage message)
|
||||
{
|
||||
if (HasComp<ActiveReagentGrinderComponent>(entity))
|
||||
return;
|
||||
|
||||
var inputContainer = _containerSystem.EnsureContainer<Container>(entity.Owner, SharedReagentGrinder.InputContainerId);
|
||||
var ent = GetEntity(message.EntityId);
|
||||
|
||||
if (_containerSystem.Remove(ent, inputContainer))
|
||||
{
|
||||
_randomHelper.RandomOffset(ent, 0.4f);
|
||||
ClickSound(entity);
|
||||
UpdateUiState(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
|
||||
/// </summary>
|
||||
/// <param name="uid">The grinder itself</param>
|
||||
/// <param name="reagentGrinder"></param>
|
||||
/// <param name="program">Which program, such as grind or juice</param>
|
||||
private void DoWork(EntityUid uid, ReagentGrinderComponent reagentGrinder, GrinderProgram program)
|
||||
{
|
||||
var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
|
||||
var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
|
||||
|
||||
// Do we have anything to grind/juice and a container to put the reagents in?
|
||||
if (inputContainer.ContainedEntities.Count <= 0 || !HasComp<FitsInDispenserComponent>(outputContainer))
|
||||
return;
|
||||
|
||||
SoundSpecifier? sound;
|
||||
switch (program)
|
||||
{
|
||||
case GrinderProgram.Grind when inputContainer.ContainedEntities.All(x => CanGrind(x)):
|
||||
sound = reagentGrinder.GrindSound;
|
||||
break;
|
||||
case GrinderProgram.Juice when inputContainer.ContainedEntities.All(x => CanJuice(x)):
|
||||
sound = reagentGrinder.JuiceSound;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
var active = AddComp<ActiveReagentGrinderComponent>(uid);
|
||||
active.EndTime = _timing.CurTime + reagentGrinder.WorkTime * reagentGrinder.WorkTimeMultiplier;
|
||||
active.Program = program;
|
||||
|
||||
reagentGrinder.AudioStream = _audioSystem.PlayPvs(sound, uid,
|
||||
AudioParams.Default.WithPitchScale(1 / reagentGrinder.WorkTimeMultiplier))?.Entity; //slightly higher pitched
|
||||
_userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key,
|
||||
new ReagentGrinderWorkStartedMessage(program));
|
||||
}
|
||||
|
||||
private void ClickSound(Entity<ReagentGrinderComponent> reagentGrinder)
|
||||
{
|
||||
_audioSystem.PlayPvs(reagentGrinder.Comp.ClickSound, reagentGrinder.Owner, AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
}
|
||||
}
|
||||
public sealed class ReagentGrinderSystem : SharedReagentGrinderSystem;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public abstract partial class SharedSolutionContainerSystem
|
|||
return false;
|
||||
}
|
||||
|
||||
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.GrindableSolution, out soln, out solution);
|
||||
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.GrindableSolutionName, out soln, out solution);
|
||||
}
|
||||
|
||||
public bool TryGetDumpableSolution(Entity<DumpableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
|
|||
namespace Content.Shared.Chemistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Send by the client when setting the transfer amount using the BUI.
|
||||
/// Sent by the client when setting the transfer amount using the BUI.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class TransferAmountSetValueMessage(FixedPoint2 value) : BoundUserInterfaceMessage
|
||||
|
|
|
|||
|
|
@ -4,14 +4,22 @@ using Robust.Shared.GameStates;
|
|||
namespace Content.Shared.Kitchen.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Tag component that denotes an entity as Extractable
|
||||
/// This component allows an entity to be used in the reagent grinder to extract reagents.
|
||||
/// <seealso cref="ReagentGrinderComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ExtractableComponent : Component
|
||||
{
|
||||
[DataField("juiceSolution")]
|
||||
/// <summary>
|
||||
/// The reagents to spawn when the grinder is set to juice mode.
|
||||
/// This will delete the reagents in the GrindableSolution.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Solution? JuiceSolution;
|
||||
|
||||
[DataField("grindableSolutionName")]
|
||||
public string? GrindableSolution;
|
||||
/// <summary>
|
||||
/// The reagents to transfer into the beaker when the grinder is set to grind mode.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? GrindableSolutionName;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using Content.Shared.Kitchen.EntitySystems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Kitchen.Components;
|
||||
|
||||
|
|
@ -10,44 +12,100 @@ namespace Content.Shared.Kitchen.Components;
|
|||
/// converting something into its single juice form. E.g, grind an apple and get the nutriment and sugar
|
||||
/// it contained, juice an apple and get "apple juice".
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true), AutoGenerateComponentPause]
|
||||
[Access(typeof(SharedReagentGrinderSystem))]
|
||||
public sealed partial class ReagentGrinderComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The container slot id for the beaker.
|
||||
/// </summary>
|
||||
public const string BeakerSlotId = "beakerSlot";
|
||||
|
||||
/// <summary>
|
||||
/// The container id for the internal storage.
|
||||
/// </summary>
|
||||
public const string InputContainerId = "inputContainer";
|
||||
|
||||
/// <summary>
|
||||
/// The cached container for the internal storage.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Container InputContainer = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of entities that fit into the container.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public int StorageMaxEntities = 6;
|
||||
|
||||
/// <summary>
|
||||
/// The time grinding or juicing takes.
|
||||
/// Roughly matches the grind/juice sounds.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan WorkTime = TimeSpan.FromSeconds(3.5f);
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier for WorkTime, that pitches the audio accordingly.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan WorkTime = TimeSpan.FromSeconds(3.5); // Roughly matches the grind/juice sounds.
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public float WorkTimeMultiplier = 1;
|
||||
public float WorkTimeMultiplier = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when pressing a button on the UI.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier ClickSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
|
||||
public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg", AudioParams.Default.WithVolume(-2f));
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when grinding.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier GrindSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/blender.ogg");
|
||||
public SoundSpecifier GrindSound = new SoundPathSpecifier("/Audio/Machines/blender.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when juicing.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier JuiceSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/juicer.ogg");
|
||||
public SoundSpecifier JuiceSound = new SoundPathSpecifier("/Audio/Machines/juicer.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Grind automatically when inserting items?
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public GrinderAutoMode AutoMode = GrinderAutoMode.Off;
|
||||
|
||||
public EntityUid? AudioStream;
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedReagentGrinderSystem))]
|
||||
public sealed partial class ActiveReagentGrinderComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Remaining time until the grinder finishes grinding/juicing.
|
||||
/// The sound currently being played.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public TimeSpan EndTime;
|
||||
[DataField]
|
||||
public EntityUid? AudioStream;
|
||||
|
||||
[ViewVariables]
|
||||
public GrinderProgram Program;
|
||||
/// <summary>
|
||||
/// The time the grinder will finish grinding/juicing.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan? EndTime;
|
||||
|
||||
/// <summary>
|
||||
/// The currently active program (if the grinder is working).
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public GrinderProgram? Program;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marker component for active reagent grinders used to improve the EntityQueryEnumerator performance in the update loop.
|
||||
/// If you want to check if the grinder is currently active use <see cref="SharedReagentGrinderSystem.IsActive"/> instead,
|
||||
/// because this component is being removed deferred, i.e. in the following game tick.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ActiveReagentGrinderComponent : Component;
|
||||
|
||||
/// <summary>
|
||||
/// Marker component added to beakers inserted into a reagent grinder.
|
||||
/// Used to update the UI when the solution in the beaker changes.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class InsideReagentGrinderComponent : Component;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,346 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Jittering;
|
||||
using Content.Shared.Kitchen.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.Stacks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Kitchen.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
public abstract class SharedReagentGrinderSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainersSystem = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedStackSystem _stackSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedDestructibleSystem _destructible = default!;
|
||||
[Dependency] private readonly SharedJitteringSystem _jitter = default!;
|
||||
[Dependency] private readonly SharedPowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly SharedPowerStateSystem _powerState = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<InsideReagentGrinderComponent, SolutionContainerChangedEvent>(OnBeakerSolutionContainerChanged);
|
||||
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ComponentStartup>(OnGrinderStartup);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ContainerIsRemovingAttemptEvent>(OnEntRemovingAttempt);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
|
||||
SubscribeLocalEvent((EntityUid uid, ReagentGrinderComponent _, ref PowerChangedEvent _) => UpdateUi(uid));
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderStartMessage>(OnStartMessage);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderToggleAutoModeMessage>(OnToggleAutoModeMessage);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberAllMessage>(OnEjectChamberAllMessage);
|
||||
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberContentMessage>(OnEjectChamberContentMessage);
|
||||
}
|
||||
|
||||
private void OnBeakerSolutionContainerChanged(Entity<InsideReagentGrinderComponent> ent, ref SolutionContainerChangedEvent args)
|
||||
{
|
||||
// Update the UI if the reagents inside the beaker are changed.
|
||||
// This is needed in case the component state for the container is applied before that of the solution container
|
||||
// or if the beaker somehow changes its contents on its own (for with example SolutionRegenerationComponent).
|
||||
UpdateUi(Transform(ent).ParentUid);
|
||||
}
|
||||
|
||||
private void OnGrinderStartup(Entity<ReagentGrinderComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
ent.Comp.InputContainer = _containerSystem.EnsureContainer<Container>(ent.Owner, ReagentGrinderComponent.InputContainerId);
|
||||
}
|
||||
|
||||
private void OnEntRemovingAttempt(Entity<ReagentGrinderComponent> ent, ref ContainerIsRemovingAttemptEvent args)
|
||||
{
|
||||
// Allow server states to be applied without cancelling container changes.
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
if (args.Container.ID != ReagentGrinderComponent.BeakerSlotId
|
||||
&& args.Container.ID != ReagentGrinderComponent.InputContainerId)
|
||||
return;
|
||||
|
||||
// Cannot remove items while the grinder is active.
|
||||
if (IsActive(ent.AsNullable()))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
var query = EntityQueryEnumerator<ActiveReagentGrinderComponent, ReagentGrinderComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var grinderComp))
|
||||
{
|
||||
if (grinderComp.EndTime == null || grinderComp.EndTime > curTime)
|
||||
continue;
|
||||
|
||||
FinishGrinding((uid, grinderComp));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEntRemoved(EntityUid uid, ReagentGrinderComponent comp, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != ReagentGrinderComponent.BeakerSlotId
|
||||
&& args.Container.ID != ReagentGrinderComponent.InputContainerId)
|
||||
return;
|
||||
|
||||
// Always update the UI on the client, both during prediction and when applying a game state.
|
||||
UpdateUi(uid);
|
||||
|
||||
// The component changes from the code below are already part of the same game state being applied.
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
if (args.Container.ID == ReagentGrinderComponent.BeakerSlotId) // Beaker removed.
|
||||
{
|
||||
RemComp<InsideReagentGrinderComponent>(args.Entity);
|
||||
_appearanceSystem.SetData(uid, ReagentGrinderVisualState.BeakerAttached, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEntInserted(EntityUid uid, ReagentGrinderComponent comp, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != ReagentGrinderComponent.BeakerSlotId
|
||||
&& args.Container.ID != ReagentGrinderComponent.InputContainerId)
|
||||
return;
|
||||
|
||||
// Always update the UI on the client, both during prediction and when applying a game state.
|
||||
UpdateUi(uid);
|
||||
|
||||
// The component changes from the code below are already part of the same game state being applied.
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
if (args.Container.ID == ReagentGrinderComponent.BeakerSlotId) // Beaker inserted.
|
||||
{
|
||||
EnsureComp<InsideReagentGrinderComponent>(args.Entity);
|
||||
_appearanceSystem.SetData(uid, ReagentGrinderVisualState.BeakerAttached, true);
|
||||
}
|
||||
|
||||
// Start grinder when in auto mode.
|
||||
if (comp.AutoMode != GrinderAutoMode.Off)
|
||||
{
|
||||
var program = comp.AutoMode == GrinderAutoMode.Grind ? GrinderProgram.Grind : GrinderProgram.Juice;
|
||||
StartGrinder((uid, comp), program);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInteractUsing(Entity<ReagentGrinderComponent> ent, ref InteractUsingEvent args)
|
||||
{
|
||||
var heldEnt = args.Used;
|
||||
|
||||
if (!HasComp<ExtractableComponent>(heldEnt))
|
||||
{
|
||||
if (!HasComp<FitsInDispenserComponent>(heldEnt))
|
||||
{
|
||||
// This is ugly but we can't use whitelistFailPopup because there are 2 containers with different whitelists.
|
||||
_popupSystem.PopupClient(Loc.GetString("reagent-grinder-component-cannot-put-entity-message"), ent.Owner, args.User);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (args.Handled)
|
||||
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 (ent.Comp.InputContainer.ContainedEntities.Count >= ent.Comp.StorageMaxEntities)
|
||||
return;
|
||||
|
||||
if (!_containerSystem.Insert(heldEnt, ent.Comp.InputContainer))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the reagent grinder BUI for the client.
|
||||
/// </summary>
|
||||
public virtual void UpdateUi(EntityUid uid) { }
|
||||
|
||||
private void OnStartMessage(Entity<ReagentGrinderComponent> ent, ref ReagentGrinderStartMessage message)
|
||||
{
|
||||
StartGrinder(ent, message.Program);
|
||||
}
|
||||
|
||||
private void OnToggleAutoModeMessage(Entity<ReagentGrinderComponent> ent, ref ReagentGrinderToggleAutoModeMessage message)
|
||||
{
|
||||
// Cycle through the enum values.
|
||||
ent.Comp.AutoMode = (GrinderAutoMode)(((byte)ent.Comp.AutoMode + 1) % Enum.GetValues<GrinderAutoMode>().Length);
|
||||
Dirty(ent);
|
||||
|
||||
UpdateUi(ent);
|
||||
}
|
||||
|
||||
private void OnEjectChamberAllMessage(Entity<ReagentGrinderComponent> ent, ref ReagentGrinderEjectChamberAllMessage message)
|
||||
{
|
||||
if (IsActive(ent.AsNullable()) || ent.Comp.InputContainer.ContainedEntities.Count <= 0)
|
||||
return;
|
||||
|
||||
_audioSystem.PlayPredicted(ent.Comp.ClickSound, ent.Owner, message.Actor);
|
||||
_containerSystem.EmptyContainer(ent.Comp.InputContainer);
|
||||
// UpdateUi is called in the resulting ContainerModifiedMessage.
|
||||
}
|
||||
|
||||
private void OnEjectChamberContentMessage(Entity<ReagentGrinderComponent> ent, ref ReagentGrinderEjectChamberContentMessage message)
|
||||
{
|
||||
if (IsActive(ent.AsNullable()))
|
||||
return;
|
||||
|
||||
if (!TryGetEntity(message.EntityId, out var toRemove))
|
||||
return;
|
||||
|
||||
if (_containerSystem.Remove(toRemove.Value, ent.Comp.InputContainer))
|
||||
{
|
||||
_audioSystem.PlayPredicted(ent.Comp.ClickSound, ent.Owner, message.Actor);
|
||||
}
|
||||
// UpdateUi is called in the resulting ContainerModifiedMessage.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The wzhzhzh of the grinder. Marks the grinder as active, but does not convert the items into reagents yet.
|
||||
/// </summary>
|
||||
private void StartGrinder(Entity<ReagentGrinderComponent> ent, GrinderProgram program)
|
||||
{
|
||||
if (IsActive(ent.AsNullable()))
|
||||
return;
|
||||
|
||||
if (!_power.IsPowered(ent.Owner))
|
||||
return;
|
||||
|
||||
var beaker = _itemSlotsSystem.GetItemOrNull(ent, ReagentGrinderComponent.BeakerSlotId);
|
||||
|
||||
// Do we have anything to grind/juice and a container to put the reagents in?
|
||||
if (ent.Comp.InputContainer.ContainedEntities.Count <= 0 || !HasComp<FitsInDispenserComponent>(beaker))
|
||||
return;
|
||||
|
||||
SoundSpecifier? sound;
|
||||
switch (program)
|
||||
{
|
||||
case GrinderProgram.Grind when ent.Comp.InputContainer.ContainedEntities.All(x => CanGrind(x)):
|
||||
sound = ent.Comp.GrindSound;
|
||||
break;
|
||||
case GrinderProgram.Juice when ent.Comp.InputContainer.ContainedEntities.All(x => CanJuice(x)):
|
||||
sound = ent.Comp.JuiceSound;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureComp<ActiveReagentGrinderComponent>(ent);
|
||||
_jitter.AddJitter(ent, -10, 100);
|
||||
_powerState.TrySetWorkingState(ent.Owner, true); // Not all grinders need power.
|
||||
ent.Comp.Program = program;
|
||||
ent.Comp.EndTime = _timing.CurTime + ent.Comp.WorkTime * ent.Comp.WorkTimeMultiplier;
|
||||
Dirty(ent);
|
||||
UpdateUi(ent);
|
||||
|
||||
// Unpredicted because we don't have the user in the update loop
|
||||
// TODO: Make the audio API sane https://github.com/space-wizards/RobustToolbox/issues/6436
|
||||
if (_net.IsServer)
|
||||
ent.Comp.AudioStream = _audioSystem.PlayPvs(sound, ent,
|
||||
AudioParams.Default.WithPitchScale(1 / ent.Comp.WorkTimeMultiplier))?.Entity; //slightly higher pitched
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts items into reagents and marks the grinder as inactive.
|
||||
/// </summary>
|
||||
private void FinishGrinding(Entity<ReagentGrinderComponent> ent)
|
||||
{
|
||||
if (ent.Comp.Program is not { } program)
|
||||
return; // Already finished.
|
||||
|
||||
ent.Comp.Program = null;
|
||||
ent.Comp.AudioStream = _audioSystem.Stop(ent.Comp.AudioStream);
|
||||
ent.Comp.EndTime = null; // It's important that we do this first or PredictedQueueDelete will fail to remove the entity from the container because the grinder is still active.
|
||||
Dirty(ent);
|
||||
// Remove deferred to avoid modifying the component we are currently enumerating over in the update loop.
|
||||
RemCompDeferred<ActiveReagentGrinderComponent>(ent);
|
||||
RemCompDeferred<JitteringComponent>(ent);
|
||||
_powerState.TrySetWorkingState(ent.Owner, false);
|
||||
|
||||
var beaker = _itemSlotsSystem.GetItemOrNull(ent.Owner, ReagentGrinderComponent.BeakerSlotId);
|
||||
if (beaker is null || !_solutionContainersSystem.TryGetFitsInDispenser(beaker.Value, out var beakerSolutionEntity, out var beakerSolution))
|
||||
return;
|
||||
|
||||
// Convert items into reagents.
|
||||
foreach (var item in ent.Comp.InputContainer.ContainedEntities.ToList())
|
||||
{
|
||||
var solution = GetGrinderSolution(item, program);
|
||||
|
||||
if (solution is null)
|
||||
continue;
|
||||
|
||||
// Delete the item or reduce its stack size.
|
||||
if (TryComp<StackComponent>(item, out var stack))
|
||||
{
|
||||
var totalVolume = solution.Volume * stack.Count;
|
||||
if (totalVolume <= 0)
|
||||
continue;
|
||||
|
||||
// Maximum number of items we can process in the stack without going over AvailableVolume
|
||||
// We add a small tolerance, because floats are inaccurate.
|
||||
var fitsCount = (int)(stack.Count * FixedPoint2.Min(beakerSolution.AvailableVolume / totalVolume + 0.01, 1));
|
||||
if (fitsCount <= 0)
|
||||
continue;
|
||||
|
||||
// Make a copy of the solution to scale
|
||||
// Otherwise we'll actually change the volume of the remaining stack too
|
||||
var scaledSolution = new Solution(solution);
|
||||
scaledSolution.ScaleSolution(fitsCount);
|
||||
solution = scaledSolution;
|
||||
|
||||
_stackSystem.SetCount((item, stack), stack.Count - fitsCount); // Setting to 0 will QueueDel
|
||||
}
|
||||
else
|
||||
{
|
||||
if (solution.Volume > beakerSolution.AvailableVolume)
|
||||
continue;
|
||||
|
||||
_destructible.DestroyEntity(item);
|
||||
}
|
||||
_solutionContainersSystem.TryAddSolution(beakerSolutionEntity.Value, solution);
|
||||
}
|
||||
// UpdateUi is called when the entity in the grinder is deleted or the solution in the beaker is changed.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the given grinder currently grinding/juicing?
|
||||
/// </summary>
|
||||
public bool IsActive(Entity<ReagentGrinderComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
// Don't use ActiveGrinderComponent for this because it is being removed deferred, meaning it will get updated at the end of the tick.
|
||||
// ActiveReagentGrinderComponent is only for improving the EntityQueryEnumerator performance in the update loop.
|
||||
return ent.Comp.EndTime != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the solutions from an entity using the specified Grinder program.
|
||||
|
|
@ -24,7 +356,7 @@ public abstract class SharedReagentGrinderSystem : EntitySystem
|
|||
switch (program)
|
||||
{
|
||||
case GrinderProgram.Grind:
|
||||
if (_solutionContainersSystem.TryGetSolution(ent.Owner, ent.Comp.GrindableSolution, out _, out var solution))
|
||||
if (_solutionContainersSystem.TryGetSolution(ent.Owner, ent.Comp.GrindableSolutionName, out _, out var solution))
|
||||
{
|
||||
return solution;
|
||||
}
|
||||
|
|
@ -41,15 +373,18 @@ public abstract class SharedReagentGrinderSystem : EntitySystem
|
|||
/// </summary>
|
||||
/// <param name="ent">The entity to check.</param>
|
||||
/// <returns>True if it can be ground, otherwise false.</returns>
|
||||
/// <remarks>
|
||||
/// Will it blend? That is the question!
|
||||
/// </remarks>
|
||||
public bool CanGrind(Entity<ExtractableComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (ent.Comp.GrindableSolution == null)
|
||||
if (ent.Comp.GrindableSolutionName == null)
|
||||
return false;
|
||||
|
||||
return _solutionContainersSystem.TryGetSolution(ent.Owner, ent.Comp.GrindableSolution, out _, out _);
|
||||
return _solutionContainersSystem.TryGetSolution(ent.Owner, ent.Comp.GrindableSolutionName, out _, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,116 +1,72 @@
|
|||
using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Kitchen
|
||||
namespace Content.Shared.Kitchen;
|
||||
|
||||
/// <summary>
|
||||
/// Sent by the client if they want to toggle the grinder's auto mode.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGrinderToggleAutoModeMessage() : BoundUserInterfaceMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Sent by the client if they want to start the grinder.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGrinderStartMessage(GrinderProgram program) : BoundUserInterfaceMessage
|
||||
{
|
||||
public sealed class SharedReagentGrinder
|
||||
{
|
||||
public static string BeakerSlotId = "beakerSlot";
|
||||
|
||||
public static string InputContainerId = "inputContainer";
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGrinderToggleAutoModeMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public ReagentGrinderToggleAutoModeMessage() { }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGrinderStartMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly GrinderProgram Program;
|
||||
public ReagentGrinderStartMessage(GrinderProgram program)
|
||||
{
|
||||
Program = program;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGrinderEjectChamberAllMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public ReagentGrinderEjectChamberAllMessage()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGrinderEjectChamberContentMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public NetEntity EntityId;
|
||||
public ReagentGrinderEjectChamberContentMessage(NetEntity entityId)
|
||||
{
|
||||
EntityId = entityId;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public GrinderProgram GrinderProgram;
|
||||
public ReagentGrinderWorkStartedMessage(GrinderProgram grinderProgram)
|
||||
{
|
||||
GrinderProgram = grinderProgram;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGrinderWorkCompleteMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public ReagentGrinderWorkCompleteMessage()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ReagentGrinderVisualState : byte
|
||||
{
|
||||
BeakerAttached
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum GrinderProgram : byte
|
||||
{
|
||||
Grind,
|
||||
Juice
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public enum ReagentGrinderUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum GrinderAutoMode : byte
|
||||
{
|
||||
Off,
|
||||
Grind,
|
||||
Juice
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public sealed class ReagentGrinderInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public bool IsBusy;
|
||||
public bool HasBeakerIn;
|
||||
public bool Powered;
|
||||
public bool CanJuice;
|
||||
public bool CanGrind;
|
||||
public NetEntity[] ChamberContents;
|
||||
public ReagentQuantity[]? ReagentQuantities;
|
||||
public GrinderAutoMode AutoMode;
|
||||
|
||||
public ReagentGrinderInterfaceState(bool isBusy, bool hasBeaker, bool powered, bool canJuice, bool canGrind, GrinderAutoMode autoMode, NetEntity[] chamberContents, ReagentQuantity[]? heldBeakerContents)
|
||||
{
|
||||
IsBusy = isBusy;
|
||||
HasBeakerIn = hasBeaker;
|
||||
Powered = powered;
|
||||
CanJuice = canJuice;
|
||||
CanGrind = canGrind;
|
||||
AutoMode = autoMode;
|
||||
ChamberContents = chamberContents;
|
||||
ReagentQuantities = heldBeakerContents;
|
||||
}
|
||||
}
|
||||
public GrinderProgram Program = program;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent by the client if they want to eject all grindable entities within the grinder.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGrinderEjectChamberAllMessage() : BoundUserInterfaceMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Sent by the client if they want eject a single grindable entity within the grinder.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGrinderEjectChamberContentMessage(NetEntity entityId) : BoundUserInterfaceMessage
|
||||
{
|
||||
public NetEntity EntityId = entityId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to be used for the grinder's appearance data.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum ReagentGrinderVisualState : byte
|
||||
{
|
||||
BeakerAttached
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The mode the grinder will use when activated. Grinding and juicing the same prototype will yield different results.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum GrinderProgram : byte
|
||||
{
|
||||
Grind,
|
||||
Juice
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key for the ReagentGrinderBoundUserInterface.
|
||||
/// </summary>
|
||||
[NetSerializable, Serializable]
|
||||
public enum ReagentGrinderUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The setting of the grinder's auto mode.
|
||||
/// </summary>
|
||||
[NetSerializable, Serializable]
|
||||
public enum GrinderAutoMode : byte
|
||||
{
|
||||
Off,
|
||||
Grind,
|
||||
Juice
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ public enum SmartFridgeUiKey : byte
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send by the client when trying to dispense an item inside the fridge.
|
||||
/// Sent by the client when trying to dispense an item inside the fridge.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SmartFridgeDispenseItemMessage(SmartFridgeEntry entry) : BoundUserInterfaceMessage
|
||||
|
|
@ -106,7 +106,7 @@ public sealed class SmartFridgeDispenseItemMessage(SmartFridgeEntry entry) : Bou
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send by the client when trying to remove an empty smart fridge entry from the list of items in the UI.
|
||||
/// Sent by the client when trying to remove an empty smart fridge entry from the list of items in the UI.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SmartFridgeRemoveEntryMessage(SmartFridgeEntry entry) : BoundUserInterfaceMessage
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
parent: [ BaseMachinePowered, SmallConstructibleMachine ]
|
||||
name: reagent grinder
|
||||
description: From BlenderTech. Will It Blend? Let's find out!
|
||||
suffix: grinder/juicer
|
||||
components:
|
||||
- type: ReagentGrinder
|
||||
- type: ActivatableUI
|
||||
|
|
@ -58,6 +57,26 @@
|
|||
machine_board: !type:Container
|
||||
machine_parts: !type:Container
|
||||
|
||||
- type: entity
|
||||
id: KitchenReagentGrinderBeaker
|
||||
parent: KitchenReagentGrinder
|
||||
suffix: beaker
|
||||
components:
|
||||
- type: ContainerFill
|
||||
containers:
|
||||
beakerSlot:
|
||||
- Beaker
|
||||
|
||||
- type: entity
|
||||
id: KitchenReagentGrinderBeakerLarge
|
||||
parent: KitchenReagentGrinder
|
||||
suffix: large beaker
|
||||
components:
|
||||
- type: ContainerFill
|
||||
containers:
|
||||
beakerSlot:
|
||||
- LargeBeaker
|
||||
|
||||
- type: entity
|
||||
parent: Recycler #too different so different parent
|
||||
id: ReagentGrinderIndustrial
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue