using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Content.Shared.Kitchen.Components;
using Content.Shared.Stacks;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.Shared.Kitchen.EntitySystems;
public abstract partial class MicrowaveSystem
{
///
/// This event tries to get secret recipes that the microwave might be capable of.
/// Currently, we only check the microwave itself, but in the future, the user might be able to learn recipes.
///
[SubscribeLocalEvent]
private void OnGetSecretRecipes(Entity ent, ref GetSecretRecipesEvent args)
{
foreach (var recipeId in ent.Comp.ProvidedRecipes)
{
if (ProtoMan.Resolve(recipeId, out var recipeProto))
args.Recipes.Add(recipeProto);
}
}
///
/// Adds all usable ingredients from a given entity to an ingredient list.
///
///
/// The entity itself is a "solid".
/// If it's a stack, then that stack is its "stacks".
/// If it has a usable ingredient solution, then the solution's contents are "reagents".
///
/// The entity to use as ingredients.
/// A dictionary of available ingredients to add to.
private void AddItemIngredients(EntityUid item, ref CookingIngredients ingredients)
{
if (TryGetSolidId(item, out var solidId))
ingredients.AddSolid(solidId.Value);
if (StackQuery.TryComp(item, out var stack) && TryGetStackId(item, out var stackId))
ingredients.AddStack(stackId.Value, stack.Count);
if (TryGetUsableIngredientSolution(item, out _, out var solution))
{
foreach (var (reagent, quantity) in solution.Contents)
{
ingredients.AddReagent(reagent.Prototype, quantity);
}
}
}
///
/// Attempts to get a solution from an entity that can be used as viable ingredients in a recipe.
///
///
/// For example, a beaker's contents will work, but not the contents of an uncracked egg.
///
/// The entity to attempt to get a usable ingredient solution for.
/// A usable solution entity, if available.
/// A usable solution, if available.
/// Whether or not a usable ingredient solution was successfully retrieved.
private bool TryGetUsableIngredientSolution(EntityUid uid,
[NotNullWhen(true)] out Entity? solutionEntity,
[NotNullWhen(true)] out Solution? solution)
{
return Solution.TryGetDrainableSolution(uid, out solutionEntity, out solution);
}
///
/// Given a dictionary of stacks that need to be spent in a recipe, and the amount of stacks
/// of a stack we have available, this function gets the number of stacks we need to remove
/// from the stack entity. It also removes this amount from the remaining stacks dictionary.
///
/// How many stacks we have available.
/// The stack ID associated with the stack.
/// How many stacks still need to be spent in the recipe.
/// How many stacks we should remove from the available stack entity.
private static int SpendStackQuantity(int availableStacks,
ProtoId stackId,
CookingIngredients ingredientsToSpend)
{
if (!ingredientsToSpend.Stacks.TryGetValue(stackId, out var remaining))
return 0;
var spent = Math.Min(availableStacks, remaining);
ingredientsToSpend.AddStack(stackId, -spent);
return spent;
}
///
/// Given a dictionary of reagents that need to be spent in a recipe, and a quantity of a reagent
/// that we have available in a solution, this function gets the amount of reagents we need to
/// remove from the solution. This also removes that amount from the "reagents to spend" dictionary.
///
/// How much reagent we have available.
/// The ID of the reagent we are spending.
/// A dictionary of reagents that still need to be spent.
/// How much we should reduce the available reagent volume by.
private static FixedPoint2 SpendReagentQuantity(FixedPoint2 availableQuantity,
ProtoId reagent,
CookingIngredients ingredientsToSpend)
{
if (!ingredientsToSpend.Reagents.TryGetValue(reagent, out var remaining))
return 0;
var spent = FixedPoint2.Min(availableQuantity, remaining);
ingredientsToSpend.AddReagent(reagent, -spent);
return spent;
}
///
/// Removes a solid ingredient that is used in a recipe, removing it from the dictionary of
/// remaining solids that still need to be spent in the recipe.
///
/// The entity to remove.
/// The solid ID of the ingredient.
/// The microwave's ingredient storage container.
/// The struct representing ingredients we still need to spend.
private void SubtractSolidContents(EntityUid item,
EntProtoId itemProto,
Container container,
CookingIngredients ingredientsToSpend)
{
if (!ingredientsToSpend.Solids.ContainsKey(itemProto))
return;
ingredientsToSpend.AddSolid(itemProto, -1);
Container.Remove(item, container);
PredictedQueueDel(item);
}
///
/// Given a dictionary of remaining stacks that need to be spent in a recipe, this function
/// reduces a stack entity's count by however many stacks need to be spent. This also removes the
/// stack count from our remaining ingredients.
///
///
/// If a recipe calls for two stacks of plasma sheets, and you put 5 sheets in the microwave, then
/// the plasma sheet stack would reduce by 2 (leaving you with 3 remaining), and plasma will
/// be removed from the remaining stacks dictionary.
///
/// The stack entity.
/// The struct representing ingredients we still need to spend.
private void SubtractStackContents(Entity ent,
CookingIngredients ingredientsToSpend)
{
if (!Resolve(ent.Owner, ref ent.Comp))
return;
var stack = ent.Comp;
var stackId = stack.StackTypeId;
var startingQuantity = stack.Count;
var quantityToRemove = SpendStackQuantity(startingQuantity, stackId, ingredientsToSpend);
_stack.ReduceCount(ent.AsNullable(), quantityToRemove);
}
///
/// Given a dictionary of remaining reagents that still need to be spent in a recipe, this function iterates
/// over a solution's contents and subtracts reagents according to the reagents to spend. This also removes
/// it from our remaining ingredients.
///
///
/// Say you still have 6u mayonnaise remaining, and the solution has 10u mayonnaise, then 6u of
/// mayonnaise would be removed from the solution, leaving you with 4u left over. Then, mayonnaise
/// is removed from the dictionary of remaining reagents.
///
/// The solution entity.
/// The solution itself.
/// The struct representing ingredients we still need to spend.
private void SubtractReagentContents(Entity solutionEntity,
Solution solution,
CookingIngredients ingredientsToSpend)
{
var reagentsToProcess = ingredientsToSpend.Reagents.Keys.ToList();
foreach (var reagent in reagentsToProcess)
{
var availableQuantity = solution.GetTotalPrototypeQuantity(reagent);
if (availableQuantity == 0)
continue;
var quantityToRemove = SpendReagentQuantity(availableQuantity, reagent, ingredientsToSpend);
Solution.RemoveReagent(solutionEntity, reagent, quantityToRemove);
}
}
///
/// Attempt to get the solid ID of a given entity.
///
/// The entity to retrieve a solid ID for.
/// The solid ID of the entity, if any.
///
/// Whether or not the solid ID was successfully retrieved. False if entity lacks an entity prototype ID.
///
// TODO: Solids should be tag-based, or something like that. Not prototype-based.
private bool TryGetSolidId(EntityUid item,
[NotNullWhen(true)] out EntProtoId? solidId)
{
solidId = MetaData(item).EntityPrototype?.ID;
return solidId != null;
}
///
/// Attempt to get the stack ID of a given entity.
///
/// The stack entity.
/// The stack prototype associated with this entity, if any.
///
/// Whether or not a stack ID is successfully retrieved. False if this entity is not a stack.
///
private bool TryGetStackId(Entity ent,
[NotNullWhen(true)] out ProtoId? stack)
{
stack = null;
if (!Resolve(ent.Owner, ref ent.Comp, logMissing: false))
return false;
stack = ent.Comp.StackTypeId;
return true;
}
///
/// Spend ingredients in the microwave based on a given recipe. This deletes ingredient entities (solids),
/// subtracts respective stack counts, and removes reagents from ingredient containers (reagents).
///
///
/// This function does not check whether or not the contents have *enough* ingredients to
/// subtract - it simply performs the subtraction.
///
/// The microwave entity.
/// The recipe used to spend ingredients.
private void SubtractContents(Entity ent, PortionedRecipe portionedRecipe)
{
if (!ProtoMan.Resolve(portionedRecipe.Recipe, out var recipe))
return;
var ingredientsToSpend = recipe.Ingredients * portionedRecipe.Count;
var solidsToSpend = ingredientsToSpend.Solids;
var stacksToSpend = ingredientsToSpend.Stacks;
var reagentsToSpend = ingredientsToSpend.Reagents;
var microwave = ent.Comp;
var microwaveItems = GetMicrowaveContents(ent.AsNullable()).ToArray();
foreach (var item in microwaveItems)
{
if (solidsToSpend.Count > 0
&& TryGetSolidId(item, out var solidId)
&& solidsToSpend.ContainsKey(solidId.Value))
{
SubtractSolidContents(item, solidId.Value, microwave.Storage, ingredientsToSpend);
continue;
// We're exiting early here; if the solid ingredient is removed from the container,
// then we shouldn't be attempting to use its stack or reagents.
}
if (stacksToSpend.Count > 0
&& StackQuery.TryComp(item, out var stack)
&& TryGetStackId((item, stack), out var stackId)
&& stacksToSpend.ContainsKey(stackId.Value))
{
SubtractStackContents((item, stack), ingredientsToSpend);
if (Deleted(item) || stack.Count <= 0)
continue;
// We're exiting early here - if the stack is empty, then the stack entity
// is gonna be deleted. Which means we shouldn't be using its reagents.
}
if (reagentsToSpend.Count > 0
&& TryGetUsableIngredientSolution(item, out var solutionEntity, out var solution)
&& solution.Volume > 0)
SubtractReagentContents(solutionEntity.Value, solution, ingredientsToSpend);
}
}
}