Wrapping minetest.register_*() Functions w/ other Functions

User avatar
SegFault22
Member
 
Posts: 870
Joined: Mon May 21, 2012 03:17

Wrapping minetest.register_*() Functions w/ other Functions

by SegFault22 » Sun Oct 30, 2016 08:41

You can wrap the minetest.register_*() Functions with other Functions inside a Mod. I'll let you decide if this is useful for your Needs.

What is really cool about this, is that if the Mod Name passed to the minetest.register_*() Function in the item/node/tool/whatever id, is gotten from a global Variable "modname", and another Mod (depending on the first Mod) sets the Value of the same global Variable to its own Mod Name, the item/whatever is registered with the Mod Name of the second Mod! Here's some Code to define exactly (pretty much) how this is done, in the most simple Implementation:

Your phone or window isn't wide enough to display the code box. If it's a phone, try rotating it to landscape mode.
Code: Select all
-- First Mod
modname = "test"

test = {}

function test.create_item(number)
   minetest.register_craftitem(modname..":testitem_"..number, {
      description = "Test Item",
      inventory_image = modname.."_testitem.png",
   })
end

do
   test.create_item(1)
end

Your phone or window isn't wide enough to display the code box. If it's a phone, try rotating it to landscape mode.
Code: Select all
-- Second Mod
modname = "testext"

do
   test.create_item(2)
end


Note that the second Mod depends on the first Mod, in order for this to work.

When the first Mod runs, it first sets its "modid" global Variable to "test" and creates its global Mod Stuff Table. Then, it creates a Function "test.create_item()" wrapping the minetest.register_craftitem() Function, taking a single Variable "number" which is concatenated to the end of the id String. Finally, it invokes the Function it just created, passing the Number "1" as the Parameter, which registers an Item with the id "test:testitem_1".

When the second Mod runs, it sets the "modid" global Variable to "testext" (means "test extended"), and calls the Function "test.create_item()" passing the Number "2" as the Parameter. This registers an Item with the id "testext:testitem_2".

This means that anyone can create a Mod which does some really cool magic Tricks with the register Functions, by wrapping said Functions inside other Functions, which use their Parameters to calculate the Parameters passed to the wrapped register Functions - this is useful to control many registry Entries from one Variable (change the digging Times, total Uses, Level or whatever Parameters of several Tools, by a single Variable which is used to calculate said Parameters).

This also means that another Mod calling the wrapping Functions from the first Mod, if configured properly, will have all of its registered Stuff given its Mod Name, instead of the first Mod which created those Functions.

However, you cannot call those wrapping Functions from another Mod, and have them register Stuff with the Mod Name of the first Mod - if you try this, you get the wonderful "does not follow naming conventions" error and the game-world doesn't load.

The reason this works, is due to how Mods are loaded. I'm not totally sure, but it goes somewhat like this: The first Mod gets the Name of its init.lua File set as the valid "internal" Mod Name, which must be written as the Mod Name within all Item/Tool/whatever ids which are registered while that Mod is loading. When that Mod finishes loading, and Minetest moves on to loading the Mod which depends on it and uses its Functions, the valid "internal" Mod Name is set to the Name of its init.lua file (of course), and this applies to all registry Entries (except id-less Stuff such as crafting Recipes and whatnot), even those which are performed by Functions that were created by other Mods which have already loaded.

Before I realized this, I thought there could be a Problem, as I thought that all of the ids of Stuff registered by my Mod's wrapping Functions must include the Mod Name of the mod which created those Functions, since they are part of Files within the Folder of that Mod, which are loaded by the Mod's init.lua File. This would be extremely bad, since it would require that all Textures for registered Items be put into the same Textures Folder, belonging to the first Mod which creates the wrapping Functions - if another Mod tries to supply its own Textures for any Stuff registered via the first Mod's Functions, they will not be used, and the Stuff would get those beautiful randomly-generated placeholder Textures instead.

I was thinking that I would have to totally redesign my Mod, with it becoming something very complicated in order to register Stuff with the right Mod Name - but now I know that it is very simple and straightforward, and the work I have to do to make my Mod work properly is much less that what it would be if the System didn't work this way. Now that I think about it, it should have been obvious that it would work this way - but I was not sure because the Functions were created within that first Mod, and I thought the Mod Name "context" would be preserved somehow.

