Need help with mass adding byproducts to recipes with a certain item

Place to get help with not working mods / modding interface.
Post Reply
prokaryotic24
Manual Inserter
Manual Inserter
Posts: 3
Joined: Mon Feb 05, 2024 12:39 pm
Contact:

Need help with mass adding byproducts to recipes with a certain item

Post by prokaryotic24 »

I know you can do it like this manually, but I would like to turn this into a loop that will find all ingredients and use table.insert to add the byproduct instead.

Code: Select all

data.raw.recipe["iron-gear-wheel"].ingredients =
{
	{"iron-plate", 2},
	{"iron-plate-scrap", 1}
}
Please help :cry:, I don't wanna do all recipes manually, as I plan to add more byproducts and not just plate scraps. :cry:

edit:
I just decided to table insert everything manually. But any help with the way of doing this with a loop is still welcome, I'd like to learn more about how to mod.
Last edited by prokaryotic24 on Mon Feb 05, 2024 2:11 pm, edited 1 time in total.

FuryoftheStars
Smart Inserter
Smart Inserter
Posts: 2587
Joined: Tue Apr 25, 2017 2:01 pm
Contact:

Re: Need help with mass adding byproducts to recipes with a certain item

Post by FuryoftheStars »

Basically, you'd need 2 loops: one running over all recipes, and then another inside of that running over the ingredients looking for and changing as needed.

Code: Select all

for name, recipe in pairs(data.raw.recipe) do
    for k, ingredient in pairs(recipe.ingredients) do
        -- do stuff
    end
end
You can work with the ingredient from the inner loop as "ingredient[1]" for its name, and "ingredient[2]" for the quantity. Removing an ingredient, if desired, is then also accomplished by setting the respective index in "recipe.ingredients" to nil.

Before inserting an ingredient, though, I do recommend checking the whole list to make sure it's not something that already exists and, if it does, increment the existing number (it may be a new item introduced by your mod, but others may introduce a similar named item, so you'll need to handle that carefully).

Similar can be done with the output of a recipe if it has multiple outputs and thus uses the ".results" key.
My Mods: Classic Factorio Basic Oil Processing | Sulfur Production from Oils | Wood to Oil Processing | Infinite Resources - Normal Yield | Tree Saplings (Redux) | Alien Biomes Tweaked | Restrictions on Artificial Tiles | New Gear Girl & HR Graphics

prokaryotic24
Manual Inserter
Manual Inserter
Posts: 3
Joined: Mon Feb 05, 2024 12:39 pm
Contact:

Re: Need help with mass adding byproducts to recipes with a certain item

Post by prokaryotic24 »

FuryoftheStars wrote:
Mon Feb 05, 2024 4:08 pm
Basically, you'd need 2 loops: one running over all recipes, and then another inside of that running over the ingredients looking for and changing as needed.

Code: Select all

for name, recipe in pairs(data.raw.recipe) do
    for k, ingredient in pairs(recipe.ingredients) do
        -- do stuff
    end
end
You can work with the ingredient from the inner loop as "ingredient[1]" for its name, and "ingredient[2]" for the quantity. Removing an ingredient, if desired, is then also accomplished by setting the respective index in "recipe.ingredients" to nil.

Before inserting an ingredient, though, I do recommend checking the whole list to make sure it's not something that already exists and, if it does, increment the existing number (it may be a new item introduced by your mod, but others may introduce a similar named item, so you'll need to handle that carefully).

Similar can be done with the output of a recipe if it has multiple outputs and thus uses the ".results" key.
I forgot to edit that I meant .result not .ingredient with my post.
Thank you, I'll play around with that code. But for now ill do it manually xD as I'm playing mostly vanilla right now. I'll do loops when I go into larger mods.

FuryoftheStars
Smart Inserter
Smart Inserter
Posts: 2587
Joined: Tue Apr 25, 2017 2:01 pm
Contact:

Re: Need help with mass adding byproducts to recipes with a certain item

Post by FuryoftheStars »

For the result side, adding a new result will require checking if it's .result or .results. .results can be handled just like .ingredients. .result (along with its respective .result_count property) will need to be converted into .results.
My Mods: Classic Factorio Basic Oil Processing | Sulfur Production from Oils | Wood to Oil Processing | Infinite Resources - Normal Yield | Tree Saplings (Redux) | Alien Biomes Tweaked | Restrictions on Artificial Tiles | New Gear Girl & HR Graphics

