Creating custom environments
For creating custom environments you need the binary of the original Luanti game engine. Thus, the first step is to clone the original repo and build the binary following the instructions here. Note that you should have all the dependencies already met if you have craftium installed in your system.
Once compiled, execute the luanti binary.
./bin/luanti
Installing the minetest game
Remember that Luanti isn't a game per se, it's a game engine, and we'll need a game to build our environment on. Thus, the first step is to install the "minetest game".
Go to the "Content" tab, click "Browse online content", and find and install "Minetest Game". Once installed go to the main menu, a small Luanti logo should appear at the bottom of the menu, click it to select the game.

Adding mods (optional)
Before creating a world, you can install any of the many available mods for Luanti (check ContentDB for all available mods!). Mods extend Luanti in many ways, adding extra functionality to the base minetest game, or can create completely new games.
For the sake of example, let's install the superflat mod. This mod creates a flat world that can be very useful for generating our environments.
To install the mod follow the same process as installing the minetest game: click the Content tab in the main menu, then click the Browse online content button. Finally, search for superflat and install it by clicking the green button with the plus sign:

Creating the world
In this example, we will start from an empty environment in an infinite and flat world. To create this world, follow the steps from the video below. Note that the created world should be named world. Although this can be changed in CraftiumEnv's option we recommend you to keep this name for the sake of simplicity.

Environment's Lua script
Although environments can be manually created by playing in the newly created world, in this example, we will create the environment using a mod (a Lua script). Creating Luanti mods is outside the scope of this tutorial, but there's an excellent Luanti's Modding Book available, and the Luanti's API reference.
Make sure to cd into the luanti's installation directory (the original one, not the one modified by craftium), and create a directory inside mods/ with the name craftium-builder.
Then create the following directory structure inside mods/craftium-builder with the specified contents:
mod.conf:
name = craftium_builder
description = Craftium environment builder script
depends = default
init.lua:
SIZE = 10 -- the size of the room in blocks
HEIGHT = 2 -- walls' height in blocks
MATERIAL = "default:stone"
core.register_on_joinplayer(function(_player, _last_login)
-- Build the floor
for x=0,SIZE do
for z=0,SIZE do
pos = {x = x, z = z, y = 4.5}
core.set_node(pos, { name = MATERIAL })
end
end
-- Build the walls
for i=0,SIZE do
for h=1,HEIGHT do
pos = {x = i, z = 0, y = 4.5 + h}
core.set_node(pos, { name = MATERIAL })
pos = {x = i, z = SIZE, y = 4.5 + h}
core.set_node(pos, { name = MATERIAL })
pos = {x = 0, z = i, y = 4.5 + h}
core.set_node(pos, { name = MATERIAL })
pos = {x = SIZE, z = i, y = 4.5 + h}
core.set_node(pos, { name = MATERIAL })
end
end
end)
Finally, add the mod to the newly created world, and start the game. When the game starts, press T (a chat window should open in the upper part of the screen) and type /teleport 0,10,0, this command teleports the player above the created room. You should see a rectangular room of stone blocks as in the video below.

Once the room is created, you can safely remove the craftium_builder mod from the mods/ directory.
The room should look like this:

