Submission Method: HTTP Server

The HTTP interface is a Minecraft Mod (based on the Forge Mod Framework), that opens an HTTP Server that can, among other things read blocks and place blocks from within Minecraft through an API. The repository is graciously developed and maintained by Nils Gawlik and Blinkenlights, members of the GDMC community. For real-time troubleshooting or for questions, go to the discord and utilize the #ℹ-framework-support channel.

Quick Info

Why we do like it?

Using HTTP requests to modify the Minecraft world allows for a new level of interaction not available in other frameworks like MCEdit. You can edit the world while logged into your world and see the changes occur in front of you.

In addition to that, HTTP also allows a single point of entry for modifying the world, making it much easier to keep up with Minecraft versions. While MCEdit is frozen in Minecraft 1.12, HTTP can support the most recent stable release of Minecraft.

Before you submit

In order to let your generator know the expected location of the settlement, we are using the build area provided by the mod. You can retrieve the build area coordinates by using the GET request 'buildarea'. We are providing an example in the ‘How it works’ section.

How to submit with this method?

  1. Login to the submission website
  2. Go to the submission page
  3. In the file section, add a ZIP file, which contains the script that will send requests to the HTTP server and any other required files necessary for your code (data files, etc). There is no need to include the server itself in your submission, since we will already have one. This will be unzipped and run by our submission server to produce your entry maps.
  4. If you have other requirements, please document them and include them with your submission. We strongly recommend using a setup.py file or including your java libraries as part of your submission to streamline the process, for example, or we may have to contact you for instructions.

Getting Started

To get started go over here and follow the install instructions. It is enough to install the jar file, there is no reason for you to build the mod from source.

You can also check out this excellent video walkthrough of setting up the interface and getting started created by Dave Churchill.

After you are done you should have a working installation of the mod. Make sure to open a Minecraft world, or else the http server will not be running.

If you are already familiar with HTTP requests it's probably best to take a look at the different endpoints over here or to jump straight to the python example further down.

The basics

HTTP is a protocol that is entirely based around requests: You make a request to the server for some data, and the you get a response back which contains that data (or an error message).

To test if everything works we can just open a web browser and visit the following url http://localhost:9000/blocks?x=0&y=0&z=0. In this example localhost:9000 is the IP and port of the server, blocks is the endpoint for accessing blocks and ?x=0&y=0&z=0 are the query parameters further specifying the request.

You should see a webpage containing the text minecraft:bedrock. This is the block at position (0, 0, 0).

While this works for simple stuff, to be able to properly play around with the interface I would recommend using an application like Postman.

In Postman a request for a block would look like this.

postman_get_block.png

If you switch the request method to PUT, you can easily place a block in the world. You specify which block to set in the request body using the "namespaced id" of the block. (You can also omit the "minecraft:" part of the id)

postman_put_block.png

There are many ways to find out the id of a block. Pressing F3+H in Minecraft will toggle a setting that shows the block id when hovering over an item in your inventory. You can also press F3 to show the debug overlay, if you look at a block the block's id will also be displayed along with it's block states.

block_id_hover.png
block_id_f3.png

Small village example in python

While you can use any programming language to access the interface, a good way to get started is the example/template project based on python that you can find here

I'm going to show some of the features by using the simple village generator (example.py) as an example.

village_generator_result.jpg

running the example

I'm assuming some familiarity with python. You can install the dependencies for this project from the requirements.txt file. It's assumed that the python version is >=3.7. You can do this by running pip install -r requirements.txt, or just take a look into the file and install the requirements manually.

In order to tell the program where to build you can specify the build area within Minecraft: Simply write this command in the Minecraft chat: /setbuildarea ~-50 0 ~-50 ~50 255 ~50 This will set the build area to the 100x100 area around the player (buildarea endpoint). It won't have any effect on block placement, but we can query this data in our client script later, so we don't have to copy-paste coordinates.

You can now run the program with python example.py, or using an IDE of your choice, if you quickly switch to your Minecraft window you should see a village appear in front of your eyes.

If the generation was to fast for you you can set USE_BATCHING to False in line 11 of example.py.

How it works

This example can be thought of as a three step process:

Step 1: Loading the world data / getting a heightmap

There is currently no easy endpoint in the interface to get large amounts of blocks and other world data. Instead there is the chunks endpoint which returns the word data in a raw nbt format.

Luckily if you work of this example you can simply use the WorldSlice class implemented in worldLoader.py, which abstracts away the parsing of this nbt data, so you don't need to worry about it.

We can get a "world slice" like this:

def requestBuildArea():
    response = requests.get('http://localhost:9000/buildarea')
    if response.ok:
        return response.json()
    else:
        print(response.text)
        return -1
 
area = (0, 0, 128, 128) # x, z, sizeX, sizeZ
 
