using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Audio; using Content.Server.Cargo.Systems; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Construction; using Content.Server.DoAfter; using Content.Server.Fluids.EntitySystems; using Content.Server.Kitchen.Components; using Content.Server.Nutrition; using Content.Server.Nutrition.Components; using Content.Server.Nyanotrasen.Kitchen.Components; using Content.Server.Popups; using Content.Server.Power.EntitySystems; using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; 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.Destructible; using Content.Shared.DoAfter; using Content.Shared.EntityEffects; using Content.Shared.Examine; using Content.Shared.FixedPoint; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Events; using Content.Shared.Nyanotrasen.Kitchen; using Content.Shared.Nyanotrasen.Kitchen.Components; using Content.Shared.Nyanotrasen.Kitchen.Prototypes; using Content.Shared.Nyanotrasen.Kitchen.UI; using Content.Shared.Popups; using Content.Shared.Power; using Content.Shared.Throwing; using Content.Shared.UserInterface; using Content.Shared.Whitelist; 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; using Content.Shared._NF.Kitchen.Components; // Frontier using Content.Server._NF.Kitchen.Components; // Frontier using Content.Shared.Cargo; // Frontier using Content.Shared.NameModifier.EntitySystems; // Frontier namespace Content.Server.Nyanotrasen.Kitchen.EntitySystems; public sealed partial class DeepFryerSystem : SharedDeepfryerSystem { [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 SharedSolutionContainerSystem _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!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly NameModifierSystem _nameModifier = default!; // Frontier 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, 5f, 1.5f, 1f, false, 0f, 0.2f); private ISawmill _sawmill = default!; public override void Initialize() { 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); } 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())); _uiSystem.SetUiState(uid, DeepFryerUiKey.Key, 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) { 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) { if (component.Solution.Volume > 0) // Frontier: ensure no negative division. return GetOilVolume(uid, component) / component.Solution.Volume; return FixedPoint2.Zero; } /// /// Returns a percentage of how much of the total volume is usable oil. /// public FixedPoint2 GetOilLevel(EntityUid uid, DeepFryerComponent component) { if (component.Solution.MaxVolume > 0) // Frontier: ensure no negative division or division by zero. return GetOilVolume(uid, component) / component.Solution.MaxVolume; return FixedPoint2.Zero; } /// /// 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) * _temperature.GetHeatCapacity(item, tempComp); if (delta > 0f) _temperature.ChangeHeat(item, delta, false, tempComp); } if (TryComp(item, out var solutions) && solutions.Solutions != null) { foreach (var (_, solution) in solutions.Solutions) { if (_solutionContainerSystem.TryGetSolution(item, solution.Name, out var solutionRef)) _solutionContainerSystem.SetTemperature(solutionRef!.Value, 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?.GetTotal() > 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); _containerSystem.Insert(charred, component.Storage); Del(item); } } private void UpdateDeepFriedName(EntityUid uid, DeepFriedComponent component, CrispinessLevelSetPrototype? crispinessLevels = null) // Frontier: add crispinessLevelSet { if (component.OriginalName == null) return; // Frontier: assign crispiness levels to a prototype if (crispinessLevels == null && !_prototypeManager.TryIndex(component.CrispinessLevelSet, out crispinessLevels)) return; if (crispinessLevels.Levels.Count <= 0) return; int crispiness = int.Max(0, component.Crispiness); { string name; if (crispiness < crispinessLevels.Levels.Count) name = crispinessLevels.Levels[crispiness].Name; else name = crispinessLevels.Levels[^1].Name; _metaDataSystem.SetEntityName(uid, Loc.GetString(name, ("entity", component.OriginalName))); } // End Frontier } /// /// 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; // Frontier: deep fryer-specific "recipes" if (TryComp(item, out var deepFriable)) { deepFriable.Cycles--; if (deepFriable.Cycles <= 0) { // Get oil volume to spawn before deleting item. var friableVolume = GetOilAndWasteVolumeForItem(uid, component, item); var spawn = Spawn(deepFriable.Output, Transform(uid).Coordinates); EnsureComp(spawn); _containerSystem.Insert(spawn, component.Storage); Del(item); // Reduce volume, replace waste component.Solution.RemoveSolution(friableVolume); component.WasteToAdd += friableVolume; } return; } else if (TryComp(item, out var blacklist)) { blacklist.Cycles += 1; if (blacklist.Cycles >= GetMaximumCrispiness(component.CrispinessLevelSet)) { BurnItem(uid, component, item); } return; } // End Frontier // 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; var maxCrispiness = MaximumCrispiness; // Default maximum crispiness (should burn if something goes wrong) if (_prototypeManager.TryIndex(deepFriedComponent.CrispinessLevelSet, out var crispinessLevels)) { maxCrispiness = int.Max(0, crispinessLevels.Levels.Count - 1); } if (deepFriedComponent.Crispiness > MaximumCrispiness) { BurnItem(uid, component, item); return; } UpdateDeepFriedName(item, deepFriedComponent, crispinessLevels); return; } // 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 (_whitelistSystem.IsBlacklistPass(component.Blacklist, item)) { _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, component.CrispinessLevelSet); var solutionQuantity = GetOilAndWasteVolumeForItem(uid, component, item); if (_whitelistSystem.IsWhitelistPass(component.Whitelist, item) || beingEvent.TurnIntoFood) MakeEdible(uid, component, item, solutionQuantity); else component.Solution.RemoveSolution(solutionQuantity); component.WasteToAdd += solutionQuantity; } // Frontier: oil/waste volume to a function. private FixedPoint2 GetOilAndWasteVolumeForItem(EntityUid uid, DeepFryerComponent component, EntityUid item) { var itemComponent = Comp(item); // Determine how much solution to spend on this item. return FixedPoint2.Min( component.Solution.Volume, itemComponent.Size.Id switch { "Tiny" => 1, "Small" => 5, "Medium" => 10, "Large" => 15, "Huge" => 30, "Ginormous" => 50, _ => 10 } * component.SolutionSizeCoefficient); } // End Frontier // Frontier: maximum crispiness private int GetMaximumCrispiness(ProtoId crispinessLevelSet) { var maxCrispiness = MaximumCrispiness; // Default maximum crispiness (should burn if something goes wrong) if (_prototypeManager.TryIndex(crispinessLevelSet, out var crispinessLevels)) { maxCrispiness = int.Max(0, crispinessLevels.Levels.Count - 1); } return maxCrispiness; } // End Frontier 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."); if (_solutionContainerSystem.EnsureSolution(uid, component.SolutionName, out var solutionExisted, out var solution)) component.Solution = solution; 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 EntityEffectReagentArgs(uid, EntityManager, null, component.Solution, reagent.Quantity, proto!, 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) { 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) || !_containerSystem.Insert(args.Thrown, component.Storage)) { _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); } 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."); } 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 (!_containerSystem.Remove(args.Entity, component.Storage, 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) { //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 (!_containerSystem.Remove(removedItem, component.Storage)) return; var user = args.Actor; _handsSystem.TryPickupAnyHand(user, removedItem); _adminLogManager.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user)} took {ToPrettyString(args.Item)} out of {ToPrettyString(uid)}."); _audioSystem.PlayPvs(component.SoundRemoveItem, uid, AudioParamsInsertRemove); UpdateUserInterface(uid, component); } } /// /// 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) { _popupSystem.PopupEntity( Loc.GetString("deep-fryer-need-liquid-container-in-hand"), fryer, user); return false; } solution = solEnt; transferAmount = solutionTransferComponent.TransferAmount; return true; } private void OnScoopVat(EntityUid uid, DeepFryerComponent component, DeepFryerScoopVatMessage args) { var user = args.Actor; if (!TryGetActiveHandSolutionContainer(uid, user, out var heldItem, out var heldSolution, out var transferAmount)) return; if (!_solutionContainerSystem.TryGetSolution(uid, component.Solution.Name, out var solution)) return; _solutionTransferSystem.Transfer(user, 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.Actor; if (!TryGetActiveHandSolutionContainer(uid, user, 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); return; } var delay = TimeSpan.FromSeconds(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, delay, ev, uid, uid, heldItem) { BreakOnDamage = true, BreakOnMove = 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.Actor; _adminLogManager.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user)} removed all items from {ToPrettyString(uid)}."); _audioSystem.PlayPvs(component.SoundRemoveItem, uid, AudioParamsInsertRemove); UpdateUserInterface(uid, 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) { var removed = component.Solution.RemoveReagent(reagent.Reagent.ToString(), args.Amount / reagentCount); removingSolution.AddReagent(reagent.Reagent.ToString(), removed); } if (!_solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out var solution)) return; if (!_solutionContainerSystem.TryGetSolution(args.Used!.Value, args.Solution.Name, out var targetSolution)) return; _solutionContainerSystem.UpdateChemicals(solution.Value); _solutionContainerSystem.TryMixAndOverflow(targetSolution.Value, removingSolution, args.Solution.MaxVolume, out var _); } private void OnInitDeepFried(EntityUid uid, DeepFriedComponent component, ComponentInit args) { component.OriginalName = _nameModifier.GetBaseName(uid); // Frontier: prevent reapplying name modifiers UpdateDeepFriedName(uid, component); } private void OnExamineFried(EntityUid uid, DeepFriedComponent component, ExaminedEvent args) { // Frontier: assign crispiness levels to a prototype if (_prototypeManager.TryIndex(component.CrispinessLevelSet, out var crispinessLevels)) { if (crispinessLevels.Levels.Count <= 0) return; int crispiness = int.Max(0, component.Crispiness); { string examineString; if (crispiness < crispinessLevels.Levels.Count) examineString = crispinessLevels.Levels[crispiness].ExamineText; else examineString = crispinessLevels.Levels[^1].ExamineText; args.PushMarkup(Loc.GetString(examineString)); } } // End Frontier } private void OnPriceCalculation(EntityUid uid, DeepFriedComponent component, ref PriceCalculationEvent args) { args.Price *= component.PriceCoefficient; } private void OnSliceDeepFried(EntityUid uid, DeepFriedComponent component, FoodSlicedEvent args) { MakeCrispy(args.Slice, component.CrispinessLevelSet); // 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)) { sliceFlavorProfileComponent.Flavors.UnionWith(sourceFlavorProfileComponent.Flavors); sliceFlavorProfileComponent.IgnoreReagents.UnionWith(sourceFlavorProfileComponent.IgnoreReagents); } } public void SetDeepFriedCrispinessLevelSet(EntityUid uid, DeepFriedComponent component, ProtoId crispiness) { component.CrispinessLevelSet = crispiness; UpdateDeepFriedName(uid, component); } } 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; } }