Pi-C
Smart Inserter
Smart Inserter
Posts: 1655
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: Need help with mass adding byproducts to recipes with a certain item

Post by Pi-C »

prokaryotic24 wrote:
Mon Feb 05, 2024 1:09 pm
I know you can do it like this manually, but I would like to turn this into a loop that will find all ingredients and use table.insert to add the byproduct instead.

Code: Select all

data.raw.recipe["iron-gear-wheel"].ingredients =
{
	{"iron-plate", 2},
	{"iron-plate-scrap", 1}
}

Code: Select all

local function add_to(ingredients)
	if ingredients then
		table.insert(ingredients, {"byproduct_name", 1})
	end
end

for r, recipe in pairs(data.raw.recipe) do
	add_to(recipe.ingredients)
	if recipe.normal then
		add_to(recipe.normal.ingredients)
	end
	if recipe.expensive then
		add_to(recipe.expensive.ingredients)
	end
end
Recipe handling is a bit complicated because you have to consider recipes where normal and expensive difficulties, normal or
expensive difficulty, or no difficulty at all are given. If any difficulty is defined, its properties will override the properties given at the recipe root (e.g. if recipe.normal exists, recipe.ingredients will be ignored). If only one difficulty is defined, its properties will be reused by the other difficulty.

Trying to access a non-existent value is OK, but checking for a value inside a table that doesn't exist will give you an error, so it's important to check that tables really exist before you try to modify them. The code is the same for all "ingredients" lists, the only difference is which table (recipe.ingredients or recipe[difficulty].ingredients) you want to modify. So instead of repeating the same code three times, I've defined a function add_to that will be called either with a table or with nil.
FuryoftheStars wrote:
Mon Feb 05, 2024 4:08 pm
You can work with the ingredient from the inner loop as "ingredient[1]" for its name, and "ingredient[2]" for the quantity. … Similar can be done with the output of a recipe if it has multiple outputs and thus uses the ".results" key.
Another thing that makes recipe handling complicated is that ingredients and results may be given in short or long format:

Code: Select all

-- Short format (only for items):
ingredients = {
	{ "item_1", 1 },
	{ "item_2", 5 },
	…
}


-- Long format (for items and fluids): 
ingredients = {
	{ type = "item",  name = "item_1", amount = 1 },
	{ type = " item", name = "item_2", amount = 5 },
	{ type = "fluid", name = "fluid_1", amount = 10 },
}
If you want to check whether a particular item already is among the ingredients/results list, you must make sure to check both formats -- even for recipes you've defined yourself at an earlier stage! (You never know what other mods may have done to your recipes after you've first defined them.) Something like this would work to check whether a list of recipe ingredients or results contains an item called "name":

Code: Select all

-- Returns index of the entry called "name", or nil if list doesn't have such an entry
local function table_contains(list, name)
	for e, entry in pairs(list) do
		if name == entry.name or name == entry[1] then
			return e
		end
	end
end
Yet another complication: If you have a single result, use key "result" for the item or fluid name and "result_count" for the amount produced by the recipe. "result" and "result_count" will be ignored if "results" exists. Just like "ingredients", "result"+"result_count" and "results" may be given with or without difficulty.
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

BraveCaperCat
Long Handed Inserter
Long Handed Inserter
Posts: 50
Joined: Mon Jan 15, 2024 10:10 pm
Contact:

Adding by-products to recipes

Post by BraveCaperCat »

This is actually quite simple (if you are accustomed to lua coding, that is). First you define your by-products:

Code: Select all

local ModID = 'sr:' -- In this variable, put the initials of your mod's name in lowercase followed by a double dot character.
for i, item in pairs(data.raw['item']) do
    local ScrapItem = table.deepcopy(item)
    ScrapItem.localised_name = {'', {'item-name.' .. ModID .. 'scrap'}, ScrapItem.name} -- Make sure the locale works just fine.
    ScrapItem.name = ModID .. 'scrap-' .. ScrapItem.name -- Append the ModID and then the string 'scrap-' to the start of the original name
    -- You may want to add other stuff here, like stack size changes, icons, etc.
    data:extend({ScrapItem})I
end
Done.
Now we add recipes for each of these:

Code: Select all