buildArea = interfaceUtils.requestBuildArea()
if buildArea != -1:
    x1 = buildArea["xFrom"]
    z1 = buildArea["zFrom"]
    x2 = buildArea["xTo"]
    z2 = buildArea["zTo"]
    area = (x1, z1, x2 - x1, z2 - z1)
 
worldSlice = WorldSlice(area)

You can get a heightmap by using this function in mapUtils. This function automatically ignores trees.

heightmap = mapUtils.calcGoodHeightmap(worldSlice)

Or you can one of these four heightmaps, which Minecraft includes in it's chunk data:

heightmap = worldSlice.heightmaps["MOTION_BLOCKING"]
heightmap = worldSlice.heightmaps["MOTION_BLOCKING_NO_LEAVES"]
heightmap = worldSlice.heightmaps["OCEAN_FLOOR"]
heightmap = worldSlice.heightmaps["WORLD_SURFACE"]

This heightmap is a numpy array, you can access values in it like this: heightmap[x,z]

You can also visualize a heightmap using this utility function:

mapUtils.visualize(heightmap, title="heightmap")

Step 2: Building a fence

In order to place blocks we define a little helper function.

# a wrapper function for setting blocks
def setBlock(x, y, z, block):
    if USE_BATCHING:
        interfaceUtils.placeBlockBatched(x, y, z, block, 100)
    else:
        interfaceUtils.setBlock(x, y, z, block)

Here we can see the two slightly different functions in interfaceUtils. setBlock just directly sends a http request to place a block. placeBlockBatched makes use of the "setting blocks in bulk" feature of the API. This will be a lot faster, instead of sending a request directly it will add the block and it's position to a buffer. And only when it has reached the specified number of block placements (100) it will send them all at once, speeding up the process significantly.

We also define a helper function to query the height at a x,z position, this function accounts for the offset of the build area, which is otherwise easy to forget:

# define a function for easier heightmap access
# heightmap coordinates are not equal to world coordinates!
def heightAt(x, z):
    return heightmap[(x - area[0], z - area[1])]

We can now use the setBlock function to build a fence in a big rectangle around the building area. For the sake of simplicity there is a separate for-loop for every edge of the rectangle, there are of course smarter ways to do this. But this should give you an idea of how to use this function.

You can also see how we use the heightAt function to adapt to the terrain.

# build a fence around the perimeter
for x in range(area[0], area[0] + area[2]):
    z = area[1]
    y = heightAt(x, z)
    setBlock(x, y-1, z, "cobblestone")
    setBlock(x, y,   z, "oak_fence")
for z in range(area[1], area[1] + area[3]):
    x = area[0]
    y = heightAt(x, z)
    setBlock(x, y-1, z, "cobblestone")
    setBlock(x, y  , z, "oak_fence")
for x in range(area[0], area[0] + area[2]):
    z = area[1] + area[3] - 1
    y = heightAt(x, z)
    setBlock(x, y-1, z, "cobblestone")
    setBlock(x, y,   z, "oak_fence")
for z in range(area[1], area[1] + area[3]):
    x = area[0] + area[2] - 1
    y = heightAt(x, z)
    setBlock(x, y-1, z, "cobblestone")
    setBlock(x, y  , z, "oak_fence")
fence_result.png

Step 3: Building a bunch of houses

I will skip over the details of how to build the houses since it's not really relevant to the http interface.

In summary we just place a bunch of houses at random positions with randomly sized rectangular footprints. If the rectangular footprint overlaps with an already placed house we don't place it.

to build a house we query the heightmap at the four corners and use the lowest point as our base y coordinate. Then with a bunch of clever for loops we place the floor, the four walls, a roof and fill the interior with air.

The finished result will look something like this:

result_houses.png

Restrictions and Pitfalls

It's important to remember that the http interface will never be as fast as other submission methods like MCEdit, since the networking and string parsing as well as placing the blocks into the minecraft world has a significant overhead.

Don't be afraid to send bulk block placement requests, even up to bigger numbers such as a 1000 blocks. Generally Minecraft will handle them fine.

Being a network interface it makes sense to program the code somewhat defensively, since things can always go wrong. If you look at the sendBlocks function in interfaceUtils, you will see that it will retry a few times when receiving a ConnectionError. Other functions deal with ConnectionError in different ways. As far as I'm aware ConnectionErrors can happen because of timeouts on a system level. My best guess is that these timeouts are caused by the Minecraft world saving at an un-opportune moment, delaying the request. So be aware of that.

If you have the minecraft world open your block placements will interact live with blocks in the world such as flowing water, lava and falling sand. This can slow down the game as well as mess up your generation.

I experienced that certain blocks can be overridden by outstanding block updates, such as new water sources spawning. So be careful around water.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License