Dependencies: Are "optional requirements" possible at all?

Place to get help with not working mods / modding interface.
Pi-C
Smart Inserter
Smart Inserter
Posts: 1647
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by Pi-C »

Adamo wrote: ↑
Tue Jun 11, 2019 12:15 pm
I do this often in my mods. You have to write code to handle it. Make any mods that may or may not appear optional using ?,
I'm afraid you didn't understand the question correctly -- I admit it's phrased in a way that makes it easy to get it wrong. :-)

I was familiar with the basic dependency operators: hard dependency (must be active), optional dependency ("? mod_name" -- mod may or may not be activated), and conflict ("! mod_name" -- having conflicting mods installed together with your own will break stuff). I've also learned about hidden optional dependencies ("(?) mod_name") that are not shown in the mod manager, but provide that mod_name is loaded before your own mod. However, I didn't look for "optional dependencies", but "optional requirements" (a term I used for lack of a better one). What I meant was a hard dependency on one mod from a list of mods or, more generally, using logic operators between dependencies. Could be as easy as "require A and (B or C)", or more complex like "require A and ( (B and not C) or (D and E))". Something like that isn't supported by the game yet, unfortunately.
Here is a piece of code I have for a chemistry mod. It optionally requires a number of mods that I know add chemistry recipes to the game. It doesn't even ask about particular mods, although you could do that with the api, ask for them by name, instead, this code just trolls the recipes, whatever is available from any mod, and copies the recipes into new recipes that work with my chemistry buildings to make the same things.

Code: Select all

require("util")

function add_recipe_to_tech(tech_name,recipe_name)
	if data.raw["technology"][tech_name] then
		table.insert(data.raw["technology"][tech_name].effects, {
			type="unlock-recipe",
			recipe=recipe_name
		})
	end
end
Interesting, I also use table.insert, and it works although I don't have require("util"). I'd assumed that was more or less a standard class that would always be there. Also, I haven't found any documentation in the modding wiki on these things.

Code: Select all

if ingredient.type ~= "fluid" then
	solid_input_count = solid_input_count + 1
end
That seemed so wrong! Then I realized that "~=" means "is not equal" instead of "matches string". :-)
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

Qon
Smart Inserter
Smart Inserter
Posts: 2119
Joined: Thu Mar 17, 2016 6:27 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by Qon »

eradicator wrote: ↑
Tue Jun 11, 2019 10:44 am
Normalize recipes into a non-ambigious form obviously. If you think that's only a few lines of code you haven't really looked at all the stuff that recipes can contain.
That sounds like 10 times too many lines though. I looked at the Recipe documentation. There's several things to Qonsider but nothing that requires hundreds of lines of code.

Library code. 7 lines, but not specific to normalization or recipes. A real language would come with these included:

Code: Select all

function has_value(tab, val) for _, v in ipairs(tab) do if v == val then return true end end end
function B(f, g) return function(a) return f(g(a)) end end --[[Bluebird ( . )--]]
function map(a, f)
    local r = {}
    for i, c in ipairs(a) do table.insert(r, f(c, i, a)) end
    return r
end
27 lines for the recipe normalization:

Code: Select all

function typeFromName(name) return (data.raw.fluid[name] and 'fluid' or 'item') end
function norm(keys) return function(item)
    local n = {}
    for k, v in pairs(keys) do if string.find(k, '^%d+$') then n[v] = item[v] or item[k] elseif not n[k] then n[k] = v end end
    for k, v in pairs(item) do if not string.find(k, '^%d+$') then n[k] = v end end
    return n
end end
function rdata(recipe)
    if not recipe then return nil end
    local r = {}
    local b = B(norm{'name', 'count', count = 1}, function(item) item.type = typeFromName(item.name) return item end)
    r.results = map(recipe.results or ({{recipe.result, recipe.result_count}}), b)
    r.ingredients = map(recipe.ingredients, b)
    return r
