Allow settings that present multiple input fields based on prototypes and other data
Posted: Tue Sep 06, 2022 7:47 pm
About once a week I see someone on the Discord in #mod-making asking how to make a dynamic group of settings based on some game data, such as one setting for each turret or tile. Sadly we have to shoot them down every time, and a few folks end up implementing something like a string setting with a delimited list of key value pairs. This post is my proposal for how the game might support such settings, with minimal fundamental changes:
1. Add a new setting property identifying the setting as being an enumerable type. The value of such a setting is of type {{string,setting-type}, ...}
2. Add a new data.raw category for enumerable setting "keys"
3. Modify the settings UI to present enumerable types as multiple inputs, based on the single setting and multiple entries in the enumerable-settings data for that setting.
Here's an example mod using such functionality to set the sizes of containers:
Here's a walk through how using this mod might look to the user and the engine:
1. Install the mod and restart the game
2. Settings stage. A setting is created named container-size. As a new enumerable setting its value is stored as an empty table.
3. Data stage. No containers are modified. A list of non-hidden containers is created and stored in data.raw["enumerable-settings"]["container-size"].
4. Go to the mod settings menu, startup tab. You'll be presented with not one but three input fields. I am fuzzy on how localization can be accomplished for the labels of the fields. Each input field will have been initialized to the setting's default value of -1, and can be interacted with the same way you would interact with three separate int-type settings.
5. Change one of the values and restart the game. Suppose we changed the value for steel chest to 100. Now the value of the setting has been serialized and stored as {{"iron-chest",-1},{"steel-chest",100},{"wooden-chest",-1}}.
6. Settings stage. A setting is created named container-size. The value of that setting is loaded and validated against the type {{string,int},...}. The int values are validated against the min and max for the setting. The string values are not validated.
7. Data stage. The steel chest entity has its inventory size changed. The same list of non-hidden containers from before is created and stored.
8. You'll see the mod settings menu the same way you left it, with values for the three apparent settings (which are really one setting) as -1, 100, -1.
9. Load a mod that removes steel-chest from the game (or unload the mod that provides it, in the hypothetical where it's not a vanilla entity). Restart the game.
10. Settings stage. Same as #6. This stage has no idea what the string value "steel-chest" means and doesn't care.
11. Data stage. No containers are modified, because "steel-chest" doesn't match the name of an entity. The {"steel-chest",100} entry is removed from the setting table value (this is optional; some mods might want to keep "defunct" settings around). The now shorter list of non-hidden containers is created and stored.
12. The mod settings menu will now show just two settings for container size.
1. Add a new setting property identifying the setting as being an enumerable type. The value of such a setting is of type {{string,setting-type}, ...}
2. Add a new data.raw category for enumerable setting "keys"
3. Modify the settings UI to present enumerable types as multiple inputs, based on the single setting and multiple entries in the enumerable-settings data for that setting.
Here's an example mod using such functionality to set the sizes of containers:
Code: Select all
-- settings.lua
data:extend({{
type = "int-setting",
name = "container-size",
setting_type = "startup",
default_value = -1,
minimum_value = -1,
maximum_value = 512,
enumerable = true
}})
-- data.lua
for index,entry in pairs(settings.startup["container-size"].value) do
if data.raw["container"][entry[1]] then
if entry[2] > -1 then
data.raw["container"][entry[1]].inventory_size = entry[2]
end
else
table.remove(settings.startup["container-size"].value, index)
end
end
-- data-final-fixes.lua
local container_ids = {}
for id,entity in pairs(data.raw["container"]) do
local keep = true
if entity.flags then
for _,flag in ipairs(entity.flags)
if flag == "hidden" then
keep = false
break
end
end
end
if keep then table.insert(container_ids, id) end
end
data.raw["enumerable-settings"]["container-size"] = container_ids
1. Install the mod and restart the game
2. Settings stage. A setting is created named container-size. As a new enumerable setting its value is stored as an empty table.
3. Data stage. No containers are modified. A list of non-hidden containers is created and stored in data.raw["enumerable-settings"]["container-size"].
4. Go to the mod settings menu, startup tab. You'll be presented with not one but three input fields. I am fuzzy on how localization can be accomplished for the labels of the fields. Each input field will have been initialized to the setting's default value of -1, and can be interacted with the same way you would interact with three separate int-type settings.
5. Change one of the values and restart the game. Suppose we changed the value for steel chest to 100. Now the value of the setting has been serialized and stored as {{"iron-chest",-1},{"steel-chest",100},{"wooden-chest",-1}}.
6. Settings stage. A setting is created named container-size. The value of that setting is loaded and validated against the type {{string,int},...}. The int values are validated against the min and max for the setting. The string values are not validated.
7. Data stage. The steel chest entity has its inventory size changed. The same list of non-hidden containers from before is created and stored.
8. You'll see the mod settings menu the same way you left it, with values for the three apparent settings (which are really one setting) as -1, 100, -1.
9. Load a mod that removes steel-chest from the game (or unload the mod that provides it, in the hypothetical where it's not a vanilla entity). Restart the game.
10. Settings stage. Same as #6. This stage has no idea what the string value "steel-chest" means and doesn't care.
11. Data stage. No containers are modified, because "steel-chest" doesn't match the name of an entity. The {"steel-chest",100} entry is removed from the setting table value (this is optional; some mods might want to keep "defunct" settings around). The now shorter list of non-hidden containers is created and stored.
12. The mod settings menu will now show just two settings for container size.