namespace FoodFactory.Recipes; using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Extensions.Dangerous; using FoodFactory.Items; using FoodFactory.Math; using Godot; public abstract record Recipe(string Name, BlueprintId[]? BlueprintIds, bool[] RequireAll, Tags[] Inputs, Tags[] Exclude, RecipeOutput RecipeOutput, RecipeAction Action = default, bool InputsOrdered = true) { public abstract bool CanProcess(in RecipeContext context); public abstract RecipeResult Process(in RecipeContext context, ref RecipeResultBuilder builder); } public readonly record struct RecipeOutput { public int? Min { get; } public int? Max { get; } [MemberNotNullWhen(true, nameof(Min), nameof(Max))] public bool IsExact => Min.HasValue && Max.HasValue && Min == Max; [MemberNotNullWhen(true, nameof(Min), nameof(Max))] public bool IsRange => Min.HasValue && Max.HasValue && Min != Max; public bool IsUnbounded => Min == null && Max == null; public RecipeOutput(int exact) { Min = exact; Max = exact; } public RecipeOutput(int min, int max) { Min = min; Max = max; } private RecipeOutput(int? min, int? max) { Min = min; Max = max; } public static RecipeOutput Any => new(null, null); } public struct CompiledRecipe { public Recipe RecipeRef; // public BlueprintId[] InputBlueprints; public ItemEntry[] ItemEntries; public bool IsOrdered; } public struct ItemEntry { public bool RequireAll; public Tags InputTags; public Tags ExcludeTags; public BlueprintId? Blueprint; } public static class RecipeCompiler { public static CompiledRecipe Compile(Recipe recipe) => new() { RecipeRef = recipe, ItemEntries = BuildEntries(recipe), // IsOrdered = recipe.InputsOrdered, }; private static ItemEntry[] BuildEntries(Recipe recipe) { var entries = new ItemEntry[recipe.Inputs.Length]; for (var i = 0; i < entries.Length; i++) { entries[i] = new ItemEntry { RequireAll = recipe.RequireAll[i], InputTags = recipe.Inputs[i], ExcludeTags = recipe.Exclude[i], // Blueprint = recipe.BlueprintIds?[i], }; } return entries; } } public record BurnItemRecipe : Recipe { public BurnItemRecipe() : base("Burn_item", BlueprintIds: null, RequireAll: [false], Inputs: [new Tags("burnable")], Exclude: [new Tags("burned")], RecipeOutput: RecipeOutput.Any, Action: new RecipeAction("cook"), InputsOrdered: true) { } public override bool CanProcess(in RecipeContext context) { if (context.Items[0].TryGet(out var temp) && context.Items[0].TryGet(out var burnableTemp) && temp > burnableTemp.Temperature) { return true; } return false; } public override RecipeResult Process(in RecipeContext context, ref RecipeResultBuilder builder) => new(BurnItem); private void BurnItem(RecipeContext context) { // context.Entity[0].Remove(); context.Items[0].Add(new BurnedItem(context.Items[0].Get(), "Was Burned as some time thing")); context.Items[0].Set(context.Items[0].Get().WithOut("burnable").With("burned")); context.Items[0].Set($"Burned_{context.Items[0].Get().Value}"); if (context.Items[0].Has()) { context.Items[0].Set(context.Items[0].Get() * Colors.Black); } else { context.Items[0].Add(Colors.Black); } } } public record OnionSliceRecipe() : Recipe(Name: "Onion_Slice", BlueprintIds: null, RequireAll: [false], Inputs: [new Tags("onion", "sliceable")], Exclude: [new Tags()], Action: "slice", RecipeOutput: new RecipeOutput(2) ) { public override bool CanProcess(in RecipeContext context) => true; public override RecipeResult Process(in RecipeContext context, ref RecipeResultBuilder builder) { builder.AddRemove(true); builder.AddCreate(Slice); builder.AddCreate(Slice); return builder.Build(); } private Entity Slice(in RecipeContext context) { Span span = stackalloc Entity[1]; var signature = context.World.GetSignature(context.Items[0]); context.World.Create(span, signature, 1); PotatoCookRecipe.CopyEntities(context.Items, span, context.World); span[0].Set(new Name("Sliced " + context.Items[0].Get().Value)); span[0].Set(new Mass(context.Items[0].Get().Value * .5)); span[0].Set(span[0].Get().With("sliced")); return span[0]; } } public record PotatoCookRecipe() : Recipe(Name: "Potato_Cook", BlueprintIds: null, RequireAll: [false], Inputs: [new Tags("potato", "raw")], Exclude: [new Tags()], InputsOrdered: true, Action: "cook", RecipeOutput: new RecipeOutput(1)) { public override bool CanProcess(in RecipeContext context) { if (!(context.Items[0].TryGet(out var temp) && temp.Fahrenheit > 350)) { return false; } return true; } public override RecipeResult Process(in RecipeContext context, ref RecipeResultBuilder builder) { builder.AddRemove(true); builder.AddCreate(NewPotato); return builder.Build(); } private Entity NewPotato(in RecipeContext context) { Span span = stackalloc Entity[1]; var signature = context.World.GetSignature(context.Items[0]); context.World.Create(span, signature, 1); CopyEntity(context.Items[0], span[0], context.World); span[0].Set(new Name("Cooked_Potato")); span[0].Set(context.Items[0].Get().With("cooked").WithOut("raw")); // span[0].Set(context.Entity[0].Get()); if (context.Items[0].Has()) { context.Items[0].Set(context.Items[0].Get() * Colors.Brown); } else { context.Items[0].Add(Colors.Brown); } return span[0]; } public static void CopyEntities(Span from, Span to, World world) { Debug.Assert(from.Length == to.Length); for (int i = 0; i < from.Length; i++) { CopyEntity(from[i], to[i], world); } } public static void CopyEntity(Entity src, Entity dst, World world) { var srcData = world.GetEntityData(src); var dstData = world.GetEntityData(dst); var components = srcData.Archetype .GetChunk(srcData.Slot.ChunkIndex) .Components; var componentsDst = dstData.Archetype .GetChunk(dstData.Slot.ChunkIndex) .Components; int srcIndex = srcData.Slot.Index; int dstIndex = dstData.Slot.Index; for (int i = 0; i < components.Length; i++) { componentsDst[i].SetValue(components[i].GetValue(srcIndex), dstIndex); } } }