Scripting Minecraft server with Python
Summary
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