Repopulate a Minecraft world from the command line

From http://minecraft.gamepedia.com/Chunk_format

TerrainPopulated: 1 or not present (true/false) indicate whether the terrain in this chunk was populated with special things. (Ores, special blocks, trees, dungeons, flowers, waterfalls, etc.) If set to zero then Minecraft will regenerate these features in the blocks that already exist.

Sometimes people want to “repopulate” their worlds, i.e. set the TerrainPopulated property to False for all chunks of their world, in order to regenerate the special things mentioned above. MCEdit, a Minecraft world editor with a graphical user interface can do this. However, there is a more straight-forward solution to this task, especially if you are running a remote Minecraft server on a headless machine.

MCEdit is backed by pymclevel, a Python library for reading and modifying Minecraft worlds. It has been created by David Rio Vierra (kudos!) and is in development since 2010. Its documentation is rather weak, but its API is not too difficult to understand. I could rather quickly come up with a small Python application that reads a world, iterates through all chunks, resets the TerrainPopulated property for all of them, and saves the world back to disk. This is the code, which I just tested for our own world, but it should very well work for yours, too:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
 
"""
Copyright 2014 Jan-Philip Gehrcke (http://gehrcke.de)
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
 
"""
Re-populate a minecraft world. That is, set the TerrainPopulated property
to False for all chunks in a world.
 
From http://minecraft.gamepedia.com/Chunk_format:
 
TerrainPopulated: 1 or not present (true/false) indicate whether the terrain
in this chunk was populated with special things. (Ores, special blocks, trees,
dungeons, flowers, waterfalls, etc.) If set to zero then Minecraft will
regenerate these features in the blocks that already exist.
 
Based on the notes in
https://github.com/mcedit/pymclevel/blob/master/mclevel.py
 
and on the repop method used in
https://github.com/mcedit/mcedit/blob/master/editortools/chunk.py
 
Usage:
 
repop.py path/to/world/directory
 
