Lua bindings for MinaModAPI, so Mina the Hollower mods can be written in Lua as well as C/C++.
The game's modding SDK is C, and hands a mod a big function table at load time. This project puts LuaJIT in front of it so people who know Lua can write mods against that API directly.
You get the whole engine API, no build step, and argument checking on every call. Pass the wrong thing and you get a Lua error naming the function and the argument, instead of a crash or a corrupted game that falls over ten minutes later. docs/how-it-works.md explains what the C++ layer does on your behalf; read it once you have a mod loading.
Factorio is referenced heavily for structure and conventions.
Modding is not in the default build. You need Mina the Hollower on Steam's experimental-modding branch.
-
Download the archive for your platform from Releases:
minamodlua-<version>-windows-x86_64.ziporminamodlua-<version>-linux-x86_64.zip.SHA256SUMS.txtis published alongside them. -
Extract it into the game's mods folder, so you end up with a
minamodlua/folder inside it:Windows %APPDATA%\Yacht Club Games\Mina the Hollower\modsLinux ~/.local/share/Yacht Club Games/Mina the Hollower/mods -
Launch the game with
-mod -mod-allow-code.
minamodlua is a host. On its own it does nothing visible. It writes its own log beside the game's mod.log, in the folder one level above mods/, and a line reading bound N of M MinaModAPI functions means it started cleanly. That log is also where your print output and any error your mod raises will appear. There is no console.
Lua mods install the same way: one folder each, alongside minamodlua/.
A Lua mod is a folder in the game's mods directory. No binary, no build step:
mods/my-mod/
mod.yc manifest, in the game's own format
main.lua entry point
[YCD Version: 1]
MinaModDef
{
id: "my-mod",
name: "My Mod",
modVersion: 1,
minGameVersion: 0,
maxGameVersion: 0,
loadPriority: 0,
}
0 for minGameVersion/maxGameVersion means unconstrained. loadPriority orders mods against each other, highest first. modVersion is yours to bump; the host logs it at load, and other mods can read it with mina.raw.get_mod_version("my-mod").
main.lua runs at load. If a settings.lua is present it runs first, and every mod's settings.lua runs before any mod's main.lua, so one mod can read another's configuration before it starts.
print("hello from my-mod")
mina.on_event("world_update", function(e)
-- e.world, e.elapsed
end)Load is early. main.lua runs while the game is still assembling itself, so most engine systems are not up yet and there is unlikely to be a world or a player to ask about. Register handlers and set up your own state here. Leave engine queries for the game_init event, which fires once most systems are up.
Each mod gets its own environment, so a mod that reassigns string.format breaks only itself, and two mods can use the same global name without colliding.
mina.raw— every bound engine function (reference)mina.on_event— the 20 events the engine dispatches into Lua (reference)mina.signatures— the same signatures the reference lists, available at runtimeprint— writes to the log, tagged with your mod idrequire— loads.luafiles from your own mod folder, and nowhere else- The Lua standard library, minus anything that reaches outside the game: no
io, nopackage, noos.exit, and noffi
The missing ffi is what people notice first. mina.raw is the only route to the engine, and being the only route is what lets it check every argument. docs/how-it-works.md has the rest of the list and the reasoning.
examples/smoketest is a worked example. It registers a handler on all 20 events and checks what each one reports against an independent source.
- CMake 3.19+
- GCC, Clang, or MSVC with C++17
- Mina the Hollower on the experimental modding branch, to run anything in-game
MinaModAPI and LuaJIT are fetched automatically at configure time, each pinned to an exact commit.
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build buildMods land in build/mods/<id>/ as mod.so (Linux) or mod.dll (Windows), beside a generated mod.yc. cmake --build build --target deploy copies them into the host's own game mods folder; launch the game with -mod -mod-allow-code.
On Windows, run that from a Visual Studio developer prompt (LuaJIT builds via its own msvcbuild.bat, which needs cl and the Windows SDK on PATH).
Useful when the game is easier to run on Windows than on the machine you build on. Requires mingw-w64:
cmake -B build-win -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw64.cmake -DCMAKE_BUILD_TYPE=Release
cmake --build build-win- LuaJIT, bound through the Lua C API rather than the FFI. The SDK headers only compile as C++, the FFI cannot detect misdeclarations, and argument type-checking costs almost nothing.
- Bindings are deduced, not parsed. A generator extracts only the member names; C++ pointer-to-data-member and parameter-pack deduction do all the type handling. Anything unhandled is a compile error.
- Mod bugs must not crash the game. Every argument is checked, and every call into Lua is protected so an error can never unwind into engine frames.
- Lua mods are ordinary mods:
mods/<id>/withmod.yc+main.luaand no binary. In theory the game's own resource replacement should work on them too.
See CONTRIBUTING.md.