end
local general_data = {'category', 'subgroup', 'crafting_machine_tint', 'type', 'name', 'order', 'localised_name', 'localised_description',}
function normalize_recipe(recipe)
    local r = {}
    recipe = table.deepcopy(recipe)
    r.normal = rdata(recipe.normal)
    r.expensive = rdata(recipe.expensive) or table.deepcopy(r.normal)
    for k, v in pairs(recipe) do if has_value(general_data, k) then r[k] = v end end
    if not r.normal and not r.expensive then r.expensive = rdata(recipe) end
    r.normal = r.normal or table.deepcopy(r.expensive)
    r.icons = (recipe.icon or recipe.icons) and map(recipe.icons or {{recipe.icon, recipe.icon_size}}, norm{'icon', 'icon_size', icon_size = 32}) or nil
    return r
end
I've only done some minor testing so might Qontain some bug. Just threw it together to be able to CLoC it. Some places might benefit from extra newlines to make it readable since Lua has ugly bloated syntax. But I didn't choose Lua so I'm not going to take responsibility for the mistakes of others. I could make it longer but I don't think I could make it hundreds of lines even if I tried :?
Maybe I misunderstood what you meant by normalization?

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by eradicator »

Personal discussion with Qon
Edit: Added spoiler to personal discussion to improve future thread readability.
Last edited by eradicator on Wed Jun 12, 2019 2:20 pm, edited 1 time in total.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: ζ—₯本θͺž, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.

Qon
Smart Inserter
Smart Inserter
Posts: 2119
Joined: Thu Mar 17, 2016 6:27 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by Qon »

eradicator wrote: ↑
Tue Jun 11, 2019 6:39 pm
Your coding style is pretty difficult to read with all those unnamed variables. If you add proper indentation and named variables to that you are already at 100+ lines.
"proper indendation and line breaks" still don't get it above 50 lines. Longer variable names don't increase line count. And the 1 char variables are loop variables or just temp storage for the return value, so it's standard to not give them pointless names.
eradicator wrote: ↑
Tue Jun 11, 2019 6:39 pm
If you make it readable for "normal people" probably more.
Any professional programmer will understand the simple functional style. Without a formal introduction, it is probably not known to many amateurs. But code understandability and maintainability are judged by professionals. Doesn't make sense to make it understandable to those that don't want to learn more advanced Qoncepts. It might be possible to make almost anything with just if and for but it would be silly to do so. Some topics are irreducibly complex and need higher abstraction to be manageable. But Bluebird and Map are defined for those that are unfamiliar and they are just a handful of simple lines so for those that want to learn how they work, just read them :)
eradicator wrote: ↑
Tue Jun 11, 2019 6:39 pm
Also pretty sure you're treating normal/expensive=false wrongly.
Yeah, oops. Well, it wouldn't make the code that much longer to handle that.

Adamo
Filter Inserter
Filter Inserter
Posts: 479
Joined: Sat May 24, 2014 7:00 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by Adamo »

I did understand. I'm making the case to you that it is equivalent to simply require them both as optional dependencies and then write the code to determine which ones popped and prioritize the data as you see fit.
Pi-C wrote: ↑
Tue Jun 11, 2019 1:45 pm
Adamo wrote: ↑
Tue Jun 11, 2019 12:15 pm
I do this often in my mods. You have to write code to handle it. Make any mods that may or may not appear optional using ?,
I'm afraid you didn't understand the question correctly -- I admit it's phrased in a way that makes it easy to get it wrong. :-)

I was familiar with the basic dependency operators: hard dependency (must be active), optional dependency ("? mod_name" -- mod may or may not be activated), and conflict ("! mod_name" -- having conflicting mods installed together with your own will break stuff). I've also learned about hidden optional dependencies ("(?) mod_name") that are not shown in the mod manager, but provide that mod_name is loaded before your own mod. However, I didn't look for "optional dependencies", but "optional requirements" (a term I used for lack of a better one). What I meant was a hard dependency on one mod from a list of mods or, more generally, using logic operators between dependencies. Could be as easy as "require A and (B or C)", or more complex like "require A and ( (B and not C) or (D and E))". Something like that isn't supported by the game yet, unfortunately.
Here is a piece of code I have for a chemistry mod. It optionally requires a number of mods that I know add chemistry recipes to the game. It doesn't even ask about particular mods, although you could do that with the api, ask for them by name, instead, this code just trolls the recipes, whatever is available from any mod, and copies the recipes into new recipes that work with my chemistry buildings to make the same things.

Code: Select all

require("util")