Defining the environment
In this step, we will create the mod that defines the mechanics of the environment. The idea is to spawn a red block in a random position of the room, and the player on the other side of the room. If the distance between the player and the red block is less than a threshold, the episode terminates, else, the reward is set to -1 every timestep. Thus, the objective of the agent will be to reach the red block as fast as possible.
Let's create the mod following the same steps as in the previous section. Create the mods/craftium-env directory with the following files inside:
mod.conf:
name = craftium_env
description = Craftium environment
depends = default
init.lua:
function rand(lower, greater)
return lower + math.random() * (greater - lower);
end
SIZE = 10 -- the size of the room in blocks
FLOOR = 4.5 -- height of the floor
-- Executed when the player joins the game
core.register_on_joinplayer(function(player, _last_login)
-- Set the player's initial position
player:set_pos({x = SIZE / 2, z = 1, y = FLOOR + 1})
-- --- Spawn a red block inside the room in a random position
target_pos = {x = rand(1, SIZE-1), z = rand(5, SIZE-1), y = 5.5 }
core.set_node(target_pos, { name = "default:coral_orange" })
-- Disable HUD elements
player:hud_set_flags({
hotbar = false,
crosshair = false,
healthbar = false,
})
end)
-- Executed on every timestep
core.register_globalstep(function(dtime)
-- set timeofday to midday
core.set_timeofday(0.5)
-- get the first connected player
local player = core.get_connected_players()[1]
-- if the player is not connected end here
if player == nil then
return nil
end
-- if the player is connected:
-- get the position of the player and compute its
-- distance to he target
local player_pos = player:get_pos()
distance = math.pow(target_pos.x-player_pos.x, 2) +
math.pow(target_pos.z-player_pos.z, 2)
-- the reward at each timestep is -1
set_reward(-1.0)
-- terminate the episode if the distance to the target is
-- less than a threshold
if distance < 5.0 then
set_termination()
end
end)
Note that the mod above mostly uses Lua functions from the Luanti's Lua API. However, few functions (eg., set_reward) are specific to craftium, check Extensions to the Luanti's Lua API for the complete list of Lua functions added by craftium and their documentation.
Playing like an agent
At this step, you can load the Luanti mod and play like an agent. However, note that the functions set_reward() and set_termination are functions only available in the craftium's modified version of Luanti and not in the original Luanti binary we are using to create the mod. To play the game like an agent, you have to comment or remove these functions from init.lua.
⚠️ Warning: Be sure to leave the world's map in its initial state, otherwise the map will be saved and the initial state of the environment will be modified. For example, if you add or remove a block, be sure to remove or add it before exiting the game.
Packing the environment
Once the environment is set up, the final step is to create a directory with all the data required to run our environment. First, create the directory (anywhere in your system) where the environment will be saved (for example, small-room/).
Then, copy mods/ (should have only one subdirectory with the mod of the previous step, craftium-env), games/ (only include the games/minetest_game subdirectory, exclude games/devtest), and worlds/ (should contain only one subdirectory worlds/world) from the Luanti's build directory to small-room/.
Finally, remove all the data about a player from the environment's world. This is done by removing the players' database: just rm small-room/worlds/world/players.sqlite.
Using the custom environment
We are ready to test our newly created environment!
The following python script should open a window with Luanti and the environment loaded and playable!
The only thing you might need to modify is the path to the directory where the environment's data has been saved (./small-room/ for this example).
import time
import os
from craftium import CraftiumEnv
env = CraftiumEnv(
env_dir="small-room",
render_mode="human",
obs_width=512,
obs_height=512,
minetest_dir=os.getcwd(),
)
iters = 1000
observation, info = env.reset()
ep_ret = 0
start = time.time()
for i in range(iters):
action = dict() # empty action (do nothing)
observation, reward, terminated, truncated, _info = env.step(action)
ep_ret += reward
print(i, reward, terminated, truncated, ep_ret)
if terminated or truncated:
observation, info = env.reset()
ep_ret = 0
end = time.time()
print(f"** {iters} frames in {end-start}s => {(end-start)/iters} per frame")
env.close()
The created environment in action 🤠:

As expected, the player and the box start with a random position on opposite sides of the room. Note that the reward at each timestep is -1, making the return more negative over time. When the agent reaches the red box, the episode is terminated and the game is reset.
Next steps
This tutorial shows a step-by-step guide to implementing a custom environment in Craftium. For the sake of clarity, not every detail has been covered in this tutorial, but additional information on how to develop more complex (or simple) environments can be found in the following links:
-
The Lua environment cookbook🧑🍳. A collection of examples demonstrating how to accomplish many tasks from Lua mods in a Craftium environment.
-
Craftium's environment implementations 🤖. These are located in the
craftium-envsdirectory of the repo. -
Luanti's modding book 📖: the best guide on how to create Luanti mods.
-
Luanti's API reference 🔎. Contains all the available functionalities, and their documentation, of the Lua API.