require "arrays" require "items" require "mathhelper" function parseIngredient(entry, toFull) local type = entry.name and entry.name or entry[1] local amt = entry.amount and entry.amount or entry[2] local form = getItemType(type) return toFull and {name=type, amount=amt, type=form} or {type, amt, form} end function changeRecipeTime(recipe, factor, delta) if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if recipe.normal and recipe.normal.energy_required then recipe.normal.energy_required = roundToPlaces(recipe.normal.energy_required*factor+delta, -1) recipe.expensive.energy_required = roundToPlaces(recipe.expensive.energy_required*factor+delta, -1) else recipe.energy_required = roundToPlaces(recipe.energy_required*factor+delta, -1) end end ---@param recipe table|string ---@param item string function getRecipeCost(recipe, item) if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if recipe.normal and recipe.normal.ingredients then local norm = -1 local exp = -1 for _,ing in pairs(recipe.normal.ingredients) do local parse = parseIngredient(ing) if parse[1] == item then norm = parse[2] break end end for _,ing in pairs(recipe.expensive.ingredients) do local parse = parseIngredient(ing) if parse[1] == item then exp = parse[2] break end end return {norm, exp} elseif recipe.ingredients then for _,ing in pairs(recipe.ingredients) do local parse = parseIngredient(ing) if parse[1] == item then return parse[2] end end else log(serpent.block(recipe)) error("Recipe '" .. recipe .. "' has no ingredients?!") end end function recipeStartsEnabled(recipe) if recipe.normal then return recipe.normal.enabled == nil or recipe.normal.enabled == true else return recipe.enabled == nil or recipe.enabled == true end end ---@return string|table function getRecipeOutput(recipe) if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if not recipe then error(serpent.block("No such recipe found!")) end if recipe.results then return recipe.results elseif recipe.result then return recipe.result end if recipe.normal then if recipe.normal.results then return recipe.normal.results elseif recipe.normal.result then return recipe.normal.result end end error("Recipe '" .. recipe.name .. "' has no output!") end ---@param recipe table ---@param item string ---@return boolean function recipeProduces(recipe, item) --log("Checking outputs of recipe '" .. recipe.name .. "' for '" .. item .. "'") local out = getRecipeOutput(recipe) --log(serpent.block(out)) if type(out) == "table" then for _,e in pairs(out) do if listHasValue(e, item) then return true end end return false else return out == item end end function convertRecipeToResultTable(recipe) if recipe.result and not recipe.results then recipe.results = {{type = "item", name = recipe.result, amount = recipe.result_count}} end if recipe.normal and recipe.normal.result and not recipe.normal.results then recipe.normal.results = {{type = "item", name = recipe.normal.result, amount = recipe.normal.result_count}} end if recipe.expensive and recipe.expensive.result and not recipe.expensive.results then recipe.expensive.results = {{type = "item", name = recipe.expensive.result, amount = recipe.expensive.result_count}} end end function addRecipeProduct(recipe, item, amountnormal, amountexpensive, addIfPresent) if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if not recipe then error(serpent.block("No such recipe found!")) end convertRecipeToResultTable(recipe) if recipe.results then addIngredientToList(recipe.results, item, amountnormal, addIfPresent) end if recipe.normal and recipe.normal.results then addIngredientToList(recipe.normal.results, item, amountnormal, addIfPresent) end if recipe.expensive and recipe.expensive.results then local amt = amountexpensive and amountexpensive or amountnormal addIngredientToList(recipe.expensive.results, item, amt, addIfPresent) end if not recipe.icon and not recipe.icons then -- needs an icon when >1 output recipe.icons = {} local seek = data.raw.item[recipe.name] if not seek then seek = data.raw.fluid[recipe.name] end if seek then table.insert(recipe.icons, {icon = seek.icon, icon_size = seek.icon_size}) else local li = recipe.results if not li and recipe.normal then li = recipe.normal.results end for _,ing in pairs(li) do if not ing.name then error("Found nil-named from: " .. serpent.block(li)) end local val = data.raw.item[ing.name] if not val then val = data.raw.fluid[ing.name] end if not val then error("Product " .. ing.name .. " not indexable?!") end table.insert(recipe.icons, {icon = val.icon, icon_size = val.icon_size}) end end table.insert(recipe.icons, {icon = "__DragonIndustries__/graphics/multi-recipe.png", icon_size = 32}) end end function turnRecipeIntoConversion(from, to) local tgt = data.raw.recipe[to] if not tgt then return end local rec = createConversionRecipe(from, to, false) tgt.ingredients = rec.ingredients if tgt.normal then tgt.normal.ingredients = rec.normal.ingredients end if tgt.expensive then tgt.expensive.ingredients = rec.expensive.ingredients end end function splitRecipeToNormalExpensive(recipe) recipe.normal = { enabled = recipe.enabled, ingredients = table.deepcopy(recipe.ingredients), results = recipe.results and table.deepcopy(recipe.results) or nil, result = recipe.result, energy_required = recipe.energy_required, } recipe.expensive = table.deepcopy(recipe.normal) recipe.ingredients = nil recipe.results = nil recipe.result = nil recipe.enabled = nil recipe.energy_required = nil end ---@param list table ---@param item string ---@param amount number ---@param addIfPresent? boolean ---@param catalyst? boolean function addIngredientToList(list, item, amount, addIfPresent, catalyst) local added = false local itype = type(item) == "table" and item.type or "item" local name = type(item) == "table" and item.name or item log("Inserting recipe item " .. itype .. "/" .. name .. " x " .. amount) for _,ing in pairs(list) do local parse = parseIngredient(ing, true) --log(serpent.block(parse)) if parse.name == item then --log("Match") if addIfPresent then if ing.amount then ing.amount = parse.amount+amount else if ing.amount then ing.amount = parse.amount+amount else ing[2] = parse.amount+amount end end --log("Adding " .. amount .. " to " .. serpent.block(ing)) end added = true break end end if not added then local add = {type = itype, name = name, amount = amount} if catalyst then add.catalyst = catalyst end table.insert(list, add) end end function changeIngredientInList(list, item, repl, ratio, skipError) for i = 1,#list do local ing = parseIngredient(list[i]) --[[ if ing[1] then log("Pos " .. i .. ": " .. ing[1] .. " x" .. ing[2] .. " for " .. item .. "->" .. repl) else log("Pos " .. i .. " is invalid!") end--]] --log("Comparing '" .. ing[1] .. "' and '" .. item .. "': " .. (ing[1] == item and "true" or "false")) if ing[1] == item then ing[1] = repl ing[2] = math.ceil(ing[2]*ratio) ing.name = repl ing.amount = ing[2] ing.type = ing[3] list[i] = ing return ing.amount end end if skipError then --log("No such item '" .. item .. "' in recipe!\n" .. debug.traceback()) return 0 else error("No such item '" .. item .. "' in recipe!\n" .. debug.traceback()) end end function changeCountInList(list, item, delta, skipError) for i = #list,1,-1 do local ing = parseIngredient(list[i]) --[[ if ing[1] then log("Pos " .. i .. ": " .. ing[1] .. " x" .. ing[2] .. " for " .. item .. "->" .. repl) else log("Pos " .. i .. " is invalid!") end--]] --log("Comparing '" .. ing[1] .. "' and '" .. item .. "': " .. (ing[1] == item and "true" or "false")) if ing[1] == item then ing[2] = ing[2]+delta ing.name = item ing.amount = ing[2] ing.type = ing[3] ing = {name = ing.name, amount = ing.amount, type = ing.type} list[i] = ing if ing.amount <= 0 then table.remove(list, i) return 0 else return ing.amount end end end if skipError then log("No such item '" .. item .. "' in recipe!\n" .. debug.traceback()) return 0 else error("No such item '" .. item .. "' in recipe!\n" .. debug.traceback()) end end function replaceItemInRecipe(recipe, item, repl, ratio, skipError) if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if not recipe then error(serpent.block("No such recipe found! " .. debug.traceback())) end if type(ratio) == "table" and recipe.ingredients then splitRecipeToNormalExpensive(recipe) end local def, norm, exp = 0, 0, 0 local ratn = type(ratio) == "table" and ratio[1] or ratio local rate = type(ratio) == "table" and ratio[2] or ratio if recipe.ingredients then def = changeIngredientInList(recipe.ingredients, item, repl, ratn, skipError) end if recipe.normal and recipe.normal.ingredients then norm = changeIngredientInList(recipe.normal.ingredients, item, repl, ratn, skipError) end if recipe.expensive and recipe.expensive.ingredients then exp = changeIngredientInList(recipe.expensive.ingredients, item, repl, rate, skipError) end log("Replaced item " .. item .. " with " .. repl .. " in recipe " .. recipe.name .. " with a ratio of " .. serpent.block(ratio) .. "x") return {def, norm, exp} end function changeItemCountInRecipe(recipe, item, delta, skipError) if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if not recipe then error(serpent.block("No such recipe found! " .. debug.traceback())) end local def, norm, exp = 0, 0, 0 if recipe.ingredients then def = changeCountInList(recipe.ingredients, item, delta, skipError) end if recipe.normal and recipe.normal.ingredients then norm = changeCountInList(recipe.normal.ingredients, item, delta, skipError) end if recipe.expensive and recipe.expensive.ingredients then exp = changeCountInList(recipe.expensive.ingredients, item, delta, skipError) end log("Changed count of item " .. item .. " + " .. delta .. " in recipe " .. recipe.name) --log(serpent.block(recipe)) return {def, norm, exp} end function removeItemFromRecipe(recipe, item) if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if not recipe then error(serpent.block("No such recipe found!")) end if recipe.ingredients then for i,ing in pairs(recipe.ingredients) do if ing[1] == item then table.remove(recipe.ingredients, i) break end end end if recipe.normal and recipe.normal.ingredients then for i,ing in pairs(recipe.normal.ingredients) do if ing[1] == item then table.remove(recipe.normal.ingredients, i) break end end end if recipe.expensive and recipe.expensive.ingredients then for i,ing in pairs(recipe.expensive.ingredients) do if ing[1] == item then table.remove(recipe.expensive.ingredients, i) break end end end end ---@param recipe table|string ---@param item string ---@param amountnormal number ---@param amountexpensive? number ---@param addIfPresent? boolean ---@param catalyst? boolean function addItemToRecipe(recipe, item, amountnormal, amountexpensive, addIfPresent, catalyst) if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if not recipe then error(serpent.block("No such recipe found!")) end if not item then error("Tried to add null item!") end local find = data.raw.item[item] if not find then find = data.raw.fluid[item] end if not find then error("No such item '" .. item .. "'!") end log("Adding '" .. find.name .. "' x" .. amountnormal .. " to recipe '" .. recipe.name .. "'") if recipe.ingredients then addIngredientToList(recipe.ingredients, find, amountnormal, addIfPresent, catalyst) end if recipe.normal and recipe.normal.ingredients then addIngredientToList(recipe.normal.ingredients, find, amountnormal, addIfPresent, catalyst) end if recipe.expensive and recipe.expensive.ingredients then local amt = amountexpensive and amountexpensive or amountnormal addIngredientToList(recipe.expensive.ingredients, find, amt, addIfPresent, catalyst) end end ---@param recipe table|string ---@param item string ---@param recipeRef string|table ---@param ratio number ---@param addIfPresent? boolean function addRecipeIngredientToRecipe(recipe, item, recipeRef, ratio, addIfPresent) local cost = getRecipeCost(recipeRef, item) if cost == nil then cost = 0 end if cost == 0 then cost = 1 end local amountnormal = type(cost) == "table" and cost[1] or cost local amountexpensive = type(cost) == "table" and cost[2] or cost addItemToRecipe(recipe, item, amountnormal*ratio, amountexpensive*ratio, addIfPresent) end function moveRecipe(recipe, from, to) local tech = data.raw.technology[from] local tech2 = data.raw.technology[to] if not tech then error("Tech '" .. from .. "' does not exist!") end if not tech2 then error("Tech '" .. to .. "' does not exist!") end local effects = {} for _,effect in pairs(tech.effects) do if effect.type == "unlock-recipe" and effect.recipe == recipe then else table.insert(effects, effect) end end tech.effects = effects for _,eff in pairs(tech2.effects) do if eff.type == "unlock-recipe" and eff.recipe == recipe then return end end table.insert(tech2.effects, {type = "unlock-recipe", recipe = recipe}) end function lockRecipe(recipe, from) if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if not recipe then return end local tech = data.raw.technology[from] if not tech then error("Tech '" .. from .. "' does not exist!") end if not tech.effects then tech.effects = {} end table.insert(tech.effects, {type = "unlock-recipe", recipe = recipe.name}) log("Putting recipe '" .. recipe.name .. "' behind tech '" .. tech.name .. "'") recipe.enabled = false if recipe.normal then recipe.normal.enabled = false end if recipe.expensive then recipe.expensive.enabled = false end end --returns nil if none, not {} local function buildRecipeSurplus(name1, name2, list1, list2) local counts = {} local ret = nil for i = 1,#list1 do local ing = parseIngredient(list1[i]) --log("Parsing input ingredient: " .. (ing[1] and ing[1] or "nil") .. " x " .. (ing[2] and ing[2] or "nil")) if #ing > 0 then --log(#ing .. " > " .. tostring(ing)) counts[ing[1]] = (counts[ing[1]] and counts[ing[1]] or 0)+(ing[2] and ing[2] or 1) -- += in case recipe specifies an ingredient multiple times else log("Found empty entry in recipe " .. name1 .. "!") end end for i = 1,#list2 do local ing = parseIngredient(list2[i]) --log("Parsing output ingredient: " .. (ing[1] and ing[1] or "nil") .. " x " .. (ing[2] and ing[2] or "nil")) if #ing > 0 then if counts[ing[1]] then counts[ing[1]] = counts[ing[1]]-ing[2] end else log("Found empty entry in recipe " .. name2 .. "!") end end for item,amt in pairs(counts) do if counts[item] > 0 and data.raw.item[item] then if not ret then ret = {} end ret[item] = amt end end return ret end --Supply nil for list1 to get a plain ingredient list for list2 local function buildRecipeDifference(name1, name2, list1, list2, form, recursion) if recursion then for i,ing in ipairs(list1) do --log(serpent.block(ing)) if listHasValue(list2, ing[1]) and data.raw.recipe[ing[1]] then log("Recursing " .. ing[1]) table.remove(list1, i) local list = form == "expensive" and data.raw.recipe[ing[1]].expensive.ingredients or ("normal" and data.raw.recipe[ing[1]].normal.ingredients or data.raw.recipe[ing[1]].ingredients) for _,e in pairs(buildRecipeDifference("", ing[1], nil, list)) do table.insert(list1, e) log("Adding " .. e[1]) end end end end if not list2 then error(debug.traceback()) end local counts = {} local ret = {} if list1 then for i = 1,#list1 do local ing = parseIngredient(list1[i]) --log("Parsing input ingredient: " .. (ing[1] and ing[1] or "nil") .. " x " .. (ing[2] and ing[2] or "nil")) if #ing > 0 and ing[1] then --log(#ing .. " > " .. tostring(ing)) counts[ing[1]] = (counts[ing[1]] and counts[ing[1]] or 0)+(ing[2] and ing[2] or 1) -- += in case recipe specifies an ingredient multiple times else log("Found empty entry in recipe " .. name1 .. "!") end end end for i = 1,#list2 do local ing = parseIngredient(list2[i]) --log("Parsing output ingredient: " .. (ing[1] and ing[1] or "nil") .. " x " .. (ing[2] and ing[2] or "nil")) if #ing > 0 and ing[1] then local amt = ing[2]-(counts[ing[1]] and counts[ing[1]] or 0) if amt > 0 then ret[#ret+1] = {ing[1], amt} end else log("Found empty entry in recipe " .. name2 .. "!") end end for i = #ret,1,-1 do local e = ret[i] if e == nil or e[1] == nil then log("Slot #" .. i .. " in the recipe difference between '" .. name1 .. "' and '" .. name2 .. "' was nil or nil-named!") table.remove(ret, i) end end return ret end ---@param recipe table local function createRecipeProfile(recipe) return { enabled = recipe.enabled, ingredients = table.deepcopy(recipe.ingredients), result = recipe.result, results = recipe.results and table.deepcopy(recipe.results) or nil, } end ---@param recipe table|string local function swapRecipeIO(recipe) if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if not recipe then return end local temp = table.deepcopy(recipe.ingredients) for _,e in pairs(temp) do if not e.name then e.name = e[1] e.amount = e[2] e.type = "item" end end local result = recipe.results if not result then result = {{type = "item", name = recipe.result, amount = recipe.result_count and recipe.result_count or 1}} end recipe.ingredients = table.deepcopy(result) recipe.results = temp end ---@param recipe table ---@param ref table function createUncraftingRecipe(recipe, ref) local ret = table.deepcopy(recipe) swapRecipeIO(ret) ret.name = recipe.name .. "-uncraft" ret.icons = {{icon = ref.icon, icon_size = ref.icon_size}, {icon = "__DragonIndustries__/graphics/icons/uncrafting_overlay.png", icon_size = 32}} ret.subgroup = ref.subgroup ret.allow_decomposition = false ret.allow_as_intermediate = false ret.allow_intermediates = false ret.localised_name = {"uncraft-recipe.name", {"entity-name." .. ref.name}} return ret end ---@param from string|table ---@param to string|table ---@param register? boolean ---@param tech? string|table ---@param recursion? table function createConversionRecipe(from, to, register, tech, recursion) local rec1 = type(from) == "table" and from or data.raw.recipe[from] local rec2 = type(to) == "table" and from or data.raw.recipe[to] if tech and type(tech) == "table" then tech = tech.name end if not rec1 then error("No such recipe '" .. from .. "'!") end if not rec2 then error("No such recipe '" .. to .. "'!") end rec1 = table.deepcopy(rec1) rec2 = table.deepcopy(rec2) local name = rec1.name .. "-conversion-to-" .. rec2.name if data.raw.recipe.name then error("Conversion recipe already exists: " .. name) else log("Creating conversion recipe " .. name) if recursion then log("Recursing ingredients " .. serpent.block(recursion)) end end local list = nil local exp = nil local norm = nil local e_list = nil local e_exp = nil local e_norm = nil if rec1.normal and not rec2.normal then --harmonize the recipe styles rec2.normal = createRecipeProfile(rec2) rec2.expensive = createRecipeProfile(rec2) elseif rec2.normal and not rec1.normal then rec1.normal = createRecipeProfile(rec1) rec1.expensive = createRecipeProfile(rec1) end local prev = rec1.expensive and rec1.expensive.result or rec1.result if not prev then local list = rec1.results and rec1.results or rec1.expensive.results --[[@as table]] for _,res in pairs(list) do if res.type == "item" then prev = res.name break end end end if rec1.ingredients and rec2.ingredients then list = buildRecipeDifference(from, to, rec1.ingredients, rec2.ingredients, "basic", recursion) e_list = buildRecipeSurplus(from, to, rec1.ingredients, rec2.ingredients) end if rec1.expensive or rec2.expensive then local exp1 = rec1.expensive and rec1.expensive.ingredients or rec1.ingredients local exp2 = rec2.expensive and rec2.expensive.ingredients or rec2.ingredients exp = buildRecipeDifference(from, to, exp1, exp2, "expensive", recursion) e_exp = buildRecipeSurplus(from, to, exp1, exp2) end if rec1.normal or rec2.normal then local norm1 = rec1.normal and rec1.normal.ingredients or rec1.ingredients local norm2 = rec2.normal and rec2.normal.ingredients or rec2.ingredients norm = buildRecipeDifference(from, to, norm1, norm2, "normal", recursion) e_norm = buildRecipeSurplus(from, to, norm1, norm2) end if prev then if list then table.insert(list, {prev, rec1.result_count and rec1.result_count or 1}) end if exp then table.insert(exp, {prev, rec1.expensive.result_count and rec1.expensive.result_count or 1}) end if norm then table.insert(norm, {prev, rec1.normal.result_count and rec1.normal.result_count or 1}) end end local ret = table.deepcopy(rec2) ret.name = name ret.ingredients = list local main = rec1.result and rec1.result or rec1.normal.result local result = rec2.result and rec2.result or rec2.normal.result if main == nil then local rec1results = rec1.results and rec1.results or rec1.normal.results if rec1results == nil then error("Recipe '" .. rec1.name .. "' has neither a result on the recipe nor its cost subrecipe!" .. serpent.block(rec1)) end for _,ing in pairs(rec1results) do if ing.type == "item" then main = ing.name break end end end if result == nil then local rec2results = rec2.results and rec2.results or rec2.normal.results if rec2results == nil then error("Recipe '" .. rec1.name .. "' has neither a result on the recipe nor its cost subrecipe!" .. serpent.block(rec1)) end for _,ing in pairs(rec2results) do if ing.type == "item" then result = ing.name break end end end if not main then error("Cannot create a conversion recipe from a recipe that has no output!" .. serpent.block(rec1)) end if not result then error("Cannot create a conversion recipe to a recipe that has no output!" .. serpent.block(rec2)) end local out = getItemByName(result) if not out then error("Cannot create a conversion recipe to an item that does not exist!" .. serpent.block(rec2)) end local itemType = out.type ret.localised_name = {"conversion-recipe.name", {"entity-name." .. main}, {"entity-name." .. result}} local orig_icon_src = rec2 if (not (orig_icon_src.icon or orig_icon_src.icons)) and data.raw[itemType][result] then orig_icon_src = data.raw[itemType][result] end if not (orig_icon_src and (orig_icon_src.icon or orig_icon_src.icons)) then error("Could not find an icon for " .. rec2.name .. ", in either the recipe or its produced item! This item is invalid and would have crashed the game anyways!") end local ico = orig_icon_src.icon and orig_icon_src.icon or orig_icon_src.icons[1].icon local icosz = orig_icon_src.icon_size and orig_icon_src.icon_size or orig_icon_src.icons[1].icon_size ret.icons = {{icon = ico, icon_size = icosz}, {icon = "__DragonIndustries__/graphics/icons/conversion_overlay.png", icon_size = 32}} if not ret.icon then if data.raw[itemType][result] then ret.icon = data.raw[itemType][result].icon else log("Could not create icon for conversion recipe '" .. name .. "'! No such item '" .. result .. "'") end end if e_list then local out = ret.result if out == nil then for _,ing in pairs(ret.results) do if ing.type == "item" then out = ing.name break end end end ret.results = {{type = "item", name = out, amount = ret.result_count and ret.result_count or 1}} for type,count in pairs(e_list) do if not type then log("Found a nameless entry in a recipe result/ingredient list when generating a conversion recipe?") else table.insert(ret.results, {type = "item", name = type, amount = count}) end end ret.result = nil ret.subgroup = data.raw[itemType][result].subgroup end if ret.normal then ret.normal.ingredients = norm if e_norm then local out = ret.normal.result if out == nil then for _,ing in pairs(ret.normal.results) do if ing.type == "item" then out = ing.name break end end end ret.normal.results = {{type = "item", name = out, amount = ret.normal.result_count and ret.normal.result_count or 1}} for type,count in pairs(e_norm) do if not type then log("Found a nameless entry in a recipe result/ingredient list when generating a conversion recipe?") else table.insert(ret.normal.results, {type = "item", name = type, amount = count}) end end ret.normal.result = nil ret.subgroup = data.raw[itemType][result].subgroup end end if ret.expensive then ret.expensive.ingredients = exp if e_exp then local out = ret.expensive.result if out == nil then for _,ing in pairs(ret.expensive.results) do if ing.type == "item" then out = ing.name break end end end ret.expensive.results = {{type = "item", name = out, amount = ret.expensive.result_count and ret.expensive.result_count or 1}} for type,count in pairs(e_exp) do if not type then log("Found a nameless entry in a recipe result/ingredient list when generating a conversion recipe?") else table.insert(ret.expensive.results, {type = "item", name = type, amount = count}) end end ret.expensive.result = nil ret.subgroup = data.raw[itemType][result].subgroup end end if data.raw.item["basic-circuit-board"] then replaceItemInRecipe(ret, "electronic-circuit", "basic-circuit-board", 1, true) end ret.allow_decomposition = false ret.allow_as_intermediate = false ret.allow_intermediates = false if ret.normal then ret.normal.allow_decomposition = false ret.normal.allow_as_intermediate = false ret.normal.allow_intermediates = false end if ret.expensive then ret.expensive.allow_decomposition = false ret.expensive.allow_as_intermediate = false ret.expensive.allow_intermediates = false end if ret.ingredients == nil and (ret.normal == nil or ret.normal.ingredients == nil) then error("Conversion recipe " .. ret.name .. " has no specified ingredients! Source recipes: " .. serpent.block(rec1) .. " , " .. serpent.block(rec2)) end if ret.ingredients then for _,ing in pairs(ret.ingredients) do if ing[2] == 0 then error("Conversion recipe " .. ret.name .. " has no 0-count ingredients! Source recipes: " .. serpent.block(rec1) .. " , " .. serpent.block(rec2)) end end end if ret.normal and ret.normal.ingredients then for _,ing in pairs(ret.normal.ingredients) do if ing[2] == 0 then error("Conversion recipe " .. ret.name .. " has no 0-count ingredients! Source recipes: " .. serpent.block(rec1) .. " , " .. serpent.block(rec2)) end end end if register then data:extend({ret}) if tech then table.insert(data.raw.technology[tech].effects, {type = "unlock-recipe", recipe = name}) end end --log(serpent.block(ret)) return ret end function streamlineRecipeOutputWithRecipe(recipe, with, main, skipError) log("Streamlining '" .. recipe.name .. "' with recipe '" .. with .. "' for main item '" .. main .. "'") if not recipe.results then error("You can only output-streamline multi-output recipes!") end local stream = data.raw.recipe[with] if not stream then error("Recipe '" .. with .. "' does not exist!") end local extras = {} local extraAmt = {} local amt = -1 for _,ing in pairs(recipe.results) do local parse = parseIngredient(ing) if parse[1] == main then amt = parse[2] else table.insert(extras, parse[1]) extraAmt[parse[1]] = parse[2] end end log("Extras: " .. serpent.block(extraAmt)) local need = stream.ingredients if not need and stream.normal then need = stream.normal.ingredients end if not need then error("Recipe '" .. with .. "' has no ingredients!") end log("Total needed: " .. serpent.block(need)) need = table.deepcopy(need) for i=#need,1,-1 do local ing = need[i] local parse = parseIngredient(ing) if listHasValue(extras, parse[1]) then local ext = extraAmt[parse[1]] if parse[2] <= ext then log("Output contained sufficient extra. Removing from need.") table.remove(need, i) table.remove(recipe.results, i) else log("Output did not contain sufficient extra. Removing only " .. ext .. " from need of " .. parse[2] .. ".") extraAmt[parse[1]] = 0 parse[2] = parse[2]-ext if ing.amount then ing.amount = parse[2] recipe.results[i].amount = ing.amount else ing[2] = parse[2] recipe.results[i][2] = ing[2] end end end end --log("Diff needed: " .. serpent.block(need)) for _,ing in pairs(need) do local parse = parseIngredient(ing) local amt = parse[2] addItemToRecipe(recipe, parse[1], amt, amt, true) end changeItemCountInRecipe(recipe, with, -1, skipError) --log(serpent.block(recipe)) end ---@param item string|table function markItemForProductivityAllowed(item) if type(item) == "string" then item = getItemByName(item) end if not item then error("No such item '" .. item .. "'") end log("Adding item '" .. item.name .. "' to the valid-with-productivity list") for name,recipe in pairs(data.raw.recipe) do if recipeProduces(recipe, item.name) then markForProductivityAllowed(recipe) end end end ---@param recipe string|table function markForProductivityAllowed(recipe) local arg = recipe if type(recipe) == "string" then recipe = data.raw.recipe[recipe] end if not recipe then error("No such recipe '" .. arg .. "'") end --if not recipe.allow_as_intermediate then log("Skipping productivity allowance on uncraft/upgrade recipe to prevent exploits: " .. recipe.name) return end for name,module in pairs(data.raw.module) do if module.effect and module.effect["productivity"] and module.limitation and getTableSize(module.limitation) > 0 then log("Adding recipe '" .. recipe.name .. "' to the valid-with-productivity list on '" .. name .. "'") table.insert(module.limitation, recipe.name) end end end