From 56cd917c7351355817d0396802c3d43571aad0af Mon Sep 17 00:00:00 2001 From: Debug <49997488+DebugOk@users.noreply.github.com> Date: Fri, 26 Jan 2024 21:22:43 +0100 Subject: [PATCH] Fix Nyano systems for solution refactor --- .../Kitchen/Components/DeepFriedComponent.cs | 1 + .../Kitchen/Components/DeepFryerComponent.cs | 1 + .../Kitchen/UI/DeepFryerBoundUserInterface.cs | 2 +- .../Kitchen/UI/DeepFryerWindow.xaml.cs | 2 +- .../Visualizers/DeepFriedVisualizer.cs | 1 + .../Visualizers/DeepFryerVisualizer.cs | 1 + .../Tests/Nyanotrasen/DeepFryerTest.cs | 2 + .../Abilities/Felinid/FelinidSystem.cs | 7 +- .../PsionicRegenerationPowerSystem.cs | 1 + .../SolutionRegenerationSwitcherComponent.cs | 20 - .../SolutionRegenerationSwitcherSystem.cs | 98 - .../Kitchen/Components/DeepFriedComponent.cs | 1 + .../Kitchen/Components/DeepFryerComponent.cs | 29 +- .../EntitySystems/DeepFryerSystem.Results.cs | 181 ++ .../EntitySystems/DeepFryerSystem.Storage.cs | 93 + .../EntitySystems/DeepFryerSystem.Update.cs | 128 ++ .../Kitchen/EntitySystems/DeepFryerSystem.cs | 1573 +++++++---------- Content.Server/Nyanotrasen/Mail/MailSystem.cs | 1 + .../Research/Oracle/OracleSystem.cs | 3 +- .../Kitchen/ClearSlagDoAfterEvent.cs | 4 +- .../Components/SharedDeepFriedComponent.cs | 2 +- .../Components/SharedDeepFryerComponent.cs | 2 +- .../Kitchen/SharedDeepfryerSystem.cs | 5 + .../Kitchen/UI/DeepFryerMessages.cs | 4 +- 24 files changed, 1077 insertions(+), 1085 deletions(-) delete mode 100644 Content.Server/Nyanotrasen/Chemistry/SolutionRegenerationSwitcher/SolutionRegenerationSwitcherComponent.cs delete mode 100644 Content.Server/Nyanotrasen/Chemistry/SolutionRegenerationSwitcher/SolutionRegenerationSwitcherSystem.cs create mode 100644 Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Results.cs create mode 100644 Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Storage.cs create mode 100644 Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Update.cs create mode 100644 Content.Shared/Nyanotrasen/Kitchen/SharedDeepfryerSystem.cs diff --git a/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs b/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs index a1252cf43ed..689dcc00fcb 100644 --- a/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs +++ b/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Kitchen.Components; +using Content.Shared.Nyanotrasen.Kitchen.Components; namespace Content.Client.Kitchen.Components { diff --git a/Content.Client/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs b/Content.Client/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs index 4123eb341ca..34d5a864d49 100644 --- a/Content.Client/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs +++ b/Content.Client/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Kitchen.Components; +using Content.Shared.Nyanotrasen.Kitchen.Components; namespace Content.Client.Kitchen.Components { diff --git a/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerBoundUserInterface.cs b/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerBoundUserInterface.cs index 8db884328e4..1d4427c8154 100644 --- a/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerBoundUserInterface.cs +++ b/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerBoundUserInterface.cs @@ -1,5 +1,5 @@ +using Content.Shared.Nyanotrasen.Kitchen.UI; using Robust.Client.GameObjects; -using Content.Shared.Kitchen.UI; namespace Content.Client.Nyanotrasen.Kitchen.UI { diff --git a/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerWindow.xaml.cs b/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerWindow.xaml.cs index 7eb8eabd2ca..4f9e274eb06 100644 --- a/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerWindow.xaml.cs +++ b/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerWindow.xaml.cs @@ -1,9 +1,9 @@ +using Content.Shared.Nyanotrasen.Kitchen.UI; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; -using Content.Shared.Kitchen.UI; namespace Content.Client.Nyanotrasen.Kitchen.UI { diff --git a/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs b/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs index 58580e3f5ac..97fea3d0ca9 100644 --- a/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs +++ b/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs @@ -5,6 +5,7 @@ using Content.Client.Kitchen.Components; using Content.Shared.Clothing; using Content.Shared.Hands; using Content.Shared.Kitchen.Components; +using Content.Shared.Nyanotrasen.Kitchen.Components; namespace Content.Client.Kitchen.Visualizers { diff --git a/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFryerVisualizer.cs b/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFryerVisualizer.cs index 90bef2bb8e7..5d208d09598 100644 --- a/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFryerVisualizer.cs +++ b/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFryerVisualizer.cs @@ -3,6 +3,7 @@ using Content.Client.Chemistry.Visualizers; using Content.Client.Kitchen.Components; using Content.Shared.Chemistry.Components; using Content.Shared.Kitchen.Components; +using Content.Shared.Nyanotrasen.Kitchen.Components; namespace Content.Client.Kitchen.Visualizers { diff --git a/Content.IntegrationTests/Tests/Nyanotrasen/DeepFryerTest.cs b/Content.IntegrationTests/Tests/Nyanotrasen/DeepFryerTest.cs index 3e1a7ff52c8..1181f3a169e 100644 --- a/Content.IntegrationTests/Tests/Nyanotrasen/DeepFryerTest.cs +++ b/Content.IntegrationTests/Tests/Nyanotrasen/DeepFryerTest.cs @@ -1,6 +1,8 @@ #nullable enable annotations using Content.Server.Kitchen.Components; using Content.Server.Kitchen.EntitySystems; +using Content.Server.Nyanotrasen.Kitchen.Components; +using Content.Server.Nyanotrasen.Kitchen.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.Reflection; diff --git a/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidSystem.cs b/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidSystem.cs index 8c66593f00b..4426d8b40eb 100644 --- a/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidSystem.cs +++ b/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.IdentityManagement; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Server.Body.Components; +using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Medical; using Content.Server.Nutrition.EntitySystems; using Content.Server.Nutrition.Components; @@ -162,13 +163,13 @@ public sealed partial class FelinidSystem : EntitySystem var hairball = EntityManager.SpawnEntity(component.HairballPrototype, Transform(uid).Coordinates); var hairballComp = Comp(hairball); - if (TryComp(uid, out var bloodstream)) + if (TryComp(uid, out var bloodstream) && bloodstream.ChemicalSolution.HasValue) { - var temp = bloodstream.ChemicalSolution.SplitSolution(20); + var temp = _solutionSystem.SplitSolution(bloodstream.ChemicalSolution.Value, 20); if (_solutionSystem.TryGetSolution(hairball, hairballComp.SolutionName, out var hairballSolution)) { - _solutionSystem.TryAddSolution(hairball, hairballSolution, temp); + _solutionSystem.TryAddSolution(hairballSolution.Value, temp); } } } diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs index be6b5efe95c..2eca3173b6d 100644 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs @@ -4,6 +4,7 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Content.Server.Body.Components; using Content.Server.Body.Systems; +using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Chemistry.EntitySystems; using Content.Server.DoAfter; using Content.Shared.Abilities.Psionics; diff --git a/Content.Server/Nyanotrasen/Chemistry/SolutionRegenerationSwitcher/SolutionRegenerationSwitcherComponent.cs b/Content.Server/Nyanotrasen/Chemistry/SolutionRegenerationSwitcher/SolutionRegenerationSwitcherComponent.cs deleted file mode 100644 index 03745113e9f..00000000000 --- a/Content.Server/Nyanotrasen/Chemistry/SolutionRegenerationSwitcher/SolutionRegenerationSwitcherComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Shared.Chemistry.Components; - -namespace Content.Server.Chemistry.Components -{ - [RegisterComponent] - public sealed partial class SolutionRegenerationSwitcherComponent : Component - { - [DataField("options", required: true), ViewVariables(VVAccess.ReadWrite)] - public List Options = default!; - - [DataField("currentIndex"), ViewVariables(VVAccess.ReadWrite)] - public int CurrentIndex = 0; - - /// - /// Should the already generated solution be kept when switching? - /// - [DataField("keepSolution"), ViewVariables(VVAccess.ReadWrite)] - public bool KeepSolution = false; - } -} diff --git a/Content.Server/Nyanotrasen/Chemistry/SolutionRegenerationSwitcher/SolutionRegenerationSwitcherSystem.cs b/Content.Server/Nyanotrasen/Chemistry/SolutionRegenerationSwitcher/SolutionRegenerationSwitcherSystem.cs deleted file mode 100644 index d1edef1ee88..00000000000 --- a/Content.Server/Nyanotrasen/Chemistry/SolutionRegenerationSwitcher/SolutionRegenerationSwitcherSystem.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Robust.Shared.Prototypes; -using Content.Server.Chemistry.Components; -using Content.Server.Popups; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Verbs; - -namespace Content.Server.Chemistry.EntitySystems -{ - public sealed class SolutionRegenerationSwitcherSystem : EntitySystem - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; - [Dependency] private readonly PopupSystem _popups = default!; - - private ISawmill _sawmill = default!; - - public override void Initialize() - { - base.Initialize(); - - _sawmill = Logger.GetSawmill("chemistry"); - - SubscribeLocalEvent>(AddSwitchVerb); - } - - private void AddSwitchVerb(EntityUid uid, SolutionRegenerationSwitcherComponent component, GetVerbsEvent args) - { - if (!args.CanInteract || !args.CanAccess) - return; - - if (component.Options.Count <= 1) - return; - - AlternativeVerb verb = new() - { - Act = () => - { - SwitchReagent(uid, component, args.User); - }, - Text = Loc.GetString("autoreagent-switch"), - Priority = 2 - }; - args.Verbs.Add(verb); - } - - private void SwitchReagent(EntityUid uid, SolutionRegenerationSwitcherComponent component, EntityUid user) - { - if (!TryComp(uid, out var solutionRegenerationComponent)) - { - _sawmill.Warning($"{ToPrettyString(uid)} has no SolutionRegenerationComponent."); - return; - } - - if (component.CurrentIndex + 1 == component.Options.Count) - component.CurrentIndex = 0; - else - component.CurrentIndex++; - - if (!_solutionSystem.TryGetSolution(uid, solutionRegenerationComponent.Solution, out var solution)) - { - _sawmill.Error($"Can't get SolutionRegeneration.Solution for {ToPrettyString(uid)}"); - return; - } - - var newSolution = component.Options[component.CurrentIndex]; - var primaryId = newSolution.GetPrimaryReagentId(); - if (primaryId == null) - { - _sawmill.Error($"Can't get PrimaryReagentId for {ToPrettyString(uid)} on index {component.CurrentIndex}."); - return; - } - ReagentPrototype? proto; - - //Only reagents with spritePath property can change appearance of transformable containers! - if (!string.IsNullOrWhiteSpace(primaryId?.Prototype)) - { - if (!_prototypeManager.TryIndex(primaryId.Value.Prototype, out proto)) - { - _sawmill.Error($"Can't get get reagent prototype {primaryId} for {ToPrettyString(uid)}"); - return; - } - } - else return; - - // Empty out the current solution. - if (!component.KeepSolution) - solution.RemoveAllSolution(); - - // Replace the generating solution with the newly selected solution. - var generated = solutionRegenerationComponent.Generated; - generated.RemoveAllSolution(); - _solutionSystem.TryAddSolution(uid, generated, newSolution); - - _popups.PopupEntity(Loc.GetString("autoregen-switched", ("reagent", proto.LocalizedName)), user, user); - } - } -} diff --git a/Content.Server/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs b/Content.Server/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs index 3bbb96e65d0..a0cf888307b 100644 --- a/Content.Server/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs +++ b/Content.Server/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Kitchen.Components; +using Content.Shared.Nyanotrasen.Kitchen.Components; namespace Content.Server.Kitchen.Components { diff --git a/Content.Server/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs b/Content.Server/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs index 07cc96e8e78..72c3297feb6 100644 --- a/Content.Server/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs +++ b/Content.Server/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs @@ -1,23 +1,22 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Construction.Prototypes; +using Content.Shared.FixedPoint; +using Content.Shared.Nutrition; +using Content.Shared.Nyanotrasen.Kitchen; +using Content.Shared.Nyanotrasen.Kitchen.Components; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; -using Content.Server.Kitchen.EntitySystems; -using Content.Server.Nutrition; -using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Construction.Prototypes; -using Content.Shared.Kitchen.Components; -using Content.Shared.Nutrition; -using Content.Shared.FixedPoint; -using Content.Shared.Whitelist; -namespace Content.Server.Kitchen.Components +namespace Content.Server.Nyanotrasen.Kitchen.Components { [RegisterComponent] - [Access(typeof(DeepFryerSystem))] + [Access(typeof(SharedDeepfryerSystem))] // This line appears to be depracted: [ComponentReference(typeof(SharedDeepFryerComponent))] public sealed partial class DeepFryerComponent : SharedDeepFryerComponent { @@ -86,7 +85,7 @@ namespace Content.Server.Kitchen.Components /// /// What reagents are added to tasty deep-fried food? - /// JJ Comment: I removed Solution from this. Unsure if I need to replace it with something. + /// JJ Comment: I removed Solution from this. Unsure if I need to replace it with something. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("goodReagents")] @@ -94,7 +93,7 @@ namespace Content.Server.Kitchen.Components /// /// What reagents are added to terrible deep-fried food? - /// JJ Comment: I removed Solution from this. Unsure if I need to replace it with something. + /// JJ Comment: I removed Solution from this. Unsure if I need to replace it with something. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("badReagents")] @@ -102,7 +101,7 @@ namespace Content.Server.Kitchen.Components /// /// What reagents replace every 1 unit of oil spent on frying? - /// JJ Comment: I removed Solution from this. Unsure if I need to replace it with something. + /// JJ Comment: I removed Solution from this. Unsure if I need to replace it with something. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("wasteReagents")] @@ -158,7 +157,7 @@ namespace Content.Server.Kitchen.Components /// [ViewVariables(VVAccess.ReadWrite)] [DataField("solutionSizeCoefficient")] - public FixedPoint2 SolutionSizeCoefficient { get; set; } = 0.5f; + public FixedPoint2 SolutionSizeCoefficient { get; set; } = 1f; /// /// What's the maximum amount of solution that should ever be imparted? diff --git a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Results.cs b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Results.cs new file mode 100644 index 00000000000..ae3e8b10811 --- /dev/null +++ b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Results.cs @@ -0,0 +1,181 @@ +using System.Text; +using Content.Server.Atmos.Components; +using Content.Server.Body.Components; +using Content.Server.Ghost.Roles.Components; +using Content.Server.Kitchen.Components; +using Content.Server.Nutrition.Components; +using Content.Server.Nyanotrasen.Kitchen.Components; +using Content.Server.Paper; +using Content.Shared.Atmos.Rotting; +using Content.Shared.Buckle.Components; +using Content.Shared.Chemistry.Components; +using Content.Shared.FixedPoint; +using Content.Shared.Mobs.Components; +using Content.Shared.NPC; +using Content.Shared.Nutrition.Components; +using Content.Shared.Nyanotrasen.Kitchen.Components; +using Robust.Shared.Random; + +namespace Content.Server.Nyanotrasen.Kitchen.EntitySystems; + +public sealed partial class DeepFryerSystem +{ + /// + /// Make an item look deep-fried. + /// + private void MakeCrispy(EntityUid item) + { + EnsureComp(item); + EnsureComp(item); + + _appearanceSystem.SetData(item, DeepFriedVisuals.Fried, true); + } + + /// + /// Turn a dead mob into food. + /// + /// + /// This is meant to be an irreversible process, similar to gibbing. + /// + public bool TryMakeMobIntoFood(EntityUid mob, MobStateComponent mobStateComponent, bool force = false) + { + // Don't do anything to mobs until they're dead. + if (force || _mobStateSystem.IsDead(mob, mobStateComponent)) + { + RemComp(mob); + RemComp(mob); + RemComp(mob); + RemComp(mob); + RemComp(mob); + RemComp(mob); + RemComp(mob); + RemComp(mob); + RemComp(mob); + + // Ensure it's Food here, so it passes the whitelist. + var mobFoodComponent = EnsureComp(mob); + _solutionContainerSystem.EnsureSolution(mob, mobFoodComponent.Solution, out var alreadyHadFood); + + if (!_solutionContainerSystem.TryGetSolution(mob, mobFoodComponent.Solution, out var mobFoodSolution)) + return false; + + // This line here is mainly for mice, because they have a food + // component that mirrors how much blood they have, which is + // used for the reagent grinder. + if (alreadyHadFood) + _solutionContainerSystem.RemoveAllSolution(mobFoodSolution.Value); + + if (TryComp(mob, out var bloodstreamComponent) && bloodstreamComponent.ChemicalSolution != null) + { + // Fry off any blood into protein. + var bloodSolution = bloodstreamComponent.BloodSolution; + var solPresent = bloodSolution!.Value.Comp.Solution.Volume; + _solutionContainerSystem.RemoveReagent(bloodSolution.Value, "Blood", FixedPoint2.MaxValue); + var bloodRemoved = solPresent - bloodSolution.Value.Comp.Solution.Volume; + + var proteinQuantity = bloodRemoved * BloodToProteinRatio; + mobFoodSolution.Value.Comp.Solution.MaxVolume += proteinQuantity; + _solutionContainerSystem.TryAddReagent(mobFoodSolution.Value, "Protein", proteinQuantity); + + // This is a heuristic. If you had blood, you might just taste meaty. + if (bloodRemoved > FixedPoint2.Zero) + EnsureComp(mob).Flavors.Add(MobFlavorMeat); + + // Bring in whatever chemicals they had in them too. + mobFoodSolution.Value.Comp.Solution.MaxVolume += + bloodstreamComponent.ChemicalSolution.Value.Comp.Solution.Volume; + _solutionContainerSystem.AddSolution(mobFoodSolution.Value, + bloodstreamComponent.ChemicalSolution.Value.Comp.Solution); + } + + return true; + } + + return false; + } + + /// + /// Make an item actually edible. + /// + private void MakeEdible(EntityUid uid, DeepFryerComponent component, EntityUid item, FixedPoint2 solutionQuantity) + { + if (!TryComp(item, out var deepFriedComponent)) + { + _sawmill.Error($"{ToPrettyString(item)} is missing the DeepFriedComponent before being made Edible."); + return; + } + + // Remove any components that wouldn't make sense anymore. + RemComp(item); + + if (TryComp(item, out var paperComponent)) + { + var stringBuilder = new StringBuilder(); + + for (var i = 0; i < paperComponent.Content.Length; ++i) + { + var uchar = paperComponent.Content.Substring(i, 1); + + if (uchar == "\n" || _random.Prob(0.4f)) + stringBuilder.Append(uchar); + else + stringBuilder.Append("x"); + } + + paperComponent.Content = stringBuilder.ToString(); + } + + var foodComponent = EnsureComp(item); + var extraSolution = new Solution(); + if (TryComp(item, out FlavorProfileComponent? flavorProfileComponent)) + { + HashSet goodFlavors = new(flavorProfileComponent.Flavors); + goodFlavors.IntersectWith(component.GoodFlavors); + + HashSet badFlavors = new(flavorProfileComponent.Flavors); + badFlavors.IntersectWith(component.BadFlavors); + + deepFriedComponent.PriceCoefficient = Math.Max(0.01f, + 1.0f + + goodFlavors.Count * component.GoodFlavorPriceBonus + - badFlavors.Count * component.BadFlavorPriceMalus); + + if (goodFlavors.Count > 0) + { + foreach (var reagent in component.GoodReagents) + { + extraSolution.AddReagent(reagent.Reagent.ToString(), reagent.Quantity * goodFlavors.Count); + + // Mask the taste of "medicine." + flavorProfileComponent.IgnoreReagents.Add(reagent.Reagent.ToString()); + } + } + + if (badFlavors.Count > 0) + { + foreach (var reagent in component.BadReagents) + { + extraSolution.AddReagent(reagent.Reagent.ToString(), reagent.Quantity * badFlavors.Count); + } + } + } + else + { + flavorProfileComponent = EnsureComp(item); + // TODO: Default flavor? + } + + // Make sure there's enough room for the fryer solution. + var foodSolution = _solutionContainerSystem.EnsureSolution(item, foodComponent.Solution); + if (!_solutionContainerSystem.TryGetSolution(item, foodSolution.Name, out var foodContainer)) + return; + + // The solution quantity is used to give the fried food an extra + // buffer too, to support injectables or condiments. + foodSolution.MaxVolume = 2 * solutionQuantity + foodSolution.Volume + extraSolution.Volume; + _solutionContainerSystem.AddSolution(foodContainer.Value, + component.Solution.SplitSolution(solutionQuantity)); + _solutionContainerSystem.AddSolution(foodContainer.Value, extraSolution); + _solutionContainerSystem.UpdateChemicals(foodContainer.Value); + } +} diff --git a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Storage.cs b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Storage.cs new file mode 100644 index 00000000000..8858286dbf6 --- /dev/null +++ b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Storage.cs @@ -0,0 +1,93 @@ +using Content.Server.Kitchen.Components; +using Content.Server.Nyanotrasen.Kitchen.Components; +using Content.Shared.Chemistry.Components; +using Content.Shared.Database; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using Content.Shared.Item; +using Content.Shared.Nyanotrasen.Kitchen.UI; +using Content.Shared.Storage; +using Content.Shared.Tools.Components; + +namespace Content.Server.Nyanotrasen.Kitchen.EntitySystems; + +public sealed partial class DeepFryerSystem +{ + public bool CanInsertItem(EntityUid uid, DeepFryerComponent component, EntityUid item) + { + // Keep this consistent with the checks in TryInsertItem. + return HasComp(item) && + !HasComp(item) && + component.Storage.ContainedEntities.Count < component.StorageMaxEntities; + } + + private bool TryInsertItem(EntityUid uid, DeepFryerComponent component, EntityUid user, EntityUid item) + { + if (!HasComp(item)) + { + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-interact-using-not-item"), + uid, + user); + return false; + } + + if (HasComp(item)) + { + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-storage-no-fit", + ("item", item)), + uid, + user); + return false; + } + + if (component.Storage.ContainedEntities.Count >= component.StorageMaxEntities) + { + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-storage-full"), + uid, + user); + return false; + } + + if (!_handsSystem.TryDropIntoContainer(user, item, component.Storage)) + return false; + + AfterInsert(uid, component, item); + + _adminLogManager.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(user)} put {ToPrettyString(item)} inside {ToPrettyString(uid)}."); + + return true; + } + + private void OnInteractUsing(EntityUid uid, DeepFryerComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; + + // By default, allow entities with SolutionTransfer or Tool + // components to perform their usual actions. Inserting them (if + // the chef really wants to) will be supported through the UI. + if (HasComp(args.Used) || + HasComp(args.Used)) + return; + + if (TryInsertItem(uid, component, args.User, args.Used)) + args.Handled = true; + } + + private void OnInsertItem(EntityUid uid, DeepFryerComponent component, DeepFryerInsertItemMessage args) + { + var user = args.Session.AttachedEntity; + + if (user == null || + !TryComp(user, out var handsComponent) || + handsComponent.ActiveHandEntity == null) + return; + + if (handsComponent.ActiveHandEntity != null) + TryInsertItem(uid, component, user.Value, handsComponent.ActiveHandEntity.Value); + } +} diff --git a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Update.cs b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Update.cs new file mode 100644 index 00000000000..0978cffeb4b --- /dev/null +++ b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Update.cs @@ -0,0 +1,128 @@ +using System.Linq; +using Content.Server.Kitchen.Components; +using Content.Server.Nyanotrasen.Kitchen.Components; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Content.Shared.Popups; +using Robust.Shared.Player; + +namespace Content.Server.Nyanotrasen.Kitchen.EntitySystems; + +public sealed partial class DeepFryerSystem +{ + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var component in EntityManager.EntityQuery()) + { + var uid = component.Owner; + + if (_gameTimingSystem.CurTime < component.NextFryTime || + !_powerReceiverSystem.IsPowered(uid)) + { + continue; + } + + UpdateNextFryTime(uid, component); + + if (!_solutionContainerSystem.TryGetSolution(uid, component.Solution.Name, out var solution)) + continue; + + // Heat the vat solution and contained entities. + _solutionContainerSystem.SetTemperature(solution.Value, component.PoweredTemperature); + + foreach (var item in component.Storage.ContainedEntities) + CookItem(uid, component, item); + + // Do something bad if there's enough heat but not enough oil. + var oilVolume = GetOilVolume(uid, component); + + if (oilVolume < component.SafeOilVolume) + { + foreach (var item in component.Storage.ContainedEntities.ToArray()) + BurnItem(uid, component, item); + + if (oilVolume > FixedPoint2.Zero) + { + //JJ Comment - this code block makes the Linter fail, and doesn't seem to be necessary with the changes I made. + foreach (var reagent in component.Solution.Contents.ToArray()) + { + _prototypeManager.TryIndex(reagent.Reagent.ToString(), out var proto); + + foreach (var effect in component.UnsafeOilVolumeEffects) + { + effect.Effect(new ReagentEffectArgs(uid, + null, + component.Solution, + proto!, + reagent.Quantity, + EntityManager, + null, + 1f)); + } + + } + + component.Solution.RemoveAllSolution(); + + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-oil-volume-low", + ("deepFryer", uid)), + uid, + PopupType.SmallCaution); + + continue; + } + } + + // We only alert the chef that there's a problem with oil purity + // if there's anything to cook beyond this point. + if (!component.Storage.ContainedEntities.Any()) + { + continue; + } + + if (GetOilPurity(uid, component) < component.FryingOilThreshold) + { + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-oil-purity-low", + ("deepFryer", uid)), + uid, + Filter.Pvs(uid, PvsWarningRange), + true); + continue; + } + + foreach (var item in component.Storage.ContainedEntities.ToArray()) + DeepFry(uid, component, item); + + // After the round of frying, replace the spent oil with a + // waste product. + if (component.WasteToAdd > FixedPoint2.Zero) + { + foreach (var reagent in component.WasteReagents) + component.Solution.AddReagent(reagent.Reagent.ToString(), reagent.Quantity * component.WasteToAdd); + + component.WasteToAdd = FixedPoint2.Zero; + + _solutionContainerSystem.UpdateChemicals(solution.Value, true); + } + + UpdateUserInterface(uid, component); + } + } + + private void UpdateAmbientSound(EntityUid uid, DeepFryerComponent component) + { + _ambientSoundSystem.SetAmbience(uid, HasBubblingOil(uid, component)); + } + + private void UpdateNextFryTime(EntityUid uid, DeepFryerComponent component) + { + component.NextFryTime = _gameTimingSystem.CurTime + component.FryInterval; + } + +} diff --git a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs index 2816eb01e34..2c9a1f21ade 100644 --- a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs +++ b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs @@ -1,46 +1,33 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Text; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Timing; using Content.Server.Administration.Logs; -using Content.Server.Atmos.Components; using Content.Server.Audio; -using Content.Server.Body.Components; using Content.Server.Cargo.Systems; -using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Chemistry.EntitySystems; using Content.Server.Construction; -using Content.Server.Construction.Components; using Content.Server.DoAfter; using Content.Server.Fluids.EntitySystems; -using Content.Server.Ghost.Roles.Components; using Content.Server.Kitchen.Components; -using Content.Server.NPC.Components; using Content.Server.Nutrition; using Content.Server.Nutrition.Components; -using Content.Server.Nutrition.EntitySystems; -using Content.Server.Paper; +using Content.Server.Nyanotrasen.Kitchen.Components; using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; using Content.Server.UserInterface; -using Content.Shared.Atmos.Rotting; -using Content.Shared.Buckle.Components; using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reagent; +using Content.Shared.Construction; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.Database; -using Content.Shared.DoAfter; using Content.Shared.Destructible; +using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.FixedPoint; using Content.Shared.Hands.Components; @@ -48,1021 +35,727 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Item; -using Content.Shared.Kitchen; -using Content.Shared.Kitchen.Components; -using Content.Shared.Kitchen.UI; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Events; -using Content.Shared.Nutrition.Components; +using Content.Shared.Nyanotrasen.Kitchen; +using Content.Shared.Nyanotrasen.Kitchen.Components; +using Content.Shared.Nyanotrasen.Kitchen.UI; using Content.Shared.Popups; -using Content.Shared.Storage; using Content.Shared.Throwing; -using Content.Shared.Tools.Components; using FastAccessors; -using Content.Shared.NPC; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Construction; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; -namespace Content.Server.Kitchen.EntitySystems +namespace Content.Server.Nyanotrasen.Kitchen.EntitySystems; + +public sealed partial class DeepFryerSystem : SharedDeepfryerSystem { - public sealed class DeepFryerSystem : EntitySystem + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly IAdminLogManager _adminLogManager = default!; + [Dependency] private readonly IGameTiming _gameTimingSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + [Dependency] private readonly SolutionTransferSystem _solutionTransferSystem = default!; + [Dependency] private readonly PuddleSystem _puddleSystem = default!; + [Dependency] private readonly TemperatureSystem _temperature = default!; + [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly AmbientSoundSystem _ambientSoundSystem = default!; + [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; + + private static readonly string CookingDamageType = "Heat"; + private static readonly float CookingDamageAmount = 10.0f; + private static readonly float PvsWarningRange = 0.5f; + private static readonly float ThrowMissChance = 0.25f; + private static readonly int MaximumCrispiness = 2; + private static readonly float BloodToProteinRatio = 0.1f; + private static readonly string MobFlavorMeat = "meaty"; + + private static readonly AudioParams + AudioParamsInsertRemove = new(0.5f, 1f, "Master", 5f, 1.5f, 1f, false, 0f, 0.2f); + + private ISawmill _sawmill = default!; + + public override void Initialize() { - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly IAdminLogManager _adminLogManager = default!; - [Dependency] private readonly IGameTiming _gameTimingSystem = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; - [Dependency] private readonly SharedAudioSystem _audioSystem = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; - [Dependency] private readonly SolutionTransferSystem _solutionTransferSystem = default!; - [Dependency] private readonly PuddleSystem _puddleSystem = default!; - [Dependency] private readonly TemperatureSystem _temperature = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly AmbientSoundSystem _ambientSoundSystem = default!; - [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; + base.Initialize(); - private static readonly string CookingDamageType = "Heat"; - private static readonly float CookingDamageAmount = 10.0f; - private static readonly float PvsWarningRange = 0.5f; - private static readonly float ThrowMissChance = 0.25f; - private static readonly int MaximumCrispiness = 2; - private static readonly float BloodToProteinRatio = 0.1f; - private static readonly string MobFlavorMeat = "meaty"; - private static readonly AudioParams AudioParamsInsertRemove = new(0.5f, 1f, "Master", 5f, 1.5f, 1f, false, 0f, 0.2f); + _sawmill = Logger.GetSawmill("deepfryer"); - private ISawmill _sawmill = default!; + SubscribeLocalEvent(OnInitDeepFryer); + SubscribeLocalEvent(OnPowerChange); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnDeconstruct); + SubscribeLocalEvent(OnDestruction); + SubscribeLocalEvent(OnThrowHitBy); + SubscribeLocalEvent(OnSolutionChange); + SubscribeLocalEvent(OnRelayMovement); + SubscribeLocalEvent(OnInteractUsing); - public override void Initialize() + SubscribeLocalEvent(OnBeforeActivatableUIOpen); + SubscribeLocalEvent(OnRemoveItem); + SubscribeLocalEvent(OnInsertItem); + SubscribeLocalEvent(OnScoopVat); + SubscribeLocalEvent(OnClearSlagStart); + SubscribeLocalEvent(OnRemoveAllItems); + SubscribeLocalEvent(OnClearSlag); + + SubscribeLocalEvent(OnInitDeepFried); + SubscribeLocalEvent(OnExamineFried); + SubscribeLocalEvent(OnPriceCalculation); + SubscribeLocalEvent(OnSliceDeepFried); + } + + private void UpdateUserInterface(EntityUid uid, DeepFryerComponent component) + { + var state = new DeepFryerBoundUserInterfaceState( + GetOilLevel(uid, component), + GetOilPurity(uid, component), + component.FryingOilThreshold, + EntityManager.GetNetEntityArray(component.Storage.ContainedEntities.ToArray())); + + if (!_uiSystem.TrySetUiState(uid, DeepFryerUiKey.Key, state)) + _sawmill.Warning($"{ToPrettyString(uid)} was unable to set UI state."); + } + + /// + /// Does the deep fryer have hot oil? + /// + /// + /// This is mainly for audio. + /// + private bool HasBubblingOil(EntityUid uid, DeepFryerComponent component) + { + return _powerReceiverSystem.IsPowered(uid) && GetOilVolume(uid, component) > FixedPoint2.Zero; + } + + /// + /// Returns how much total oil is in the vat. + /// + public FixedPoint2 GetOilVolume(EntityUid uid, DeepFryerComponent component) + { + var oilVolume = FixedPoint2.Zero; + + foreach (var reagent in component.Solution) { - base.Initialize(); - - _sawmill = Logger.GetSawmill("deepfryer"); - - SubscribeLocalEvent(OnInitDeepFryer); - SubscribeLocalEvent(OnPowerChange); - SubscribeLocalEvent(OnRefreshParts); - SubscribeLocalEvent(OnDeconstruct); - SubscribeLocalEvent(OnDestruction); - SubscribeLocalEvent(OnThrowHitBy); - SubscribeLocalEvent(OnSolutionChange); - SubscribeLocalEvent(OnRelayMovement); - SubscribeLocalEvent(OnInteractUsing); - - SubscribeLocalEvent(OnBeforeActivatableUIOpen); - SubscribeLocalEvent(OnRemoveItem); - SubscribeLocalEvent(OnInsertItem); - SubscribeLocalEvent(OnScoopVat); - SubscribeLocalEvent(OnClearSlagStart); - SubscribeLocalEvent(OnRemoveAllItems); - SubscribeLocalEvent(OnClearSlag); - - SubscribeLocalEvent(OnInitDeepFried); - SubscribeLocalEvent(OnExamineFried); - SubscribeLocalEvent(OnPriceCalculation); - SubscribeLocalEvent(OnSliceDeepFried); + if (component.FryingOils.Contains(reagent.Reagent.ToString())) + oilVolume += reagent.Quantity; } - public override void Update(float frameTime) + return oilVolume; + } + + /// + /// Returns how much total waste is in the vat. + /// + public FixedPoint2 GetWasteVolume(EntityUid uid, DeepFryerComponent component) + { + var wasteVolume = FixedPoint2.Zero; + + foreach (var reagent in component.WasteReagents) { - base.Update(frameTime); + wasteVolume += component.Solution.GetReagentQuantity(reagent.Reagent); + } - foreach (var component in EntityManager.EntityQuery()) + return wasteVolume; + } + + /// + /// Returns a percentage of how much of the total solution is usable oil. + /// + public FixedPoint2 GetOilPurity(EntityUid uid, DeepFryerComponent component) + { + return GetOilVolume(uid, component) / component.Solution.Volume; + } + + /// + /// Returns a percentage of how much of the total volume is usable oil. + /// + public FixedPoint2 GetOilLevel(EntityUid uid, DeepFryerComponent component) + { + return GetOilVolume(uid, component) / component.Solution.MaxVolume; + } + + /// + /// This takes care of anything that would happen to an item with or + /// without enough oil. + /// + private void CookItem(EntityUid uid, DeepFryerComponent component, EntityUid item) + { + if (TryComp(item, out var tempComp)) + { + // Push the temperature towards what it should be but no higher. + var delta = (component.PoweredTemperature - tempComp.CurrentTemperature) * tempComp.HeatCapacity; + + if (delta > 0f) + _temperature.ChangeHeat(item, delta, false, tempComp); + } + + if (TryComp(item, out var solutions) && solutions.Solutions != null) + { + foreach (var (_, solution) in solutions.Solutions) { - var uid = component.Owner; - - if (_gameTimingSystem.CurTime < component.NextFryTime || - !_powerReceiverSystem.IsPowered(uid)) - { - continue; - } - - UpdateNextFryTime(uid, component); - - // Heat the vat solution and contained entities. - _solutionContainerSystem.SetTemperature(uid, component.Solution, component.PoweredTemperature); - - foreach (var item in component.Storage.ContainedEntities) - CookItem(uid, component, item); - - // Do something bad if there's enough heat but not enough oil. - var oilVolume = GetOilVolume(uid, component); - - if (oilVolume < component.SafeOilVolume) - { - foreach (var item in component.Storage.ContainedEntities.ToArray()) - BurnItem(uid, component, item); - - if (oilVolume > FixedPoint2.Zero) - { - //JJ Comment - this code block makes the Linter fail, and doesn't seem to be necessary with the changes I made. - foreach (var reagent in component.Solution.Contents.ToArray()) - { - _prototypeManager.TryIndex(reagent.Reagent.ToString(), out var proto); - - foreach (var effect in component.UnsafeOilVolumeEffects) - { - effect.Effect(new ReagentEffectArgs(uid, - null, - component.Solution, - proto!, - reagent.Quantity, - EntityManager, - null, - 1f)); - } - - } - - component.Solution.RemoveAllSolution(); - - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-oil-volume-low", - ("deepFryer", uid)), - uid, - PopupType.SmallCaution); - - continue; - } - } - - // We only alert the chef that there's a problem with oil purity - // if there's anything to cook beyond this point. - if (!component.Storage.ContainedEntities.Any()) - { - continue; - } - - if (GetOilPurity(uid, component) < component.FryingOilThreshold) - { - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-oil-purity-low", - ("deepFryer", uid)), - uid, - Filter.Pvs(uid, PvsWarningRange), - true); - continue; - } - - foreach (var item in component.Storage.ContainedEntities.ToArray()) - DeepFry(uid, component, item); - - // After the round of frying, replace the spent oil with a - // waste product. - if (component.WasteToAdd > FixedPoint2.Zero) - { - foreach (var reagent in component.WasteReagents) - component.Solution.AddReagent(reagent.Reagent.ToString(), reagent.Quantity * component.WasteToAdd); - - component.WasteToAdd = FixedPoint2.Zero; - - _solutionContainerSystem.UpdateChemicals(uid, component.Solution, true); - } - - UpdateUserInterface(uid, component); + if(_solutionContainerSystem.TryGetSolution(item, solution.Name, out var solutionRef)) + _solutionContainerSystem.SetTemperature(solutionRef!.Value, component.PoweredTemperature); } } - private void UpdateUserInterface(EntityUid uid, DeepFryerComponent component) + // Damage non-food items and mobs. + if ((!HasComp(item) || HasComp(item)) && + TryComp(item, out var damageableComponent)) { - var state = new DeepFryerBoundUserInterfaceState( - GetOilLevel(uid, component), - GetOilPurity(uid, component), - component.FryingOilThreshold, - EntityManager.GetNetEntityArray(component.Storage.ContainedEntities.ToArray())); + var damage = new DamageSpecifier(_prototypeManager.Index(CookingDamageType), + CookingDamageAmount); - if (!_uiSystem.TrySetUiState(uid, DeepFryerUiKey.Key, state)) - _sawmill.Warning($"{ToPrettyString(uid)} was unable to set UI state."); - } - - /// - /// Does the deep fryer have hot oil? - /// - /// - /// This is mainly for audio. - /// - private bool HasBubblingOil(EntityUid uid, DeepFryerComponent component) - { - return _powerReceiverSystem.IsPowered(uid) && GetOilVolume(uid, component) > FixedPoint2.Zero; - } - - private void UpdateAmbientSound(EntityUid uid, DeepFryerComponent component) - { - _ambientSoundSystem.SetAmbience(uid, HasBubblingOil(uid, component)); - } - - private void UpdateNextFryTime(EntityUid uid, DeepFryerComponent component) - { - component.NextFryTime = _gameTimingSystem.CurTime + component.FryInterval; - } - - /// - /// Make an item look deep-fried. - /// - private void MakeCrispy(EntityUid item) - { - EnsureComp(item); - EnsureComp(item); - - _appearanceSystem.SetData(item, DeepFriedVisuals.Fried, true); - } - - /// - /// Turn a dead mob into food. - /// - /// - /// This is meant to be an irreversible process, similar to gibbing. - /// - public bool TryMakeMobIntoFood(EntityUid mob, MobStateComponent mobStateComponent, bool force = false) - { - // Don't do anything to mobs until they're dead. - if (force || _mobStateSystem.IsDead(mob, mobStateComponent)) - { - RemComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); - - // Ensure it's Food here, so it passes the whitelist. - var mobFoodComponent = EnsureComp(mob); - var mobFoodSolution = _solutionContainerSystem.EnsureSolution(mob, mobFoodComponent.Solution, out bool alreadyHadFood); - - // This line here is mainly for mice, because they have a food - // component that mirrors how much blood they have, which is - // used for the reagent grinder. - if (alreadyHadFood) - _solutionContainerSystem.RemoveAllSolution(mob, mobFoodSolution); - - if (TryComp(mob, out var bloodstreamComponent)) - { - // Fry off any blood into protein. - var bloodSolution = bloodstreamComponent.BloodSolution; - var removedBloodQuantity = bloodSolution.RemoveReagent("Blood", FixedPoint2.MaxValue); - var proteinQuantity = removedBloodQuantity * BloodToProteinRatio; - mobFoodSolution.MaxVolume += proteinQuantity; - mobFoodSolution.AddReagent("Protein", proteinQuantity); - - // This is a heuristic. If you had blood, you might just taste meaty. - if (removedBloodQuantity > FixedPoint2.Zero) - EnsureComp(mob).Flavors.Add(MobFlavorMeat); - - // Bring in whatever chemicals they had in them too. - mobFoodSolution.MaxVolume += bloodstreamComponent.ChemicalSolution.Volume; - mobFoodSolution.AddSolution(bloodstreamComponent.ChemicalSolution, _prototypeManager); - } - - return true; - } - - return false; - } - - /// - /// Make an item actually edible. - /// - private void MakeEdible(EntityUid uid, DeepFryerComponent component, EntityUid item, FixedPoint2 solutionQuantity) - { - if (!TryComp(item, out var deepFriedComponent)) - { - _sawmill.Error($"{ToPrettyString(item)} is missing the DeepFriedComponent before being made Edible."); - return; - } - - // Remove any components that wouldn't make sense anymore. - RemComp(item); - - if (TryComp(item, out var paperComponent)) - { - var stringBuilder = new StringBuilder(); - - for (var i = 0; i < paperComponent.Content.Length; ++i) - { - var uchar = paperComponent.Content.Substring(i, 1); - - if (uchar == "\n" || _random.Prob(0.4f)) - stringBuilder.Append(uchar); - else - stringBuilder.Append("x"); - } - - paperComponent.Content = stringBuilder.ToString(); - } - - var foodComponent = EnsureComp(item); - var extraSolution = new Solution(); - if (TryComp(item, out FlavorProfileComponent? flavorProfileComponent)) - { - HashSet goodFlavors = new(flavorProfileComponent.Flavors); - goodFlavors.IntersectWith(component.GoodFlavors); - - HashSet badFlavors = new(flavorProfileComponent.Flavors); - badFlavors.IntersectWith(component.BadFlavors); - - deepFriedComponent.PriceCoefficient = Math.Max(0.01f, - 1.0f - + goodFlavors.Count * component.GoodFlavorPriceBonus - - badFlavors.Count * component.BadFlavorPriceMalus); - - if (goodFlavors.Count > 0) - foreach (var reagent in component.GoodReagents) - { - extraSolution.AddReagent(reagent.Reagent.ToString(), reagent.Quantity * goodFlavors.Count); - - // Mask the taste of "medicine." - flavorProfileComponent.IgnoreReagents.Add(reagent.Reagent.ToString()); - } - - if (badFlavors.Count > 0) - foreach (var reagent in component.BadReagents) - extraSolution.AddReagent(reagent.Reagent.ToString(), reagent.Quantity * badFlavors.Count); - } - else - { - flavorProfileComponent = EnsureComp(item); - // TODO: Default flavor? - } - - // Make sure there's enough room for the fryer solution. - var foodContainer = _solutionContainerSystem.EnsureSolution(item, foodComponent.Solution); - - // The solution quantity is used to give the fried food an extra - // buffer too, to support injectables or condiments. - foodContainer.MaxVolume = 2 * solutionQuantity + foodContainer.Volume + extraSolution.Volume; - foodContainer.AddSolution(component.Solution.SplitSolution(solutionQuantity), _prototypeManager); - foodContainer.AddSolution(extraSolution, _prototypeManager); - _solutionContainerSystem.UpdateChemicals(item, foodContainer, true); - } - - /// - /// Returns how much total oil is in the vat. - /// - public FixedPoint2 GetOilVolume(EntityUid uid, DeepFryerComponent component) - { - var oilVolume = FixedPoint2.Zero; - - foreach (var reagent in component.Solution) - if (component.FryingOils.Contains(reagent.Reagent.ToString())) - oilVolume += reagent.Quantity; - - return oilVolume; - } - - /// - /// Returns how much total waste is in the vat. - /// - public FixedPoint2 GetWasteVolume(EntityUid uid, DeepFryerComponent component) - { - var wasteVolume = FixedPoint2.Zero; - - foreach (var reagent in component.WasteReagents) - { - wasteVolume += component.Solution.GetReagentQuantity(reagent.Reagent); - } - - return wasteVolume; - } - - /// - /// Returns a percentage of how much of the total solution is usable oil. - /// - public FixedPoint2 GetOilPurity(EntityUid uid, DeepFryerComponent component) - { - return GetOilVolume(uid, component) / component.Solution.Volume; - } - - /// - /// Returns a percentage of how much of the total volume is usable oil. - /// - public FixedPoint2 GetOilLevel(EntityUid uid, DeepFryerComponent component) - { - return GetOilVolume(uid, component) / component.Solution.MaxVolume; - } - - /// - /// This takes care of anything that would happen to an item with or - /// without enough oil. - /// - private void CookItem(EntityUid uid, DeepFryerComponent component, EntityUid item) - { - if (TryComp(item, out var tempComp)) - { - // Push the temperature towards what it should be but no higher. - var delta = (component.PoweredTemperature - tempComp.CurrentTemperature) * tempComp.HeatCapacity; - - if (delta > 0f) - _temperature.ChangeHeat(item, delta, false, tempComp); - } - - if (TryComp(item, out var solutions)) - { - foreach (var (_, solution) in solutions.Solutions) - { - _solutionContainerSystem.SetTemperature(item, solution, component.PoweredTemperature); - } - } - - // Damage non-food items and mobs. - if ((!HasComp(item) || HasComp(item)) && - TryComp(item, out var damageableComponent)) - { - var damage = new DamageSpecifier(_prototypeManager.Index(CookingDamageType), CookingDamageAmount); - - var result = _damageableSystem.TryChangeDamage(item, damage, origin: uid); - if (result?.Total > FixedPoint2.Zero) - { - // TODO: Smoke, waste, sound, or some indication. - } - } - } - - /// - /// Destroy a food item and replace it with a charred mess. - /// - private void BurnItem(EntityUid uid, DeepFryerComponent component, EntityUid item) - { - if (HasComp(item) && - !HasComp(item) && - MetaData(item).EntityPrototype?.ID != component.CharredPrototype) - { - var charred = Spawn(component.CharredPrototype, Transform(uid).Coordinates); - component.Storage.Insert(charred); - Del(item); - } - } - - private void UpdateDeepFriedName(EntityUid uid, DeepFriedComponent component) - { - if (component.OriginalName == null) - return; - - switch (component.Crispiness) - { - case 0: - // Already handled at OnInitDeepFried. - break; - case 1: - _metaDataSystem.SetEntityName(uid, Loc.GetString("deep-fried-crispy-item", - ("entity", component.OriginalName))); - break; - default: - _metaDataSystem.SetEntityName(uid, Loc.GetString("deep-fried-burned-item", - ("entity", component.OriginalName))); - break; - } - } - - /// - /// Try to deep fry a single item, which can - /// - be cancelled by other systems, or - /// - fail due to the blacklist, or - /// - give it a crispy shader, and possibly also - /// - turn it into food. - /// - private void DeepFry(EntityUid uid, DeepFryerComponent component, EntityUid item) - { - if (MetaData(item).EntityPrototype?.ID == component.CharredPrototype) - return; - - // This item has already been deep-fried, and now it's progressing - // into another stage. - if (TryComp(item, out var deepFriedComponent)) + var result = _damageableSystem.TryChangeDamage(item, damage, origin: uid); + if (result?.Total > FixedPoint2.Zero) { // TODO: Smoke, waste, sound, or some indication. + } + } + } - deepFriedComponent.Crispiness += 1; + /// + /// Destroy a food item and replace it with a charred mess. + /// + private void BurnItem(EntityUid uid, DeepFryerComponent component, EntityUid item) + { + if (HasComp(item) && + !HasComp(item) && + MetaData(item).EntityPrototype?.ID != component.CharredPrototype) + { + var charred = Spawn(component.CharredPrototype, Transform(uid).Coordinates); + component.Storage.Insert(charred); + Del(item); + } + } - if (deepFriedComponent.Crispiness > MaximumCrispiness) - { - BurnItem(uid, component, item); - return; - } + private void UpdateDeepFriedName(EntityUid uid, DeepFriedComponent component) + { + if (component.OriginalName == null) + return; - UpdateDeepFriedName(item, deepFriedComponent); + switch (component.Crispiness) + { + case 0: + // Already handled at OnInitDeepFried. + break; + case 1: + _metaDataSystem.SetEntityName(uid, Loc.GetString("deep-fried-crispy-item", + ("entity", component.OriginalName))); + break; + default: + _metaDataSystem.SetEntityName(uid, Loc.GetString("deep-fried-burned-item", + ("entity", component.OriginalName))); + break; + } + } + + /// + /// Try to deep fry a single item, which can + /// - be cancelled by other systems, or + /// - fail due to the blacklist, or + /// - give it a crispy shader, and possibly also + /// - turn it into food. + /// + private void DeepFry(EntityUid uid, DeepFryerComponent component, EntityUid item) + { + if (MetaData(item).EntityPrototype?.ID == component.CharredPrototype) + return; + + // This item has already been deep-fried, and now it's progressing + // into another stage. + if (TryComp(item, out var deepFriedComponent)) + { + // TODO: Smoke, waste, sound, or some indication. + + deepFriedComponent.Crispiness += 1; + + if (deepFriedComponent.Crispiness > MaximumCrispiness) + { + BurnItem(uid, component, item); return; } - // Allow entity systems to conditionally forbid an attempt at deep-frying. - var attemptEvent = new DeepFryAttemptEvent(uid); - RaiseLocalEvent(item, attemptEvent); + UpdateDeepFriedName(item, deepFriedComponent); + return; + } - if (attemptEvent.Cancelled) + // Allow entity systems to conditionally forbid an attempt at deep-frying. + var attemptEvent = new DeepFryAttemptEvent(uid); + RaiseLocalEvent(item, attemptEvent); + + if (attemptEvent.Cancelled) + return; + + // The attempt event is allowed to go first before the blacklist check, + // just in case the attempt is relevant to any system in the future. + // + // The blacklist overrides all. + if (component.Blacklist != null && component.Blacklist.IsValid(item, EntityManager)) + { + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-blacklist-item-failed", + ("item", item), ("deepFryer", uid)), + uid, + Filter.Pvs(uid, PvsWarningRange), + true); + return; + } + + var beingEvent = new BeingDeepFriedEvent(uid, item); + RaiseLocalEvent(item, beingEvent); + + // It's important to check for the MobStateComponent so we know + // it's actually a mob, because functions like + // MobStateSystem.IsAlive will return false if the entity lacks the + // component. + if (TryComp(item, out var mobStateComponent)) + { + if (!TryMakeMobIntoFood(item, mobStateComponent)) return; - - // The attempt event is allowed to go first before the blacklist check, - // just in case the attempt is relevant to any system in the future. - // - // The blacklist overrides all. - if (component.Blacklist != null && component.Blacklist.IsValid(item, EntityManager)) - { - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-blacklist-item-failed", - ("item", item), ("deepFryer", uid)), - uid, - Filter.Pvs(uid, PvsWarningRange), - true); - return; - } - - var beingEvent = new BeingDeepFriedEvent(uid, item); - RaiseLocalEvent(item, beingEvent); - - // It's important to check for the MobStateComponent so we know - // it's actually a mob, because functions like - // MobStateSystem.IsAlive will return false if the entity lacks the - // component. - if (TryComp(item, out var mobStateComponent)) - if (!TryMakeMobIntoFood(item, mobStateComponent)) - return; - - MakeCrispy(item); - - var itemComponent = Comp(item); - - // Determine how much solution to spend on this item. - var solutionQuantity = FixedPoint2.Min( - component.Solution.Volume, - _random.Next(5,100)); - - if (component.Whitelist != null && component.Whitelist.IsValid(item, EntityManager) || - beingEvent.TurnIntoFood) - { - MakeEdible(uid, component, item, solutionQuantity); - } - else - { - component.Solution.RemoveSolution(solutionQuantity); - } - - component.WasteToAdd += solutionQuantity; } - private void OnInitDeepFryer(EntityUid uid, DeepFryerComponent component, ComponentInit args) - { - component.Storage = _containerSystem.EnsureContainer(uid, component.StorageName, out bool containerExisted); + MakeCrispy(item); - if (!containerExisted) - _sawmill.Warning($"{ToPrettyString(uid)} did not have a {component.StorageName} container. It has been created."); + var itemComponent = Comp(item); - component.Solution = _solutionContainerSystem.EnsureSolution(uid, component.SolutionName, out bool solutionExisted); - - if (!solutionExisted) - _sawmill.Warning($"{ToPrettyString(uid)} did not have a {component.SolutionName} solution container. It has been created."); - foreach (var reagent in component.Solution.Contents.ToArray()) + // Determine how much solution to spend on this item. + var solutionQuantity = FixedPoint2.Min( + component.Solution.Volume, + itemComponent.Size.Id switch { - //JJ Comment - not sure this works. Need to check if Reagent.ToString is correct. - _prototypeManager.TryIndex(reagent.Reagent.ToString(), out var proto); - var effectsArgs = new ReagentEffectArgs(uid, - null, - component.Solution, - proto!, - reagent.Quantity, - EntityManager, - null, - 1f); - foreach (var effect in component.UnsafeOilVolumeEffects) - { - if (!effect.ShouldApply(effectsArgs, _random)) - continue; - effect.Effect(effectsArgs); - } + "Tiny" => 1, + "Small" => 5, + "Medium" => 10, + "Large" => 15, + "Huge" => 30, + "Ginormous" => 50, + _ => 10 + } * component.SolutionSizeCoefficient); + + if (component.Whitelist != null && component.Whitelist.IsValid(item, EntityManager) || + beingEvent.TurnIntoFood) + MakeEdible(uid, component, item, solutionQuantity); + else + component.Solution.RemoveSolution(solutionQuantity); + + component.WasteToAdd += solutionQuantity; + } + + private void OnInitDeepFryer(EntityUid uid, DeepFryerComponent component, ComponentInit args) + { + component.Storage = + _containerSystem.EnsureContainer(uid, component.StorageName, out var containerExisted); + + if (!containerExisted) + _sawmill.Warning( + $"{ToPrettyString(uid)} did not have a {component.StorageName} container. It has been created."); + + component.Solution = + _solutionContainerSystem.EnsureSolution(uid, component.SolutionName, out var solutionExisted); + + if (!solutionExisted) + _sawmill.Warning( + $"{ToPrettyString(uid)} did not have a {component.SolutionName} solution container. It has been created."); + foreach (var reagent in component.Solution.Contents.ToArray()) + { + //JJ Comment - not sure this works. Need to check if Reagent.ToString is correct. + _prototypeManager.TryIndex(reagent.Reagent.ToString(), out var proto); + var effectsArgs = new ReagentEffectArgs(uid, + null, + component.Solution, + proto!, + reagent.Quantity, + EntityManager, + null, + 1f); + foreach (var effect in component.UnsafeOilVolumeEffects) + { + if (!effect.ShouldApply(effectsArgs, _random)) + continue; + effect.Effect(effectsArgs); } } + } - /// - /// Make sure the UI and interval tracker are updated anytime something - /// is inserted into one of the baskets. - /// - /// - /// This is used instead of EntInsertedIntoContainerMessage so charred - /// items can be inserted into the deep fryer without triggering this - /// event. - /// - private void AfterInsert(EntityUid uid, DeepFryerComponent component, EntityUid item) + /// + /// Make sure the UI and interval tracker are updated anytime something + /// is inserted into one of the baskets. + /// + /// + /// This is used instead of EntInsertedIntoContainerMessage so charred + /// items can be inserted into the deep fryer without triggering this + /// event. + /// + private void AfterInsert(EntityUid uid, DeepFryerComponent component, EntityUid item) + { + if (HasBubblingOil(uid, component)) + _audioSystem.PlayPvs(component.SoundInsertItem, uid, AudioParamsInsertRemove); + + UpdateNextFryTime(uid, component); + UpdateUserInterface(uid, component); + } + + private void OnPowerChange(EntityUid uid, DeepFryerComponent component, ref PowerChangedEvent args) + { + _appearanceSystem.SetData(uid, DeepFryerVisuals.Bubbling, args.Powered); + UpdateNextFryTime(uid, component); + UpdateAmbientSound(uid, component); + } + + private void OnDeconstruct(EntityUid uid, DeepFryerComponent component, MachineDeconstructedEvent args) + { + // The EmptyOnMachineDeconstruct component handles the entity container for us. + _puddleSystem.TrySpillAt(uid, component.Solution, out var _); + } + + private void OnDestruction(EntityUid uid, DeepFryerComponent component, DestructionEventArgs args) + { + _containerSystem.EmptyContainer(component.Storage, true); + } + + private void OnRefreshParts(EntityUid uid, DeepFryerComponent component, RefreshPartsEvent args) + { + var ratingStorage = args.PartRatings[component.MachinePartStorageMax]; + + component.StorageMaxEntities = component.BaseStorageMaxEntities + + (int) (component.StoragePerPartRating * (ratingStorage - 1)); + } + + /// + /// Allow thrown items to land in a basket. + /// + private void OnThrowHitBy(EntityUid uid, DeepFryerComponent component, ThrowHitByEvent args) + { + if (args.Handled) + return; + + // Chefs never miss this. :) + var missChance = HasComp(args.User) ? 0f : ThrowMissChance; + + if (!CanInsertItem(uid, component, args.Thrown) || + _random.Prob(missChance) || + !component.Storage.Insert(args.Thrown)) { - if (HasBubblingOil(uid, component)) - _audioSystem.PlayPvs(component.SoundInsertItem, uid, AudioParamsInsertRemove); - - UpdateNextFryTime(uid, component); - UpdateUserInterface(uid, component); - } - - private void OnPowerChange(EntityUid uid, DeepFryerComponent component, ref PowerChangedEvent args) - { - _appearanceSystem.SetData(uid, DeepFryerVisuals.Bubbling, args.Powered); - UpdateNextFryTime(uid, component); - UpdateAmbientSound(uid, component); - } - - private void OnDeconstruct(EntityUid uid, DeepFryerComponent component, MachineDeconstructedEvent args) - { - // The EmptyOnMachineDeconstruct component handles the entity container for us. - _puddleSystem.TrySpillAt(uid, component.Solution, out var _); - } - - private void OnDestruction(EntityUid uid, DeepFryerComponent component, DestructionEventArgs args) - { - _containerSystem.EmptyContainer(component.Storage, true); - } - - private void OnRefreshParts(EntityUid uid, DeepFryerComponent component, RefreshPartsEvent args) - { - var ratingStorage = args.PartRatings[component.MachinePartStorageMax]; - - component.StorageMaxEntities = component.BaseStorageMaxEntities + (int) (component.StoragePerPartRating * (ratingStorage - 1)); - } - - /// - /// Allow thrown items to land in a basket. - /// - private void OnThrowHitBy(EntityUid uid, DeepFryerComponent component, ThrowHitByEvent args) - { - if (args.Handled) - return; - - // Chefs never miss this. :) - float missChance = HasComp(args.User) ? 0f : ThrowMissChance; - - if (!CanInsertItem(uid, component, args.Thrown) || - _random.Prob(missChance) || - !component.Storage.Insert(args.Thrown)) - { - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-thrown-missed"), - uid); - - if (args.User != null) - _adminLogManager.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(args.User.Value)} threw {ToPrettyString(args.Thrown)} at {ToPrettyString(uid)}, and it missed."); - - return; - } - - if (GetOilVolume(uid, component) < component.SafeOilVolume) - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-thrown-hit-oil-low"), - uid); - else - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-thrown-hit-oil"), - uid); + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-thrown-missed"), + uid); if (args.User != null) + { _adminLogManager.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(args.User.Value)} threw {ToPrettyString(args.Thrown)} at {ToPrettyString(uid)}, and it landed inside."); + $"{ToPrettyString(args.User.Value)} threw {ToPrettyString(args.Thrown)} at {ToPrettyString(uid)}, and it missed."); + } - AfterInsert(uid, component, args.Thrown); - - args.Handled = true; + return; } - private void OnSolutionChange(EntityUid uid, DeepFryerComponent component, SolutionChangedEvent args) + if (GetOilVolume(uid, component) < component.SafeOilVolume) { - UpdateUserInterface(uid, component); - UpdateAmbientSound(uid, component); - } - - private void OnRelayMovement(EntityUid uid, DeepFryerComponent component, ref ContainerRelayMovementEntityEvent args) - { - if (!component.Storage.Remove(args.Entity, EntityManager, destination: Transform(uid).Coordinates)) - return; - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-entity-escape", - ("victim", Identity.Entity(args.Entity, EntityManager)), - ("deepFryer", uid)), - uid, - PopupType.SmallCaution); + Loc.GetString("deep-fryer-thrown-hit-oil-low"), + uid); + } + else + { + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-thrown-hit-oil"), + uid); } - public bool CanInsertItem(EntityUid uid, DeepFryerComponent component, EntityUid item) + if (args.User != null) { - // Keep this consistent with the checks in TryInsertItem. - return (HasComp(item) && - !HasComp(item) && - component.Storage.ContainedEntities.Count < component.StorageMaxEntities); - } - - private bool TryInsertItem(EntityUid uid, DeepFryerComponent component, EntityUid user, EntityUid item) - { - if (!HasComp(item)) - { - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-interact-using-not-item"), - uid, - user); - return false; - } - - if (HasComp(item)) - { - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-storage-no-fit", - ("item", item)), - uid, - user); - return false; - } - - if (component.Storage.ContainedEntities.Count >= component.StorageMaxEntities) - { - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-storage-full"), - uid, - user); - return false; - } - - if (!_handsSystem.TryDropIntoContainer(user, item, component.Storage)) - return false; - - AfterInsert(uid, component, item); - _adminLogManager.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(user)} put {ToPrettyString(item)} inside {ToPrettyString(uid)}."); - - return true; + $"{ToPrettyString(args.User.Value)} threw {ToPrettyString(args.Thrown)} at {ToPrettyString(uid)}, and it landed inside."); } - private void OnInteractUsing(EntityUid uid, DeepFryerComponent component, InteractUsingEvent args) + AfterInsert(uid, component, args.Thrown); + + args.Handled = true; + } + + private void OnSolutionChange(EntityUid uid, DeepFryerComponent component, SolutionChangedEvent args) + { + UpdateUserInterface(uid, component); + UpdateAmbientSound(uid, component); + } + + private void OnRelayMovement(EntityUid uid, DeepFryerComponent component, + ref ContainerRelayMovementEntityEvent args) + { + if (!component.Storage.Remove(args.Entity, EntityManager, destination: Transform(uid).Coordinates)) + return; + + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-entity-escape", + ("victim", Identity.Entity(args.Entity, EntityManager)), + ("deepFryer", uid)), + uid, + PopupType.SmallCaution); + } + + private void OnBeforeActivatableUIOpen(EntityUid uid, DeepFryerComponent component, + BeforeActivatableUIOpenEvent args) + { + UpdateUserInterface(uid, component); + } + + private void OnRemoveItem(EntityUid uid, DeepFryerComponent component, DeepFryerRemoveItemMessage args) + { + var removedItem = EntityManager.GetEntity(args.Item); + if (removedItem.Valid) { - if (args.Handled) + //JJ Comment - This line should be unnecessary. Some issue is keeping the UI from updating when converting straight to a Burned Mess while the UI is still open. To replicate, put a Raw Meat in the fryer with no oil in it. Wait until it sputters with no effect. It should transform to Burned Mess, but doesn't. + if (!component.Storage.Remove(removedItem)) return; - // By default, allow entities with SolutionTransfer or Tool - // components to perform their usual actions. Inserting them (if - // the chef really wants to) will be supported through the UI. - if (HasComp(args.Used) || - HasComp(args.Used)) - { - return; - } - - if (TryInsertItem(uid, component, args.User, args.Used)) - args.Handled = true; - } - - private void OnBeforeActivatableUIOpen(EntityUid uid, DeepFryerComponent component, BeforeActivatableUIOpenEvent args) - { - UpdateUserInterface(uid, component); - } - - private void OnRemoveItem(EntityUid uid, DeepFryerComponent component, DeepFryerRemoveItemMessage args) - { - var removedItem = EntityManager.GetEntity( args.Item ); - if (removedItem.Valid) { //JJ Comment - This line should be unnecessary. Some issue is keeping the UI from updating when converting straight to a Burned Mess while the UI is still open. To replicate, put a Raw Meat in the fryer with no oil in it. Wait until it sputters with no effect. It should transform to Burned Mess, but doesn't. - if (!component.Storage.Remove(removedItem)) - return; - - var user = args.Session.AttachedEntity; - - if (user != null) - { - _handsSystem.TryPickupAnyHand(user.Value, removedItem); - - _adminLogManager.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(user.Value)} took {ToPrettyString(args.Item)} out of {ToPrettyString(uid)}."); - } - - _audioSystem.PlayPvs(component.SoundRemoveItem, uid, AudioParamsInsertRemove); - - UpdateUserInterface(component.Owner, component); - } - } - - private void OnInsertItem(EntityUid uid, DeepFryerComponent component, DeepFryerInsertItemMessage args) - { - var user = args.Session.AttachedEntity; - - if (user == null || - !TryComp(user, out var handsComponent) || - handsComponent.ActiveHandEntity == null) - { - return; - } - - if (handsComponent.ActiveHandEntity != null) - TryInsertItem(uid, component, user.Value, handsComponent.ActiveHandEntity.Value); - } - - /// - /// This is a helper function for ScoopVat and ClearSlag. - /// - private bool TryGetActiveHandSolutionContainer( - EntityUid fryer, - EntityUid user, - [NotNullWhen(true)] out EntityUid? heldItem, - [NotNullWhen(true)] out Solution? solution, - out FixedPoint2 transferAmount) - { - heldItem = null; - solution = null; - transferAmount = FixedPoint2.Zero; - - if (!TryComp(user, out var handsComponent)) - return false; - - heldItem = handsComponent.ActiveHandEntity; - - if (heldItem == null || - !TryComp(heldItem, out var solutionTransferComponent) || - !_solutionContainerSystem.TryGetRefillableSolution(heldItem.Value, out var refillableSolution) || - !solutionTransferComponent.CanReceive) - { - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-need-liquid-container-in-hand"), - fryer, - user); - - return false; - } - - solution = refillableSolution; - transferAmount = solutionTransferComponent.TransferAmount; - - return true; - } - - private void OnScoopVat(EntityUid uid, DeepFryerComponent component, DeepFryerScoopVatMessage args) - { - var user = args.Session.AttachedEntity; - - if (user == null || - !TryGetActiveHandSolutionContainer(uid, user.Value, out var heldItem, out var heldSolution, out var transferAmount)) - { - return; - } - - _solutionTransferSystem.Transfer(user.Value, - uid, - component.Solution, - heldItem.Value, - heldSolution, - transferAmount); - - // UI update is not necessary here, because the solution change event handles it. - } - - private void OnClearSlagStart(EntityUid uid, DeepFryerComponent component, DeepFryerClearSlagMessage args) - { - var user = args.Session.AttachedEntity; - - if (user == null || - !TryGetActiveHandSolutionContainer(uid, user.Value, out var heldItem, out var heldSolution, out var transferAmount)) - { - return; - } - - var wasteVolume = GetWasteVolume(uid, component); - if (wasteVolume == FixedPoint2.Zero) - { - _popupSystem.PopupEntity( - Loc.GetString("deep-fryer-oil-no-slag"), - uid, - user.Value); - - return; - } - - var delay = Math.Clamp((float) wasteVolume * 0.1f, 1f, 5f); - - var ev = new ClearSlagDoAfterEvent(heldSolution, transferAmount); - - //JJ Comment - not sure I have DoAfterArgs configured correctly. - var doAfterArgs = new DoAfterArgs(EntityManager, user.Value, delay, ev, uid, uid, used: heldItem) - { - BreakOnDamage = true, - BreakOnTargetMove = true, - BreakOnUserMove = true, - MovementThreshold = 0.25f, - NeedHand = true, - }; - - _doAfterSystem.TryStartDoAfter(doAfterArgs); - } - - private void OnRemoveAllItems(EntityUid uid, DeepFryerComponent component, DeepFryerRemoveAllItemsMessage args) - { - if (component.Storage.ContainedEntities.Count == 0) - return; - - _containerSystem.EmptyContainer(component.Storage, false); - var user = args.Session.AttachedEntity; if (user != null) + { + _handsSystem.TryPickupAnyHand(user.Value, removedItem); + _adminLogManager.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(user.Value)} removed all items from {ToPrettyString(uid)}."); + $"{ToPrettyString(user.Value)} took {ToPrettyString(args.Item)} out of {ToPrettyString(uid)}."); + } _audioSystem.PlayPvs(component.SoundRemoveItem, uid, AudioParamsInsertRemove); UpdateUserInterface(component.Owner, component); } + } - private void OnClearSlag(EntityUid uid, DeepFryerComponent component, ClearSlagDoAfterEvent args) + /// + /// This is a helper function for ScoopVat and ClearSlag. + /// + private bool TryGetActiveHandSolutionContainer( + EntityUid fryer, + EntityUid user, + [NotNullWhen(true)] out EntityUid? heldItem, + [NotNullWhen(true)] out Entity? solution, + out FixedPoint2 transferAmount) + { + heldItem = null; + solution = null; + transferAmount = FixedPoint2.Zero; + + if (!TryComp(user, out var handsComponent)) + return false; + + heldItem = handsComponent.ActiveHandEntity; + + if (heldItem == null || + !TryComp(heldItem, out var solutionTransferComponent) || + !_solutionContainerSystem.TryGetRefillableSolution(heldItem.Value, out var solEnt, out var _) || + !solutionTransferComponent.CanReceive) { - if (args.Handled || args.Cancelled || args.Args.Used == null) - return; + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-need-liquid-container-in-hand"), + fryer, + user); - FixedPoint2 reagentCount = component.WasteReagents.Count(); - - var removingSolution = new Solution(); - foreach (var reagent in component.WasteReagents) - { - var removed = component.Solution.RemoveReagent(reagent.Reagent.ToString(), args.Amount / reagentCount); - removingSolution.AddReagent(reagent.Reagent.ToString(), removed); - } - - _solutionContainerSystem.UpdateChemicals(uid, component.Solution); - _solutionContainerSystem.TryMixAndOverflow(args.Args.Used.Value, args.Solution, removingSolution, args.Solution.MaxVolume, out var _); - } - private void OnInitDeepFried(EntityUid uid, DeepFriedComponent component, ComponentInit args) - { - var meta = MetaData(uid); - component.OriginalName = meta.EntityName; - _metaDataSystem.SetEntityName(uid, Loc.GetString("deep-fried-crispy-item", ("entity", meta.EntityName))); + return false; } - private void OnExamineFried(EntityUid uid, DeepFriedComponent component, ExaminedEvent args) + solution = solEnt; + transferAmount = solutionTransferComponent.TransferAmount; + + return true; + } + + private void OnScoopVat(EntityUid uid, DeepFryerComponent component, DeepFryerScoopVatMessage args) + { + var user = args.Session.AttachedEntity; + + if (user == null || + !TryGetActiveHandSolutionContainer(uid, user.Value, out var heldItem, out var heldSolution, + out var transferAmount)) + return; + + if (!_solutionContainerSystem.TryGetSolution(component.Owner, component.Solution.Name, out var solution)) + return; + + _solutionTransferSystem.Transfer(user.Value, + uid, + solution.Value, + heldItem.Value, + heldSolution.Value, + transferAmount); + + // UI update is not necessary here, because the solution change event handles it. + } + + private void OnClearSlagStart(EntityUid uid, DeepFryerComponent component, DeepFryerClearSlagMessage args) + { + var user = args.Session.AttachedEntity; + + if (user == null || + !TryGetActiveHandSolutionContainer(uid, user.Value, out var heldItem, out var heldSolution, + out var transferAmount)) + return; + + var wasteVolume = GetWasteVolume(uid, component); + if (wasteVolume == FixedPoint2.Zero) { - switch (component.Crispiness) - { - case 0: - args.PushMarkup(Loc.GetString("deep-fried-crispy-item-examine")); - break; - case 1: - args.PushMarkup(Loc.GetString("deep-fried-fried-item-examine")); - break; - default: - args.PushMarkup(Loc.GetString("deep-fried-burned-item-examine")); - break; - } + _popupSystem.PopupEntity( + Loc.GetString("deep-fryer-oil-no-slag"), + uid, + user.Value); + + return; } - private void OnPriceCalculation(EntityUid uid, DeepFriedComponent component, ref PriceCalculationEvent args) + var delay = Math.Clamp((float) wasteVolume * 0.1f, 1f, 5f); + + var ev = new ClearSlagDoAfterEvent(heldSolution.Value.Comp.Solution, transferAmount); + + //JJ Comment - not sure I have DoAfterArgs configured correctly. + var doAfterArgs = new DoAfterArgs(EntityManager, user.Value, delay, ev, uid, uid, heldItem) { - args.Price *= component.PriceCoefficient; + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + MovementThreshold = 0.25f, + NeedHand = true + }; + + _doAfterSystem.TryStartDoAfter(doAfterArgs); + } + + private void OnRemoveAllItems(EntityUid uid, DeepFryerComponent component, DeepFryerRemoveAllItemsMessage args) + { + if (component.Storage.ContainedEntities.Count == 0) + return; + + _containerSystem.EmptyContainer(component.Storage); + + var user = args.Session.AttachedEntity; + + if (user != null) + { + _adminLogManager.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(user.Value)} removed all items from {ToPrettyString(uid)}."); } - private void OnSliceDeepFried(EntityUid uid, DeepFriedComponent component, SliceFoodEvent args) + _audioSystem.PlayPvs(component.SoundRemoveItem, uid, AudioParamsInsertRemove); + + UpdateUserInterface(component.Owner, component); + } + + private void OnClearSlag(EntityUid uid, DeepFryerComponent component, ClearSlagDoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Args.Used == null) + return; + + FixedPoint2 reagentCount = component.WasteReagents.Count(); + + var removingSolution = new Solution(); + foreach (var reagent in component.WasteReagents) { - MakeCrispy(args.Slice); + var removed = component.Solution.RemoveReagent(reagent.Reagent.ToString(), args.Amount / reagentCount); + removingSolution.AddReagent(reagent.Reagent.ToString(), removed); + } - // Copy relevant values to the slice. - var sourceDeepFriedComponent = Comp(args.Food); - var sliceDeepFriedComponent = Comp(args.Slice); + if (!_solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out var solution)) + return; - sliceDeepFriedComponent.Crispiness = sourceDeepFriedComponent.Crispiness; - sliceDeepFriedComponent.PriceCoefficient = sourceDeepFriedComponent.PriceCoefficient; + if (!_solutionContainerSystem.TryGetSolution(args.Used!.Value, args.Solution.Name, out var targetSolution)) + return; - UpdateDeepFriedName(args.Slice, sliceDeepFriedComponent); + _solutionContainerSystem.UpdateChemicals(solution.Value); + _solutionContainerSystem.TryMixAndOverflow(targetSolution.Value, removingSolution, + args.Solution.MaxVolume, out var _); + } - // TODO: Flavor profiles aren't copied to the slices. This should - // probably be handled on upstream, but for now let's assume the - // oil of the deep fryer is overpowering enough for this small - // hack. This is likely the only place where it would be useful. - if (TryComp(args.Food, out var sourceFlavorProfileComponent) && - TryComp(args.Slice, out var sliceFlavorProfileComponent)) - { - sliceFlavorProfileComponent.Flavors.UnionWith(sourceFlavorProfileComponent.Flavors); - sliceFlavorProfileComponent.IgnoreReagents.UnionWith(sourceFlavorProfileComponent.IgnoreReagents); - } + private void OnInitDeepFried(EntityUid uid, DeepFriedComponent component, ComponentInit args) + { + var meta = MetaData(uid); + component.OriginalName = meta.EntityName; + _metaDataSystem.SetEntityName(uid, Loc.GetString("deep-fried-crispy-item", ("entity", meta.EntityName))); + } + + private void OnExamineFried(EntityUid uid, DeepFriedComponent component, ExaminedEvent args) + { + switch (component.Crispiness) + { + case 0: + args.PushMarkup(Loc.GetString("deep-fried-crispy-item-examine")); + break; + case 1: + args.PushMarkup(Loc.GetString("deep-fried-fried-item-examine")); + break; + default: + args.PushMarkup(Loc.GetString("deep-fried-burned-item-examine")); + break; } } - - public sealed class DeepFryAttemptEvent : CancellableEntityEventArgs + private void OnPriceCalculation(EntityUid uid, DeepFriedComponent component, ref PriceCalculationEvent args) { - public EntityUid DeepFryer { get; } - - public DeepFryAttemptEvent(EntityUid deepFryer) - { - DeepFryer = deepFryer; - } + args.Price *= component.PriceCoefficient; } - public sealed class BeingDeepFriedEvent : EntityEventArgs + private void OnSliceDeepFried(EntityUid uid, DeepFriedComponent component, SliceFoodEvent args) { - public EntityUid DeepFryer { get; } - public EntityUid Item { get; } - public bool TurnIntoFood { get; set; } + MakeCrispy(args.Slice); - public BeingDeepFriedEvent(EntityUid deepFryer, EntityUid item) + // Copy relevant values to the slice. + var sourceDeepFriedComponent = Comp(args.Food); + var sliceDeepFriedComponent = Comp(args.Slice); + + sliceDeepFriedComponent.Crispiness = sourceDeepFriedComponent.Crispiness; + sliceDeepFriedComponent.PriceCoefficient = sourceDeepFriedComponent.PriceCoefficient; + + UpdateDeepFriedName(args.Slice, sliceDeepFriedComponent); + + // TODO: Flavor profiles aren't copied to the slices. This should + // probably be handled on upstream, but for now let's assume the + // oil of the deep fryer is overpowering enough for this small + // hack. This is likely the only place where it would be useful. + if (TryComp(args.Food, out var sourceFlavorProfileComponent) && + TryComp(args.Slice, out var sliceFlavorProfileComponent)) { - DeepFryer = deepFryer; - Item = item; + sliceFlavorProfileComponent.Flavors.UnionWith(sourceFlavorProfileComponent.Flavors); + sliceFlavorProfileComponent.IgnoreReagents.UnionWith(sourceFlavorProfileComponent.IgnoreReagents); } } } + +public sealed class DeepFryAttemptEvent : CancellableEntityEventArgs +{ + public EntityUid DeepFryer { get; } + + public DeepFryAttemptEvent(EntityUid deepFryer) + { + DeepFryer = deepFryer; + } +} + +public sealed class BeingDeepFriedEvent : EntityEventArgs +{ + public EntityUid DeepFryer { get; } + public EntityUid Item { get; } + public bool TurnIntoFood { get; set; } + + public BeingDeepFriedEvent(EntityUid deepFryer, EntityUid item) + { + DeepFryer = deepFryer; + Item = item; + } +} diff --git a/Content.Server/Nyanotrasen/Mail/MailSystem.cs b/Content.Server/Nyanotrasen/Mail/MailSystem.cs index 5f63e7f2223..0f2352b29a9 100644 --- a/Content.Server/Nyanotrasen/Mail/MailSystem.cs +++ b/Content.Server/Nyanotrasen/Mail/MailSystem.cs @@ -9,6 +9,7 @@ using Content.Server.Access.Systems; using Content.Server.Cargo.Components; using Content.Server.Cargo.Systems; using Content.Server.Chat.Systems; +using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Chemistry.EntitySystems; using Content.Server.Damage.Components; using Content.Server.Destructible; diff --git a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs index 8267aad972f..eccf064e33d 100644 --- a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs +++ b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Server.Botany; using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; +using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Fluids.EntitySystems; using Content.Server.Psionics; using Content.Shared.Abilities.Psionics; @@ -175,7 +176,7 @@ public sealed class OracleSystem : EntitySystem sol.AddReagent(reagent, amount); - _solutionSystem.TryMixAndOverflow(uid, fountainSol, sol, fountainSol.MaxVolume, out var overflowing); + _solutionSystem.TryMixAndOverflow(fountainSol.Value, sol, fountainSol.Value.Comp.Solution.MaxVolume, out var overflowing); if (overflowing != null && overflowing.Volume > 0) _puddleSystem.TrySpillAt(uid, overflowing, out var _); diff --git a/Content.Shared/Nyanotrasen/Kitchen/ClearSlagDoAfterEvent.cs b/Content.Shared/Nyanotrasen/Kitchen/ClearSlagDoAfterEvent.cs index e4ecb88d9a4..681dae88c4d 100644 --- a/Content.Shared/Nyanotrasen/Kitchen/ClearSlagDoAfterEvent.cs +++ b/Content.Shared/Nyanotrasen/Kitchen/ClearSlagDoAfterEvent.cs @@ -1,9 +1,9 @@ -using Robust.Shared.Serialization; using Content.Shared.Chemistry.Components; using Content.Shared.DoAfter; using Content.Shared.FixedPoint; +using Robust.Shared.Serialization; -namespace Content.Shared.Kitchen +namespace Content.Shared.Nyanotrasen.Kitchen { [Serializable, NetSerializable] public sealed partial class ClearSlagDoAfterEvent : DoAfterEvent diff --git a/Content.Shared/Nyanotrasen/Kitchen/Components/SharedDeepFriedComponent.cs b/Content.Shared/Nyanotrasen/Kitchen/Components/SharedDeepFriedComponent.cs index 51eca172981..d1f71670f20 100644 --- a/Content.Shared/Nyanotrasen/Kitchen/Components/SharedDeepFriedComponent.cs +++ b/Content.Shared/Nyanotrasen/Kitchen/Components/SharedDeepFriedComponent.cs @@ -1,7 +1,7 @@ using Robust.Shared.GameStates; using Robust.Shared.Serialization; -namespace Content.Shared.Kitchen.Components +namespace Content.Shared.Nyanotrasen.Kitchen.Components { [NetworkedComponent] public abstract partial class SharedDeepFriedComponent : Component diff --git a/Content.Shared/Nyanotrasen/Kitchen/Components/SharedDeepFryerComponent.cs b/Content.Shared/Nyanotrasen/Kitchen/Components/SharedDeepFryerComponent.cs index da7319869d9..7144ed5cdd9 100644 --- a/Content.Shared/Nyanotrasen/Kitchen/Components/SharedDeepFryerComponent.cs +++ b/Content.Shared/Nyanotrasen/Kitchen/Components/SharedDeepFryerComponent.cs @@ -1,6 +1,6 @@ using Robust.Shared.Serialization; -namespace Content.Shared.Kitchen.Components +namespace Content.Shared.Nyanotrasen.Kitchen.Components { public abstract partial class SharedDeepFryerComponent : Component { } diff --git a/Content.Shared/Nyanotrasen/Kitchen/SharedDeepfryerSystem.cs b/Content.Shared/Nyanotrasen/Kitchen/SharedDeepfryerSystem.cs new file mode 100644 index 00000000000..efc177de916 --- /dev/null +++ b/Content.Shared/Nyanotrasen/Kitchen/SharedDeepfryerSystem.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.Nyanotrasen.Kitchen; + +public abstract class SharedDeepfryerSystem : EntitySystem +{ +} diff --git a/Content.Shared/Nyanotrasen/Kitchen/UI/DeepFryerMessages.cs b/Content.Shared/Nyanotrasen/Kitchen/UI/DeepFryerMessages.cs index fa1df8a71e8..31b107310b0 100644 --- a/Content.Shared/Nyanotrasen/Kitchen/UI/DeepFryerMessages.cs +++ b/Content.Shared/Nyanotrasen/Kitchen/UI/DeepFryerMessages.cs @@ -1,7 +1,7 @@ -using Robust.Shared.Serialization; using Content.Shared.FixedPoint; +using Robust.Shared.Serialization; -namespace Content.Shared.Kitchen.UI +namespace Content.Shared.Nyanotrasen.Kitchen.UI { [Serializable, NetSerializable] public sealed class DeepFryerBoundUserInterfaceState : BoundUserInterfaceState