Custom prototypes for inter-mod expansion
Posted: Thu Aug 09, 2018 7:19 pm
I would like to be able to define custom prototype types which can then be used by other mods to expand my mod.
Eg:
I make a mod that adds a way to go to other planets. My mod adds a few basic planets, but players want more.
Another mod is created which adds several new planets, and modifies the ones in the base mod.
Somewhere in my mod, I parse through all the planet prototypes, and populate my own data structures with them.
Usage:
Perhaps a more formal system could be put into place, where a mod can register a custom prototype type mapped onto a function (or script) that gets called with each prototype. This would allow two things:
1. Prevent other mods from reacting for prototypes created for a specific mod (probably throwing an error if two mods try to register to the same custom prototype type). This could cause an issue where one of the two mods could have to rename their prototype type, which would then also need to be updated in any dependency mods. Perhaps a better solution would be to specify the mod name that each prototype is to be used for?
2. Allow access to the custom prototypes in the data phase, after data-final-fixes. This way, the base mod could create additional (regular) prototypes that are required for each of the custom prototypes. Eg. in my planet mod, I might want to create a new sprite for each planet that has the correct color tinting. This would probably require notably more work to implement though, as it would add an additional stage to loading mods.
The reason I went for a function instead of a read-only dictionary is to further prevent other mods from accessing data that my mod defines. A function that takes the prototype type as a string means that mods can't parse over every single prototype type, which would most likely be done following some bad practice.
I proposed the idea to Rseding in the discord. His main concern was that people could include data.raw (or any prototype) into one of the custom prototypes, then in the control phase access it and assume it is accurate, when in reality the values could have been modified by another mod.
I don't think this will be a serious issue, as all the prototypes are available in the control phase anyway via game.X_prototypes, and if anyone were to do it, they would eventually run into an error that would be fairly easy to track down.
If deemed that it is problem, a slightly severe solution could be to disallow populating values with tables. This initially sounds harsh, but modders could use other custom prototypes to store nested data. In the above example, terrain_generation could instead be the name of a custom "planet_generation" prototype that defines the behaviour desired. Because of this, it wouldn't completely solve the issue either, but it would complicate the steps enough that modders would question what they are doing.
The potential uses for inter-mod interaction are numerous. Mods that previously had to rely on checking if dependent mods existed to add features from that mod into their own can now just rely on the other mod adding the required custom prototypes.
becomes
Eg:
I make a mod that adds a way to go to other planets. My mod adds a few basic planets, but players want more.
Another mod is created which adds several new planets, and modifies the ones in the base mod.
Somewhere in my mod, I parse through all the planet prototypes, and populate my own data structures with them.
Usage:
Code: Select all
data:extend(
{
{
type = "custom",
custom_type = "planet",
name = "Mars",
mass = 1000,
radius = 5,
terrain_generation = { ... },
--Alternatively, the custom fields could be within a table stored in custom_data, if that is easier for implementation.
custom_data =
{
mass = 1000,
etc
},
}
})
Code: Select all
for planet_prototype in game.get_custom_prototypes("planet") do
make_planet(planet_prototype)
end
1. Prevent other mods from reacting for prototypes created for a specific mod (probably throwing an error if two mods try to register to the same custom prototype type). This could cause an issue where one of the two mods could have to rename their prototype type, which would then also need to be updated in any dependency mods. Perhaps a better solution would be to specify the mod name that each prototype is to be used for?
2. Allow access to the custom prototypes in the data phase, after data-final-fixes. This way, the base mod could create additional (regular) prototypes that are required for each of the custom prototypes. Eg. in my planet mod, I might want to create a new sprite for each planet that has the correct color tinting. This would probably require notably more work to implement though, as it would add an additional stage to loading mods.
The reason I went for a function instead of a read-only dictionary is to further prevent other mods from accessing data that my mod defines. A function that takes the prototype type as a string means that mods can't parse over every single prototype type, which would most likely be done following some bad practice.
I proposed the idea to Rseding in the discord. His main concern was that people could include data.raw (or any prototype) into one of the custom prototypes, then in the control phase access it and assume it is accurate, when in reality the values could have been modified by another mod.
I don't think this will be a serious issue, as all the prototypes are available in the control phase anyway via game.X_prototypes, and if anyone were to do it, they would eventually run into an error that would be fairly easy to track down.
If deemed that it is problem, a slightly severe solution could be to disallow populating values with tables. This initially sounds harsh, but modders could use other custom prototypes to store nested data. In the above example, terrain_generation could instead be the name of a custom "planet_generation" prototype that defines the behaviour desired. Because of this, it wouldn't completely solve the issue either, but it would complicate the steps enough that modders would question what they are doing.
The potential uses for inter-mod interaction are numerous. Mods that previously had to rely on checking if dependent mods existed to add features from that mod into their own can now just rely on the other mod adding the required custom prototypes.
Code: Select all
if mod_exists("dependency_mod") then
add_feature(feature_from_dependency_mod)
end
if mod_exists("dependency_mod_2") then
add_feature(feature_from_dependency_mod_2)
end
Code: Select all
--In base mod:
for feature in game.get_custom_prototypes("feature") do
add_feature(feature)
end
--In dependency mods:
data:extend({{ type = "custom", custom_type = "feature", name = "feature from mod", feature = { ... } }})