function add_recipe_to_tech(tech_name,recipe_name)
	if data.raw["technology"][tech_name] then
		table.insert(data.raw["technology"][tech_name].effects, {
			type="unlock-recipe",
			recipe=recipe_name
		})
	end
end
Interesting, I also use table.insert, and it works although I don't have require("util"). I'd assumed that was more or less a standard class that would always be there. Also, I haven't found any documentation in the modding wiki on these things.

Code: Select all

if ingredient.type ~= "fluid" then
	solid_input_count = solid_input_count + 1
end
That seemed so wrong! Then I realized that "~=" means "is not equal" instead of "matches string". :-)

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by eradicator »

Personal discussion with Qon
Edit: Added spoiler to personal discussion to improve future thread readability.
Last edited by eradicator on Wed Jun 12, 2019 2:19 pm, edited 1 time in total.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: ζ—₯本θͺž, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.

Qon
Smart Inserter
Smart Inserter
Posts: 2119
Joined: Thu Mar 17, 2016 6:27 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by Qon »

You said normalization was hundreds of lines. It was off by an order of magnitude of how I would solve it so I was confused if normalization was all that was going on in those lines. Turns out you did some other things also, like changing defaults and restructuring to your own format. But you said my answer was confusing and 100+ lines with "proper indentation". Which I disagreed with. And if there's something that's confusing about the code I can explain it if you want, just tell me which part :)
eradicator wrote: ↑
Tue Jun 11, 2019 7:58 pm
I am at this point unsure why you even bothered to write that "answer".
I'm responding to what you wrote, why is it an "answer" instead of an answer? Why bother writing anything at all? Isn't that why we are on a forum, to write and read? If you don't want to do that then... don't? ;)

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by eradicator »

Personal discussion with Qon
Edit: Added spoiler to personal discussion to improve future thread readability.

If you're actually interested in creating a high-quality standard recipe normalizer...

After proper indentation and naming of all the variables i'm at 85 lines for your code (Based on the common community style guide i percieve exists by having looked at a great many other mods, plus my preference for python-style in-block endings which do not affect line count. I also cut out "typeFromName" because it's used only once). And given the fact that my "200+ lines estimate" was meant as an eyeball estimate for intermediate hobby programmers this is hardly "off by an order of magnitude".

Code: Select all

function promise(f2,f1)
  return function(arg)
    return f2( f1(arg) )
    end
  end

function map (tableA,f)
  local new = {}
  for index, value in ipairs(tableA) do
    table.insert(new, f(value, index, tableA))
    end
  return new
  end
  
function reformat (item) --no good name for this
  local new = {}
  for k,v in pairs(item_defaults)       do
    if string.find(k, '^%d+$') then --if type(k) == 'number' then
      new[v] = item[v] or item[k]
    elseif (not new[k]) then
      new[k] = v
      end
    end
  for k, v in pairs(item) do
    if not string.find(k, '^%d+$') then
      new[k] = v
      end
    end
  return new
  end

function add_type(item)
  item.type = data.raw.fluid[item.name] and 'fluid' or 'item'
  return item
  end
  
local non_difficulty_data = {
  'name'                  = true,
  'type'                  = true,
  'order'                 = true,
  'localised_name'        = true,
  'localised_description' = true,
  'crafting_machine_tint' = true,
  'category'              = true,
  'subgroup'              = true,
  }
  
function difficulty_data(difficulty)
  if (difficulty == nil) then return end
  local new = {}
  local normalize_and_type = promise(reformat({'name', 'count', count = 1}), add_type)
  new.results = map(
    difficulty.results or {{difficulty.result, difficulty.result_count}},
    normalize_and_type
    )
  new.ingredients = map(
    recipe.ingredients,
    normalize_and_type
    )
  return new
  end

local deepcopy = util.table.deepcopy
function normalize_recipe(old_recipe)
  local new     = {}
  old_recipe    = deepcopy(old_recipe)
  new.normal    = difficulty_data(recipe.normal   )
  new.expensive = difficulty_data(recipe.expensive) or deepcopy(new.normal)
  for k,v in pairs(old_recipe) do
    if non_difficulty_data[k] then
      new[k] = v
      end
    end
  if not (new.normal or new.expensive) then
    new.expensive = difficulty_data(old_recipe)
    end
  new.normal    = new.normal or deepcopy(new.expensive)
  new.icons     = not (recipe.icon or recipe.icons) and nil or map(
    recipe.icons or {{recipe.icon, recipe.icon_size}},
    reformat{'icon', 'icon_size', icon_size = 32}
    )
  if old_recipe.normal    == false then new.normal   .enabled = false end
  if old_recipe.expensive == false then new.expensive.enabled = false end
  return new
  end