local function scraprecipe(recipe)

    If recipe.results == nil then -- If the recipe only produces one item, move the result data to the results table and delete the original data
        recipe.results = {{type = 'item', name = recipe.result, count = recipe.result_count}}
        recipe.result = nil
        recipe.result_count = nil
    end
    for i, result in pairs(recipe.results) do
        If data.raw['item'][ModID .. 'scrap-' .. results.name] ~= nil then
            table.insert(recipe.results, {type = 'item', name = ModID .. 'scrap-' .. results.name, amount = 1, probability = 0.5})
            result.probability = 0.5
        end
    end
    return recipe
end

for i, recipe in pairs(data.raw['recipe']) do
    If recipe.result or recipe.results then
    recipe = scraprecipe(recipe)
    end
    if recipe.normal then
    recipe.normal = scraprecipe(recipe.normal)
    end
    if recipe.expensive then
    recipe.expensive = scraprecipe(recipe.expensive)
    end
end
Done. Again.
If you don't want a version of every item in scrap, just create scrap prototypes using that same code, using the loop to look through specific items. That would look like this:

Code: Select all

local ModID = 'sr:' -- In this variable, put the initials of your mod's name in lowercase followed by a double dot character.
local ScrapItems = {'iron-plate', 'iron-gear-wheel'} -- ScrapItems is an array of ItemID (as the documentation would say).
for i, itemname in pairs(ScrapItems) do
    local item = data.raw['item'][itemname]
    local ScrapItem = table.deepcopy(item)
    ScrapItem.localised_name = {'', {'item-name.' .. ModID .. 'scrap'}, ScrapItem.name} -- Make sure the locale works just fine.
    ScrapItem.name = ModID .. 'scrap-' .. ScrapItem.name -- Append the ModID and then the string 'scrap-' to the start of the original name
    -- You may want to add other stuff here, like stack size changes, icons, etc.
    data:extend({ScrapItem})
end
Done. Again, Again.

prokaryotic24
Manual Inserter
Manual Inserter
Posts: 3
Joined: Mon Feb 05, 2024 12:39 pm
Contact:

Re: Need help with mass adding byproducts to recipes with a certain item

Post by prokaryotic24 »

I managed to frankenstein this code and it works.

Code: Select all

-- Add byproducts
data.raw.recipe["edrill-0to1"].main_product="electric-mining-drill"
data.raw.recipe["edrill-0to1"].results={{name="electric-mining-drill",probability=1,amount=1},{name="iron-dust",probability=0.4,amount=1}}
local recipes =
{
	-- {<item_name>, <probability_1>, <amount_1>, <dust_name_1>, <dust_probability_1>, <dust_amount_1>, <dust_name_2>, <dust_probability_2>, <dust_amount_2>}
	-- Logistics
	{"iron-chest"                 , 1, 1, "iron-dust", 0.8, 1},
	{"transport-belt"             , 1, 2, "iron-dust", 0.1, 1},
	{"chute-miniloader"           , 1, 1, "iron-dust", 0.1, 1},--modded
	{"burner-inserter"            , 1, 1, "iron-dust", 0.1, 1},
	{"burner-long-handed-inserter", 1, 1, "iron-dust", 0.1, 1},
	{"fast-inserter"              , 1, 1, "iron-dust", 0.2, 1},
	{"pipe"                       , 1, 1, "iron-dust", 0.1, 1},
	{"pipe-to-ground"             , 1, 1, "iron-dust", 0.5, 1},
	{"underground-belt"     	  , 1, 1, "iron-dust", 1, 1},
	{"splitter"                   , 1, 1, "iron-dust", 0.5, 1},
	{"aai-loader"                 , 1, 1, "iron-dust", 0.6, 1},--modded
	
	-- Production
	{"steam-engine"               , 1, 1, "iron-dust", 1.0, 1},
	{"burner-mining-drill"        , 1, 1, "iron-dust", 0.3, 1},
	{"electric-mining-drill"      , 1, 1, "iron-dust", 1.0, 1},
	--{"edrill-0to1"              , 1, 1, "iron-dust", 0.4, 1}, recipe name doesnt match with item name
	{"assembling-machine-1"       , 1, 1, "iron-dust", 0.9, 1},
	
	-- Intermediate products	
	{"copper-cable"               , 1, 2, "copper-dust", 0.1, 1},
	{"iron-stick"                 , 1, 2, "iron-dust", 0.1, 1},
	{"iron-gear-wheel"            , 1, 1, "iron-dust", 0.2, 1},
	{"electronic-circuit"         , 1, 1, "iron-dust", 0.1, 1},
	{"automation-science-pack"    , 1, 1, "copper-dust", 0.1, 1},
	{"rp-steam-calculator"  	  , 1, 1, "copper-dust", 0.5, 1},--modded
	
	-- Combat	
	{"pistol"                     , 1, 1, "iron-dust", 0.5, 1, "copper-dust", 0.5, 1},
	{"firearm-magazine"           , 1, 1, "iron-dust", 0.4, 1},
	{"light-armor"                , 1, 1, "iron-dust", 1.0, 4},
	{"radar"                      , 1, 1, "iron-dust", 1.0, 1}
}

