CatStation/Content.Shared/Kitchen/EntitySystems/MicrowaveSystem.Cooking.cs
portfiend b9b794deef
Microwave Refactor and Prediction (#43129)
* adjust fields of meal recipe prototype

* document microwave recipe datafields

* use file scoped namespace for microwave recipes

* more of this

* localize every microwave recipe

* make microwave system shared

* refactor recipe reagent contents

* refactor recipe solids + add material ingredient datafield

* im making this a bit less silly

* awa

* move this shit out

* move this shit out also

* refactor Wzhzhzh

* im gonna shorten this parameter by 1 actually

* move this into recipes

* make processcontents a little better(?)

* clean imports

* rename a struct

* use cooking ingredients struct

* comment

* use whitelist for this

* unused dependency

* rearranged the parameters of get recipe portions

* rearrange some stuff

* clean up more imports

* move container related functions into separate file

* move this function out of ProcessContents

* prevent microwaves from having items inserted while active

* use IsPowered in microwave system

* use byref syntax and Entity<>

* moving the override up
sorry i like it being up here

* refactor microwave updating

* clean up recipe finish logic

* im renaming this function actually

* move some functions around

* move more stuff around

* some comments

* fix compiler error

* fix the yml of all microwave recipes

* use materials instead of solids for this stuff

* oops

* fix this

* turn microwave sparks into datafield

* move activelymicrowaved events into separate folder

* make UpdateUserInterfaceState use entity only

* rearrange some functions

* move UI functions out

* comment microwave UI stuff

* more comments

* comment recipe functions

* refactor GetTotalIngredients to use an operator

* give CookingIngredients addition methods

* comment the rest of CookingIngredients

* make subtraction functions clearer?

* remove redundant comment

* handle zero/negative ingredient cases

* simplify subtract contents logic

* fix these comments

* typo

* add microwave recipe test

* move active microwave methods

* comment active microwave methods

* comment cooking methods

* comment microwave methods

* change setappearance to use entity<>

* add some public api decorators

* comment all of MicrowaveComponent

* use file-scoped namespace for MicrowaveComponent

* comment

* fix baguette sword

* maybe...?

* oh oops

* document the rest of the owl

* kill the massive YML breaking change

* fix protoman dependency

* move shared microwave stuff to SharedMicrowaveSystem

* fix usings

* move microwave components to shared

* fix up microwave datafields

* move APIs to shared

* move activemicrowave methods to shared

* initialize on shared

* move ui functions to shared

* add actor here

* stuff

* predict microwave update loop

* make clientside microwave system

* adjust the update loop for microwaves

* clean up

* add field deltas to microwaves

* dirty fields instead of whole component

* move this to the partial

* move GetRecipesForMicrowave to shared

* comments

* predict microwave BUI

* oops

* refactor microwave bui update

* move UI messages to shared

* move messages to UI file

* move init to shared

* move container methosd to shared

* oops my using

* move cooking functions into partial

* move cooking methods to shared

* move microwave recipe methods to shared

* move the rest of the microwave events to shared

* update imports

* remove custom type serializer

* better (?) microwave UI prediction

* fix list notations

* OK IT WAS THE DATAFIELDS

* add IOC inject

* dont use IOC for htis

* sealed

* prediction fixes pt1

* make microwave cooking updates more consistent

* datafield tweaks

* test for multiple portions in microwave recipes

* test for multiple recipe portions

* better(?) prediction for malfunctions

* remove redundant comment

* prevent solution reactions in microwave

* cleanup some microwave stuff

* eh i think this should be container actually

* this can be API i think

* reduce system redundancy

* this has to be public

* use resolve pattern for explode

* use GetMicrowaveContents in microwave system

* this can be an API too

* add conversion script for unlocalized recipes

* use EnsureComp instead of AddComp

* suffering

* fix recipe prediction

* fix the audio

* reduce redundant networking

* i think this should be autopaused?

* nevermind we use dynamic portions in the test

* oops i wrote the test wrong

* make lemoon cake take 2 lemoons again
sorry test fail

* rename "materials" to "stacks"
realized theyre different

* add stack guidebook quantities

* Kill audio

* fix lemoon cake recipe for real?

* mark PortionedRecipe as a datadefinition

* fix the microwave timer

* review

* cleanup

* Update SharedMicrowaveSystem.Recipes.cs

* update

* add audio params

* review: inject microwavesystem

* review: named variable

* review: move dependencies

* review: use gametest for recipe test

* review: make access attributes standalone

* rename prototypes and datafields

* review: rename this var

* review: move this function down

* review: remove these scripts

* actually this should stay

* refactor cook time buttons

* review: use a color name for this

* fix timer reset

* Dirty

* review: make ActivelyMicrowavedComponent networked

* review: slarti comments part 1

* review: remove serializable attribute from CookingIngredients

* rename file

* review: more microwave audio boilerplate. yippee!

* review: use resolve pattern for TryGetStackId
i dont like the way this makes the rest of the code look but we ball maybe. i dont know what else to do here

* review: move ui update to constructor

* review: kill LastCookUpdated

* actually i do this instead

* fixed up the PR

* cleanup

* ///

* Update microwave.yml

---------

Co-authored-by: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>
Co-authored-by: Pok245 <113675512+Pok27@users.noreply.github.com>
2026-10-04 23:38:48 +00:00

285 lines
11 KiB
C#

using System.Linq;
using Content.Shared.Kitchen.Components;
using Content.Shared.Random.Helpers;
namespace Content.Shared.Kitchen.EntitySystems;
public abstract partial class MicrowaveSystem
{
/// <summary>
/// Adds temperature to every item in the microwave based on the time it took to microwave.
/// </summary>
/// <param name="ent">The microwave entity.</param>
/// <param name="time">The heating time that has elapsed, in seconds.</param>
private void AddTemperature(Entity<MicrowaveComponent> ent, float time)
{
var component = ent.Comp;
var heatToAdd = time * component.DeltaHeat;
var objHeatToAdd = heatToAdd * component.ObjectHeatMultiplier;
// TODO: Divide heat amongst contents?
foreach (var entity in GetMicrowaveContents(ent.AsNullable()))
{
_temperature.ChangeHeat(entity, objHeatToAdd);
foreach (var (_, soln) in Solution.EnumerateSolutions(entity))
{
var solution = soln.Comp.Solution;
if (solution.Temperature > component.TemperatureUpperThreshold)
continue;
Solution.AddThermalEnergy(soln, heatToAdd);
}
}
}
/// <summary>
/// Attempts to roll random "malfunction" events on a malfunctioning microwave.
/// </summary>
protected virtual void RollMalfunction(Entity<MicrowaveComponent> ent)
{
if (SharedRandomExtensions.PredictedProb(Timing, ent.Comp.ExplosionChance, GetNetEntity(ent)))
Explode(ent.AsNullable());
}
public virtual void Explode(Entity<MicrowaveComponent?> ent)
{
if (!MicrowaveQuery.Resolve(ent.Owner, ref ent.Comp))
return;
ent.Comp.Broken = true;
DirtyField(ent.AsNullable(), nameof(ent.Comp.Broken));
}
/// <summary>
/// Start up the microwave. This processes each item in the microwave to experience microwave "on-cooking"
/// effects, attempts to retrieve a microwave recipe with its valid ingredients, and activates the
/// microwave visually.
/// </summary>
public void StartCooking(Entity<MicrowaveComponent> microwave, EntityUid? user)
{
if (!HasContents(microwave.AsNullable())
|| IsActiveMicrowave(microwave.AsNullable())
|| !_power.IsPowered(microwave.Owner))
return;
var contents = GetMicrowaveContents(microwave.AsNullable());
var malfunctioning = false;
var shouldContinue = TryStartMicrowavingContents(microwave,
contents,
user,
ref malfunctioning,
out var ingredientContents);
if (!shouldContinue)
return;
var ingredients = GetTotalIngredients(ingredientContents);
var cookTime = microwave.Comp.CurrentCookTimerTime;
var recipe = GetRecipe(microwave, ingredients, cookTime);
ActivateMicrowave(microwave, recipe, malfunctioning, user);
}
/// <summary>
/// Starts up the microwave cooking operation, setting the starting time and recipe of the microwave.
/// </summary>
/// <param name="microwave">The microwave entity.</param>
/// <param name="recipe">The recipe and portion count associated with this operaton.</param>
/// <param name="malfunctioning">Whether or not this microwave is malfunctioning.</param>
/// <param name="user">The entity that is activating the microwave.</param>
private void ActivateMicrowave(Entity<MicrowaveComponent> microwave,
PortionedRecipe? recipe,
bool malfunctioning,
EntityUid? user)
{
var uid = microwave.Owner;
var component = microwave.Comp;
var curTime = Timing.CurTime;
var cookTime = component.CurrentCookTimerTime * component.CookTimeMultiplier;
Audio.PlayPredicted(component.BeginCookingSound, uid, user);
var activeComp = new ActiveMicrowaveComponent
{
PortionedRecipe = recipe,
TotalTime = component.CurrentCookTimerTime,
CookTimeEnd = curTime + TimeSpan.FromSeconds(cookTime),
Malfunctioning = malfunctioning,
NextMalfunction = malfunctioning
? curTime + component.MalfunctionInterval
: TimeSpan.Zero,
};
AddComp(uid, activeComp);
UpdateUI(microwave.AsNullable());
}
/// <summary>
/// Iterates over the contents of a microwave and performs some on-microwaved effects.
/// Plastic items will melt and turn into burned messes.
/// Metal items will cause the microwave to malfunction.
/// </summary>
/// <remarks>
/// This also raises a BeingMicrowavedEvent on each item in the microwave. The result of
/// this event may cause us to exit early and proceed with cooking - for example, an
/// entity that causes the microwave to explode when microwaved.
///
/// If cooking is not cancelled, the items inside will gain `ActiveMicrowaveComponent.`.
/// </remarks>
/// <param name="microwave">The microwave entity.</param>
/// <param name="contents">The contents of the microwave.</param>
/// <param name="user">The entity that is activating the microwave.</param>
/// <param name="malfunctioning">Whether or not the microwave is malfunctioning.</param>
/// <param name="ingredientContents">A list of entities usable as ingredients.</param>
/// <returns>Wheteher or not we can proceed to use the microwave.</returns>
private bool TryStartMicrowavingContents(Entity<MicrowaveComponent> microwave,
IReadOnlyList<EntityUid> contents,
EntityUid? user,
ref bool malfunctioning,
out List<EntityUid> ingredientContents)
{
ingredientContents = contents.ToList();
foreach (var item in contents)
{
MicrowaveItem(microwave,
item,
user,
ref malfunctioning,
out var shouldExit,
out var shouldRemoveFromIngredients);
if (shouldExit)
return false;
if (shouldRemoveFromIngredients)
ingredientContents.Remove(item);
}
foreach (var item in ingredientContents)
{
BeginActivelyMicrowaving(item, microwave.Owner);
}
return true;
}
/// <summary>
/// Processes a single entity in the microwave. This modifies / results in three
/// different parameters related to the operation: if the microwave should malfunction,
/// if the microwave should stop, and if the entity is still usable as an ingredient.
/// </summary>
/// <param name="microwave">The microwave entity.</param>
/// <param name="item">The entity being microwaved.</param>
/// <param name="user">The entity activating the microwave.</param>
/// <param name="malfunctioning">Whether or not this microwave is malfunctioning.</param>
/// <param name="shouldStopMicrowave">Whether or not this microwave should stop.</param>
/// <param name="shouldRemoveFromIngredients">Whether or not this entity remains a valid ingredient.</param>
private void MicrowaveItem(Entity<MicrowaveComponent> microwave,
EntityUid item,
EntityUid? user,
ref bool malfunctioning,
out bool shouldStopMicrowave,
out bool shouldRemoveFromIngredients)
{
shouldStopMicrowave = false;
shouldRemoveFromIngredients = false;
// Special item-in-microwave interactions. Certain "being microwaved' interactions
// may cancel out any actual cooking, so this may early exit.
var beingMicrowaved = new BeingMicrowavedEvent(microwave.Owner, user);
RaiseLocalEvent(item, beingMicrowaved);
if (beingMicrowaved.Handled)
{
UpdateUI(microwave.AsNullable());
shouldStopMicrowave = true;
return;
}
if (_whitelist.IsWhitelistPass(microwave.Comp.MalfunctionWhenCookedWhitelist, item))
malfunctioning = true;
if (_whitelist.IsWhitelistPass(microwave.Comp.BurnWhenCookedWhitelist, item))
{
shouldRemoveFromIngredients = true;
CreateBurnedMess(microwave, item);
}
}
/// <summary>
/// Turns a single entity in the microwave into a failed "burned mess" recipe.
/// </summary>
/// <remarks>
/// This happens to entities that pass <see cref="MicrowaveComponent.BurnWhenCookedWhitelist"/>
/// when microwaved.
/// </remarks>
/// <param name="microwave">The microwave entity.</param>
/// <param name="item">The entity to burn.</param>
private void CreateBurnedMess(Entity<MicrowaveComponent> microwave, EntityUid item)
{
var entProto = microwave.Comp.FailureResult;
var coords = Transform(microwave).Coordinates;
var junk = PredictedSpawnAtPosition(entProto, coords);
Container.Insert(junk, microwave.Comp.Storage);
PredictedDel(item);
}
/// <summary>
/// Completes a recipe in a microwave, removing its relevant ingredient contents
/// from the microwave and producing finished dish entities in their place.
/// </summary>
/// <param name="microwave">The microwave entity.</param>
/// <param name="portionedRecipe">The recipe we are using.</param>
private void SpawnFinishedRecipe(Entity<MicrowaveComponent> microwave, PortionedRecipe portionedRecipe)
{
SubtractContents(microwave, portionedRecipe);
var coords = Transform(microwave).Coordinates;
for (var i = 0; i < portionedRecipe.Count; i++)
{
if (!ProtoMan.Resolve(portionedRecipe.Recipe, out var recipe))
continue;
PredictedSpawnAtPosition(recipe.Result, coords);
}
}
/// <summary>
/// Finishes a cooking operation in the microwave, resulting in a finished food recipe,
/// the ejection of all remaining ingredients, and a sound cue.
/// </summary>
/// <param name="ent">The micorawve entity.</param>
private void CompleteCooking(Entity<ActiveMicrowaveComponent, MicrowaveComponent> ent)
{
var active = ent.Comp1;
var microwave = ent.Comp2;
var microwaveEnt = (ent.Owner, microwave);
// Spawn a finished recipe, if there is one.
if (active.PortionedRecipe != null)
SpawnFinishedRecipe(microwaveEnt, active.PortionedRecipe.Value);
// TODO: Audio prediction sucks. Predict this properly when audio prediction is less broken
// https://github.com/space-wizards/RobustToolbox/issues/6436
if (_net.IsServer)
Audio.PlayPvs(microwave.FoodDoneSound, ent); // beep... beep... beep
// Clean up the microwave.
Container.EmptyContainer(microwave.Storage);
StopCooking(microwaveEnt);
}
/// <summary>
/// Removes components from a microwave and its contents related to active microwave use.
/// </summary>
/// <remarks>
/// When the ActiveMicrowaveComponent is removed, it will trigger <see cref="OnCookEnd"/> on shutdown.
/// </remarks>
/// <param name="ent">The microwave entity.</param>
private void StopCooking(Entity<MicrowaveComponent> ent)
{
RemComp<ActiveMicrowaveComponent>(ent);
}
}