But sure here's some questions:

What is "bluebird"? Google finds exactly one hit, which seems to be a JS library. And it seems to be what you really meant was the general concept of a "promise"?

Why...are you using string.find instead of type()?

And some problems:

typeFromName looks like it's being called onto the item table before ".name" is guaranteed to exist. I.e. the "bluebird" function creation has the wrong order of arguments (i fixed this by changing the constructor to take arguments in execution order instead of reverse order).

rdata() completely ignores anything but results and ingredients. Needs another round of norm()'ing for all possible attributes.

typeFromName needs to do something about the case where there's both an item *and* a fluid of that name.
Last edited by eradicator on Wed Jun 12, 2019 2:19 pm, edited 1 time in total.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: ζ—₯本θͺž, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.

Qon
Smart Inserter
Smart Inserter
Posts: 2119
Joined: Thu Mar 17, 2016 6:27 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by Qon »

eradicator wrote: ↑
Wed Jun 12, 2019 7:01 am
Qon wrote: ↑
Tue Jun 11, 2019 7:23 pm
Any professional programmer will understand the simple functional style. Without a formal introduction, it is probably not known to many amateurs. But code understandability and maintainability are judged by professionals.
Do you not percieve yourself as extremely patronizing and arrogant? To me it looks mostly like you wanted to brag about your own "professionalism". Especially since you're saying it on a forum full of amateur and hobby-coders. (And i say "coders" here on purpose). Your tendency to ignore basic english spelling strengthens that impression to me. Written communication is difficult i know. And i'm half expexting a "it's your fault for not being a native speaker"-type answer. But maybe it's worth some time to think about it.
No. And not my intention. Factorio has many hobby coders but also many professionals in various fields, including programming. It's not really an achievement to brag about. It's not even a very specific accomplishment, it's just a statement of vocation. We all (adults) have our fields of expertise, programming is just a relevant field. You wouldn't say that I'm bragging if I state that I'm an adult, even if there are kids on this forum, right?
And I wasn't sure what was hard to understand about the code so I had to assume that it was in part the more esoteric concepts and not just the linebreaks.

I'm just spelling things with "Qon" for my own amusement. Nothing to take notice of.

I'm not a native English speaker either, so nuance might get lost when a message is translated twice and also in text.
eradicator wrote: ↑
Wed Jun 12, 2019 7:01 am
And given the fact that my "200+ lines estimate" was meant as an eyeball estimate for intermediate hobby programmers this is hardly "off by an order of magnitude".
Yes, in hindsight I realise that your estimate was for hobby programmers. And the line count doubles when a few more needed line breaks are added.
eradicator wrote: ↑
Wed Jun 12, 2019 7:01 am
What is "bluebird"? Google finds exactly one hit, which seems to be a JS library. And it seems to be what you really meant was the general concept of a "promise"?
Bluebird was a comment for myself mostly. It's just a lambda calculus combinator, a language which uses lambda ("anonymous") functions only.
It's the compositional combinator (function composition), written as "." in Haskell. It composes two functions into a new combined function. So not an asynchronous function call wrapper "promise". Though you can compose with those too.

You mentioned your python style preference. I like to use functional programming where it fits. And in languages with proper lambda function syntax it is standard to not split lines between each parameter in you function definition.

Code: Select all

local B = function(f) return function(g) return function(a) return f(g(a)) end end end
Is styled like these:
JS:

Code: Select all

let B = f=>g=>a=> f(g(a))
Haskell:

Code: Select all

B f g a = f (g a)
B = ( . ) -- or just use the built in function
Even though it doesn't make as much sense in Lua as in those languages because of it's awkward syntax. But as I said, I didn't choose Lua and I prefer to make it clear that the functions are going to be curried.
eradicator wrote: ↑
Wed Jun 12, 2019 7:01 am
Why...are you using string.find instead of type()?
I didn't feel like checking how Lua stores table arrays. And I thought I had read that Lua table keys were strings (always) like in JS. Because if so the keys would always be strings. But apparently you can do :

