/* * 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!; /// /// When overcooking food, we will replace the reagents inside with this reagent. /// private readonly ProtoId _burntFoodReagent = "CP14BurntFood"; /// /// 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. /// protected List OrderedRecipes = []; public override void Initialize() { base.Initialize(); InitTransfer(); InitDoAfter(); CacheAndOrderRecipes(); SubscribeLocalEvent(OnPrototypesReloaded); SubscribeLocalEvent(OnExaminedEvent); SubscribeLocalEvent(OnLand); } public override void Update(float frameTime) { UpdateDoAfter(frameTime); } private void CacheAndOrderRecipes() { OrderedRecipes = _proto.EnumeratePrototypes() .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()) return; CacheAndOrderRecipes(); } private void OnExaminedEvent(Entity 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 ent, ref LandEvent args) { ent.Comp.FoodData = null; Dirty(ent); } /// /// Transfer food data from cooker to holder /// private void MoveFoodToHolder(Entity ent, Entity cooker) { if (!TryComp(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(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 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>(); foreach (var contained in container.ContainedEntities) { if (!TryComp(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 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(recipe.FoodData.Visuals), Trash = new List(recipe.FoodData.Trash), Flavors = new HashSet(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(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(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 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)); } }