How to Load and Save Schematics with the WorldEdit API

Posted on Mon 03 December 2018 in Minecraft

A few people seem to be fairly confused with the correct way of loading and saving schematics with the WorldEdit API, so I'm taking a break from my usual broadcast to give a quick guide.

Loading

Loading a schematic from a file is fairly simple now. In WorldEdit 7 the API was modified to allow other plugins to register new formats, as well as better support for multiple formats. Because of this, you can easily load a clipboard from a file without knowing what kind of schematic it is.

The ClipboardFormats file is the centre of this system, containing a few methods to help you find what format something is. You can get by file with findByFile, or get by registered alias with findByAlias.

Once you have the format of the schematic, you'll want to create an InputStream from the file (such as a FileInputStream). With that, you can call getReader(InputStream) to get a copy of a ClipboardReader, which provides read() in order to obtain a Clipboard. ClipboardReaders are Closeable, meaning they can be used in try-with-resources statements.

The following code is a complete example:

ClipboardFormat format = ClipboardFormats.findByFile(file);
try (ClipboardReader reader = format.getReader(new FileInputStream(file))) {
    Clipboard clipboard = reader.read();
}

Pasting

Once you've loaded a schematic, you'll probably want to paste it somewhere.

Pasting, like all WorldEdit operations that modify the world, requires an EditSession. This can be obtained from the EditSessionFactory provided by WorldEdit at WorldEdit.getInstance().getEditSessionFactory(). You'll want to pass it the world you want to paste into, as well as -1 to signify that you don't want to limit the number of blocks it can paste.

Next, you want to take the clipboard you have, and wrap it in a ClipboardHolder. This is done by using the constructor that takes a Clipboard. On the holder object, you can set a transform and create a PasteBuilder.

To start the pasting process, call createPaste(EditSession) with the EditSession you created earlier. There are a few options on the PasteBuilder, such as to and ignoreAirBlocks. Providing a BlockVector3 to to(BlockVector3) allows you to specify where the schematic will be pasted. The ignoreAirBlocks option lets you ignore any air blocks in the schematic when pasting, causing it to keep whatever is already in the world rather than removing it.

Once you've set the correct options on the PasteBuilder, call build to receive an Operation. Operations are what WorldEdit uses to modify the world, in this case, the operation it returns is one that pastes a clipboard. They are run using the Operations.complete(Operation) method.

It's also best practice (and in many cases required) to flush EditSessions after you're done with them. This can be done either by calling editSession.flushSession();, or by wrapping the creation of it in a try-with-resources statement.

The following code is a complete example:

try (EditSession editSession = WorldEdit.getInstance().getEditSessionFactory().getEditSession(world, -1)) {
    Operation operation = new ClipboardHolder(clipboard)
            .createPaste(editSession)
            .to(BlockVector3.at(x, y, z))
            .ignoreAirBlocks(false)
            .build();
    Operations.complete(operation);
}

Copying

To copy a structure from the world into a Clipboard, you'll need to create a CuboidRegion. Firstly, create a BlockVector3 of both the minimum and maximum points of the region, and then use them in the constructor CuboidRegion(World, BlockVector3, BlockVector3) alongside the world to get a region object.

Then create a BlockArrayClipboard, passing in the region. Next, you need to make an EditSession, the same way as explained in the section on pasting.

To actually copy this region into the clipboard, you need to use a ForwardExtentCopy. This is an operation that copies from one extent to another. In this case, from the EditSession to the BlockArrayClipboard. The constructor arguments you use should be the EditSession, the region, the clipboard, and the minimum point of the region. If you wish to copy entities you can set the setCopyingEntities method to true on the operation.

From here you can just run the operation with Operations.complete and the region area will be copied into the clipboard. From here a clipboard can be used to paste in another world or saved to a schematic.

The following code is a complete example:

CuboidRegion region = new CuboidRegion(world, min, max);
BlockArrayClipboard clipboard = new BlockArrayClipboard(region);

EditSession editSession = WorldEdit.getInstance().getEditSessionFactory().getEditSession(region.getWorld(), -1);

ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
forwardExtentCopy.setCopyingEntities(true);
Operations.complete(forwardExtentCopy);

Saving

To save a clipboard to file, you need to first choose what format you're saving in. Currently, as of writing, the Sponge Schematic Format is the recommended one. This is accessed via BuildInClipboardFormat.SPONGE_SCHEMATIC.

From here, create an OutputStream (such as a FileOutputStream) and pass it to the getWriter(OutputStream) method of the ClipboardFormat to get a writer. Simply call write(Clipboard) and pass in the clipboard to write to the file. Don't forget to close the writer, or use a try-with-resources statement, so that the changes are flushed to disk.

The following code is a complete example:

try (ClipboardWriter writer = BuiltInClipboardFormat.SPONGE_SCHEMATIC.getWriter(new FileOutputStream(file))) {
    writer.write(clipboard);
}

Register your own Schematic Format

Have your own Schematic Format you want to integrate with WorldEdit? To do this you'll need to implement the following classes:

  • ClipboardFormat
  • ClipboardReader
  • ClipboardWriter

In order to ensure that you don't interfere with other formats, make sure that the isFormat method in ClipboardFormat returns true if and only if it is definitely in the format you're implementing.

Once this has been done, simply call ClipboardFormats.registerClipboardFormat with your newly implemented format. From now on any calls to findByFile or findByAlias will include your format.

For Help

If you still need help, feel free to ask on the official EngineHub discord guild.

If you would like to support my work on these plugins, I graciously accept donations on my Patreon page. Thanks :)