* frying pan start * fix data saving * Refactor cooking system and add localization support Refactored food data handling in the cooking system to use Visuals instead of FoodData on holders, improved food transfer logic, and added popups and examine messages for user feedback. Added English and Russian localization files for cooking examine and meal descriptions, and updated prototype files to increase plate solution volume and add a whitelist for the cooking furnace. * Refactor cooking system solution handling Removed Solution field from CP14FoodData and updated CP14SharedCookingSystem to use solution containers directly. Improved solution transfer, mixing, and overflow handling during cooking. Increased pan max volume from 30 to 100 in cooking_furnace.yml. * Improve cooking system trash handling and update food sprites Enhanced trash distribution in CP14SharedCookingSystem to randomly assign trash to plates, only adding all trash to the last plate. Updated meat_dish and salad prototypes to use new wooden plate sprites. Added wideAnimationRotation and DrainableSolution to cooking_furnace for improved functionality. * Remove unused usings and redundant comments in cooking components Cleaned up CP14 cooking-related components by removing unused using directives and empty XML comments. This improves code readability and maintainability. * Add tag-based cooking requirements and new meal recipes Introduces tag-based cooking requirement classes (TagRequired, TagBlocked, SolutionRequired) and updates the cooking system to support tag checks. Adds new tags for food items, updates food and meal prototypes to include relevant tags, and implements new meal recipes (Zellasian Breakfast, Monster Egg) with corresponding sprites and localization. Also refactors and renames some tag and structure prototype files for better organization. * Refactor and expand CP14 cooking recipes and tags Reworked meal recipes to use tag-based requirements, added new tags for food categorization (meat, cheese, vegetables, etc.), and removed hardcoded entity recipes for ham in cheese and stuffed potato. Updated and added new meal recipes (bread plate, ham in cheese, stuffed potato), localized their names and descriptions, and migrated/added corresponding sprites. Deprecated SolutionRequired requirement, cleaned up unused tags, and improved TagRequired/TagBlocked logic. Updated prototype files to use new tags and added missing tags to food entities. * Refactor and expand green salad recipes and assets Replaces the old green salad entity and workbench recipe with new cooking recipes for green salad and green salad with meat. Updates English and Russian localization, moves and adds new salad textures, and removes obsolete salad assets and trading request. * mashed potatoes * Add mashed potatoes with herbs meal Introduced a new 'mashed potatoes with herbs' meal, including English and Russian localization, recipe definition, and associated sprite. Updated the texture metadata and added the new image asset. * Update dye.yml * Fix frying pan item size and update plate recipe Moved the 'size: Ginormous' property to the correct component in frying_pen.yml. Renamed the plate recipe and result from 'CP14Plate' to 'CP14PlateWooden' in misc.yml for clarity. * Update meals.yml * Update migration.yml * fixes * fix heating food sounds * Nyranu temperature transmission * Update plate.yml * Cooking DoAfter works * burning logic * cooking more work * auto transform transformable * Update CP14SharedCookingSystem.DoAfter.cs * cool burning visuals! * Add MIT license headers to cooking system files Added sublicensing notice and MIT license header to all files in the CP14 cooking system. Also made minor code cleanups, such as removing unnecessary default initializations and improving XML documentation for clarity. * constant heating required * cooking visuals improve! * sandwiches with processed cheese + reagent burnt fuckuping * inhand pan sprites + FIRE fuckup * crafting & buying pan * fix predicting * random food spawner + chef closet * mapping update * Update frying_pen.yml * move WIP to blacksmith lol
315 lines
11 KiB
C#
315 lines
11 KiB
C#
/*
|
|
* This file is sublicensed under MIT License
|
|
* https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT
|
|
*/
|
|
|
|
using System.Linq;
|
|
using Content.Shared._CP14.Cooking.Components;
|
|
using Content.Shared.Audio;
|
|
using Content.Shared.Chemistry.EntitySystems;
|
|
using Content.Shared.Chemistry.Reagent;
|
|
using Content.Shared.DoAfter;
|
|
using Content.Shared.Examine;
|
|
using Content.Shared.Fluids;
|
|
using Content.Shared.Nutrition.Components;
|
|
using Content.Shared.Popups;
|
|
using Content.Shared.Tag;
|
|
using Content.Shared.Throwing;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Random;
|
|
|
|
namespace Content.Shared._CP14.Cooking;
|
|
|
|
public abstract partial class CP14SharedCookingSystem : EntitySystem
|
|
{
|
|
[Dependency] protected readonly IPrototypeManager _proto = default!;
|
|
[Dependency] protected readonly SharedContainerSystem _container = default!;
|
|
[Dependency] protected readonly IRobustRandom _random = default!;
|
|
[Dependency] protected readonly MetaDataSystem _metaData = default!;
|
|
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
|
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
|
[Dependency] private readonly SharedPuddleSystem _puddle = default!;
|
|
[Dependency] private readonly INetManager _net = default!;
|
|
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
|
[Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
|
|
[Dependency] protected readonly SharedAppearanceSystem _appearance = default!;
|
|
|
|
/// <summary>
|
|
/// When overcooking food, we will replace the reagents inside with this reagent.
|
|
/// </summary>
|
|
private readonly ProtoId<ReagentPrototype> _burntFoodReagent = "CP14BurntFood";
|
|
|
|
/// <summary>
|
|
/// Stores a list of all recipes sorted by complexity: the most complex ones at the beginning.
|
|
/// When attempting to cook, the most complex recipes will be checked first,
|
|
/// gradually moving down to the easiest ones.
|
|
/// The easiest recipes are usually the most “abstract,”
|
|
/// so they will be suitable for the largest number of recipes.
|
|
/// </summary>
|
|
protected List<CP14CookingRecipePrototype> OrderedRecipes = [];
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
InitTransfer();
|
|
InitDoAfter();
|
|
|
|
CacheAndOrderRecipes();
|
|
|
|
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
|
SubscribeLocalEvent<CP14FoodCookerComponent, ExaminedEvent>(OnExaminedEvent);
|
|
SubscribeLocalEvent<CP14FoodCookerComponent, LandEvent>(OnLand);
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
UpdateDoAfter(frameTime);
|
|
}
|
|
|
|
private void CacheAndOrderRecipes()
|
|
{
|
|
OrderedRecipes = _proto.EnumeratePrototypes<CP14CookingRecipePrototype>()
|
|
.Where(recipe => recipe.Requirements.Count > 0) // Only include recipes with requirements
|
|
.OrderByDescending(recipe => recipe.Requirements.Sum(condition => condition.GetComplexity()))
|
|
.ToList();
|
|
}
|
|
|
|
private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev)
|
|
{
|
|
if (!ev.WasModified<EntityPrototype>())
|
|
return;
|
|
|
|
CacheAndOrderRecipes();
|
|
}
|
|
|
|
private void OnExaminedEvent(Entity<CP14FoodCookerComponent> ent, ref ExaminedEvent args)
|
|
{
|
|
if (ent.Comp.FoodData?.Name is null)
|
|
return;
|
|
|
|
if (!_solution.TryGetSolution(ent.Owner, ent.Comp.SolutionId, out _, out var solution))
|
|
return;
|
|
|
|
if (solution.Volume == 0)
|
|
return;
|
|
|
|
var remaining = solution.Volume;
|
|
|
|
args.PushMarkup(Loc.GetString("cp14-cooking-examine",
|
|
("name", Loc.GetString(ent.Comp.FoodData.Name)),
|
|
("count", remaining)));
|
|
}
|
|
|
|
private void OnLand(Entity<CP14FoodCookerComponent> ent, ref LandEvent args)
|
|
{
|
|
ent.Comp.FoodData = null;
|
|
Dirty(ent);
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Transfer food data from cooker to holder
|
|
/// </summary>
|
|
private void MoveFoodToHolder(Entity<CP14FoodHolderComponent> ent, Entity<CP14FoodCookerComponent> cooker)
|
|
{
|
|
if (!TryComp<FoodComponent>(ent, out var foodComp))
|
|
return;
|
|
|
|
if (cooker.Comp.FoodData is null)
|
|
return;
|
|
|
|
if (!_solution.TryGetSolution(cooker.Owner, cooker.Comp.SolutionId, out _, out var cookerSolution))
|
|
return;
|
|
|
|
//Solutions
|
|
if (_solution.TryGetSolution(ent.Owner, foodComp.Solution, out var soln, out var solution))
|
|
{
|
|
if (solution.Volume > 0)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("cp14-cooking-popup-not-empty", ("name", MetaData(ent).EntityName)),
|
|
ent);
|
|
return;
|
|
}
|
|
|
|
_solution.TryTransferSolution(soln.Value, cookerSolution, solution.MaxVolume);
|
|
}
|
|
|
|
//Trash
|
|
//If we have a lot of trash, we put 1 random trash in each plate. If it's a last plate (out of solution in cooker), we put all the remaining trash in it.
|
|
if (cooker.Comp.FoodData.Trash.Count > 0)
|
|
{
|
|
if (cookerSolution.Volume <= 0)
|
|
{
|
|
foodComp.Trash.AddRange(cooker.Comp.FoodData.Trash);
|
|
}
|
|
else
|
|
{
|
|
if (_net.IsServer)
|
|
{
|
|
var newTrash = _random.Pick(cooker.Comp.FoodData.Trash);
|
|
cooker.Comp.FoodData.Trash.Remove(newTrash);
|
|
foodComp.Trash.Add(newTrash);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Name and Description
|
|
if (cooker.Comp.FoodData.Name is not null)
|
|
_metaData.SetEntityName(ent, Loc.GetString(cooker.Comp.FoodData.Name));
|
|
if (cooker.Comp.FoodData.Desc is not null)
|
|
_metaData.SetEntityDescription(ent, Loc.GetString(cooker.Comp.FoodData.Desc));
|
|
|
|
//Flavors
|
|
EnsureComp<FlavorProfileComponent>(ent, out var flavorComp);
|
|
foreach (var flavor in cooker.Comp.FoodData.Flavors)
|
|
{
|
|
flavorComp.Flavors.Add(flavor);
|
|
}
|
|
|
|
//Visuals
|
|
ent.Comp.Visuals = cooker.Comp.FoodData.Visuals;
|
|
|
|
//Clear cooker data
|
|
if (cookerSolution.Volume <= 0)
|
|
cooker.Comp.FoodData = null;
|
|
|
|
Dirty(ent);
|
|
Dirty(cooker);
|
|
}
|
|
|
|
private CP14CookingRecipePrototype? GetRecipe(Entity<CP14FoodCookerComponent> ent)
|
|
{
|
|
if (!_container.TryGetContainer(ent, ent.Comp.ContainerId, out var container))
|
|
return null;
|
|
|
|
_solution.TryGetSolution(ent.Owner, ent.Comp.SolutionId, out _, out var solution);
|
|
|
|
//Get all tags
|
|
var allTags = new List<ProtoId<TagPrototype>>();
|
|
foreach (var contained in container.ContainedEntities)
|
|
{
|
|
if (!TryComp<TagComponent>(contained, out var tags))
|
|
continue;
|
|
|
|
allTags.AddRange(tags.Tags);
|
|
}
|
|
|
|
if (OrderedRecipes.Count == 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"No cooking recipes found. Please ensure that the CP14CookingRecipePrototype is defined and loaded.");
|
|
}
|
|
|
|
CP14CookingRecipePrototype? selectedRecipe = null;
|
|
foreach (var recipe in OrderedRecipes)
|
|
{
|
|
if (recipe.FoodType != ent.Comp.FoodType)
|
|
continue;
|
|
|
|
var conditionsMet = true;
|
|
foreach (var condition in recipe.Requirements)
|
|
{
|
|
if (!condition.CheckRequirement(EntityManager, _proto, container.ContainedEntities, allTags, solution))
|
|
{
|
|
conditionsMet = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!conditionsMet)
|
|
continue;
|
|
|
|
selectedRecipe = recipe;
|
|
break;
|
|
}
|
|
|
|
return selectedRecipe;
|
|
}
|
|
|
|
protected void CookFood(Entity<CP14FoodCookerComponent> ent, CP14CookingRecipePrototype recipe)
|
|
{
|
|
if (!_solution.TryGetSolution(ent.Owner, ent.Comp.SolutionId, out var soln, out var solution))
|
|
return;
|
|
|
|
if (!_container.TryGetContainer(ent, ent.Comp.ContainerId, out var container))
|
|
return;
|
|
|
|
var newData = new CP14FoodData
|
|
{
|
|
Visuals = new List<PrototypeLayerData>(recipe.FoodData.Visuals),
|
|
Trash = new List<EntProtoId>(recipe.FoodData.Trash),
|
|
Flavors = new HashSet<LocId>(recipe.FoodData.Flavors),
|
|
Name = recipe.FoodData.Name,
|
|
Desc = recipe.FoodData.Desc,
|
|
CurrentRecipe = recipe
|
|
};
|
|
|
|
newData.Name = recipe.FoodData.Name;
|
|
newData.Desc = recipe.FoodData.Desc;
|
|
|
|
//Process entities
|
|
foreach (var contained in container.ContainedEntities)
|
|
{
|
|
if (TryComp<FoodComponent>(contained, out var food))
|
|
{
|
|
//Merge trash
|
|
newData.Trash.AddRange(food.Trash);
|
|
|
|
//Merge solutions
|
|
if (_solution.TryGetSolution(contained, food.Solution, out _, out var foodSolution))
|
|
{
|
|
_solution.TryMixAndOverflow(soln.Value, foodSolution, solution.MaxVolume, out var overflowed);
|
|
if (overflowed is not null)
|
|
{
|
|
_puddle.TrySplashSpillAt(ent, Transform(ent).Coordinates, overflowed, out _);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TryComp<FlavorProfileComponent>(contained, out var flavorComp))
|
|
{
|
|
//Merge flavors
|
|
foreach (var flavor in flavorComp.Flavors)
|
|
{
|
|
newData.Flavors.Add(flavor);
|
|
}
|
|
}
|
|
|
|
QueueDel(contained);
|
|
}
|
|
|
|
if (solution.Volume <= 0)
|
|
return;
|
|
|
|
ent.Comp.FoodData = newData;
|
|
Dirty(ent);
|
|
}
|
|
|
|
protected void BurntFood(Entity<CP14FoodCookerComponent> ent)
|
|
{
|
|
if (ent.Comp.FoodData is null)
|
|
return;
|
|
|
|
if (!_solution.TryGetSolution(ent.Owner, ent.Comp.SolutionId, out var soln, out var solution))
|
|
return;
|
|
|
|
//Brown visual
|
|
foreach (var visuals in ent.Comp.FoodData.Visuals)
|
|
{
|
|
visuals.Color = Color.FromHex("#212121");
|
|
}
|
|
|
|
ent.Comp.FoodData.Name = Loc.GetString("cp14-meal-recipe-burned-trash-name");
|
|
ent.Comp.FoodData.Desc = Loc.GetString("cp14-meal-recipe-burned-trash-desc");
|
|
|
|
var replacedVolume = solution.Volume / 2;
|
|
solution.SplitSolution(replacedVolume);
|
|
solution.AddReagent(_burntFoodReagent, replacedVolume);
|
|
|
|
DirtyField(ent.Owner, ent.Comp, nameof(CP14FoodCookerComponent.FoodData));
|
|
}
|
|
}
|