However, after trying this simple test, I have found that is not what happens, and I don't have to worry about this problem. I just have to create a global Variable "modname" or something like that, which is used by all of the wrapping Functions that need to compute the id for different Stuff, and put some information in the Mod's API "Manual" to tell Developers that they must set this global Variable to the proper Name of their Mod, in order for their Stuff to be registered properly.

I will not have to totally redesign my Mod, and it will be released sometime soon, after I finish some other simple Stuff - such as Ore Nodes for Types of Stone which have a different top/bottom Texture (slate or gneiss Ores - imagine that it becomes a little bit more difficult to find Ores in Gneiss, because the "banded vein" Textures only appear along the Foliations or "Layers" at the sides, and not on the Top which is a single flat Foliation/Layer), Tools, crafting Recipes (integrated with the Crafter mod, to allow creating different processing Types than the standard ones), Ore Generation with many available Patterns (most of the built-in ones except for "vein", and maybe a few custom ones), maybe work on the Groups System some more so that it is easier to assign Dig Groups and Dig Group Times, make very many pretty Textures for all of the default Stuff (Stuff which is added by my Mod by default, not Stuff which is added by the mod Default), then make a couple of simple Mods as Examples of how to use it to do cool Stuff, and finally release it to everyone.
 

Byakuren
Member
 
Posts: 441
Joined: Tue Apr 14, 2015 01:59
GitHub: raymoo
IRC: Hijiri

Re: Wrapping minetest.register_*() Functions w/ other Functi

by Byakuren » Mon Oct 31, 2016 08:59

Wrapping functions is powerful, but this is not a good use-case for it. Wrapping functions that are facing other modules that expect a certain interface should be done only if you keep the interface the same. If you change the interface then you might as well expose a new function for mods to use, because they will have to adapt to the different interface anyway, and can break mods that are not expecting it. Also, the use of a global mutable variable for the purpose of passing parameters is prone to errors, and it would be better to add it as an actual parameter to the function (you are changing the interface anyway by requiring some global-setting). Though for this specific case, you could use minetest.get_current_modname.
Every time a mod API is left undocumented, a koala dies.
 

User avatar
SegFault22
Member
 
Posts: 870
Joined: Mon May 21, 2012 03:17

Re: Wrapping minetest.register_*() Functions w/ other Functi

by SegFault22 » Mon Oct 31, 2016 20:59

I won't be changing the interface significantly, ever; the parameters of my functions are tangible things that anyone can understand (like "id", "strength", "density", and so on), and if anything changes it will be for making the system more efficient, and not what the parameters mean or their order or anything of that nature. For example, the parameter "density" is one parameter of a function, which adds an entry to a table of materials; those entries are then used by a function which wraps minetest.register_tool(), and calculates the parameters for the registry call based on data from several tables and a few parameters. In the table parameter to the minetest.register_tool() function, properties such as "full_swing_interval" and "groupcaps" will always be the same as long as the material's "density" and the tool's "volume" are the same.

If the registry functions change significantly, in such a way that I have to change my interface, then I will - but that won't be causing any extra bother, because all of the mods using the "old" registry functions would be broken anyways. I don't believe this will happen, because that is a significant problem experienced when developing mods for Minecraft with Forge - code within the "vanilla" game changes a lot between versions, then Forge code has to be changed to work with it properly, and the can is eventually kicked down the road to all of the mod developers, whom have to change their code to work properly with the new Forge code. Minetest is being designed such that this is avoided wherever it is possible, for the same reason that minecraft mod developers hate updating to new versions.

It is a bad practice to wrap functions this way, as the interface could have to be changed at any time. But I will avoid changing existing parts of the interface. If I add anything new, such as another parameter (such as a light level for nodes), it will be optional (ignore or don't use if nil) so that there is backwards compatibility with older mods that don't expect to pass the new parameters. When I release this mod, the existing parameters of all of my functions, and the parameters which they pass to the registry functions, will have to stay the same forever - because of this, I have been working on this project for quite a while, trying to make it work as best as possible, so that when it is released, the interface won't have to be changed. I have re-written the mod several times, and it now doesn't look anything like it used to (first it was an ipairs machine, now it is this wonderful monstrosity). If I have to re-write it after it is released, then I will call it something else, so that old mods using the older versions won't be broken by the mod which they depend on.

I will be using minetest.get_current_modname(), instead of setting a global variable at the start of each mod, as it is much cleaner. I had forgotten about it a long time ago, and it is a wonderful tool for situations like this. Thank you for telling me about it.
 


Return to Modding Discussion

Who is online

Users browsing this forum: No registered users and 5 guests

cron