Graham King

Solvitas perambulum

Scripting Minecraft server with Python

Minecraft has an API. If you run your own server you can program it from Python. Here are notes from how I set it up. It’s a lot of fun to make things happen in Minecraft with Python.

Basic server setup

I used a Digital Ocean 2GB single CPU instance, which seems plenty, with Ubuntu 20.04. There are cheaper hosting providers. Do some basic setup, initially as root user:

apt-get update
apt-get upgrade
adduser <your-username>
usermod -aG sudo <your-username> # on Ubuntu the `sudo` group gives you sudo permissions

Put your ssh key in /home/<your-username>/.ssh/authorized-keys

Allow yourself to sudo without password: sudo visduo add NOPASSWD: to the sudo group so it looks like this:

%sudo ALL=(ALL:ALL) NOPASSWD: ALL

Check you can login as the user you just created. If that works edit /etc/ssh/sshd_config to disable root login and make sure password authentication is already disabled.

Setup and Run the Minecraft server

Open the port Minecraft server listens on, and enable the firewall:

ufw allow 25565
ufw enable

Later we’ll add the API server which listens on port 4711, and you don’t want the whole Internet scripting your Minecraft server. Hence the firewall.

Minecraft is Java so install that:

sudo apt install openjdk-17-jre-headless

Instead of running the official Minecraft server, we’re going to run a variant called Paper MC. It’s very popular, faster than the offical build, and supports a wider range of plugins.

Download latest Paper MC JAR, make a directory

mkdir minecraft-server

and drop it in there.

Don’t run it from your home directory because it will create a bunch of files after it starts. I used the latest 1.18, but possibly the 1.17 would have been wiser as some plugins weren’t updated yet. Depends if there’s some specific plugins you want (if you don’t know what those are then you’re fine).

Start minecraft server:

/usr/bin/java -Xmx1536M -Xms1536M -jar Paper<version>.jar nogui

Adjust those Xmx / Xms values if you have more of less memory on your server; these are about right for a 2GB instance.

It will probably ask you to accept the EULA, so do that by editing the eula.txt it created to read eula=true. Don’t start the server again yet. Optionally also edit server.properties and change motd (message of the day) to something fun.

Add the API

The API seems to have two parts: a server part and a python library to talk to it. The server part is called Raspberry Juice (because it came from the Raspberry PI device).

Download the most recent JAR from https://github.com/zhuowei/RaspberryJuice/.

I have 1.11 (but I should probably have 1.12.1).

The client part is mcpi. Make sure you have pip (Python package manager) then install the library:

sudo apt install pip3
pip3 install mcpi

Start the server again. RaspberryJuice should create it’s directory. Stop the server.

In the Raspberry PI version of Minecraft co-ordinates are relative to the player’s spawn point, and that’s what RaspberryJuice uses by default. In Minecraft server they are not, they are absolute, so we want the API to match. Edit minecraft-server/plugins/RaspberryJuice/config.yml and change location to read

location: ABSOLUTE

Start Minecraft more permanently

We want Minecraft to keep running when you disconnect from the server. The correct way to do this is with systemd. Most people use screen. I used tmux, it works well, but do whatever you’re comfortable with. Instal tmux and start it:

sudo apt install tmux
tmux

and run Minecraft in there (the java line earlier).

Now detach from tmux by pressing Ctrl-b then d (by itself, not Ctrl-d). The server will continue running after you disconnect from ssh.

Connect and whitelist

Digital Ocean should tell you your server’s IP address. Start the Minecraft client, go to Multiplayer / Add Server and join that. To make your server more private whitelist your players' usernames after you connect. In chat type:

/whitelist add <your-minecraft-username> # for each of your players including yourself
/whitelist on

If you mess it up edit whitelist.json on the server.

Your server is setup! You can play, which sure that’s fun, but we want to code!

Code!

On the server run python3 to get the Python command line. Import the library:

from mcpi.minecraft import Minecraft
import mcpi.block as block

Connect:

mc = Minecraft.create()

Send a message in the chat:

mc.postToChat("Hello World!")

Find a list of player IDs:

mc.getPlayerEntityIds()

Find and save your player’s position - this should match what you see when you press F3 in minecraft client:

x, y, z = mc.entity.getPos(230) # The player entity ID from above

Create a block near your player:

mc.setBlock(x+1, y+1, z+1, block.DIRT)

Some of the blocks are defined in block, but for most of them you need to know their ID. I don’t think they will all work, we’re kinda hacking things here none of this is official. Some blocks have a state in which case I think you pass the id then the variant as the next parameter.

You can explore the API by typing mc.<Tab> in the Python client. It’s also documented on the github page and the API reference in depth is here.

An alternative to mcpi that I haven’t tried is picraft.

I got into all this in the first place because I bought my children Learn to Program with Minecraft. It could use an update, but it’s definitely a solid choice. Lots and lots of examples there as it gradually teaches programming.

Happy Minecraft scripting!

PS: as a bonus here’s a Python script that flattens an area around the player (to e.g. build a farm). The pauses are unnecessary but make it more fun to watch.

import time
import math
from mcpi.minecraft import Minecraft
import mcpi.block as block

def clear_pillar(lx, ly, lz):
    id = mc.getBlock(lx,ly,lz)
    while id != 0:
        mc.setBlock(lx,ly,lz, block.AIR)
        ly += 1
        id = mc.getBlock(lx,ly,lz)
        time.sleep(PAUSE)

PAUSE = 0.02
WIDTH = 5
HEIGHT = 10

mc = Minecraft.create()

start_x, start_y, start_z = mc.entity.getPos(230)
print(start_x, start_y, start_z)

x, y, z = math.floor(start_x), math.floor(start_y), math.floor(start_z)

def dirt_below():
    for xx in range(x-WIDTH, x+WIDTH):
        for zz in range(z-WIDTH, z+WIDTH):
            for yy in range(y-HEIGHT, y):
                mc.setBlock(xx, yy, zz, block.DIRT)
                time.sleep(PAUSE)

def air_above():
    for xx in range(x-WIDTH, x+WIDTH):
        for zz in range(z-WIDTH, z+WIDTH):
            for yy in range(y, y+HEIGHT):
                mc.setBlock(xx, yy, zz, block.AIR)
                time.sleep(PAUSE)

air_above()
dirt_below()

This post was featured in PyCoder’s Weekly #504