Files
crystall-punk-14/Content.Shared/_CP14/Cooking/CP14SharedCookingSystem.cs
nukkuminen 7e39f78df5 Soup bugfix and price change (#1630)
* adjust prices for food contracts

* fix unsellable soups

* move thing outside the cycle
2025-08-07 09:56:14 +03:00

354 lines
12 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 System.Numerics;
using Content.Shared._CP14.Cooking.Components;
using Content.Shared._CP14.Cooking.Prototypes;
using Content.Shared.Audio;
using Content.Shared.Chemistry.Components;
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 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 SharedContainerSystem _container = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] protected readonly SharedSolutionContainerSystem _solution = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly IPrototypeManager _proto = 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] private 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<CP14FoodHolderComponent, ExaminedEvent>(OnExaminedEvent);
}
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<CP14FoodHolderComponent> 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)));
}
/// <summary>
/// Transfer food data from cooker to holder
/// </summary>
protected virtual bool TryTransferFood(Entity<CP14FoodHolderComponent> target,
Entity<CP14FoodHolderComponent> source)
{
if (!source.Comp.CanGiveFood || !target.Comp.CanAcceptFood)
return false;
if (target.Comp.FoodType != source.Comp.FoodType)
return false;
if (source.Comp.FoodData is null)
return false;
if (!TryComp<FoodComponent>(target, out var holderFoodComp))
return false;
if (!_solution.TryGetSolution(source.Owner, source.Comp.SolutionId, out var cookerSoln, out var cookerSolution))
return false;
//Solutions
if (_solution.TryGetSolution(target.Owner, holderFoodComp.Solution, out var holderSoln, out var solution))
{
if (solution.Volume > 0)
{
_popup.PopupEntity(Loc.GetString("cp14-cooking-popup-not-empty", ("name", MetaData(target).EntityName)),
target);
return false;
}
_solution.TryTransferSolution(holderSoln.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 (source.Comp.FoodData?.Trash.Count > 0)
{
if (cookerSolution.Volume <= 0)
{
holderFoodComp.Trash.AddRange(source.Comp.FoodData.Trash);
}
else
{
if (_net.IsServer)
{
var newTrash = _random.Pick(source.Comp.FoodData.Trash);
source.Comp.FoodData.Trash.Remove(newTrash);
holderFoodComp.Trash.Add(newTrash);
}
}
}
if (source.Comp.FoodData is not null)
UpdateFoodDataVisuals(target, source.Comp.FoodData);
Dirty(target);
Dirty(source);
_solution.UpdateChemicals(cookerSoln.Value);
return true;
}
private void UpdateFoodDataVisuals(
Entity<CP14FoodHolderComponent> ent,
bool rename = true)
{
var data = ent.Comp.FoodData;
if (data is null)
return;
UpdateFoodDataVisuals(ent, data, rename);
}
protected virtual void UpdateFoodDataVisuals(
Entity<CP14FoodHolderComponent> ent,
CP14FoodData data,
bool rename = true)
{
//Name and Description
if (rename)
{
if (data.Name is not null)
_metaData.SetEntityName(ent, Loc.GetString(data.Name));
if (data.Desc is not null)
_metaData.SetEntityDescription(ent, Loc.GetString(data.Desc));
}
//Flavors
EnsureComp<FlavorProfileComponent>(ent, out var flavorComp);
foreach (var flavor in data.Flavors)
{
flavorComp.Flavors.Add(flavor);
}
//Visuals
ent.Comp.FoodData = new CP14FoodData(data);
foreach (var layer in data.Visuals)
{
if (_random.Prob(0.5f))
layer.Scale = new Vector2(-1, 1);
}
DirtyField(ent, ent.Comp, nameof(CP14FoodHolderComponent.FoodData));
//Sliceable
// > on server overrided side
}
public 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);
}
return GetRecipe(ent.Comp.FoodType, solution, allTags);
}
public CP14CookingRecipePrototype? GetRecipe(ProtoId<CP14FoodTypePrototype> foodType,
Solution? solution,
List<ProtoId<TagPrototype>> allTags)
{
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 != foodType)
continue;
var conditionsMet = true;
foreach (var condition in recipe.Requirements)
{
if (!condition.CheckRequirement(EntityManager, _proto, allTags, solution))
{
conditionsMet = false;
break;
}
}
if (!conditionsMet)
continue;
selectedRecipe = recipe;
break;
}
return selectedRecipe;
}
protected void CreateFoodData(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(recipe.FoodData);
//Assign recipe to the FoodData
newData.CurrentRecipe = recipe.ID;
//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;
if (TryComp<CP14FoodHolderComponent>(ent.Owner, out var holder))
{
holder.FoodData = newData;
Dirty(ent.Owner, holder);
}
Dirty(ent);
}
protected void BurntFood(Entity<CP14FoodCookerComponent> ent)
{
if (!TryComp<CP14FoodHolderComponent>(ent, out var holder) || holder.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 holder.FoodData.Visuals)
{
visuals.Color = Color.FromHex("#212121");
}
holder.FoodData.Name = Loc.GetString("cp14-meal-recipe-burned-trash-name");
holder.FoodData.Desc = Loc.GetString("cp14-meal-recipe-burned-trash-desc");
var replacedVolume = solution.Volume / 2;
solution.SplitSolution(replacedVolume);
solution.AddReagent(_burntFoodReagent, replacedVolume / 2);
DirtyField(ent.Owner, holder, nameof(CP14FoodHolderComponent.FoodData));
}
}