Developer Documentation

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.

⚡ Native .so / .dll 🔵 C ABI ✅ C++17 📦 Auto-loaded 🪟 Cross-platform

🗺️ 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.

ℹ️
Platform

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.

C++ my_first_mod.cpp
#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!");
}
Shell Build & Install
# 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:

C++Required export
extern "C" void mod_init(ModAPI* api);

Inside mod_init you must do three things:

  • Set api->modName and api->modVersion
  • Set each callback pointer (onBlockPlaced, onBlockBroken, onPlayerDamage) to a function or nullptr
  • Call any api->register* functions you need
⚠️
No capturing lambdas for callbacks

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.

ShellMinimal build command
g++ -shared -fPIC -std=c++17 \
    -o mymod.so mymod.cpp \
    -I/path/to/VoxelGame/include

CMakeLists.txt template

CMakeCMakeLists.txt
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)

ShellLinux → Windows .dll
# 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)

ShellMSVC Developer Command Prompt
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)

ShellMSYS2 / MinGW shell
g++ -shared -std=c++17 \
    -o mymod.dll mymod.cpp \
    -I/c/VoxelGame/include

CMakeLists.txt — cross-platform template

CMakeCMakeLists.txt
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

CMakemingw-w64-x86_64.cmake
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

PlatformPrimary mod pathFallback
Linux ~/.config/VoxelGame/mods/*.so ./mods/*.so
Windows %APPDATA%\VoxelGame\mods\*.dll .\mods\*.dll
⚠️
No runtime dependencies

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.

📘
Looking for the full API surface?

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

FieldSignatureDescription
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

FieldTypeDescription
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.
⚠️
Struct layout must match exactly

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.

FieldTypeDescription
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

Diagram48×16 block texture layout
┌────────────┬────────────┬────────────┐
│  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.

FieldTypeDescription
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.

C++Registering multiple blocks
// 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;
}
💡
Block IDs are stable per session

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.

C++Shaped recipe: 2×2 Neon Stone → 1 Neon Bricks
// 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
);
ℹ️
Shape normalisation

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:

BlockIDBlockID
Air (empty)0Stone1
Wood (Oak Log)6Coal Ore30
Iron Ore31Diamond Ore37
Iron Block129Gold Block133
Cobblestone64Glass85

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.

C++Registering a 'ping' command
// 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.
⚠️
Always malloc your response

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

C++Command with 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.

onBlockPlaced
void (*)(const ModBlockEvent*)
Fired after any block is placed. The event contains the block's world position and type ID. You cannot cancel placement.
onBlockBroken
void (*)(const ModBlockEvent*)
Fired after any block is broken. blockType is the type that was there — the position is already Air when this fires.
onPlayerDamage
void (*)(ModDamageEvent*)
Fired for every player-damage source — melee, fall, fire, lava, drowning, pressure, projectiles, boss attacks — right before it's applied. ev->hp is a pointer to the live HP float; ev->dmg is the incoming amount.
ℹ️
The other ~27 hooks

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

C++Block-specific break detection
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

C++50% damage reduction
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.

PriorityPathNotes
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.
💡
Load order

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).

ShellInstall commands
# 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

C++neon_blocks.cpp
#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

RoleValueCapabilities
Owner5All permissions; cannot be demoted by Admins
Admin4Can promote/demote up to Moderator; all Mod permissions
Moderator3Kick, ban, unban, tp, broadcast; access to F3 console
Member2Can place and break blocks (trusted builder)
Player1Default role — can interact but not build
Banned0Connection refused on join

roles.json format

JSONsaves/roles.json
{
    "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 JoinInfo packet stage.
  • Kick/ban/promote commands only work when the issuer's role is high enough (Moderator+ for kick/ban; Admin+ for promote).
ℹ️
Singleplayer

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

CommandSyntaxMin RoleDescription
listlistModeratorShow all connected players and their roles.
kickkick <name> [reason]ModeratorDisconnect a player with an optional message.
banban <name>ModeratorSet role to Banned and kick immediately. Persisted.
unbanunban <name>ModeratorReset role to Player. Persisted.
promotepromote <name> <role>AdminSet any role up to Admin. Role name is case-insensitive.
demotedemote <name> <role>AdminAlias for promote — sets role to the given value.
tptp <name> [x y z]ModeratorTeleport a player. Omit coords to tp them to yourself.
broadcastbroadcast <message>ModeratorSend a chat message visible to all connected players.
helphelpModeratorList all available commands.
ConsoleExample session
> 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

  1. 1
    Obtain the wand

    Find NpcWand or NpcWand2 in the Items tab of the creative inventory.

  2. 2
    Right-click an NPC

    Aim at any NPC or mob within 8 blocks and right-click. The editor panel opens.

  3. 3
    Navigate fields

    Use / or Tab to move between fields.

  4. 4
    Edit a field

    Press Enter to begin editing the selected field. Type the new value, then Enter again to confirm or Esc to cancel.

  5. 5
    Close

    Press Esc or right-click away from any NPC to close the panel.

Editable fields

FieldTypeDescription
namestringNPC display name shown above its head.
kindintSpecies / mob type index.
hpfloatCurrent hit points.
maxhpfloatMaximum hit points.
levelintCombat level (affects scaling).
tint R/G/Bfloat 0–1Body colour tint. A colour swatch previews the result live.
aggressionfloat 0–1Genome: chance to attack on sight.
lungefloat 0–1Genome: lunge attack probability.
strafefloat 0–1Genome: lateral strafing weight.
defensefloat 0–1Genome: damage reduction factor.
limbSpeedfloatAnimation 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

BlockPairingTriggerVelocity
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:

  1. First right-click: charges the gateway (status: Charged).
  2. 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 ThemeCharacteristics
Deep JungleHigh humidity, tall dense canopy, heavy erosion
Arctic TundraLow terrain, snow cover, sparse fir/spruce forests
Scorched DesertExtreme height variance, red sand, dead trees
Mystic ForestMagic log trees, bioluminescent flora, rolling foothills
Sky ArchipelagoFloating islands, low water level, wide terrain scale
Deep OceanVery low terrain, broad coral reefs, shallow islands
Volcanic MesaHigh ridges, heavy terracing, marble/alabaster layers
Willow SwampFlat, 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.

💡
Cooldowns

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

BiomeSurface blockTreesFlora
JungleForestGrass5 forest sub-types (see below)Ferns, mushrooms, flowers, moss
PlainsGrassBirch, Maple, Cherry, Apple, Willow (near water)Red/Yellow/Blue flowers, Rose Bushes, Ferns, Mushrooms
DesertRedSandDead (rare)Cacti
TundraTundraGrass / SnowFir, Spruce, Redwood, DeadFerns, Moss, Brown mushrooms, Blue flowers (sparse)
BeachTropicalSandPalmFerns, Yellow flowers, Tall grass (near water)
MesaMarbleRed / MarbleWhiteDead (ridges), Willow (valleys)Rose Bushes, Yellow flowers (valleys)
OceanOceanMudCoral 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-typePrimary treesNotes
Deep JungleJungle, Dark Oak, Redwood giants, MapleDensest canopy
Dark Oak ForestDark Oak (dominant), occasional MapleWide canopy, dark floor
Birch ForestBirch (dominant), CherryBright, open feel
Willow SwampWillow (dominant), sparse Dark OakSlightly less dense, drooping canopy
Magic GroveMagic, Birch, Dark OakRare bioluminescent trees

WorldConfig terrain sliders

When creating a world, the following parameters are exposed as menu sliders:

ParameterRangeEffect
Terrain Scale0.003 – 0.15Noise frequency — low = giant continents, high = chaotic hills
Height20 – 200Terrain height multiplier above sea level
Water LeveladjustableSea level Y coordinate (default 96)
Ridge Peaks0 – 1Sharpness of mountain ridges
Terracing0 – 1Stepped plateau effect
Erosion0 – 1Cave frequency and cliff undercutting. 0 = no caves, 1 = heavy carving
Foothills0 – 1Width of rolling transition between plains and mountains
Biome SizeadjustableScale 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):

IDEnum NameDescription
328FarmLandTilled soil created by using a hoe on Dirt or Grass. Crops grow on FarmLand near water.
329WheatStage0Wheat crop — just planted.
330WheatStage1Wheat crop — mid growth.
331WheatStage2Wheat crop — nearly mature.
332WheatMatureWheat crop — ready to harvest.
333CarrotStage0Carrot crop — just planted.
334CarrotStage1Carrot crop — mid growth.
335CarrotMatureCarrot crop — ready to harvest. Drops Carrot (food/companion tameness).
336PotatoStage0Potato crop — just planted.
337PotatoStage1Potato crop — mid growth.
338PotatoMaturePotato crop — ready to harvest.
339CampfireDecorative fire block. Emits light.
340GraveMarkerPlaced automatically at player death location.
341SpellForgeCrafts 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 nullptr from 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.
🔬
Debugging tip

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.