for _, recipe in ipairs(recipes) do
	local mainRecipe, prob1, amt1, dust1, dProb1, amt2, dust2, dProb2, amt3 = unpack(recipe)
	if data.raw.recipe[mainRecipe] then
		if data.raw.recipe[mainRecipe].normal then
			data.raw.recipe[mainRecipe].normal.main_product = mainRecipe
			data.raw.recipe[mainRecipe].normal.results = {{name = mainRecipe, probability = prob1, amount = amt1}}
			if dust1 then
				table.insert(data.raw.recipe[mainRecipe].normal.results, {name = dust1, probability = dProb1, amount = amt2})
			end
			if dust2 then
				table.insert(data.raw.recipe[mainRecipe].normal.results, {name = dust2, probability = dProb2, amount = amt3})
			end
		end
		if data.raw.recipe[mainRecipe].expensive then
			data.raw.recipe[mainRecipe].expensive.main_product = mainRecipe
			data.raw.recipe[mainRecipe].expensive.results = {{name = mainRecipe, probability = prob1, amount = amt1}}
			if dust1 then
				table.insert(data.raw.recipe[mainRecipe].expensive.results, {name = dust1, probability = dProb1, amount = amt2})
			end
			if dust2 then
				table.insert(data.raw.recipe[mainRecipe].expensive.results, {name = dust2, probability = dProb2, amount = amt3})
			end
			else
			data.raw.recipe[mainRecipe].main_product = mainRecipe
			data.raw.recipe[mainRecipe].results = {{name = mainRecipe, probability = prob1, amount = amt1}}
			if dust1 then
				table.insert(data.raw.recipe[mainRecipe].results, {name = dust1, probability = dProb1, amount = amt2})
			end
			if dust2 then
				table.insert(data.raw.recipe[mainRecipe].results, {name = dust2, probability = dProb2, amount = amt3})
			end
		end
		else
	end
end
But now, I'd like to try adding the byproducts automatically, based on how much each recipe has of a certain ingredient.
Something like this:

Pistol x5 Iron x5 Copper
table.insert iron byproduct probability = 0.5, amount = 1
table.insert copper byproduct probability = 0.5, amount = 1

Low density structure x20 Copper x2 Steel
table.insert copper byproduct probability = 1, amount = 2
table.insert Steel byproduct probability = 0.2, amount = 1

Please help, I'm a total noob at programming and modding. Thanks.

BraveCaperCat
Long Handed Inserter
Long Handed Inserter
Posts: 50
Joined: Mon Jan 15, 2024 10:10 pm
Contact:

Re: Need help with mass adding byproducts to recipes with a certain item

Post by BraveCaperCat »

prokaryotic24 wrote:
Thu Feb 08, 2024 4:07 am
I managed to frankenstein this code and it works.

Code: Select all

