Hacking Minecraft with Python
Calvin Robinson creates a custom toolbox for quickly manipulating a Minecraft environment, complete with graphical user interface.
Calvin Robinson creates a custom toolbox for quickly manipulating a Minecraft environment, complete with GUI.
Using Python, in this tutorial we’re going to hook directly into Minecraft to edit our player world and the characters within in it, all using Python. While Python 3 is an incredibly versatile high-level programming language, as you may know Minecraft was originally developed using Java, so we’re going to have to install a few add-ons to be able to hook directly into
Minecraft with Python.
Fortunately, Minecraft’s developers Mojang has released a version specifically for the Raspberry Pi – the Minecraft Pi Edition – that comes with an API for budding programmers, so we’re going to take advantage of this. The following tutorial will work whether you’re using a Raspberry Pi or a regular desktop variety of Linux.
We’ve put together a package that allows Python to hook into Minecraft, called Mcpifomo (http:// rogerthat.co.uk/mcpifomo.rar). Simply extract the contents of its .minecraft directory into your ~/home/.minecraft directory and you’re good to go.
Mcpifomo includes MCPIPY (from http://mcpipy. wordpress.com) –a Minecraft Pi Python plug-in – and Raspberry Jam (http://alexanderpruss.blogspot. com), which uses Forge to enable the Mcpipy plugin to work on regular Linux Minecraft. Provided you have Python installed, which is pretty standard on most distros, no additional software is required other than your favourite text editor or Python IDLE.
Python scripts in this tutorial should always be saved in ~/home/.minecraft/mcpipy/, regardless of whether you’re running Minecraft Pi Edition or Linux
Minecraft. Make sure you run Minecraft with the ‘Forge 1.8’ profile included in Mcpifomo for your scripts to work correctly.
World of GUI
‘guizero’ is a quick and easy Python 3 library that enables inexperienced programmers to get a GUI up and running. Essentially it taps into tkinter, which can certainly take some getting used to. Download guizero from Github (https://lawsie.github.io/guizero) and extract the guizero directory into ~/home/. minecraft/mcpipy/ and we’re ready to start. Create a new Python file in ~/home/.minecraft/ mcpipy/ and import the two libraries we’re going to be using, namely guizero and Minecraft , and create a new instance of the Minecraft function: from guizero import App, Pushbutton, Slider, Text, Window, Textbox, Picture from mc import * mc = Minecraft()
Now let’s set up the main window of our application: app = App(title=”minecraft Hax”, width=456, height=420, layout=”grid”, bg = (155, 155, 250))
Here app is obviously the variable we’ll be using to refer to this window going forward (the primary window in guizero is always referred to as the app). Use whatever title you like, and set your preferences for width/height. bg is the background colour and can use hexadecimal colour codes (prefaced with a # ) or RGB as above. We’re using what’s known as a grid layout, which will become clear as we add objects.
As a starting point, let’s create a button to gift our player character a diamond block. In guizero we add a new object of the type Pushbutton , specifying the window in which it should appear (in this case app ), a command for the button (this will be a function we’ll get to soon), the text to display on the button (a string – alter accordingly) and the width of the button. The grid
location x/y is quite literally the column/row at which the button will appear in that given window.
givediamond = Pushbutton(app, command=drawdiamond, text=”diamond”, width=10, grid=[1,2])
In order for our button to do anything, we’ll need to create a function further up in our code. Here we’re using our first bit of Minecraft code:
def drawdiamond(): playerpos = mc.player.getpos() mc.setblock(playerpos.x, playerpos.y + 1, playerpos.z-1, 57) mc.posttochat(“diamond granted to player.”)
To ensure the diamond block is placed near our player we first find their coordinates, then use setblock
to place the block very near to those coordinates, give or take a space. 57 in this case is the code for a diamond block; we could use any blockid here. Google is your friend when it comes to finding these. We’ve also taken the liberty of posting a message in the game chat to let players know that a diamond block has been placed; this is great for testing purposes as we can see if our button is functioning even if we can’t find the block we’re placing.
Right at the bottom of our program we’ll want to display the actual window for when the program is run: app.display()
Enter the Minecraft
In order to test the program we’ll first need to open
Minecraft with the Forge 1.8 profile, start a new game world and then run the Python script. We’ve found it helps to create a ‘superflat’ world for testing purposes. If all goes well, when we press the diamond button we should see a diamond appear in front of our player character and a message in chat saying “Diamond granted to player”. If you don’t see the block you may want to play around with the x/y/z coordinates in the button function.
You can run the script from the Python IDLE, or by typing python filename.py directly in Minecraft, provided it’s saved in the /mcpipy directory.
Now that we’ve got a working button we’ll probably want to make things look a little more user-friendly. After all, that’s the point in having a graphical user interface. Above and below our givediamond button (which was in column 1, row 2) we’re going to add an icon and a status box. In the following code we’ve placed a picture in column 1 row 1, and a text box in column 1 row 3, sandwiching our original button: givediamondpic = Picture(app, image=”diamond.png”, grid=[1,1]) givendiamond = Textbox(app, text=””, width=10, grid=[1,3])
An image named diamond.png will need to be saved in the same directory as our .py file, of course. You can draw this yourself or find a Minecraft diamond image on Google. The text box will become a status box of sorts: we can add the following line of code to our
drawdiamond() function to change the status of the text box when the button is pressed, thus ensuring we have a visible alert in our app.
givendiamondbox.value = “Granted”
Granting more goodies in-game is simply a case of copying the button, picture, text box and function to duplicate this setup to add a row of buttons for granting different items in-game: def drawgold(): playerpos = mc.player.getpos() mc.setblock(playerpos.x, playerpos.y + 1, playerpos.z-1, 41) mc.posttochat(“gold granted to player.”) givengoldbox.value = “Granted” def drawemerald(): playerpos = mc.player.getpos() mc.setblock(playerpos.x, playerpos.y + 1, playerpos.z-1, 133) mc.posttochat(“emerald granted to player.”) givenemeraldbox.value = “Granted” givegoldpic = Picture(app, image=”gold.png”, grid=[2,1]) giveemeraldpic = Picture(app, image=”emerald.png”, grid=[3,1]) givegoldbut = Pushbutton(app, command=drawgold, text=”gold”, width=10, grid=[2,2]) giveemeraldbut = Pushbutton(app, command=drawemerald, text=”emerald”, width=10, grid=[3,2]) givengoldbox = Textbox(app, text=””, width=10, grid=[2,3]) givenemeraldbox = Textbox(app, text=””, width=10, grid=[3,3])
While having fancy buttons to instantly spawn items in-game is great fun, we might also want to occasionally
spawn a custom item that we have no pre-made button for. In the following code we’re having to take a few extra steps. By adding another text box we can enable the user to input a specific Minecraft itemid/blockid.
In the following function we’re storing the value of said textbox in a variable named itemid when the ‘Spawn’ button is pressed. We’re then granting that item in-game and printing a message in chat as before. However, we’ve had to convert the whole message into a string ( giveitemstring ) before posting it to chat, because the Minecraft API only allows one input. def giveitem(): playerpos = mc.player.getpos() itemid = giveitembox.value mc.setblock(playerpos.x, playerpos.y + 1, playerpos.z-1, int(itemid)) giveitemstring = “Itemid %s granted to player.” % itemid mc.posttochat(giveitemstring) giveitembox.value = “Granted” giveitemspic = Picture(app, image=”diamondhammer. png”, width=50, height=50, grid=[1,5]) giveitemmsg = Text(app, text=”custom item:”, width=10, grid=[1,6]) giveitembox = Textbox(app, text=”itemid”, width=10, grid=[1,7]) giveitembutton = Pushbutton(app, text=”spawn”, command=giveitem, width=10, grid=[1,8])
Automatic for the People
Spawning items in game at the touch of a button can be incredibly time-saving. If you want to build some big rudimentary structures though, you’re still going to have to place blocks manually into the world. That is, unless we add an auto-builder to our haxy app…
We’re going to need a new window for this one. Define a second window immediately after our original app / App definition:
autobuilderwindow = Window(app, title=”minecraft Hax: Auto Builder”, width=180, height=160, layout=”grid”, bg = (155, 155, 250)) autobuilderwindow.hide()
Notice we set the window to hide by default – we’ll create a button and function to ‘show’ it. In guizero our default app is shown with display() , whereas all additional windows use show() :
def abwindowopen(): autobuilderwindow.show() openabbutton = Pushbutton(app, text=”auto Builder”, command=abwindowopen, width=10, grid=[2,7])
Now for the actual functionality of the auto-builder. This is the complicated bit. We’re essentially taking a value for height, width and depth, then creating a vector from the coordinates with the given blockid. Notice we’re using setblocks instead of the singular setblock : this is the command that enables us to give a starting point and end point for our vector build, from x,y,z to x2,y2,z2. We’ve set the first values to be our player’s position, and the second set of values to be taken from the user input. def autobuilder(): playerpos = mc.player.getpos() playerpos.y = playerpos.y + 10 mc.player.setpos(playerpos.x,playerpos.y,playerpos.z) height = float(abheighttb.value) width = float(abwidthtb.value) depth = float(abdepthtb.value) blockid = float(abblocktb.value) mc.setblocks(playerpos.x, playerpos.y, playerpos.z, playerpos.y + height, playerpos.x + width, playerpos.z + depth, blockid)
abdone.value = “Built” abheightt = Text(autobuilderwindow, text=”height: “, grid=[1,1]) abheighttb = Textbox(autobuilderwindow, text=” “, grid=[2,1]) abwidtht = Text(autobuilderwindow, text=”width: “, grid=[1,3]) abwidthtb = Textbox(autobuilderwindow, text=” “, grid=[2,3]) abdeptht = Text(autobuilderwindow, text=”depth: “, grid=[1,4]) abdepthtb = Textbox(autobuilderwindow, text=” “, grid=[2,4]) abblockt = Text(autobuilderwindow, text=”blockid: “, grid=[1,5]) abblocktb = Textbox(autobuilderwindow, text=” “, grid=[2,5]) abbutton = Pushbutton(autobuilderwindow, text=”build”, command=autobuilder, grid=[2,7]) abdone = Textbox(autobuilderwindow, text=” “, grid=[2,8])
Admin tool
Since we’ve created what amounts to an admin tool, it only makes sense for us to provide an admin chat option. Let’s set up another window for this, so as to keep the main app tidy. Again, remember to set the new window as hidden by default, and adding a button and function to open it: chatwindow = Window(app, title=”minecraft Hax: Chat”, width=456, height=185, layout=”grid”, bg = (155, 155, 250)) chatwindow.hide() def chatwindowopen():
chatwindow.show() openchatbutton = Pushbutton(app, text=”chat”, command=chatwindowopen, width=10, grid=[2,8]) The code for the chat interface and the button to submit it consists mainly of converting strings into a singular variable to post to chat. We’ve prefixed our admin messages with “Admin:” but you can alter that:
chatitemmsg = Text(chatwindow, text=”admin chat. Enter message below: “, grid=[2,1]) chatitembox = Textbox(chatwindow, text=”enter chat message here.”, multiline=true, height=5, width=56, grid=[2,2]) chatitembutton = Pushbutton(chatwindow, text=”submit”, command=adminchat, grid=[2,3]) chatitembox2 = Textbox(chatwindow, text=””, width=8, grid=[2,4]) def adminchat(): chatmsg = “Admin: “+ chatitembox.value mc.posttochat(chatmsg) chatitembox2.value = “Sent.”
A nice next step would be to provide usernames for different admins, which could be done by simply adding another text box for users to place their name into and including the value from that textbox in place of ‘Admin:’. So, something like: chatmsg = usernamebox. value +chatitembox.value .
Multiplayer camera hacking
Next up, the most haxy feature of all: viewing another player character’s camera. With the Minecraft API we can find player IDS ( mc.getplayerentityids ) and then set our camera to follow that one instead of our own. Starting with another hidden window and button to open said window: playerswindow = Window(app, title=”minecraft Hax: Players”, width=456, height=228, layout=”grid”, bg = (155, 155, 250)) playerswindow.hide() openplayersbutton = Pushbutton(app, text=”multiplayer”, command=playerswindowopen, width=10, grid=[3,8]) def playerswindowopen(): playerswindow.show() getplayers()
You may notice that we’re running the getplayers()
function the moment we open the Multiplayer window. This will grab the IDS of all currently connected players, ready to list them in a text box: def getplayers(): players = mc.getplayerentityids() playersbox.value = players
Of course, we’re also going to need to provide a method of resetting the camera back to the user’s own player character. def setcamera():
mc.camera.setfollow(players[playersubbox.value]) def resetcamera():
mc.camera.setfollow(players[1]) playersbox = Textbox(playerswindow, text=”players list”, multiline=true, height=5, width=56, grid=[2,1]) playermsg = Text(playerswindow, text=”enter a Playerid to view their camera: “, grid=[2,2]) playersubbox = Textbox(playerswindow, text=”playerid”, grid=[2,3]) setcambutton = Pushbutton(playerswindow, text=”set Camera”, command=setcamera, grid=[2,4]) resetcambutton = Pushbutton(playerswindow, text=”reset Camera”, command=resetcamera, grid=[2,5])
Note that playersbox is listening to all the connected players that we discovered when we opened the Multiplayer window. A decent next step would be to provide a way of refreshing that list, perhaps with a simple button that re-runs the getplayers() function.
Now, by typing a player ID from the list into the text box and submitting the button, the camera should change. Quite an effective little hack for spying on other players, this is also often used for ‘spectator mode’ in mini-games.
With this script pretty much complete, we can now spawn items, build structures, take over a player’s camera and launch admin chat, all at the press of a button. Feel free to mess around with the layouts and even add new functions!