Modding Guide
Build native shared-library mods that extend Surviving Day! with new blocks, crafting recipes, terminal commands, and event hooks — all via a clean C ABI. Supports Linux .so and Windows .dll.
Overview
VoxelGame's modloader scans two directories on startup and loads every .so (Linux) or .dll (Windows) it finds. Each mod exports a single C function — mod_init — which receives a pointer to a ModAPI struct pre-filled with engine callbacks.
New Block Types
Register blocks with custom names, solidity, and a texture PNG. They appear in the creative inventory automatically. IDs start at 342 (BlockType::COUNT).
Crafting Recipes
Add shaped 3×3 recipes using any block IDs — vanilla or your own. Recipes are checked at craft-time.
Terminal Commands
Register new commands that players can run on any in-game Computer block.
Event Hooks
React to blocks being placed or broken anywhere in the world, or intercept player damage to modify HP.
Permissions System
Six role tiers (Owner → Banned) enforced server-side. Persisted to saves/roles.json. Accessible via the F3 admin console.
Portals & Gateways
WarpPortal A/B, Portal A/B, and Gateway blocks. Gateways open entirely separate procedurally-generated pocket worlds with return shrines.
Mods are native shared libraries — .so on Linux, .dll on Windows. The C ABI makes them compiler-agnostic. Your only required export is extern "C" void mod_init(ModAPI*). See Cross-Compiling for Windows build instructions.
Quick Start
The fastest way to get a mod running — a block that logs when placed.
#include "ModLoader.h"
extern "C" void mod_init(ModAPI* api) {
// ── Identity ──────────────────────────────────────────
api->modName = "My First Mod";
api->modVersion = "1.0";
// ── Register a block ─────────────────────────────────
ModBlockDef stone {
"Neon Stone", // display name
true, // isSolid
"" // no custom texture (uses fallback)
};
uint16_t stoneId = api->registerBlock(&stone);
// ── Event hook: log every block placement ────────────
api->onBlockPlaced = [](const ModBlockEvent* ev) {
// handler must be a plain function pointer, not a capturing lambda
};
api->onBlockBroken = nullptr;
api->onPlayerDamage = nullptr;
api->logMsg("My First Mod loaded!");
}
# Compile as a shared library
g++ -shared -fPIC -std=c++17 \
-o my_first_mod.so my_first_mod.cpp \
-Iinclude
# Install to the primary mod directory
mkdir -p ~/.config/SurvivingDay/mods
cp my_first_mod.so ~/.config/SurvivingDay/mods/
# Launch the game — you should see in stderr:
# [ModLoader] Loaded: My First Mod v1.0
Mod Structure
Every mod is a single .so file. It must export exactly one symbol:
extern "C" void mod_init(ModAPI* api);
Inside mod_init you must do three things:
- Set
api->modNameandapi->modVersion - Set each callback pointer (
onBlockPlaced,onBlockBroken,onPlayerDamage) to a function ornullptr - Call any
api->register*functions you need
The callback fields in ModAPI are raw C function pointers — they cannot hold capturing lambdas. Use static functions or non-capturing lambdas only. To share state, use a global or static variable inside your mod.
Building Your Mod
Mods are compiled as position-independent shared libraries. You only need ModLoader.h from the Surviving Day! source tree — no linking against the game binary.
g++ -shared -fPIC -std=c++17 \
-o mymod.so mymod.cpp \
-I/path/to/VoxelGame/include
CMakeLists.txt template
cmake_minimum_required(VERSION 3.16)
project(MyMod)
add_library(MyMod SHARED mymod.cpp)
target_compile_features(MyMod PRIVATE cxx_std_17)
target_include_directories(MyMod PRIVATE
/path/to/VoxelGame/include
)
set_target_properties(MyMod PROPERTIES PREFIX "") # removes "lib" prefix
Cross-Compiling (Linux → Windows & native Windows)
VoxelGame supports mods as .dll on Windows. The same ModLoader.h header works on both platforms — the C ABI is identical.
Option A — Cross-compile on Linux (MinGW-w64)
# Install MinGW-w64 cross-compiler (Debian/Ubuntu)
sudo apt install mingw-w64
# Build a Windows .dll from Linux
x86_64-w64-mingw32-g++ -shared -fPIC -std=c++17 \
-o mymod.dll mymod.cpp \
-I/path/to/VoxelGame/include \
-Wl,--out-implib,mymod.lib
# The .dll is ready to copy to the Windows mod directory
Option B — Native Windows (MSVC / Visual Studio)
rem Compile as a DLL using MSVC cl.exe
cl.exe /std:c++17 /LD /EHsc \
/I"C:\VoxelGame\include" \
mymod.cpp \
/Fe:mymod.dll
Option C — Native Windows (MinGW / MSYS2)
g++ -shared -std=c++17 \
-o mymod.dll mymod.cpp \
-I/c/VoxelGame/include
CMakeLists.txt — cross-platform template
cmake_minimum_required(VERSION 3.16)
project(MyMod)
add_library(MyMod SHARED mymod.cpp)
target_compile_features(MyMod PRIVATE cxx_std_17)
target_include_directories(MyMod PRIVATE
/path/to/VoxelGame/include
)
# Remove "lib" prefix on Linux, keep clean name on Windows
set_target_properties(MyMod PROPERTIES PREFIX "")
# Cross-compile to Windows from Linux:
# cmake -DCMAKE_TOOLCHAIN_FILE=mingw-w64-x86_64.cmake ..
MinGW-w64 toolchain file
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Windows installation path
| Platform | Primary mod path | Fallback |
|---|---|---|
| Linux | ~/.config/VoxelGame/mods/*.so |
./mods/*.so |
| Windows | %APPDATA%\VoxelGame\mods\*.dll |
.\mods\*.dll |
Your mod only needs ModLoader.h at build time — you do not link against the game binary. On Windows, ensure the MinGW runtime DLLs (libstdc++-6.dll, libgcc_s_seh-1.dll) are either statically linked (-static-libgcc -static-libstdc++) or present alongside your .dll.
ModAPI Reference
The ModAPI struct is the complete interface between the engine and your mod. The engine fills its function pointers before calling mod_init; you fill the rest.
This page covers the core registration/event flow. For every engine function (NPC control, player state, inventory, UI/sound, world info, save data, multiplayer) and every mod hook with exact fire conditions, see the full Modding Tutorial Series — Lessons 3 and 4 are a complete reference.
Engine-provided — call these
| Field | Signature | Description |
|---|---|---|
registerBlock |
uint16_t (*)(const ModBlockDef*) |
Register a new block. Returns the assigned ID starting at 342 (BlockType::COUNT). The block immediately appears in the creative inventory. |
registerItem |
uint16_t (*)(const ModItemDef*) |
Register a new item (armor, weapon, or misc). Returns the assigned item ID. See Custom Items for the ModItemDef field table. |
registerRecipe |
void (*)(const uint16_t[9], uint16_t out, int count, const char* name) |
Add a shaped 3×3 crafting recipe. The pattern array is row-major (index 0 = top-left, 8 = bottom-right). Use 0 for Air/empty slots. Use your registered block ID for mod blocks. |
registerCommand |
void (*)(const char* name, char* (*handler)(const char*)) |
Register a Computer terminal command. handler receives the remainder of the typed line as args. Return a malloc-allocated char* response string — the engine will free() it. Return nullptr for "not handled." |
registerGuiPanel |
void (*)(const char* panelId, ModGuiPanel*) |
Registers a custom drawable panel (title, size, onDraw/onClose callbacks). Open it later with openGuiPanel(panelId). |
logMsg |
void (*)(const char* msg) |
Writes [Mod] <msg> to stderr. Use for startup confirmation and debugging. |
getBlock / setBlock |
uint16_t(int,int,int) / void(int,int,int,uint16_t) |
Read or write a block at a world position. Main-thread only — use getBlockGen/writeBlockGen instead inside onChunkGenerate. |
getBlockGen / writeBlockGen |
uint16_t(int,int,int) / void(int,int,int,uint16_t) |
Gen-thread-safe block access — use only inside onChunkGenerate, which runs off the main thread. |
spawnHologram |
void (*)(float x, float y, float z, const char* text) |
Spawns a floating text hologram at the given world position. |
explodeAt |
void(float x,y,z,float radius,bool griefing) |
Triggers an explosion — damages nearby NPCs and fires onExplosion to all mods. |
| NPC functions | spawnNPC, killNPC, getNPCCount, get/setNPCPos, get/setNPCHP, setNPCCompanion |
Spawn and control mobs/NPCs by index. Full signatures in the tutorial's NPC lesson. |
| Player functions | get/setPlayerPos, get/setPlayerHP, get/setPlayerMana, getSkillLevel, addXP, getTotalLevel |
Read/modify the local player's position, stats, and skills. See the Player State API. |
| Inventory functions | giveItem, removeItem, getItemCount |
Add, remove, or query items across the hotbar and bag. |
| UI/sound functions | broadcastChat, showTitle, playSound, open/closeGuiPanel, create/removeWaypoint |
playSound plays one of a documented set of built-in engine sounds by string ID — see the UI, Sound & Waypoints lesson for the full ID table. |
| World-info functions | getDayTime, getSeason, getWeather, setWeather |
Query or force the world's time-of-day, season, and weather state. |
saveData / loadData |
void(const char*,const char*) / const char*(const char*) |
Flat string key/value persistence scoped to your mod and the current save. |
| Multiplayer functions | isMultiplayer, isServer, sendModPacket, getLocalUsername |
Send arbitrary byte payloads between client/server on a named channel; received via onModPacket. |
Mod-provided — set these
| Field | Type | Description |
|---|---|---|
modName / modVersion / modDescription |
const char* |
Identity fields shown in the engine startup log. modDescription is optional. |
onUpdate / onUnload |
void(float dt) / void() |
onUpdate fires every frame on the main thread. onUnload fires once at shutdown. |
onBlockPlaced / onBlockBroken / onBlockInteract |
void(const ModBlockEvent*) / same / void(const ModBlockInteractEvent*) |
Fired on any block placement, break, or right-click/E interaction anywhere in the world. |
onPlayerDamage |
void (*)(ModDamageEvent*) |
Fired for every player-damage source (melee, fall, fire, lava, drowning, projectiles, boss attacks) right before it's applied. ev->hp is a live pointer you may write to; ev->dmg is the incoming amount. |
onPlayerDeath / onPlayerRespawn / onPlayerLogin / onPlayerMove |
various | Player lifecycle and movement events. onPlayerMove can fire up to once per frame — keep it cheap. |
onItemUsed |
void (*)(const ModItemEvent*) |
Called when the player uses/activates an item. ev->itemType plus target position — not a raw int, despite older docs. |
onInventoryChange / onLevelUp / onFishCaught / onCropHarvest |
various | Fired on inventory count changes, skill level-ups, fishing catches, and crop harvests respectively. |
onChatMessage |
bool (*)(const char* user, const char* msg) |
The one hook with a meaningful return value — return false to swallow the message for everyone, true to let it through. Not a raw void(const char*), despite older docs. |
| NPC hooks | onNPCDamage, onNPCDeath, onNPCSpawn, onNPCInteract |
Mirror the block hooks but for NPCs/mobs — including ones your own mod spawns. |
| World hooks | onWeatherChange, onSeasonChange, onExplosion, onChunkGenerate, onChunkLoad, onChunkUnload |
onChunkGenerate is the one hook that runs off the main thread — use only getBlockGen/writeBlockGen inside it. All others are main-thread-safe. |
onAchievementUnlock / onCraft |
various | Progression events — achievement unlocks and completed crafts (including mod-registered recipes). |
| Multiplayer hooks | onPlayerJoin, onPlayerLeave, onServerTick, onModPacket |
Join/leave fire server-side only; onServerTick is like onUpdate but server-only; onModPacket receives sendModPacket payloads from the other side. |
ModAPI is a flat sequence of function pointers with no per-field versioning. If you vendor your own copy of these structs instead of including ModLoader.h directly, every field must match the engine's order, type, and count exactly — a missing or reordered field silently shifts every pointer after it to the wrong function. See Threading & Gotchas in the tutorial for the full explanation.
ModBlockDef
Passed to api->registerBlock() to define a new block type.
| Field | Type | Description |
|---|---|---|
name |
const char* |
Display name shown in the creative inventory and search. E.g. "Neon Cobble". |
isSolid |
bool |
Whether the block is a full opaque cube. Affects face-culling (solid blocks cull adjacent faces), collision, and pathfinding. |
texturePath |
const char* |
Path to a 48 × 16 px PNG. The three 16×16 columns map to: top face, side faces, bottom face — left to right. Pass "" to use a placeholder checkerboard texture. |
Texture format
┌────────────┬────────────┬────────────┐
│ Top face │ Side faces │Bottom face │
│ (16×16) │ (16×16) │ (16×16) │
└────────────┴────────────┴────────────┘
col 0–15 col 16–31 col 32–47
ModBlockEvent
Passed to onBlockPlaced and onBlockBroken callbacks.
| Field | Type | Description |
|---|---|---|
x |
int |
World X coordinate of the affected block. |
y |
int |
World Y coordinate (vertical axis). Sea level ≈ 96. |
z |
int |
World Z coordinate of the affected block. |
blockType |
uint16_t |
Numeric block type ID. IDs 0–341 are vanilla blocks. IDs ≥ 342 are mod-registered blocks. |
Register Blocks
Call api->registerBlock() for each block type you want to add. Block IDs are assigned sequentially starting at 342 (BlockType::COUNT). Save the returned ID — you'll need it for recipes and to compare against ModBlockEvent.blockType.
// Globals so callbacks can use them
static uint16_t gNeonStoneId = 0;
static uint16_t gVoidGlassId = 0;
extern "C" void mod_init(ModAPI* api) {
api->modName = "Neon Blocks";
api->modVersion = "2.1";
// Solid opaque block with a custom texture
ModBlockDef neonStone {
"Neon Stone",
true,
"/home/user/mods/neon_stone.png"
};
gNeonStoneId = api->registerBlock(&neonStone); // → 342
// Transparent / non-solid block
ModBlockDef voidGlass {
"Void Glass",
false, // not solid — won't cull adjacent faces
""
};
gVoidGlassId = api->registerBlock(&voidGlass); // → 343
api->onBlockPlaced = nullptr;
api->onBlockBroken = nullptr;
api->onPlayerDamage = nullptr;
}
IDs are assigned in load order starting at 342 (BlockType::COUNT). Multiple mods adding blocks will get sequential IDs. The creative inventory search automatically includes all registered mod blocks.
Register Recipes
Recipes use a flat 9-element array of uint16_t block IDs representing a 3×3 grid in row-major order (index 0 = top-left, index 8 = bottom-right). Use 0 (Air) for empty slots.
// Pattern visualised:
// [ NS ][ NS ][ -- ]
// [ NS ][ NS ][ -- ]
// [ -- ][ -- ][ -- ]
uint16_t pattern[9] = {
gNeonStoneId, gNeonStoneId, 0,
gNeonStoneId, gNeonStoneId, 0,
0, 0, 0
};
api->registerRecipe(
pattern,
gNeonBricksId, // output block ID
4, // output count
"NEON BRICKS" // display name
);
Recipes are shape-normalised — the filled bounding box of the crafting grid is extracted and compared to the recipe bounding box. A 2×2 recipe placed in the top-left matches a 2×2 placed in the bottom-right. First registered match wins.
Using vanilla block IDs in patterns
You can reference vanilla blocks by casting their BlockType enum value to uint16_t. Key IDs to know:
| Block | ID | Block | ID |
|---|---|---|---|
| Air (empty) | 0 | Stone | 1 |
| Wood (Oak Log) | 6 | Coal Ore | 30 |
| Iron Ore | 31 | Diamond Ore | 37 |
| Iron Block | 129 | Gold Block | 133 |
| Cobblestone | 64 | Glass | 85 |
See the Blocks & Recipes reference for the complete ID table.
Terminal Commands
Register commands that players can type into any Computer block terminal. The command name is the first word typed; the remainder of the line is passed as args.
// Handler must be a plain function (not capturing lambda)
static char* handlePing(const char* args) {
// malloc the response — engine will free() it
const char* msg = "PONG! Mod is alive.\n";
char* buf = (char*)malloc(strlen(msg) + 1);
strcpy(buf, msg);
return buf;
}
// In mod_init:
api->registerCommand("ping", handlePing);
// Player types: ping
// Terminal shows: PONG! Mod is alive.
The engine calls free() on the pointer returned by your command handler. Return a malloc-allocated string, or nullptr for no output (treated as empty/not-handled and falls through to "Unknown command").
Parsing arguments
static char* handleSpawn(const char* args) {
// args = "dragon 3" when player typed: spawn dragon 3
std::string s(args);
std::ostringstream oss;
oss << "Spawning: " << s << "\n";
std::string result = oss.str();
char* buf = (char*)malloc(result.size() + 1);
memcpy(buf, result.c_str(), result.size() + 1);
return buf;
}
api->registerCommand("spawn", handleSpawn);
Event Hooks
About 30 event hooks let your mod react to blocks, players, NPCs, chat, weather, chunks, crafting, and multiplayer events in real time. The three most commonly used are shown below — for the complete hook-by-hook reference (every signature, exact fire condition, and thread-safety notes) see Lesson 4 of the Modding Tutorial.
blockType is the type that was there — the position is already Air when this fires.ev->hp is a pointer to the live HP float; ev->dmg is the incoming amount.Block interact, player death/respawn/login/move, item use, inventory change, level up, fishing, crop harvest, chat (with cancel), NPC damage/death/spawn/interact, weather/season change, explosion, chunk generate/load/unload, achievement unlock, craft, and multiplayer join/leave/server-tick/packet — all documented with exact signatures in the tutorial's full hook reference.
Example: detect when your custom block is broken
static uint16_t gMyBlockId = 0;
static void onBroken(const ModBlockEvent* ev) {
if (ev->blockType == gMyBlockId) {
// React to our specific block being broken
}
}
extern "C" void mod_init(ModAPI* api) {
api->modName = "Block Detector";
api->modVersion = "1.0";
ModBlockDef def{ "Detector Block", true, "" };
gMyBlockId = api->registerBlock(&def);
api->onBlockPlaced = nullptr;
api->onBlockBroken = onBroken;
api->onPlayerDamage = nullptr;
}
Example: damage shield
static void shieldDamage(ModDamageEvent* ev) {
// Refund half the incoming damage back onto HP
*ev->hp += ev->dmg * 0.5f;
}
// In mod_init:
api->onPlayerDamage = shieldDamage;
Installation & Paths
The modloader scans two directories in order. A mod found in the primary path will not be loaded again from the fallback, even if both directories contain the same filename.
| Priority | Path | Notes |
|---|---|---|
| Primary | ~/.config/VoxelGame/mods/ |
User-level mods. Created automatically on first launch. Recommended for end users. |
| Fallback | ./mods/ |
Working-directory-relative. Useful for development and testing alongside the game binary. |
Within each directory, mods are loaded in filesystem iteration order (typically alphabetical). Block IDs are assigned sequentially across all mods in load order, so if ordering matters for cross-mod recipes, name your mod files accordingly (e.g. 00_core.so, 01_extension.so).
# Primary location (recommended)
mkdir -p ~/.config/VoxelGame/mods
cp mymod.so ~/.config/VoxelGame/mods/
# Development / testing alongside game binary
mkdir -p mods
cp mymod.so mods/
Full Examples
Complete mod: Neon Block Pack
#include "ModLoader.h"
#include <cstdlib>
#include <cstring>
#include <cstdio>
static uint16_t gNeonStoneId = 0;
static uint16_t gNeonBricksId = 0;
static int gPlacedCount = 0;
static void onPlaced(const ModBlockEvent* ev) {
if (ev->blockType == gNeonStoneId) gPlacedCount++;
}
static char* neonStats(const char*) {
char* buf = (char*)malloc(128);
snprintf(buf, 128, "Neon Stone placed: %d times\n", gPlacedCount);
return buf;
}
extern "C" void mod_init(ModAPI* api) {
api->modName = "Neon Blocks";
api->modVersion = "1.2";
ModBlockDef stone { "Neon Stone", true, "" };
ModBlockDef bricks{ "Neon Bricks", true, "" };
gNeonStoneId = api->registerBlock(&stone);
gNeonBricksId = api->registerBlock(&bricks);
// 2×2 Neon Stone → 4 Neon Bricks
uint16_t pat[9] = {
gNeonStoneId, gNeonStoneId, 0,
gNeonStoneId, gNeonStoneId, 0,
0, 0, 0
};
api->registerRecipe(pat, gNeonBricksId, 4, "NEON BRICKS");
api->registerCommand("neonstats", neonStats);
api->onBlockPlaced = onPlaced;
api->onBlockBroken = nullptr;
api->onPlayerDamage = nullptr;
api->logMsg("Neon Blocks loaded!");
}
Permissions & Roles
VoxelGame includes a built-in six-tier permission system enforced server-side. Roles are persisted to saves/roles.json and loaded on server start.
Role hierarchy
| Role | Value | Capabilities |
|---|---|---|
Owner | 5 | All permissions; cannot be demoted by Admins |
Admin | 4 | Can promote/demote up to Moderator; all Mod permissions |
Moderator | 3 | Kick, ban, unban, tp, broadcast; access to F3 console |
Member | 2 | Can place and break blocks (trusted builder) |
Player | 1 | Default role — can interact but not build |
Banned | 0 | Connection refused on join |
roles.json format
{
"Alice": "OWNER",
"Bob": "ADMIN",
"Charlie": "MODERATOR",
"Dave": "MEMBER",
"Eve": "BANNED"
}
Role strings are case-insensitive. Unrecognised strings default to PLAYER. The file is written automatically whenever a role changes via the F3 console.
Server enforcement
- Block place/break packets are rejected server-side if the sender's role is below Member.
- A Banned player's connection is refused immediately at the
JoinInfopacket stage. - Kick/ban/promote commands only work when the issuer's role is high enough (Moderator+ for kick/ban; Admin+ for promote).
In singleplayer the permissions system is inactive — the local player is treated as Owner for all checks. Staff tools like the NPC Edit Wand are always accessible.
F3 Admin Console
Press F3 while hosting or connected to a server (Moderator role or above) to open a terminal-style overlay console. It slides over the centre of the screen and accepts commands while the game continues running.
Opening condition
- Must be in an active server session (hosting or connected).
- Local player role must be Moderator or higher.
- Pressing F3 again closes the console.
Available commands
| Command | Syntax | Min Role | Description |
|---|---|---|---|
list | list | Moderator | Show all connected players and their roles. |
kick | kick <name> [reason] | Moderator | Disconnect a player with an optional message. |
ban | ban <name> | Moderator | Set role to Banned and kick immediately. Persisted. |
unban | unban <name> | Moderator | Reset role to Player. Persisted. |
promote | promote <name> <role> | Admin | Set any role up to Admin. Role name is case-insensitive. |
demote | demote <name> <role> | Admin | Alias for promote — sets role to the given value. |
tp | tp <name> [x y z] | Moderator | Teleport a player. Omit coords to tp them to yourself. |
broadcast | broadcast <message> | Moderator | Send a chat message visible to all connected players. |
help | help | Moderator | List all available commands. |
> list
Alice [OWNER] connected
Bob [MEMBER] connected
Grieger [PLAYER] connected
> ban Grieger
Grieger banned and kicked.
> promote Bob MODERATOR
Bob promoted to MODERATOR.
> broadcast Server restarting in 5 minutes!
Broadcast sent.
NPC Edit Wand
The NPC Edit Wand (items: NpcWand and NpcWand2) lets staff right-click any NPC or mob to open an NBT-style editor panel, similar to Minecraft's NPCEdit. Available in singleplayer always; in multiplayer requires Moderator role or above.
How to use
- 1Obtain the wand
Find NpcWand or NpcWand2 in the Items tab of the creative inventory.
- 2Right-click an NPC
Aim at any NPC or mob within 8 blocks and right-click. The editor panel opens.
- 3Navigate fields
Use ↑/↓ or Tab to move between fields.
- 4Edit a field
Press Enter to begin editing the selected field. Type the new value, then Enter again to confirm or Esc to cancel.
- 5Close
Press Esc or right-click away from any NPC to close the panel.
Editable fields
| Field | Type | Description |
|---|---|---|
name | string | NPC display name shown above its head. |
kind | int | Species / mob type index. |
hp | float | Current hit points. |
maxhp | float | Maximum hit points. |
level | int | Combat level (affects scaling). |
tint R/G/B | float 0–1 | Body colour tint. A colour swatch previews the result live. |
aggression | float 0–1 | Genome: chance to attack on sight. |
lunge | float 0–1 | Genome: lunge attack probability. |
strafe | float 0–1 | Genome: lateral strafing weight. |
defense | float 0–1 | Genome: damage reduction factor. |
limbSpeed | float | Animation limb swing speed multiplier. |
Portals & Gateways
Four teleportation block types allow dimension-like travel. All are placed from the creative inventory and interact on player contact or right-click.
Block types
| Block | Pairing | Trigger | Velocity |
|---|---|---|---|
| WarpPortalA / WarpPortalB | Auto-pairs: last placed A pairs with last placed B | Walk into or stand in the block | Preserved — player keeps momentum through the portal |
| PortalA / PortalB | Auto-pairs: same as warp portals | Walk into the block | Zeroed — player arrives stationary |
| HoverBlock | N/A | Stand on top | Launches player upward at 14 m/s² |
| GatewayBlock | Requires gold frame (see below) | Right-click (two-step charge → enter) | Player arrives at pocket world spawn |
Gateway activation
A GatewayBlock must be surrounded by 6 adjacent GoldBlocks (one on each face: top, bottom, north, south, east, west) to become active. Once the frame is complete:
- First right-click: charges the gateway (status: Charged).
- Second right-click: transports the player to the pocket world.
Pocket worlds
Each Gateway generates a unique procedural world when first entered. The terrain config is randomised per biome theme associated with the gateway's location:
| Biome Theme | Characteristics |
|---|---|
| Deep Jungle | High humidity, tall dense canopy, heavy erosion |
| Arctic Tundra | Low terrain, snow cover, sparse fir/spruce forests |
| Scorched Desert | Extreme height variance, red sand, dead trees |
| Mystic Forest | Magic log trees, bioluminescent flora, rolling foothills |
| Sky Archipelago | Floating islands, low water level, wide terrain scale |
| Deep Ocean | Very low terrain, broad coral reefs, shallow islands |
| Volcanic Mesa | High ridges, heavy terracing, marble/alabaster layers |
| Willow Swamp | Flat, near-water, dense willow canopy, dark oak patches |
Return shrine
A return shrine is automatically built at the pocket world's spawn point on first entry. It consists of a stone platform, four corner columns capped with GoldBlocks, and a central GatewayBlock surrounded by its own gold frame — right-clicking it returns the player to the main world.
WarpPortal and Portal have a 2-second cooldown per activation to prevent rapid oscillation. Gateway has a 2.5-second cooldown. These are defined as PORTAL_COOLDOWN and GATEWAY_COOLDOWN constants in Circuitry.h.
World Generation
VoxelGame procedurally generates terrain using a multi-pass noise pipeline. Understanding the biome and generation system is useful if your mod places blocks via event hooks.
Biomes
| Biome | Surface block | Trees | Flora |
|---|---|---|---|
| JungleForest | Grass | 5 forest sub-types (see below) | Ferns, mushrooms, flowers, moss |
| Plains | Grass | Birch, Maple, Cherry, Apple, Willow (near water) | Red/Yellow/Blue flowers, Rose Bushes, Ferns, Mushrooms |
| Desert | RedSand | Dead (rare) | Cacti |
| Tundra | TundraGrass / Snow | Fir, Spruce, Redwood, Dead | Ferns, Moss, Brown mushrooms, Blue flowers (sparse) |
| Beach | TropicalSand | Palm | Ferns, Yellow flowers, Tall grass (near water) |
| Mesa | MarbleRed / MarbleWhite | Dead (ridges), Willow (valleys) | Rose Bushes, Yellow flowers (valleys) |
| Ocean | OceanMud | — | Coral reefs (Blue, Orange, Pink) in shallow water |
JungleForest sub-types
The JungleForest biome is divided into regional sub-types based on a per-16-block hash. Each sub-type has a distinct tree mix:
| Sub-type | Primary trees | Notes |
|---|---|---|
| Deep Jungle | Jungle, Dark Oak, Redwood giants, Maple | Densest canopy |
| Dark Oak Forest | Dark Oak (dominant), occasional Maple | Wide canopy, dark floor |
| Birch Forest | Birch (dominant), Cherry | Bright, open feel |
| Willow Swamp | Willow (dominant), sparse Dark Oak | Slightly less dense, drooping canopy |
| Magic Grove | Magic, Birch, Dark Oak | Rare bioluminescent trees |
WorldConfig terrain sliders
When creating a world, the following parameters are exposed as menu sliders:
| Parameter | Range | Effect |
|---|---|---|
| Terrain Scale | 0.003 – 0.15 | Noise frequency — low = giant continents, high = chaotic hills |
| Height | 20 – 200 | Terrain height multiplier above sea level |
| Water Level | adjustable | Sea level Y coordinate (default 96) |
| Ridge Peaks | 0 – 1 | Sharpness of mountain ridges |
| Terracing | 0 – 1 | Stepped plateau effect |
| Erosion | 0 – 1 | Cave frequency and cliff undercutting. 0 = no caves, 1 = heavy carving |
| Foothills | 0 – 1 | Width of rolling transition between plains and mountains |
| Biome Size | adjustable | Scale of biome noise — large = wider biome regions |
Sea level
The default sea level is Y = 96. Beach biomes span Y = 96–102 (shallow). Ocean biomes sit below Y = 88. When a mod places blocks via event hooks and needs to know if a position is underwater, compare against WorldConfig::waterLevel.
Ore Generation (v1.3+)
As of v1.3, ores are distributed through solid rock using 3D scatter veins rather than clustering exclusively on cavern walls. Veins are placed at biome-appropriate depth ranges and embedded throughout the rock mass. Mod blocks registered via registerBlock are not placed by world generation automatically — use onUpdate or a custom world pass to scatter your mod's ore blocks.
New Blocks in v1.3 (IDs 328–341)
Fourteen new vanilla blocks were added in v1.3, occupying IDs 328–341. Mod-registered block IDs now start at 342. The new blocks include the farming system (FarmLand, crop growth stages), decoration (Campfire, GraveMarker), and the magic crafting station (SpellForge):
| ID | Enum Name | Description |
|---|---|---|
| 328 | FarmLand | Tilled soil created by using a hoe on Dirt or Grass. Crops grow on FarmLand near water. |
| 329 | WheatStage0 | Wheat crop — just planted. |
| 330 | WheatStage1 | Wheat crop — mid growth. |
| 331 | WheatStage2 | Wheat crop — nearly mature. |
| 332 | WheatMature | Wheat crop — ready to harvest. |
| 333 | CarrotStage0 | Carrot crop — just planted. |
| 334 | CarrotStage1 | Carrot crop — mid growth. |
| 335 | CarrotMature | Carrot crop — ready to harvest. Drops Carrot (food/companion tameness). |
| 336 | PotatoStage0 | Potato crop — just planted. |
| 337 | PotatoStage1 | Potato crop — mid growth. |
| 338 | PotatoMature | Potato crop — ready to harvest. |
| 339 | Campfire | Decorative fire block. Emits light. |
| 340 | GraveMarker | Placed automatically at player death location. |
| 341 | SpellForge | Crafts spells from wands + gems. Spell strength scales with wand tier. |
Tips & Gotchas
- Save returned block IDs. Store them in static globals so callbacks and recipe patterns can reference them.
- No capturing lambdas. The callback slots are raw function pointers. Use static functions instead.
- malloc your command responses. The engine calls
free()on the pointer. Returning a string literal will crash. - Returning
nullptrfrom a command handler is treated as "this command doesn't handle this" and falls through to the engine's "Unknown command" message. - Block IDs ≥ 342 are dynamic. Don't hardcode them — always use the value returned by
registerBlock. - Load order matters for block IDs. If your mod B needs mod A's block IDs, name the files so A loads first (
00_a.so,01_b.so). - Texture PNG must be 48×16 pixels exactly. Passing a wrong-size image may result in incorrect display or a crash on some drivers.
- All event callbacks execute on the main thread — keep them fast. Do not do heavy I/O or blocking calls inside event handlers.
Run the game from a terminal to see all stderr output including [ModLoader] load messages and [Mod] log output. This is the fastest way to confirm your mod loaded and diagnose issues.