Code: Select all

local a = {}
a[1] = 'x'
a['1'] = 'y'
game.print(serpent.block(a))
But you can't do

Code: Select all

game.table_to_json(a) 
with that table. Now I know. So yeah type() would work fine.
eradicator wrote: ↑
Wed Jun 12, 2019 7:01 am
And some problems:

typeFromName looks like it's being called onto the item table before ".name" is guaranteed to exist. I.e. the "bluebird" function creation has the wrong order of arguments (i fixed this by changing the constructor to take arguments in execution order instead of reverse order).
Oops.
eradicator wrote: ↑
Wed Jun 12, 2019 7:01 am

Code: Select all

function promise(f2,f1)
  return function(arg)
    return f2( f1(arg) )
    end
  end
No. You didn't. :)
And in function composition, the "reverse" order is the correct order. Mathematicians often read from right to left q:
I switched it in
eradicator wrote: ↑
Wed Jun 12, 2019 7:01 am

Code: Select all

function reformat (item) --no good name for this
  local new = {}
  for k,v in pairs(item_defaults)       do
Why rename it if you don't have a good name for it? Reformat is equally good/bad name though I guess. And item_defaults doesn't exist because you removed the second parameter.
I fixed some other bugs you introduced (mostly missed variable renaming) and fixed some bugs I crafted myself earlier. And then styled it to a compromise between the bit excessively compact style (no more for-if combos as single lines) and your style. I don't like python blocks...

Would be 61 non-empty lines if non_difficulty_data was a one-liner.

Code: Select all

function compose(f, g) return function(arg)
    return f(g(arg))
end end

function map(array, f)
    local new = {}
    for i, v in ipairs(array) do
        table.insert(new, f(v, i, array))
    end
    return new
end

function reformat(new_format) return function(item)
    local new = {}
    for k,v in pairs(new_format) do
        if type(k) == 'number' then new[v] = item[v] or item[k]
        elseif (not new[k])    then new[k] = v end
    end
    for k, v in pairs(item) do
        if type(k) ~= 'number' then new[k] = v end
    end
    return new
end end

function add_type(item)
    item.type = data.raw.fluid[item.name] and 'fluid' or 'item'
    return item
end

local non_difficulty_data = {
    name                  = true,
    type                  = true,
    order                 = true,
    localised_name        = true,
    localised_description = true,
    crafting_machine_tint = true,
    category              = true,
    subgroup              = true,
}

function difficulty_data(difficulty)
    if (difficulty == nil) then return end
    local new = {}
    local normalize_and_type = compose(add_type, reformat{'name', 'amount', amount = 1})
    new.results = map(
        difficulty.results or {{difficulty.result, difficulty.result_count}},
        normalize_and_type
    )
    new.ingredients = map(
        difficulty.ingredients,
        normalize_and_type
    )
    return new
end

local deepcopy = util.table.deepcopy
function normalize_recipe(old_recipe)
    local new     = {}
    old_recipe    = deepcopy(old_recipe)
    new.normal    = difficulty_data(old_recipe.normal   )
    new.expensive = difficulty_data(old_recipe.expensive) or deepcopy(new.normal)
    for k,v in pairs(old_recipe) do
        if non_difficulty_data[k] then new[k] = v end
    end
    if not (new.normal or new.expensive) then
        new.expensive = difficulty_data(old_recipe)
    end
    new.normal    = new.normal or deepcopy(new.expensive)
    new.icons     = not (old_recipe.icon or old_recipe.icons) and nil or map(
        old_recipe.icons or {{old_recipe.icon, old_recipe.icon_size}},
        reformat{'icon', 'icon_size', icon_size = 32}
    )
    if old_recipe.normal    == false then new.normal   .enabled = false end
    if old_recipe.expensive == false then new.expensive.enabled = false end
    return new
end

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by eradicator »

For better thread readability for future readers i'll put the non-coding bits of the discussion into a spoiler.
Personal discussion with Qon

