833 lines
32 KiB
C#
833 lines
32 KiB
C#
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<DeepFryerComponent, ComponentInit>(OnInitDeepFryer);
|
|
SubscribeLocalEvent<DeepFryerComponent, PowerChangedEvent>(OnPowerChange);
|
|
SubscribeLocalEvent<DeepFryerComponent, RefreshPartsEvent>(OnRefreshParts);
|
|
SubscribeLocalEvent<DeepFryerComponent, MachineDeconstructedEvent>(OnDeconstruct);
|
|
SubscribeLocalEvent<DeepFryerComponent, DestructionEventArgs>(OnDestruction);
|
|
SubscribeLocalEvent<DeepFryerComponent, ThrowHitByEvent>(OnThrowHitBy);
|
|
SubscribeLocalEvent<DeepFryerComponent, SolutionChangedEvent>(OnSolutionChange);
|
|
SubscribeLocalEvent<DeepFryerComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
|
|
SubscribeLocalEvent<DeepFryerComponent, InteractUsingEvent>(OnInteractUsing);
|
|
|
|
SubscribeLocalEvent<DeepFryerComponent, BeforeActivatableUIOpenEvent>(OnBeforeActivatableUIOpen);
|
|
SubscribeLocalEvent<DeepFryerComponent, DeepFryerRemoveItemMessage>(OnRemoveItem);
|
|
SubscribeLocalEvent<DeepFryerComponent, DeepFryerInsertItemMessage>(OnInsertItem);
|
|
SubscribeLocalEvent<DeepFryerComponent, DeepFryerScoopVatMessage>(OnScoopVat);
|
|
SubscribeLocalEvent<DeepFryerComponent, DeepFryerClearSlagMessage>(OnClearSlagStart);
|
|
SubscribeLocalEvent<DeepFryerComponent, DeepFryerRemoveAllItemsMessage>(OnRemoveAllItems);
|
|
SubscribeLocalEvent<DeepFryerComponent, ClearSlagDoAfterEvent>(OnClearSlag);
|
|
|
|
SubscribeLocalEvent<DeepFriedComponent, ComponentInit>(OnInitDeepFried);
|
|
SubscribeLocalEvent<DeepFriedComponent, ExaminedEvent>(OnExamineFried);
|
|
SubscribeLocalEvent<DeepFriedComponent, PriceCalculationEvent>(OnPriceCalculation);
|
|
SubscribeLocalEvent<DeepFriedComponent, FoodSlicedEvent>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does the deep fryer have hot oil?
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is mainly for audio.
|
|
/// </remarks>
|
|
private bool HasBubblingOil(EntityUid uid, DeepFryerComponent component)
|
|
{
|
|
return _powerReceiverSystem.IsPowered(uid) && GetOilVolume(uid, component) > FixedPoint2.Zero;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns how much total oil is in the vat.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns how much total waste is in the vat.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a percentage of how much of the total solution is usable oil.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a percentage of how much of the total volume is usable oil.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This takes care of anything that would happen to an item with or
|
|
/// without enough oil.
|
|
/// </summary>
|
|
private void CookItem(EntityUid uid, DeepFryerComponent component, EntityUid item)
|
|
{
|
|
if (TryComp<TemperatureComponent>(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<SolutionContainerManagerComponent>(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<FoodComponent>(item) || HasComp<MobStateComponent>(item)) &&
|
|
TryComp<DamageableComponent>(item, out var damageableComponent))
|
|
{
|
|
var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>(CookingDamageType),
|
|
CookingDamageAmount);
|
|
|
|
var result = _damageableSystem.TryChangeDamage(item, damage, origin: uid);
|
|
if (result?.GetTotal() > FixedPoint2.Zero)
|
|
{
|
|
// TODO: Smoke, waste, sound, or some indication.
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroy a food item and replace it with a charred mess.
|
|
/// </summary>
|
|
private void BurnItem(EntityUid uid, DeepFryerComponent component, EntityUid item)
|
|
{
|
|
if (HasComp<FoodComponent>(item) &&
|
|
!HasComp<MobStateComponent>(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<CrispinessLevelSetPrototype>(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
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private void DeepFry(EntityUid uid, DeepFryerComponent component, EntityUid item)
|
|
{
|
|
if (MetaData(item).EntityPrototype?.ID == component.CharredPrototype)
|
|
return;
|
|
|
|
// Frontier: deep fryer-specific "recipes"
|
|
if (TryComp<DeepFrySpawnComponent>(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<PreventCrispingComponent>(spawn);
|
|
_containerSystem.Insert(spawn, component.Storage);
|
|
Del(item);
|
|
|
|
// Reduce volume, replace waste
|
|
component.Solution.RemoveSolution(friableVolume);
|
|
component.WasteToAdd += friableVolume;
|
|
}
|
|
return;
|
|
}
|
|
else if (TryComp<PreventCrispingComponent>(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<DeepFriedComponent>(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<CrispinessLevelSetPrototype>(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<MobStateComponent>(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<ItemComponent>(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<CrispinessLevelSetPrototype> crispinessLevelSet)
|
|
{
|
|
var maxCrispiness = MaximumCrispiness; // Default maximum crispiness (should burn if something goes wrong)
|
|
if (_prototypeManager.TryIndex<CrispinessLevelSetPrototype>(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<Container>(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<ReagentPrototype>(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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make sure the UI and interval tracker are updated anytime something
|
|
/// is inserted into one of the baskets.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is used instead of EntInsertedIntoContainerMessage so charred
|
|
/// items can be inserted into the deep fryer without triggering this
|
|
/// event.
|
|
/// </remarks>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allow thrown items to land in a basket.
|
|
/// </summary>
|
|
private void OnThrowHitBy(EntityUid uid, DeepFryerComponent component, ThrowHitByEvent args)
|
|
{
|
|
if (args.Handled)
|
|
return;
|
|
|
|
// Chefs never miss this. :)
|
|
var missChance = HasComp<ProfessionalChefComponent>(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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a helper function for ScoopVat and ClearSlag.
|
|
/// </summary>
|
|
private bool TryGetActiveHandSolutionContainer(
|
|
EntityUid fryer,
|
|
EntityUid user,
|
|
[NotNullWhen(true)] out EntityUid? heldItem,
|
|
[NotNullWhen(true)] out Entity<SolutionComponent>? solution,
|
|
out FixedPoint2 transferAmount)
|
|
{
|
|
heldItem = null;
|
|
solution = null;
|
|
transferAmount = FixedPoint2.Zero;
|
|
|
|
if (!TryComp<HandsComponent>(user, out var handsComponent))
|
|
return false;
|
|
|
|
heldItem = handsComponent.ActiveHandEntity;
|
|
|
|
if (heldItem == null ||
|
|
!TryComp<SolutionTransferComponent>(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<CrispinessLevelSetPrototype>(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<DeepFriedComponent>(args.Food);
|
|
var sliceDeepFriedComponent = Comp<DeepFriedComponent>(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<FlavorProfileComponent>(args.Food, out var sourceFlavorProfileComponent) &&
|
|
TryComp<FlavorProfileComponent>(args.Slice, out var sliceFlavorProfileComponent))
|
|
{
|
|
sliceFlavorProfileComponent.Flavors.UnionWith(sourceFlavorProfileComponent.Flavors);
|
|
sliceFlavorProfileComponent.IgnoreReagents.UnionWith(sourceFlavorProfileComponent.IgnoreReagents);
|
|
}
|
|
}
|
|
|
|
public void SetDeepFriedCrispinessLevelSet(EntityUid uid, DeepFriedComponent component, ProtoId<CrispinessLevelSetPrototype> 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;
|
|
}
|
|
}
|