import type { FluidProductPrototype, ItemProductPrototype, RecipePrototype } from "../lua-api/models.ts"; import type { Item } from "../process-data.models.ts"; export type Ingredient = { type: "fluid" | "item"; name: string; amount: number; }; export type Recipe = { name: string; icon?: string; subgroup: string; order?: string; /** The [categories](prototype:RecipeCategory) of this recipe. Controls which machines can craft this recipe. The built-in categories can be found [here](https://wiki.factorio.com/Data.raw#recipe-category). The base `"crafting"` category can not contain recipes with fluid ingredients or products. The array must contain at least one category, it cannot be empty. */ categories?: string[]; /** The amount of time it takes to make this recipe. Must be `> 0.001`. Equals the number of seconds it takes to craft at crafting speed `1`. */ energy_required?: number; /** Whether the recipe is allowed to have the extra inserter overload bonus applied (4 * stack inserter stack size). */ allow_inserter_overload?: boolean; allow_productivity?: boolean; allow_quality?: boolean; allow_speed?: boolean; /** Used to determine how many extra items are put into an assembling machine before it's considered "full enough". See [insertion limits](https://wiki.factorio.com/Inserters#Insertion_limits). If set to `0`, it instead uses the following formula: `1.166 / (energy_required / the assembler's crafting_speed)`, rounded up, and clamped to be between`2` and `100`. The numbers used in this formula can be changed by the [UtilityConstants](prototype:UtilityConstants) properties `dynamic_recipe_overload_factor`, `minimum_recipe_overload_multiplier`, and `maximum_recipe_overload_multiplier`. */ overload_multiplier?: number; ingredients?: Ingredient[]; results?: Array; }; export function getRecipeIcon(recipe: RecipePrototype, mainProduct: Item | undefined): string { if (recipe.icons || recipe.icon) return `recipe/${recipe.name}.png`; if (mainProduct && mainProduct.icon) return mainProduct.icon; console.warn(`No icon found for :${recipe.name}`); return ""; } export function getRecipeSubGroup(recipe: RecipePrototype, mainProduct: Item | undefined): string { if (recipe.subgroup) return recipe.subgroup; if (mainProduct) return mainProduct.subgroup; return "other"; } export function getMainProductName(recipe: RecipePrototype): string { if (recipe.results?.length == 1) { return recipe.results[0].name; } else if (recipe.results && recipe.main_product) { const mainProduct = recipe.main_product; const result = recipe.results.find((r) => r.name === mainProduct); if (result) { return result.name; } throw new Error(`Main product '${mainProduct}' declared by recipe '${recipe.name}' not found in results`); } return ""; } export function parseRecipe(recipe: RecipePrototype, itemsMap: Record): Recipe { const rawIngredient = Array.isArray(recipe.ingredients) ? recipe.ingredients : []; const ingredients = rawIngredient.map((i) => ({ name: i.name, amount: i.amount, type: i.type })); const mainProduct = itemsMap[getMainProductName(recipe)]; const r: Recipe = { name: recipe.name, icon: getRecipeIcon(recipe, mainProduct), subgroup: getRecipeSubGroup(recipe, mainProduct), energy_required: recipe.energy_required, allow_inserter_overload: recipe.allow_inserter_overload, overload_multiplier: recipe.overload_multiplier, order: recipe.order ?? mainProduct.order, categories: recipe.categories ?? ["crafting"], ingredients, results: recipe.results, }; if (recipe.allow_productivity) r.allow_productivity = true; if (recipe.allow_quality) r.allow_quality = true; if (recipe.allow_speed) r.allow_speed = true; return r; }