RSL is deprecated and no longer maintained. It is replaced by RSE : https://mods.factorio.com/mod/runtime-spoil https://github.com/SirPuck/runtime-spoilage-engine RSL allows you to set random and/or conditional spoilage results dependent on things accessible at runtime (like temperature, surface, surface conditions, speed, whatever). Please reach out to me on Discord or Github if you encounter issues.
Lua libraries for use by other mods and submods that are parts of a larger mod.
Hey, I thought about optionally using your mod to replace robot items with their qualitized counterparts in more-quality-scaling, and I thought it would be much more convenient (and probably a bit more efficient) if, instead of using the "condition function" and the conditional map, you could simply define a "spoil target function" that maps a spoiling item to the name it should spoil into - for my use case, something like return item.quality.."-"..item.name (instead of having to construct a conditional table of all the quality variants for each entity).
But would RSL even work when I could only define the mappings in data-final-fixes, or would I need to move parts of my code to data-updates? I saw that you process the mod-data in your own data-final-fixes, so depending on 3rd party dependencies RSL might load before MQS without a hidden dependency from RSL on MQS...
Hello, did you read the doc ? Did you search for the word "quality" in the readme ?
1) I did see "quality cycling", but that's something separate from letting each quality spoil into a different prototype while keeping the quality intact. Using conditional functions would technically work, but not elegantly.
2) the readme mentions putting the RSL code into data.lua, but that wouldn't be possible for my mod (it needs to do the copies in final-fixes, and could initialize the spoilage replacement tables in data-updates at the earliest)
Alright then.
The custom functions are pretty efficient. They are parsed and loaded once, then, they act just as regular LUA code. So if performance was your concern, it's not really an issue.
You could make a generator function, but I understand it could be a hassle.
Why do you need to do your things in final fixes ?
Now two things :
Either you can explain to me what is your end goal, (write a real spec I can understand eaily) and I can think of an implementation.
Either you can take a look at the repo, and make a pull request.
RSL is open source, and I consider every contribution.
cheers
I create copies of entities with different parameters for each quality - to work with modded entities, I need to make those copies as late as possible, so changes are copied as well.
I could set up the RSL initialization as soon as all modded entities are known (this should be the case in data-updates), but would have to be very careful in case some other mods adds or deletes new robot types between my data-updates and my data-final-fixes.
So, for the first part: I'd like to be able to specify a "result_function" that, if present, is executed to get the result the item should spoil into, instead of using conditional/weighted/etc. selection functions. Ie.:
function selection_funcs.functional(rsl_definition, event)
return functions[rsk_definitions.functionID](event)
end
with the function being loaded similarly to condition_check_functions.
For the timing issue: I think the best solution would be to make the make_rsl_definition function public, so any mod wanting to add definitions in the data-final-fixes stage could simply check for its existence and call it manually if it exists (otherwise, RSL will do so automatically when its data-final-fixes runs).
So something like runtime_spoilage_library = {["run_rsl_definition"]=run_rsl_definition} at the end of data-final-fixes.lua.
Isn't your need already fulfilled by the setting "deterministic" ?
Aside from the data final fixes question.
It should work, but for every robot, I'd have to at least create a different results table. But since this is really just a question of what you consider more elegant, this part probably isn't essential.
I just thought that in general, a universal "result function" selector would be more convenient than the "condition function" formalism, especially when that way I could use the same function for every replacement (if the original item name was inferrable from the passed event data).
In any case, whether you make the tables by hand, use a generator, place the complexity inside RSL or in your mod, the tables will have to be made and stored at some point.
RSL is built this way to favor performance. Everything is made to accomodate the constraints of runtime, and have things go as fast as possible, eliminating every possible branch as soon as possible.
I will reflect on a way to provide a more user friendly interface.
I guess I could provide different smaller forms instead of one big form witha lot of parameters.
Before, you had to "require" RSL, and import the runtime registration functions yourself. But it was simply not convenient and too hard for people to really use. I could split RSL into two parts : Runtime Spoilage Engine, and Runtime Spoilage Library, such as people would use either one, the other, or both, but it will require some work and I make no commitment towards when this will be done.
Why would there have to be tables?
RSL could simply call the selection function, it returns {name="uncommon-construction-robot"} and RSL then just creates that item in the correct place, without the possible results first being specified in some table.
Or do you do something specific so machines can have indirect spoilage of their product in their spoilage inventory?
Consider the smallest possible (very hacky) way to implement this "functional" interface: replace deterministic and check_condition with the following:
local function check_condition(rsl_definition, event)
local condition_check_func = condition_check_functions[rsl_definition.condition_checker_func_name]
return condition_check_func(event) -- removed the tostring() here
end
---@param rsl_definition RtRslDefinition
---@param event EventData.on_script_trigger_effect
function selection_funcs.deterministic(rsl_definition, event)
local result, override = check_condition(rsl_definition, event)
if override then return result end
return rsl_definition.possible_results[tostring(result)] -- added the tostring() here to keep existing functionality
end
This isn't the best way to implement this, but this would keep existing functionality intact and allow you to spoil into arbitrary items by using a function like function(e) return e.item_name.."-"..e.quality, true end.
Add me on discord to discuss this further, I'm having a hard time understanding your exact need.
Cuz right now, I don't get how this :
local function make_rsl_registration(original_name, result)
data:extend {
{
type = "mod-data",
name = "john",
data_type = "rsl_registration",
data = {
data_raw_table = "item",
loop_spoil_safe_mode = true,
original_item_name = original_name,
conditional = false,
random = false,
quality_change = false,
deterministic_result = { name = result }
}
}
}
end
Doesn't solve what you are trying to do.
Hum I think I get it.
What you have is items with quality that doesn't have inherent quality effects.
So you create items with a "fake" quality.
Which means you need to detect what the quality of the spoiling item is in order to pick the result.
For instance :
{name = "iron-plate", quality = "rare"} --> spoil --> {name = "iron-plate-rare", quality = "rare"}
Right ?
I gave some tought to it.
RSL could really be improved regarding the documentation.
Here is what you want, I guess :
local function make_rsl_registration(item_name)
local results = {}
for _, quality_name in pairs(data.raw.quality) do
results[quality_name] = {name = item_name .. quality_name, quality = quality_name}
end
data:extend {
{
type = "mod-data",
name = "john",
data_type = "rsl_registration",
data = {
data_raw_table = "item",
loop_spoil_safe_mode = true,
original_item_name = item_name,
conditional = true,
random = false,
quality_cycling = false,
condition_checker_func_name = "get_quality",
condition_checker_func = [[
function(event)
return event.quality
end
]],
conditional_results = results
}
}
}
end
I also exposed the registration function so you can call it yourself in final fixes.
local rsl = require("__runtime-spoilage-library__/data-final-fixes")
rsl.make_rsl_registration(mod_data_def)
For instance :
{name = "iron-plate", quality = "rare"} --> spoil --> {name = "iron-plate-rare", quality = "rare"}
Right ?
Exactly.
Here is what you want, I guess
yea, that's what I'd do with the current system. I still think that having just a function instead of a function and a dictionary would be more elegant (and save you a table lookup on each call), but if you dislike that approach, it's not a significant issue.
I also exposed the registration function so you can call it yourself in final fixes.
I think that this way of exposing the function would cause issues: if my mod happens to load first, the registration for my definitions would get called twice - so I'd have to hack together a way of testing the actual load order to prevent that (ie. looking for side effects that probably won't change in another version of RSL). I'm also not sure how require works with files that have side effects (like running the registrations in your case) - if I require your data-final-fixes before your mod loads, would it then later run again, or skip it? In the former case, you'd also have double registrations, in the latter, the actual registration would run earlier than some third mod might be expecting.
Since afair all mods share the same environment during the data stage, simply leaving a uniquely named global function after running the registration would allow me to do if rsl_global_registration_function then rsl_global_registration_function(...) end and guarantee exactly one registration per definition.
Unless you are sure that require works for this use case, I'd rather go the global function route...
A table lookup is O(1). It's better than any on the fly logic, that's why RSL uses this approach. Yes the name of parameters is a bit broad, but it covers almost every use case you can think of. Storing a small amount of tables in memory isnt an issue.
There is no double registration.
It simply works.
You can test it by making rsl registrations in data.lua and then calling the func directly using require.in final fixes.
And if you want to be sure your mod data wont be caught in the first wave, when you pass it to the func directly by calling it, you don't have to type it "rsl_registration". The data type does not matter then.
But you are right, i should make sure there is no side effect.
A table lookup is O(1). It's better than any on the fly logic
A function achieving the same effect can also be O(1), so time complexity isn't really a factor here.
And while I agree that using table lookups over function calls where it's either/or is probably the right choice, if you are already running a function anyway (deterministic/condition code paths), there is generally* no benefit in enforcing a table lookup on top.
You can certainly argue that it makes no practical difference, but saying your way is better is just incorrect.
Additionally, there are certainly edge cases where your way is actually worse: eg. if you wanted to make an item spoil into the first item in the container it resides in (eg. some "universal nanomachines"). The only way to achieve this your way would be a huge identity map containing all item names, while a function could simply return ((source_entity.get_inventory(defines.inventory.chest) or {})[1] or {}).name or "universal-nanomachines".
* the exception in this specific case would be being worried about the construction time of long tables, which afaik the pload-function would have no way to manually cache between calls (if that wasn't done by the interpreter) - but even that could be easily prevented, eg. by passing rsl_definition as a 2nd parameter after event, so if using a static lookup table was actually optimal, the function could just do so itself
On a more practical note: I tried to implement the quality spoiling with the current system, but items either disappear or stay in the safety loop when spoiling.
When the item spoils while in a chest, it's just gone (after triggering the condition checker function once). If it spoils while in my inventory, it stays in its loop and continues to trigger the condition checker function.
Could you check if I'm doing something wrong, or if this is a general bug with RSL and 2.1?
registration:
local results = {normal={name=iname}}
for qname, _ in pairs(qualities) do
results[qname] = {name=qname.."-"..iname, quality=qname}
end
local registration_data = {
original_item_type = "item",
original_item_name = item.name,
loop_spoil_safe_mode = true,
random = false,
conditional = true,
conditional_results = results,
condition_checker_func_name = item.name,
condition_checker_func = [[function(e) game.print(e.quality);return e.quality end]]
}
RSL_packageAndRegister(registration_data, rsl)
registered with
local function RSL_packageAndRegister(entry, rsl)
local mod_data_def = {type="mod-data",name=entry.original_item_name,data_type="rsl_registration_manual",data=entry}
data:extend({mod_data_def}) -- do I actually need to put the registration into data.raw? seems like the definition makes a deepcopy anyway...
rsl.make_rsl_definition(mod_data_def)
end
It's definitely at least one bug in RSL.
selection.lua:59 is incompatible with your example above. Either I need to do result={{name=x}}, or it should actually be return rsl_definition.possible_results[check_condition(rsl_definition, event)] (or to be compatible with either style, return rsl_definition.possible_results[check_condition(rsl_definition, event)][1] or rsl_definition.possible_results[check_condition(rsl_definition, event)]).
When correcting for that, in a chest the replacement works as expected, but in the character inventory, it keeps looping the placeholder item. I haven't looked into that, since the my goal with using RSL was to not have to deal with all these special cases with runtime scripting :)
Hello !
I thought I had solved this bug in the latest version, but anyways, I just deprecated RSL.
Check this : https://mods.factorio.com/mod/runtime-spoilage-engine
This should handle your needs way better.
The repo is public once again, so feel free to check it. I expose the registration function too.
Also, from my testing, registering a mod-data with the same name twice doesn't pose an issue.