This might be the stupidest idea of all time, but I’ve been thinking about the possibility of a modding API where mods are written in Lua instead of Java. Common sense says it wouldn’t be as powerful as Java, but I question that: native Java objects and methods can be called and created from Lua using reflection, and ASM can create new Java classes/subclasses at runtime (and thus from lua), so what can Java mods do that Lua mods inherently can’t?
The benefit is that mods could theoretically be agnostic to version and modloader if the API is kept stable and ported to each version/modloader tuple. Mods wouldn’t have to be ported individually to new Minecraft versions. Imagine how cool it’d be to write a mod and have it work on 1.7.10, 1.12.2, and whatever the latest version is. I don’t see why that’d be impossible. Of course it’d take a lot of effort to implement the functionality and wrap it in Lua functions; but maybe less effort than has been collectively spent porting mods to the zillions of new Minecraft versions and abandoning old ones? Minimal Java and Gradle involved in development. Hopefully more simplicity. Mods could be sandboxed.
As for speed, there’s an unfortunate overhead to each Java <=> native invocation. I measured calling a Lua function from Java to cost about 100 nanoseconds. But LuaJIT is very fast and shouldn’t be much slower than comparable Java code. I guess the other main drawback is that the API and all functionality would have to be reimplemented from scratch.
I do have a VERY basic proof-of-concept for 1.20.1 Forge:
--no lua highlighting options :(
vani.registerblock(
localized("cloud_block", "Cloud Block"),
vani.blockbuilder()
.hardness(4)
.texture("clouds.png")
)
vani.registerblock(
localized("pattern_block", "Pattern Block"),
vani.blockbuilder()
.texture("pattern.png")
)
local textures = {
"storm.png",
"traffic_light.png",
"tool_folder.png",
"beaker.png"
}
for i, file in ipairs(textures) do
vani.registeritem(
localized("random_item_" .. i, "Goofy " .. file
:gsub("%.png", "")
:gsub("_", " ")
),
vani.itembuilder()
.stacksize(16)
.texture(file)
)
end
It works:
The Lua “mod” is just a folder with the code put in in lua/main.lua and the textures inside /static, no JSON or mod-specific build.gradle or anything else. There is a build script written in Lua that takes the mod and outputs a forge 1.20.1 .jar containing the Lua code, resources, and some boilerplate to register the mod. It depends on a library mod that contains the LuaJIT runtime (the binaries are ~3 MB so would be wasteful to include it in every Lua mod) and runs the code.
Most of the work is done by the build step, so far: it sets up all the JSON and langfile stuff. Some blockbuilder() methods do nothing at runtime, like .texture(). Some of them do nothing at build time, like .hardness() and .stacksize(). It’s weird but should hopefully be flexible enough to support any Minecraft version and modloader: maybe in an old version you have to specify the texture of a block at runtime instead of as a resource pack, so in those cases .texture() can do stuff at runtime, and no changes to the mod are required.
It’s a super basic example, yes, but I assume adding more functionality, like block entities and stuff, is possible and is mostly a matter of just… implementing it.
- Problem: textures and whatnot are mainly defined by resource packs, which are JSON and not Lua, and can’t be created at runtime
- Solution: the build step. Data generation might also work but it seems more annoying.
- Problem: minecraft methods are obfuscated, so you can’t call them via reflection at runtime
- Solution: bundle an Intermediary → SRG name lookup table in the library. Prefer Intermediary names over MCP names due to licensing.
- Problem: how is a modded block supposed to have its own class or annotations?
- Solution: ASM.
Sorry this post is too long. I’m obviously not a minecraft modder, so I’m really not knowledgeable about this stuff. I’m way too unmotivated to work on it anymore, but I want to at least hear others’ thoughts on it.