So...as for the code. There's still the issue that difficulty_data() strips off everything except .results and .ingredients. And it also needs to handle the special case "not (normal or expensive)" where you shove the whole recipe into it, but have to prevent stuff like .name, .type, etc from bleeding into the difficulty nodes, which reformat() doesn't do. And main_product defaults to nil so i'm not sure how to handle that within your "style". Also i noticed that you can't default "amount=1" because that would conflict with probability based recipes that use amound_min and amount_max instead.

Code: Select all

function enforce_format(new_format) return function(difficulty)
  local new = deepcopy(new_format)
  for k,v in pairs(difficulty) do
    if new[k] ~= nil then
      new[k] = v
      end
    end
  return new
  end
end

local difficulty_defaults = {
  energy_required            = 0.5   ,
  emissions_multiplier       = 1.0   ,
  requester_paste_multiplier = 30    ,
  overload_multiplier        = 0     ,
  enabled                    = true  ,
  hidden                     = false ,
  hide_from_stats            = false ,
  allow_decomposition        = true  ,
  allow_as_intermediate      = true  ,
  allow_intermediates        = true  ,
  always_show_made_in        = false ,
  show_amount_in_title       = true  ,
  always_show_products       = false ,
  main_product               = nil   , --for completeness
  }

function difficulty_data(difficulty)
    if (difficulty == nil) then return end
    
    local new = enforce_format(difficulty_defaults)(difficulty)
    new.main_product = difficulty.main_product --because enforce_format doesn't handle nil defaults

    local normalize_and_type = compose(add_type, reformat{'name', 'amount'})
    new.results = map(
        difficulty.results or {{difficulty.result, difficulty.result_count}},
        normalize_and_type
    )
    new.ingredients = map(
        difficulty.ingredients,
        normalize_and_type
    )    
    return new
end
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: ζ—₯本θͺž, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.

Qon
Smart Inserter
Smart Inserter
Posts: 2119
Joined: Thu Mar 17, 2016 6:27 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by Qon »

For better thread readability for future readers i'll put the non-coding bits of the discussion into a spoiler.
Personal discussion with Eradicator
eradicator wrote: ↑
Wed Jun 12, 2019 2:17 pm
So...as for the code. There's still the issue that difficulty_data() strips off everything except .results and .ingredients. And it also needs to handle the special case "not (normal or expensive)" where you shove the whole recipe into it, but have to prevent stuff like .name, .type, etc from bleeding into the difficulty nodes, which reformat() doesn't do. And main_product defaults to nil so i'm not sure how to handle that within your "style". Also i noticed that you can't default "amount=1" because that would conflict with probability based recipes that use amound_min and amount_max instead.
I'm not sure if normalization should insert defaults. Finding things under the same property names in the same locations every time is useful. But missing properties take the default value so they are still "known" and since it's either there or not defined at all it is easier to manage the case where a property is missing. So currently I just dropped amount = 1.

For which recipe would .name etc end up in the difficulty nodes?

I'll take a look at the other things also later. Nice finds ;)

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Dependencies: Are "optional requirements" possible at all?

Post by eradicator »

@OP i'm sorry for all the derailing. Maybe a friendly mod could split this off into a new "How to normalize a recipe in data stage." thread in modding help.

For better thread readability for future readers i'll put the non-coding bits of the discussion into a spoiler.
Personal discussion with Qon
Qon wrote: ↑
Wed Jun 12, 2019 11:21 pm
I'm not sure if normalization should insert defaults.
It makes it easier to later apply generic multiplication or conditionals without constantly having to check if the value exists at all. Because that kind of existance check is kinda the point of normalizing in the first place. Especially if we supposed that such a normalized recipe might not always be used as a new recipe, but is sometimes merely generated to easily check certain parameters of it to conditionally trigger something else. Hm...it does create the problem that later you can't tell anymore if a recipe explicitly defind something or if it's merely a default. So i'd say there's a usecase for both.
Qon wrote: ↑
Wed Jun 12, 2019 11:21 pm
For which recipe would .name etc end up in the difficulty nodes?
For the not(normal or expensive) case *if* one used a generic reformat() on the whole recipe.
In your current version it doesn't happen becaues difficulty_data() completely strips off all flags. I.e. the output is incomplete.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: ζ—₯本θͺž, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.

Post Reply

Return to β€œModding help”