Delta-v/Content.Shared/_DV/Kitchen/Systems/SharedDeepFryerSystem.cs

321 lines
11 KiB
C#

using Content.Shared._DV.Kitchen.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Whitelist;
using Content.Shared.Verbs;
using Content.Shared.Hands.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.Serialization;
namespace Content.Shared._DV.Kitchen.Systems;
public abstract class SharedDeepFryerSystem : EntitySystem
{
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!;
[Dependency] protected readonly SharedSolutionContainerSystem Solution = default!;
[Dependency] protected readonly SharedTransformSystem Xform = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DeepFryerComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<DeepFryerComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<DeepFryerComponent, DeepFryerInsertDoAfterEvent>(OnInsertDoAfter);
SubscribeLocalEvent<DeepFryerComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAlternativeVerbs);
SubscribeLocalEvent<DeepFryerComponent, ExaminedEvent>(OnExamined);
}
private void OnComponentInit(Entity<DeepFryerComponent> ent, ref ComponentInit args)
{
_container.EnsureContainer<Container>(ent, ent.Comp.ContainerName);
}
private void OnExamined(Entity<DeepFryerComponent> ent, ref ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
if (Solution.TryGetSolution(ent.Owner, ent.Comp.Solution, out _, out var solution) && solution.Volume <= 0)
return;
var qualityLevel = GetOilQualityLevel(ent.Comp.OilQuality);
var (color, labelName) = GetOilQualityInfo(qualityLevel);
args.PushMarkup(Loc.GetString("deep-fryer-oil-quality-examine",
("color", color.ToHex()),
("state", labelName)));
}
private void OnInteractUsing(Entity<DeepFryerComponent> ent, ref InteractUsingEvent args)
{
if (args.Handled)
return;
// Check if the item can be inserted
if (!CanInsertItem(ent, args.Used, out var reason))
{
Popup.PopupClient(reason, ent, args.User);
return;
}
args.Handled = true;
// Start a do-after for inserting the item
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, 1f, new DeepFryerInsertDoAfterEvent(), ent, target: ent, used: args.Used)
{
BreakOnMove = true,
BreakOnDamage = true,
BlockDuplicate = true,
NeedHand = true
};
_doAfter.TryStartDoAfter(doAfterArgs);
}
private void OnInsertDoAfter(Entity<DeepFryerComponent> ent, ref DeepFryerInsertDoAfterEvent args)
{
if (args.Cancelled || args.Handled || args.Used == null)
return;
// Re-check if we can still insert (things might have changed)
if (!CanInsertItem(ent, args.Used.Value, out var reason))
{
Popup.PopupClient(reason, ent, args.User);
return;
}
// Insert the item
if (TryInsertItem(ent, args.Used.Value, args.User))
{
args.Handled = true;
}
}
private void OnGetAlternativeVerbs(Entity<DeepFryerComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (!_container.TryGetContainer(ent, ent.Comp.ContainerName, out var container))
return;
var user = args.User;
// Create an eject verb for each item in the basket
foreach (var item in container.ContainedEntities)
{
var itemName = Name(item);
var itemUid = item;
var verb = new AlternativeVerb
{
Text = Loc.GetString("deep-fryer-eject-item", ("item", itemName)),
Category = VerbCategory.Eject,
Act = () => TryEjectItem(ent, itemUid, user),
Priority = 1
};
args.Verbs.Add(verb);
}
}
/// <summary>
/// Attempts to eject an item from the deep fryer
/// </summary>
[PublicAPI]
public bool TryEjectItem(Entity<DeepFryerComponent> ent, EntityUid item, EntityUid user)
{
if (!_container.TryGetContainer(ent, ent.Comp.ContainerName, out var container))
return false;
if (!container.Contains(item))
return false;
// Remove from container
if (!_container.Remove(item, container))
return false;
// Try to put in user's hands, otherwise drop at fryer location
if (!_hands.TryPickupAnyHand(user, item))
{
var xform = Transform(ent);
Xform.SetCoordinates(item, xform.Coordinates);
}
Popup.PopupClient(Loc.GetString("deep-fryer-eject-item-success", ("item", item)), ent, user);
return true;
}
/// <summary>
/// Checks if an item can be inserted into the deep fryer
/// </summary>
[PublicAPI]
public bool CanInsertItem(Entity<DeepFryerComponent> ent, EntityUid item, out string reason)
{
reason = string.Empty;
// Skip the popup entirely if we're transferring solutions
if (HasComp<SolutionTransferComponent>(item))
return false;
// Check blacklist first since it should override
if (_whitelist.IsWhitelistPass(ent.Comp.Blacklist, item))
{
reason = Loc.GetString("deep-fryer-blacklist-item");
return false;
}
// Check whitelist
if (!_whitelist.IsWhitelistPass(ent.Comp.Whitelist, item))
{
reason = Loc.GetString("deep-fryer-not-food");
return false;
}
// Get the container
if (!_container.TryGetContainer(ent, ent.Comp.ContainerName, out var container))
{
reason = Loc.GetString("deep-fryer-no-container");
return false;
}
// Check if the fryer is full
if (container.ContainedEntities.Count >= ent.Comp.MaxItems)
{
reason = Loc.GetString("deep-fryer-full");
return false;
}
// Check if there's enough oil
if (!HasEnoughOil(ent))
{
reason = Loc.GetString("deep-fryer-insufficient-oil");
return false;
}
return true;
}
/// <summary>
/// Attempts to insert an item into the deep fryer
/// </summary>
[PublicAPI]
public bool TryInsertItem(Entity<DeepFryerComponent> ent, EntityUid item, EntityUid? user)
{
if (!_container.TryGetContainer(ent, ent.Comp.ContainerName, out var container))
return false;
// Insert the item
if (!_container.Insert(item, container))
return false;
Popup.PopupClient(Loc.GetString("deep-fryer-insert-item", ("item", item)), ent, user ?? EntityUid.Invalid);
return true;
}
/// <summary>
/// Checks if the fryer has enough oil to fry items
/// </summary>
protected bool HasEnoughOil(Entity<DeepFryerComponent> ent)
{
if (!Solution.TryGetSolution(ent.Owner, ent.Comp.Solution, out _, out var solution))
return false;
// Check if there's enough total volume
if (solution.Volume < ent.Comp.MinimumOilVolume)
return false;
// Check if any of the reagents in the solution are valid frying oils
foreach (var reagent in solution.Contents)
{
if (ent.Comp.FryingOils.Contains(reagent.Reagent.Prototype))
return true;
}
return false;
}
/// <summary>
/// Gets the number of items currently in the fryer
/// </summary>
[PublicAPI]
public int GetItemCount(Entity<DeepFryerComponent> ent)
{
if (!_container.TryGetContainer(ent, ent.Comp.ContainerName, out var container))
return 0;
return container.ContainedEntities.Count;
}
/// <summary>
/// Determines the oil quality level from the quality value
/// </summary>
[PublicAPI]
public static OilQuality GetOilQualityLevel(float quality)
{
return quality switch
{
>= 0.9f => OilQuality.Pristine,
>= 0.7f => OilQuality.Clean,
>= 0.5f => OilQuality.Used,
>= 0.3f => OilQuality.Dirty,
_ => OilQuality.Foul
};
}
/// <summary>
/// Calculates the oil degradation multiplier based on current oil volume
/// Less oil = faster degradation
/// </summary>
protected float CalculateOilDegradationMultiplier(Entity<DeepFryerComponent> ent)
{
if (!Solution.TryGetSolution(ent.Owner, ent.Comp.Solution, out _, out var solution))
return 1.0f;
var maxVolume = solution.MaxVolume;
var currentVolume = solution.Volume;
var minVolume = ent.Comp.MinimumOilVolume;
// If we don't have enough oil range, just return 1.0
if (maxVolume <= minVolume)
return 1.0f;
// Linear interpolation between 1.0x (at max volume) and MinOilVolumeDegradationMultiplier (at min volume)
// 1.0 + (maxVolume - currentVolume) / (maxVolume - minVolume) * (multiplier - 1.0)
var volumeRatio = (float)((maxVolume - currentVolume) / (maxVolume - minVolume));
var multiplier = 1.0f + volumeRatio * (ent.Comp.MinOilVolumeDegradationMultiplier - 1.0f);
return Math.Max(1.0f, multiplier);
}
/// <summary>
/// Gets the color and label name for a given oil quality level
/// </summary>
[PublicAPI]
public (Color color, string labelName) GetOilQualityInfo(OilQuality quality)
{
return quality switch
{
OilQuality.Pristine => (Color.Green, Loc.GetString("deep-fryer-oil-quality-pristine")),
OilQuality.Clean => (Color.White, Loc.GetString("deep-fryer-oil-quality-clean")),
OilQuality.Used => (Color.Yellow, Loc.GetString("deep-fryer-oil-quality-used")),
OilQuality.Dirty => (Color.Orange, Loc.GetString("deep-fryer-oil-quality-dirty")),
OilQuality.Foul => (Color.Red, Loc.GetString("deep-fryer-oil-quality-foul")),
_ => (Color.White, Loc.GetString("deep-fryer-oil-quality-unknown"))
};
}
}
[Serializable, NetSerializable]
public sealed partial class DeepFryerInsertDoAfterEvent : SimpleDoAfterEvent;