Search code examples
nim-lang

How can I support mods in my game made in Nim?


First I would like to apologize for my English.

I'm reviving an old project of mine originally made in the Lua language, where it was a Terraria-style sandbox game.

The game was designed from the beginning to support mods, where the original game itself was a mod based on an api that manipulated everything within the game.

That is, to create a mod, it was enough to follow this api, and the main code would import inside, thus creating blocks, items, entities, among others,

Doing this in Lua was simple, since it is a scripting language, and to provide this support, it was enough to import all the codes from a folder.

Already in Nim, the problem arises where the base of the game together with its api would be previously compiled. That way, there's no way for programmers to create mods for it without having the source code and recompiling the game with your modifications.

Searching a bit, I found a plausible option in dynamic libraries, but I had difficulty implementing them in a pleasant way for the project.

I also discovered a library called "nimpk", which was exactly what I was looking for. It implemented the "pocketlang" language in Nim, where I can easily create functions in Nim and run them inside pocketlang, and vice versa.

So I could create an api in Nim, create objects in pocketlang using the api, and run it inside Nim. It was perfect, but as the library hasn't had any kind of support for a while, it has serious compatibility issues with other libraries, including the one I use to make my game.

Initially they may ignore the fact that I'm making a game. I would just like an example of how I can do this type of implementation. It could be, for example, a calculator that imports all its functions from a "functions" folder.

Thanks in advance for the reply.

PS: Using methods like minecraft bedrock that use ".json" files in addons is out of the question in the project, since this method greatly limits what can be created. In that same comparison, it would be more ideal as in the most recent versions that have javascript support in the addons.


Solution

  • As you've discovered there are basically two main ways of adding mod/extensibility support. This is more or less the same options you have no matter which language you implement your project in, although some languages can make this easier or harder.

    The first option is to directly extend your program on the same level as your original. For Nim this means extending the binary by loading DLL/so/dynlib files. These files will be able to run at native performance and have access to pretty much everything in your program (and they could potentially also crash your program). This is what Lua does, but since Lua is a scripting language and works on a higher level of abstraction directly extending it simply means putting more text into the interpreter.

    The second option is to implement or integrate some form of scripting language. In Nim you could integrate a Lua interpreter, a Python interpreter, a or any other interpreter which can be integrated into a C program. One of the easiest languages to integrate however might be NimScript. NimScript is a subset of Nim which runs in macros, config files, and other compile-time executions within the Nim language compilation cycle. This is done by simply importing the compiler libraries, or even easier by using nimscripter. The downside of a scripting language is that you'll have to convert from your internal types to whatever the scripting language accepts and back again. This can be hard on performance (compared to native speeds of course).

    If you go for the first method it is of course also possible to create a module which loads a scripting language and thereby implements the second method. Because of this and the other reasons the first option is probably the better of the two. To do this you should be able to create a file with all the things you want to make easily available to your dynamic library in a sort of header file with everything marked with the {.importc.} pragma and no body. In your code you then need to have the {.exportc.} pragma on the same things. If you then import the "header" file into your dynamic library code and compile it as a dynamic library your main code should now be able to load this library and it should be able to call your procedures directly. This is at least how it works on Linux. You could probably even create your own little pragma which added exportc and wrote an importc version of the procedure to a "header" file so that the definitions are guaranteed to match.