-- Add byproducts
data.raw.recipe["edrill-0to1"].main_product="electric-mining-drill"
data.raw.recipe["edrill-0to1"].results={{name="electric-mining-drill",probability=1,amount=1},{name="iron-dust",probability=0.4,amount=1}}
local recipes =
{
	-- {<item_name>, <probability_1>, <amount_1>, <dust_name_1>, <dust_probability_1>, <dust_amount_1>, <dust_name_2>, <dust_probability_2>, <dust_amount_2>}
	-- Logistics
	{"iron-chest"                 , 1, 1, "iron-dust", 0.8, 1},
	{"transport-belt"             , 1, 2, "iron-dust", 0.1, 1},
	{"chute-miniloader"           , 1, 1, "iron-dust", 0.1, 1},--modded
	{"burner-inserter"            , 1, 1, "iron-dust", 0.1, 1},
	{"burner-long-handed-inserter", 1, 1, "iron-dust", 0.1, 1},
	{"fast-inserter"              , 1, 1, "iron-dust", 0.2, 1},
	{"pipe"                       , 1, 1, "iron-dust", 0.1, 1},
	{"pipe-to-ground"             , 1, 1, "iron-dust", 0.5, 1},
	{"underground-belt"     	  , 1, 1, "iron-dust", 1, 1},
	{"splitter"                   , 1, 1, "iron-dust", 0.5, 1},
	{"aai-loader"                 , 1, 1, "iron-dust", 0.6, 1},--modded
	
	-- Production
	{"steam-engine"               , 1, 1, "iron-dust", 1.0, 1},
	{"burner-mining-drill"        , 1, 1, "iron-dust", 0.3, 1},
	{"electric-mining-drill"      , 1, 1, "iron-dust", 1.0, 1},
	--{"edrill-0to1"              , 1, 1, "iron-dust", 0.4, 1}, recipe name doesnt match with item name
	{"assembling-machine-1"       , 1, 1, "iron-dust", 0.9, 1},
	
	-- Intermediate products	
	{"copper-cable"               , 1, 2, "copper-dust", 0.1, 1},
	{"iron-stick"                 , 1, 2, "iron-dust", 0.1, 1},
	{"iron-gear-wheel"            , 1, 1, "iron-dust", 0.2, 1},
	{"electronic-circuit"         , 1, 1, "iron-dust", 0.1, 1},
	{"automation-science-pack"    , 1, 1, "copper-dust", 0.1, 1},
	{"rp-steam-calculator"  	  , 1, 1, "copper-dust", 0.5, 1},--modded
	
	-- Combat	
	{"pistol"                     , 1, 1, "iron-dust", 0.5, 1, "copper-dust", 0.5, 1},
	{"firearm-magazine"           , 1, 1, "iron-dust", 0.4, 1},
	{"light-armor"                , 1, 1, "iron-dust", 1.0, 4},
	{"radar"                      , 1, 1, "iron-dust", 1.0, 1}
}

for _, recipe in ipairs(recipes) do
	local mainRecipe, prob1, amt1, dust1, dProb1, amt2, dust2, dProb2, amt3 = unpack(recipe)
	if data.raw.recipe[mainRecipe] then
		if data.raw.recipe[mainRecipe].normal then
			data.raw.recipe[mainRecipe].normal.main_product = mainRecipe
			data.raw.recipe[mainRecipe].normal.results = {{name = mainRecipe, probability = prob1, amount = amt1}}
			if dust1 then
				table.insert(data.raw.recipe[mainRecipe].normal.results, {name = dust1, probability = dProb1, amount = amt2})
			end
			if dust2 then
				table.insert(data.raw.recipe[mainRecipe].normal.results, {name = dust2, probability = dProb2, amount = amt3})
			end
		end
		if data.raw.recipe[mainRecipe].expensive then
			data.raw.recipe[mainRecipe].expensive.main_product = mainRecipe
			data.raw.recipe[mainRecipe].expensive.results = {{name = mainRecipe, probability = prob1, amount = amt1}}
			if dust1 then
				table.insert(data.raw.recipe[mainRecipe].expensive.results, {name = dust1, probability = dProb1, amount = amt2})
			end
			if dust2 then
				table.insert(data.raw.recipe[mainRecipe].expensive.results, {name = dust2, probability = dProb2, amount = amt3})
			end
			else
			data.raw.recipe[mainRecipe].main_product = mainRecipe
			data.raw.recipe[mainRecipe].results = {{name = mainRecipe, probability = prob1, amount = amt1}}
			if dust1 then
				table.insert(data.raw.recipe[mainRecipe].results, {name = dust1, probability = dProb1, amount = amt2})
			end
			if dust2 then
				table.insert(data.raw.recipe[mainRecipe].results, {name = dust2, probability = dProb2, amount = amt3})
			end
		end
		else
	end
end
But now, I'd like to try adding the byproducts automatically, based on how much each recipe has of a certain ingredient.
Something like this:

Pistol x5 Iron x5 Copper
table.insert iron byproduct probability = 0.5, amount = 1
table.insert copper byproduct probability = 0.5, amount = 1

Low density structure x20 Copper x2 Steel
table.insert copper byproduct probability = 1, amount = 2
table.insert Steel byproduct probability = 0.2, amount = 1

Please help, I'm a total noob at programming and modding. Thanks.
I don't understand very well what you mean, I think my solution above will help, since I think it is what you are asking for.

Post Reply

Return to “Modding help”