diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs index 7bbd7fad17..58e2ac2b8d 100644 --- a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs +++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs @@ -1,16 +1,11 @@ -using System.Collections.Generic; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; -using Content.Shared.Kitchen.Components; +using Content.Shared.Kitchen; 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.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Maths; using Robust.Shared.Prototypes; namespace Content.Client.Kitchen.UI @@ -68,15 +63,15 @@ namespace Content.Client.Kitchen.UI { switch (message) { - case SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage workStarted: + case ReagentGrinderWorkStartedMessage workStarted: GrindButton.Disabled = true; - GrindButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Grind ? Color.Green : Color.White; + GrindButton.Modulate = workStarted.GrinderProgram == GrinderProgram.Grind ? Color.Green : Color.White; JuiceButton.Disabled = true; - JuiceButton.Modulate = workStarted.GrinderProgram == SharedReagentGrinderComponent.GrinderProgram.Juice ? Color.Green : Color.White; + JuiceButton.Modulate = workStarted.GrinderProgram == GrinderProgram.Juice ? Color.Green : Color.White; BeakerContentBox.EjectButton.Disabled = true; ChamberContentBox.EjectButton.Disabled = true; break; - case SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage: + case ReagentGrinderWorkCompleteMessage: GrindButton.Disabled = false; JuiceButton.Disabled = false; GrindButton.Modulate = Color.White; diff --git a/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs b/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs index e4e8c6a073..e05c871146 100644 --- a/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs +++ b/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs @@ -1,11 +1,8 @@ using Content.Shared.Containers.ItemSlots; -using Content.Shared.Kitchen.Components; +using Content.Shared.Kitchen; using Robust.Client.GameObjects; using Robust.Client.UserInterface.Controls; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Prototypes; -using static Content.Shared.Chemistry.Components.Solution; namespace Content.Client.Kitchen.UI { @@ -55,10 +52,10 @@ namespace Content.Client.Kitchen.UI _menu?.HandleMessage(message); } - public void StartGrinding(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage()); - public void StartJuicing(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage()); - public void EjectAll(BaseButton.ButtonEventArgs? args = null) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage()); - public void EjectBeaker(BaseButton.ButtonEventArgs? args = null) => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentGrinderComponent.BeakerSlotId)); - public void EjectChamberContent(EntityUid uid) => SendMessage(new SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage(uid)); + public void StartGrinding(BaseButton.ButtonEventArgs? args = null) => SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Grind)); + public void StartJuicing(BaseButton.ButtonEventArgs? args = null) => SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Juice)); + public void EjectAll(BaseButton.ButtonEventArgs? args = null) => SendMessage(new ReagentGrinderEjectChamberAllMessage()); + public void EjectBeaker(BaseButton.ButtonEventArgs? args = null) => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentGrinder.BeakerSlotId)); + public void EjectChamberContent(EntityUid uid) => SendMessage(new ReagentGrinderEjectChamberContentMessage(uid)); } } diff --git a/Content.Client/Kitchen/Visualizers/ReagentGrinderVisualizer.cs b/Content.Client/Kitchen/Visualizers/ReagentGrinderVisualizer.cs index e02cc96ccb..04d3e178fc 100644 --- a/Content.Client/Kitchen/Visualizers/ReagentGrinderVisualizer.cs +++ b/Content.Client/Kitchen/Visualizers/ReagentGrinderVisualizer.cs @@ -1,7 +1,5 @@ using Robust.Client.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using static Content.Shared.Kitchen.Components.SharedReagentGrinderComponent; +using Content.Shared.Kitchen; namespace Content.Client.Kitchen.Visualizers { diff --git a/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs b/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs index 5d697ba2db..e8762c8203 100644 --- a/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs +++ b/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs @@ -1,8 +1,6 @@ -using Content.Shared.Chemistry.Components; -using Content.Shared.Containers.ItemSlots; -using Content.Shared.Kitchen.Components; +using Content.Shared.Kitchen; +using Content.Server.Kitchen.EntitySystems; using Robust.Shared.Audio; -using Robust.Shared.Containers; namespace Content.Server.Kitchen.Components { @@ -12,32 +10,36 @@ namespace Content.Server.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". /// - [RegisterComponent] - public sealed class ReagentGrinderComponent : SharedReagentGrinderComponent + [Access(typeof(ReagentGrinderSystem)), RegisterComponent] + public sealed class ReagentGrinderComponent : Component + { + //YAML serialization vars + [DataField("storageMaxEntities"), ViewVariables(VVAccess.ReadWrite)] + public int StorageMaxEntities = 16; + + [DataField("workTime"), ViewVariables(VVAccess.ReadWrite)] + public TimeSpan WorkTime = TimeSpan.FromSeconds(3.5); // Roughly matches the grind/juice sounds. + + [DataField("clickSound"), ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier ClickSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); + + [DataField("grindSound"), ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier GrindSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/blender.ogg"); + + [DataField("juiceSound"), ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier JuiceSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/juicer.ogg"); + } + + [Access(typeof(ReagentGrinderSystem)), RegisterComponent] + public sealed class ActiveReagentGrinderComponent : Component { /// - /// Can be null since we won't always have a beaker in the grinder. + /// Remaining time until the grinder finishes grinding/juicing. /// - [ViewVariables] public Solution? BeakerSolution; + [ViewVariables] + public float WorkTimer; - /// - /// Contains the things that are going to be ground or juiced. - /// - [ViewVariables] public Container Chamber = default!; - - /// - /// Is the machine actively doing something and can't be used right now? - /// - public bool Busy; - - //YAML serialization vars - [ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] public int StorageCap = 16; - [ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] public int WorkTime = 3500; //3.5 seconds, completely arbitrary for now. - [DataField("clickSound")] public SoundSpecifier ClickSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); - [DataField("grindSound")] public SoundSpecifier GrindSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/blender.ogg"); - [DataField("juiceSound")] public SoundSpecifier JuiceSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/juicer.ogg"); - - [DataField("beakerSlot")] - public ItemSlot BeakerSlot = new(); + [ViewVariables] + public GrinderProgram Program; } } diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index e498cdf656..1a90051471 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -1,14 +1,15 @@ using System.Linq; using Content.Server.Chemistry.EntitySystems; using Content.Server.Kitchen.Components; -using Content.Server.Kitchen.Events; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Stack; -using Content.Server.UserInterface; +using Content.Shared.Chemistry.Components; using Content.Shared.Containers.ItemSlots; +using Content.Shared.FixedPoint; using Content.Shared.Interaction; -using Content.Shared.Kitchen.Components; +using Content.Shared.Kitchen; +using Content.Shared.Popups; using Content.Shared.Random.Helpers; using JetBrains.Annotations; using Robust.Server.GameObjects; @@ -24,287 +25,272 @@ namespace Content.Server.Kitchen.EntitySystems { [Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!; [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; - - private Queue _uiUpdateQueue = new(); + [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!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(OnComponentRemove); - - SubscribeLocalEvent(OnPowerChange); + SubscribeLocalEvent((uid, component, _) => UpdateUiState(uid, component)); + SubscribeLocalEvent( + (EntityUid uid, ReagentGrinderComponent component, ref PowerChangedEvent _) => UpdateUiState(uid, component)); SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(ExtractableScaling); SubscribeLocalEvent(OnContainerModified); SubscribeLocalEvent(OnContainerModified); SubscribeLocalEvent(OnEntRemoveAttempt); - } - private void OnPowerChange(EntityUid uid, ReagentGrinderComponent component, ref PowerChangedEvent args) - { - EnqueueUiUpdate(component); - } - - private void OnEntRemoveAttempt(EntityUid uid, ReagentGrinderComponent component, ContainerIsRemovingAttemptEvent args) - { - if (component.Busy) - args.Cancel(); - } - - private void OnContainerModified(EntityUid uid, ReagentGrinderComponent component, ContainerModifiedMessage args) - { - EnqueueUiUpdate(component); - - if (args.Container.ID != SharedReagentGrinderComponent.BeakerSlotId) - return; - - if (TryComp(component.Owner, out AppearanceComponent? appearance)) - appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached, component.BeakerSlot.HasItem); - - component.BeakerSolution = null; - if (component.BeakerSlot.Item != null) - _solutionsSystem.TryGetFitsInDispenser(component.BeakerSlot.Item.Value, out component.BeakerSolution); - } - - private void ExtractableScaling(EntityUid uid, StackComponent component, ExtractableScalingEvent args) - { - args.Scalar *= component.Count; // multiply scalar by amount of items in stack - } - - private void OnInteractUsing(EntityUid uid, ReagentGrinderComponent component, InteractUsingEvent args) - { - if (args.Handled) return; - - var heldEnt = args.Used; - - //See if the user is trying to insert something they want to be ground/juiced. - if (!HasComp(heldEnt)) - { - //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, EntityManager)) - return; - - 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); - - _itemSlotsSystem.AddItemSlot(uid, SharedReagentGrinderComponent.BeakerSlotId, component.BeakerSlot); - - //A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user. - component.Chamber = - ContainerHelpers.EnsureContainer(component.Owner, - $"{component.Name}-entityContainerContainer"); - - // TODO just directly subscribe to UI events. - var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key); - if (bui != null) - { - bui.OnReceiveMessage += msg => OnUIMessageReceived(uid, component, msg); - } - } - - private void OnComponentRemove(EntityUid uid, ReagentGrinderComponent component, ComponentRemove args) - { - _itemSlotsSystem.RemoveItemSlot(uid, component.BeakerSlot); - } - - private void OnUIMessageReceived(EntityUid uid, ReagentGrinderComponent component, - ServerBoundUserInterfaceMessage message) - { - if (component.Busy || message.Session.AttachedEntity is not {} attached) - { - return; - } - - switch (message.Message) - { - case SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage msg: - if (!this.IsPowered(component.Owner, EntityManager)) break; - ClickSound(component); - DoWork(component, attached, - SharedReagentGrinderComponent.GrinderProgram.Grind); - break; - - case SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage msg: - if (!this.IsPowered(component.Owner, EntityManager)) break; - ClickSound(component); - DoWork(component, attached, - 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.TryFirstOrNull(x => x == msg.EntityID, out var ent)) - { - component.Chamber.Remove(ent.Value); - SharedEntityExtensions.RandomOffset(ent.Value, 0.4f); - EnqueueUiUpdate(component); - ClickSound(component); - } - - break; - } + SubscribeLocalEvent(OnStartMessage); + SubscribeLocalEvent(OnEjectChamberAllMessage); + SubscribeLocalEvent(OnEjectChamberContentMessage); } public override void Update(float frameTime) { base.Update(frameTime); - while (_uiUpdateQueue.TryDequeue(out var comp)) + foreach (var (active, reagentGrinder) in EntityQuery()) { - if (comp.Deleted) + var uid = reagentGrinder.Owner; + active.WorkTimer -= frameTime; + + if (active.WorkTimer > 0) continue; - bool canJuice = false; - bool canGrind = false; - if (comp.BeakerSlot.HasItem) - { - foreach (var entity in comp.Chamber.ContainedEntities) - { - if (canJuice || !EntityManager.TryGetComponent(entity, out ExtractableComponent? component)) continue; + RemCompDeferred(uid); - canJuice = component.JuiceSolution != null; - canGrind = component.GrindableSolution != null - && _solutionsSystem.TryGetSolution(entity, component.GrindableSolution, out _); + var inputContainer = _containerSystem.EnsureContainer(uid, SharedReagentGrinder.InputContainerId); + var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId); + if (outputContainer is null || !_solutionsSystem.TryGetFitsInDispenser(outputContainer.Value, out var containerSolution)) + continue; + + foreach (var item in inputContainer.ContainedEntities.ToList()) + { + var solution = active.Program switch + { + GrinderProgram.Grind => GetGrindSolution(item), + GrinderProgram.Juice => CompOrNull(item)?.JuiceSolution, + _ => null, + }; + + if (solution is null) + continue; + + if (TryComp(item, out var stack)) + { + var totalVolume = solution.TotalVolume * 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; + + solution.ScaleSolution(fitsCount); + _stackSystem.SetCount(item, stack.Count - fitsCount); // Setting to 0 will QueueDel } + else + { + if (solution.TotalVolume > containerSolution.AvailableVolume) + continue; + + QueueDel(item); + } + + _solutionsSystem.TryAddSolution(outputContainer.Value, containerSolution, solution); } - comp.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.SetState( - new ReagentGrinderInterfaceState - ( - comp.Busy, - comp.BeakerSlot.HasItem, - this.IsPowered(comp.Owner, EntityManager), - canJuice, - canGrind, - comp.Chamber.ContainedEntities.Select(item => item).ToArray(), - //Remember the beaker can be null! - comp.BeakerSolution?.Contents.ToArray() - )); + _userInterfaceSystem.TrySendUiMessage(uid, ReagentGrinderUiKey.Key, + new ReagentGrinderWorkCompleteMessage()); + + UpdateUiState(uid, reagentGrinder); + } + } + + private void OnEntRemoveAttempt(EntityUid uid, ReagentGrinderComponent reagentGrinder, ContainerIsRemovingAttemptEvent args) + { + if (HasComp(uid)) + args.Cancel(); + } + + private void OnContainerModified(EntityUid uid, ReagentGrinderComponent reagentGrinder, ContainerModifiedMessage args) + { + UpdateUiState(uid, reagentGrinder); + + var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId); + _appearanceSystem.SetData(uid, ReagentGrinderVisualState.BeakerAttached, outputContainer.HasValue); + } + + private void OnInteractUsing(EntityUid uid, ReagentGrinderComponent reagentGrinder, InteractUsingEvent args) + { + var heldEnt = args.Used; + var inputContainer = _containerSystem.EnsureContainer(uid, SharedReagentGrinder.InputContainerId); + + if (!HasComp(heldEnt)) + { + if (!HasComp(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"), uid, Filter.Entities(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 >= reagentGrinder.StorageMaxEntities) + return; + + if (!inputContainer.Insert(heldEnt, EntityManager)) + return; + + args.Handled = true; + } + + private void UpdateUiState(EntityUid uid, ReagentGrinderComponent reagentGrinder) + { + var inputContainer = _containerSystem.EnsureContainer(uid, SharedReagentGrinder.InputContainerId); + var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId); + Solution? containerSolution = null; + var isBusy = HasComp(uid); + var canJuice = false; + var canGrind = false; + + if (outputContainer is not null + && _solutionsSystem.TryGetFitsInDispenser(outputContainer.Value, out containerSolution) + && inputContainer.ContainedEntities.Count > 0) + { + canGrind = inputContainer.ContainedEntities.All(CanGrind); + canJuice = inputContainer.ContainedEntities.All(CanJuice); + } + + var state = new ReagentGrinderInterfaceState( + isBusy, + outputContainer.HasValue, + this.IsPowered(uid, EntityManager), + canJuice, + canGrind, + inputContainer.ContainedEntities.Select(item => item).ToArray(), + containerSolution?.Contents.ToArray() + ); + _userInterfaceSystem.TrySetUiState(uid, ReagentGrinderUiKey.Key, state); + } + + private void OnStartMessage(EntityUid uid, ReagentGrinderComponent reagentGrinder, ReagentGrinderStartMessage message) + { + if (!this.IsPowered(uid, EntityManager) || HasComp(uid)) + return; + + DoWork(uid, reagentGrinder, message.Session.AttachedEntity, message.Program); + } + + private void OnEjectChamberAllMessage(EntityUid uid, ReagentGrinderComponent reagentGrinder, ReagentGrinderEjectChamberAllMessage message) + { + var inputContainer = _containerSystem.EnsureContainer(uid, SharedReagentGrinder.InputContainerId); + + if (HasComp(uid) || inputContainer.ContainedEntities.Count <= 0) + return; + + ClickSound(uid, reagentGrinder); + foreach (var entity in inputContainer.ContainedEntities.ToList()) + { + inputContainer.Remove(entity); + entity.RandomOffset(0.4f); + } + UpdateUiState(uid, reagentGrinder); + } + + private void OnEjectChamberContentMessage(EntityUid uid, ReagentGrinderComponent reagentGrinder, ReagentGrinderEjectChamberContentMessage message) + { + if (HasComp(uid)) + return; + + var inputContainer = _containerSystem.EnsureContainer(uid, SharedReagentGrinder.InputContainerId); + + if (inputContainer.Remove(message.EntityId)) + { + message.EntityId.RandomOffset(0.4f); + ClickSound(uid, reagentGrinder); + UpdateUiState(uid, reagentGrinder); } } /// /// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker. /// - /// true for wanting to juice, false for wanting to grind. - private void DoWork(ReagentGrinderComponent component, EntityUid user, - SharedReagentGrinderComponent.GrinderProgram program) + /// Which program, such as grind or juice + private void DoWork(EntityUid uid, ReagentGrinderComponent reagentGrinder, EntityUid? user, GrinderProgram program) { - //Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go? - if (!this.IsPowered(component.Owner, EntityManager) || - component.Busy || component.Chamber.ContainedEntities.Count <= 0 || - component.BeakerSlot.Item is not EntityUid beakerEntity || - component.BeakerSolution == null) - { + var inputContainer = _containerSystem.EnsureContainer(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(outputContainer)) return; - } - component.Busy = true; - - var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key); - bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage(program)); + SoundSpecifier? sound = null; switch (program) { - case SharedReagentGrinderComponent.GrinderProgram.Grind: - SoundSystem.Play(component.GrindSound.GetSound(), Filter.Pvs(component.Owner), 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, () => - { - foreach (var item in component.Chamber.ContainedEntities.ToList()) - { - if (!EntityManager.TryGetComponent(item, out ExtractableComponent? extract) - || extract.GrindableSolution == null - || !_solutionsSystem.TryGetSolution(item, extract.GrindableSolution, out var solution)) continue; - - var juiceEvent = new ExtractableScalingEvent(); // default of scalar is always 1.0 - RaiseLocalEvent(item, juiceEvent, false); - if (component.BeakerSolution.CurrentVolume + solution.CurrentVolume * juiceEvent.Scalar > - component.BeakerSolution.MaxVolume) continue; - solution.ScaleSolution(juiceEvent.Scalar); - _solutionsSystem.TryAddSolution(beakerEntity, component.BeakerSolution, solution); - EntityManager.DeleteEntity(item); - } - - component.Busy = false; - EnqueueUiUpdate(component); - bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage()); - }); + case GrinderProgram.Grind when inputContainer.ContainedEntities.All(CanGrind): + sound = reagentGrinder.GrindSound; break; - - case SharedReagentGrinderComponent.GrinderProgram.Juice: - SoundSystem.Play(component.JuiceSound.GetSound(), Filter.Pvs(component.Owner), component.Owner, AudioParams.Default); - component.Owner.SpawnTimer(component.WorkTime, () => - { - foreach (var item in component.Chamber.ContainedEntities.ToList()) - { - if (!EntityManager.TryGetComponent(item, out var juiceMe) - || juiceMe.JuiceSolution == null) - { - Logger.Warning("Couldn't find a juice solution on entityUid:{0}", item); - continue; - } - var juiceEvent = new ExtractableScalingEvent(); // default of scalar is always 1.0 - if (EntityManager.HasComponent(item)) - { - RaiseLocalEvent(item, juiceEvent, true); - } - - if (component.BeakerSolution.CurrentVolume + juiceMe.JuiceSolution.TotalVolume * juiceEvent.Scalar > component.BeakerSolution.MaxVolume) - continue; - juiceMe.JuiceSolution.ScaleSolution(juiceEvent.Scalar); - _solutionsSystem.TryAddSolution(beakerEntity, component.BeakerSolution, juiceMe.JuiceSolution); - EntityManager.DeleteEntity(item); - } - - bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage()); - component.Busy = false; - EnqueueUiUpdate(component); - }); + case GrinderProgram.Juice when inputContainer.ContainedEntities.All(CanJuice): + sound = reagentGrinder.JuiceSound; break; + default: + return; } + + var active = AddComp(uid); + active.WorkTimer = (float) reagentGrinder.WorkTime.TotalSeconds; + active.Program = program; + + _audioSystem.PlayPvs(sound, uid); + _userInterfaceSystem.TrySendUiMessage(uid, ReagentGrinderUiKey.Key, + new ReagentGrinderWorkStartedMessage(program)); } - private void ClickSound(ReagentGrinderComponent component) + private void ClickSound(EntityUid uid, ReagentGrinderComponent reagentGrinder) { - SoundSystem.Play(component.ClickSound.GetSound(), Filter.Pvs(component.Owner), component.Owner, AudioParams.Default.WithVolume(-2f)); + _audioSystem.PlayPvs(reagentGrinder.ClickSound, uid, AudioParams.Default.WithVolume(-2f)); + } + + private Solution? GetGrindSolution(EntityUid uid) + { + if (TryComp(uid, out var extractable) + && extractable.GrindableSolution is not null + && _solutionsSystem.TryGetSolution(uid, extractable.GrindableSolution, out var solution)) + { + return solution; + } + else + return null; + } + + private bool CanGrind(EntityUid uid) + { + var solutionName = CompOrNull(uid)?.GrindableSolution; + + return solutionName is not null && _solutionsSystem.TryGetSolution(uid, solutionName, out _); + } + + private bool CanJuice(EntityUid uid) + { + return CompOrNull(uid)?.JuiceSolution is not null; } } } diff --git a/Content.Server/Kitchen/Events/ExtractableScalingEvent.cs b/Content.Server/Kitchen/Events/ExtractableScalingEvent.cs deleted file mode 100644 index 875ac02af8..0000000000 --- a/Content.Server/Kitchen/Events/ExtractableScalingEvent.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Content.Server.Kitchen.Events -{ - /// - /// Used in scaling amount of solution to extract in juicing - /// - public sealed class ExtractableScalingEvent : EntityEventArgs - { - - public ExtractableScalingEvent() - { - Scalar = 1f; - } - - public float Scalar - { - get; - set; - } - - } -} diff --git a/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs b/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs deleted file mode 100644 index eaa5485ad3..0000000000 --- a/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs +++ /dev/null @@ -1,104 +0,0 @@ -using Content.Shared.Chemistry.Components; -using Robust.Shared.Serialization; - -namespace Content.Shared.Kitchen.Components -{ - - public abstract class SharedReagentGrinderComponent : Component - { - public static string BeakerSlotId = "ReagentGrinder-reagentContainerContainer"; - - [Serializable, NetSerializable] - public sealed class ReagentGrinderGrindStartMessage : BoundUserInterfaceMessage - { - public ReagentGrinderGrindStartMessage() - { - } - } - - [Serializable, NetSerializable] - public sealed class ReagentGrinderJuiceStartMessage : BoundUserInterfaceMessage - { - public ReagentGrinderJuiceStartMessage() - { - } - } - - [Serializable, NetSerializable] - public sealed class ReagentGrinderEjectChamberAllMessage : BoundUserInterfaceMessage - { - public ReagentGrinderEjectChamberAllMessage() - { - } - } - - [Serializable, NetSerializable] - public sealed class ReagentGrinderEjectChamberContentMessage : BoundUserInterfaceMessage - { - public EntityUid EntityID; - public ReagentGrinderEjectChamberContentMessage(EntityUid 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 - } - - [NetSerializable, Serializable] - public enum ReagentGrinderUiKey : byte - { - Key - } - - [Serializable, NetSerializable] - public enum GrinderProgram : byte - { - 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 EntityUid[] ChamberContents; - public Solution.ReagentQuantity[]? ReagentQuantities; - public ReagentGrinderInterfaceState(bool isBusy, bool hasBeaker, bool powered, bool canJuice, bool canGrind, EntityUid[] chamberContents, Solution.ReagentQuantity[]? heldBeakerContents) - { - IsBusy = isBusy; - HasBeakerIn = hasBeaker; - Powered = powered; - CanJuice = canJuice; - CanGrind = canGrind; - ChamberContents = chamberContents; - ReagentQuantities = heldBeakerContents; - } - } -} diff --git a/Content.Shared/Kitchen/SharedReagentGrinder.cs b/Content.Shared/Kitchen/SharedReagentGrinder.cs new file mode 100644 index 0000000000..a9a3d88bb8 --- /dev/null +++ b/Content.Shared/Kitchen/SharedReagentGrinder.cs @@ -0,0 +1,99 @@ +using Content.Shared.Chemistry.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.Kitchen +{ + public sealed class SharedReagentGrinder + { + public static string BeakerSlotId = "beakerSlot"; + + public static string InputContainerId = "inputContainer"; + } + + [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 EntityUid EntityId; + public ReagentGrinderEjectChamberContentMessage(EntityUid 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 + } + + [NetSerializable, Serializable] + public sealed class ReagentGrinderInterfaceState : BoundUserInterfaceState + { + public bool IsBusy; + public bool HasBeakerIn; + public bool Powered; + public bool CanJuice; + public bool CanGrind; + public EntityUid[] ChamberContents; + public Solution.ReagentQuantity[]? ReagentQuantities; + public ReagentGrinderInterfaceState(bool isBusy, bool hasBeaker, bool powered, bool canJuice, bool canGrind, EntityUid[] chamberContents, Solution.ReagentQuantity[]? heldBeakerContents) + { + IsBusy = isBusy; + HasBeakerIn = hasBeaker; + Powered = powered; + CanJuice = canJuice; + CanGrind = canGrind; + ChamberContents = chamberContents; + ReagentQuantities = heldBeakerContents; + } + } +} diff --git a/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl b/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl index ddb8f9417f..30af6e9872 100644 --- a/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl +++ b/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl @@ -2,7 +2,8 @@ reagent-grinder-bound-user-interface-instant-button = INSTANT reagent-grinder-bound-user-interface-cook-time-label = COOK TIME - +reagent-grinder-component-cannot-put-entity-message = You can't put this in the reagent grinder! + grinder-menu-title = All-In-One Grinder 3000 grinder-menu-grind-button = Grind grinder-menu-juice-button = Juice diff --git a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml index 45c97c457d..a2c1d0ad32 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml @@ -8,14 +8,6 @@ - type: Transform anchored: true - type: ReagentGrinder - beakerSlot: - insertSound: /Audio/Machines/machine_switch.ogg - ejectSound: /Audio/Machines/machine_switch.ogg - soundOptions: - volume: -2 - whitelist: - components: - - FitsInDispenser - type: ActivatableUI key: enum.ReagentGrinderUiKey.Key - type: UserInterface @@ -44,11 +36,16 @@ - type: ApcPowerReceiver powerLoad: 300 - type: ItemSlots + slots: + beakerSlot: + whitelist: + components: + - FitsInDispenser - type: Machine board: ReagentGrinderMachineCircuitboard - type: ContainerContainer containers: - ReagentGrinder-reagentContainerContainer: !type:ContainerSlot - ReagentGrinder-entityContainerContainer: !type:Container + beakerSlot: !type:ContainerSlot + inputContainer: !type:Container machine_board: !type:Container machine_parts: !type:Container