A world directory is a single directory containing at least one
file named level.dat.
"""
 
 
import os
import sys
import time
import logging
 
logging.basicConfig(
    format='%(asctime)s,%(msecs)-6.1f - %(module)s:%(levelname)s: %(message)s',
    datefmt='%H:%M:%S')
log = logging.getLogger()
log.setLevel(logging.INFO)
 
 
install_note = """
$ virtualenv pyvenv
$ source pyvenv/bin/activate
$ pip install cython numpy pyyaml
$ pip install git+git://github.com/mcedit/pymclevel.git
"""
 
 
try:
    from pymclevel import mclevel
except ImportError:
    sys.exit("Cannot import pymclevel. Consider setting it up via %s" %
        install_note)
 
usage = "%s path/to/world/directory" % os.path.basename(sys.argv[0])
if not len(sys.argv) == 2:
    sys.exit("One argument is required. Usage: %s" % usage)
 
world_directory = sys.argv[1]
 
if not os.path.isdir(world_directory):
    sys.exit("Not a directory: %s" % world_directory)
 
log.info(("Attempting to read world. This scans the directory "
    "for chunks. Might take a while."))
world = mclevel.fromFile(world_directory)
 
log.info("Get chunk positions iterator.")
chunk_positions = world.allChunks
 
log.info("Iterate through chunks, set TerrainPopulated=0 for all of them.")
t0 = time.time()
for idx, (x, z) in enumerate(chunk_positions):
    if (idx + 1) % 1000 == 0:
        log.info("Processed %s chunks." % (idx + 1))
    # Retrieve an AnvilChunk object. This object will load and
    # decompress the chunk as needed, and remember whether it
    # needs to be saved or relighted.
    chunk = world.getChunk(x, z)
    chunk.TerrainPopulated = False
    # The above sets `chunk.dirty` which is processed during
    # `saveInPlace()` below. Leads to `saveChunk(cx, cz, data)`.
duration = time.time() - t0
chunks_per_sec = (idx + 1) / duration
log.info("Total number of modified chunks: %s." % (idx+1))
log.info("Duration: %.2f s. Chunks per second: %.2f." % (
    duration, chunks_per_sec))
 
# Save the level.dat and any chunks that have been marked for
# writing to disk. This also compresses any chunks marked for
# recompression.
log.info("Save modified world to disk (might take a moment).")
world.saveInPlace()
log.info("Exiting.")

I called it repop.py, and this is how it executes:

$ python repop.py schaumwelt
23:55:08,262.2  - materials:INFO: Loading block info from <open file 'pymclevel/minecraft.yaml', mode 'r' at 0x21546f0>
23:55:09,22.4   - materials:INFO: Loading block info from <open file 'pymclevel/classic.yaml', mode 'r' at 0x21546f0>
23:55:09,118.4  - materials:INFO: Loading block info from <open file 'pymclevel/indev.yaml', mode 'r' at 0x2154660>
23:55:09,233.3  - materials:INFO: Loading block info from <open file 'pymclevel/pocket.yaml', mode 'r' at 0x21546f0>
23:55:09,628.0  - repop:INFO: Attempting to read world. This scans the directory for chunks. Might take a while.
23:55:09,628.2  - mclevel:INFO: Identifying schaumwelt
23:55:09,628.5  - mclevel:INFO: Detected Infdev level.dat
23:55:09,728.0  - infiniteworld:INFO: Found dimension DIM1
23:55:09,728.5  - infiniteworld:INFO: Found dimension DIM-1
23:55:09,728.9  - infiniteworld:INFO: Found dimension DIM-17
23:55:09,729.2  - repop:INFO: Get chunk positions iterator.
23:55:09,729.3  - infiniteworld:INFO: Scanning for regions...
23:55:09,745.4  - regionfile:INFO: Found region file r.0.4.mca with 731/807 sectors used and 598 chunks present
[...]
23:55:10,672.4  - regionfile:INFO: Found region file r.-2.-1.mca with 1324/1418 sectors used and 1024 chunks present
23:55:10,674.2  - repop:INFO: Iterate through chunks, set TerrainPopulated=0 for all of them.
23:55:12,70.1   - regionfile:INFO: Found region file r.-2.1.mca with 2/2 sectors used and 0 chunks present
[...]
23:55:16,51.1   - regionfile:INFO: Found region file r.-2.3.mca with 2/2 sectors used and 0 chunks present
23:55:16,559.9  - regionfile:INFO: Found region file r.3.3.mca with 2/2 sectors used and 0 chunks present
23:55:18,69.6   - repop:INFO: Processed 1000 chunks.
23:55:18,958.6  - regionfile:INFO: Found region file r.-4.2.mca with 2/2 sectors used and 0 chunks present
23:55:21,334.1  - regionfile:INFO: Found region file r.2.-3.mca with 2/2 sectors used and 0 chunks present
23:55:26,524.2  - repop:INFO: Processed 2000 chunks.
23:55:35,573.8  - repop:INFO: Processed 3000 chunks.
23:55:44,324.8  - repop:INFO: Processed 4000 chunks.
[...]
00:00:18,820.3  - repop:INFO: Processed 35000 chunks.
00:00:28,940.9  - repop:INFO: Processed 36000 chunks.
00:00:38,30.5   - repop:INFO: Processed 37000 chunks.
00:00:41,787.5  - repop:INFO: Total number of modified chunks: 37426.
00:00:41,788.0  - repop:INFO: Duration: 331.11 s. Chunks per second: 113.03.
00:00:41,788.2  - repop:INFO: Save modified world to disk (might take a moment).
00:00:41,815.3  - infiniteworld:INFO: Saved 0 chunks (dim 1)
00:00:41,842.6  - infiniteworld:INFO: Saved 0 chunks (dim -1)
00:00:41,870.5  - infiniteworld:INFO: Saved 0 chunks (dim -17)
00:00:43,857.3  - regionfile:INFO: Found region file r.0.4.mca with 720/720 sectors used and 589 chunks present
[...]00:00:44,723.8  - regionfile:INFO: Found region file r.-2.-1.mca with 1310/1310 sectors used and 1014 chunks present
00:01:13,807.5  - infiniteworld:INFO: Saved 37426 chunks (dim 0)
00:01:13,808.0  - repop:INFO: Exiting.

Hope that helps.

  • Gust

    Hey there Jan-Philip.

    I am completely new to everything Python related, so bare with me.
    I’ve put the code into a file and installed Python. I executed it whilst in the same folder as my level.dat file. But all that happens here is the screen comes up for a split second, saying

    Cannot import pymclevel. Consider setting it up via
    $ virtualenv pyvenv
    $ source pyvenv/bin/activate
    $ pip install cython numpy pyyanl
    $ pip install git+git://github.com/mcedit/pymclevel.git

    I always try finding out what to do next when I find an error, but this time I have absolutely no clue.

    Can you tell me what I should do here?

    Thanks in advance,
    Gust

    • Hey,

      I understand the difficulties you are experiencing. However, getting you on track in this regard requires a little more than I can provide here. You know, for a certain class of people the error message I provide there is enough and they know what they need to do.

      In summary, the error message shows four commands that should be executed in order to set up an environment containing all dependencies (Python packages) that are required for running the small script I have provided here. So, this is just preparation. The goal is to have a Python interpreter that can successfully import pymclevel.

      You obviously have no real clue (no offense! :-)) about the Python ecosystem and the command line, so that is why you are lost. I do not know if you want to understand or just apply. In both cases, “googling” for the right things will get you on track.

      This script is meant to be executed on the command line. It *can* be executed on the Windows command line (cmd.exe, which is what you did without knowing, I guess). It *should* be executed using a Linux system. There, you at first should install “virtualenv” using your favorite package manager. On Ubuntu, that might look like “sudo apt-get install python-virtualenv”. After doing so, all that needs to be done is executing the four commands the quoted error message shows.

      I am very well aware that this leaves more question marks behind than you initially had, but that is how it is… bite yourself through these things, and you will learn a lot! :-)

      • Gust

        I will have to re-read this a dozen times, but will do, since I HAVE to get this done. I’ll let you know if I stumble upon another problem. But for now thanks for the script and the quick response!

      • Gust

        After a long evening with a friend of mine, and a million errors. We got the script to run, and it seemed to successfully change the chunks. However, upon loading up the world, it did not generate any trees, grass, ores, etc.
        It did generate villages!
        Do you know if something went wrong? Can the script be to blaim?

        Cheers,
        Gust