diff --git a/manifest.mf b/manifest.mf
index 5b0e0b1d4..fd6bccf0a 100644
--- a/manifest.mf
+++ b/manifest.mf
@@ -1,3 +1,3 @@
Manifest-Version: 1.0
-Class-Path: js.jar commons-lang3-3.0-beta.jar
+Class-Path: js.jar commons-lang3-3.0-beta.jar jnbt.jar
diff --git a/src/RegionClipboard.java b/src/RegionClipboard.java
index 125203558..909620d2c 100644
--- a/src/RegionClipboard.java
+++ b/src/RegionClipboard.java
@@ -17,6 +17,11 @@
* along with this program. If not, see .
*/
+import org.jnbt.*;
+import java.io.*;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
import com.sk89q.worldedit.*;
/**
@@ -46,6 +51,33 @@ public class RegionClipboard {
[max.getZ() - min.getZ() + 1];
}
+ /**
+ * Get the width (X-direction) of the clipboard.
+ *
+ * @return
+ */
+ public int getWidth() {
+ return max.getX() - min.getX() + 1;
+ }
+
+ /**
+ * Get the length (Z-direction) of the clipboard.
+ *
+ * @return
+ */
+ public int getLength() {
+ return max.getZ() - min.getZ() + 1;
+ }
+
+ /**
+ * Get the height (Y-direction) of the clipboard.
+ *
+ * @return
+ */
+ public int getHeight() {
+ return max.getY() - min.getY() + 1;
+ }
+
/**
* Copy to the clipboard.
*
@@ -70,16 +102,17 @@ public class RegionClipboard {
* @param noAir True to not paste air
*/
public void paste(EditSession editSession, Point newOrigin, boolean noAir) {
- int xs = max.getX() - min.getX();
- int ys = max.getY() - min.getY();
- int zs = max.getZ() - min.getZ();
+ int xs = getWidth();
+ int ys = getHeight();
+ int zs = getLength();
+
int offsetX = min.getX() - origin.getX() + newOrigin.getX();
int offsetY = min.getY() - origin.getY() + newOrigin.getY();
int offsetZ = min.getZ() - origin.getZ() + newOrigin.getZ();
for (int x = 0; x < xs; x++) {
- for (int y = 0; y <= ys; y++) {
- for (int z = 0; z <= zs; z++) {
+ for (int y = 0; y < ys; y++) {
+ for (int z = 0; z < zs; z++) {
if (noAir && data[x][y][z] == 0) { continue; }
editSession.setBlock(x + offsetX, y + offsetY, z + offsetZ,
@@ -88,4 +121,115 @@ public class RegionClipboard {
}
}
}
+
+ /**
+ * Saves the clipboard data to a .schematic-format file.
+ *
+ * @param path
+ * @throws IOException
+ */
+ public void saveSchematic(String path) throws IOException {
+ int xs = getWidth();
+ int ys = getHeight();
+ int zs = getLength();
+
+ HashMap schematic = new HashMap();
+ schematic.put("Width", new ShortTag("Width", (short)xs));
+ schematic.put("Length", new ShortTag("Length", (short)zs));
+ schematic.put("Height", new ShortTag("Height", (short)ys));
+ schematic.put("Materials", new StringTag("Materials", "Alpha"));
+
+ // Copy blocks
+ byte[] blocks = new byte[xs * ys * zs];
+ for (int x = 0; x < xs; x++) {
+ for (int y = 0; y < ys; y++) {
+ for (int z = 0; z < zs; z++) {
+ int index = y * xs * zs + z * xs + x;
+ blocks[index] = (byte)data[x][y][z];
+ }
+ }
+ }
+ schematic.put("Blocks", new ByteArrayTag("Blocks", blocks));
+
+ // Current data is not supported
+ byte[] data = new byte[xs * ys * zs];
+ schematic.put("Data", new ByteArrayTag("Data", data));
+
+ // These are not stored either
+ schematic.put("Entities", new ListTag("Entities", CompoundTag.class, new ArrayList()));
+ schematic.put("TileEntities", new ListTag("TileEntities", CompoundTag.class, new ArrayList()));
+
+ // Build and output
+ CompoundTag schematicTag = new CompoundTag("Schematic", schematic);
+ NBTOutputStream stream = new NBTOutputStream(new FileOutputStream(path));
+ stream.writeTag(schematicTag);
+ stream.close();
+ }
+
+ /**
+ * Load a .schematic file into a clipboard.
+ *
+ * @param path
+ * @param origin
+ * @return
+ * @throws SchematicLoadException
+ * @throws IOException
+ */
+ public static RegionClipboard loadSchematic(String path, Point origin)
+ throws SchematicLoadException, IOException {
+ FileInputStream stream = new FileInputStream(path);
+ NBTInputStream nbtStream = new NBTInputStream(stream);
+ CompoundTag schematicTag = (CompoundTag)nbtStream.readTag();
+ if (!schematicTag.getName().equals("Schematic")) {
+ throw new SchematicLoadException("Tag \"Schematic\" does not exist or is not first");
+ }
+ Map schematic = schematicTag.getValue();
+ if (!schematic.containsKey("Blocks")) {
+ throw new SchematicLoadException("Schematic file is missing a \"Blocks\" tag");
+ }
+ short xs = (Short)getChildTag(schematic, "Width", ShortTag.class).getValue();
+ short zs = (Short)getChildTag(schematic, "Length", ShortTag.class).getValue();
+ short ys = (Short)getChildTag(schematic, "Height", ShortTag.class).getValue();
+ String materials = (String)getChildTag(schematic, "Materials", StringTag.class).getValue();
+ if (!materials.equals("Alpha")) {
+ throw new SchematicLoadException("Schematic file is not an Alpha schematic");
+ }
+ byte[] blocks = (byte[])getChildTag(schematic, "Blocks", ByteArrayTag.class).getValue();
+
+ Point min = new Point(
+ origin.getX(),
+ origin.getY(),
+ origin.getZ()
+ );
+ Point max = new Point(
+ origin.getX() + xs - 1,
+ origin.getY() + ys - 1,
+ origin.getZ() + zs - 1
+ );
+ RegionClipboard clipboard = new RegionClipboard(min, max, origin);
+
+ for (int x = 0; x < xs; x++) {
+ for (int y = 0; y < ys; y++) {
+ for (int z = 0; z < zs; z++) {
+ int index = y * xs * zs + z * xs + x;
+ clipboard.data[x][y][z] = blocks[index];
+ }
+ }
+ }
+
+ return clipboard;
+ }
+
+ private static Tag getChildTag(Map items, String key, Class expected)
+ throws SchematicLoadException {
+ if (!items.containsKey(key)) {
+ throw new SchematicLoadException("Schematic file is missing a \"" + key + "\" tag");
+ }
+ Tag tag = items.get(key);
+ if (!expected.isInstance(tag)) {
+ throw new SchematicLoadException(
+ key + " tag is not of tag type " + expected.getName());
+ }
+ return tag;
+ }
}
diff --git a/src/WorldEdit.java b/src/WorldEdit.java
index 2cf023ea4..bdaa7f44e 100644
--- a/src/WorldEdit.java
+++ b/src/WorldEdit.java
@@ -61,6 +61,8 @@ public class WorldEdit extends Plugin {
commands.put("/editcopy", "Copies the currently selected region");
commands.put("/editpaste", "Pastes the clipboard");
commands.put("/editpasteair", "Pastes the clipboard (with air)");
+ commands.put("/editload", "[Filename] - Load .schematic into clipboard");
+ commands.put("/editsave", "[Filename] - Save clipboard to .schematic");
commands.put("/editfill", " - Fill a hole");
commands.put("/editscript", "[Filename] - Run an editscript");
}
@@ -338,6 +340,72 @@ public class WorldEdit extends Plugin {
return true;
+ // Load .schematic to clipboard
+ } else if (split[0].equalsIgnoreCase("/editload")) {
+ checkArgs(split, 1);
+ String filename = split[1].replace("\0", "") + ".schematic";
+ File dir = new File("schematics");
+ File f = new File("schematics", filename);
+
+ try {
+ String filePath = f.getCanonicalPath();
+ String dirPath = dir.getCanonicalPath();
+
+ if (!filePath.substring(0, dirPath.length()).equals(dirPath)) {
+ player.sendMessage(Colors.Rose + "Schematic could not read or it does not exist.");
+ } else {
+ int cx = (int)Math.floor(player.getX());
+ int cy = (int)Math.floor(player.getY());
+ int cz = (int)Math.floor(player.getZ());
+ Point origin = new Point(cx, cy, cz);
+ session.setClipboard(RegionClipboard.loadSchematic(filePath, origin));
+ logger.log(Level.INFO, player.getName() + " loaded " + filePath);
+ player.sendMessage(Colors.LightPurple + filename + " loaded.");
+ }
+ } catch (SchematicLoadException e) {
+ player.sendMessage(Colors.Rose + "Load error: " + e.getMessage());
+ } catch (IOException e) {
+ player.sendMessage(Colors.Rose + "Schematic could not read or it does not exist.");
+ }
+
+ return true;
+
+ // Save clipboard to .schematic
+ } else if (split[0].equalsIgnoreCase("/editsave")) {
+ if (session.getClipboard() == null) {
+ player.sendMessage(Colors.Rose + "Nothing is in your clipboard.");
+ return true;
+ }
+
+ checkArgs(split, 1);
+ String filename = split[1].replace("\0", "") + ".schematic";
+ File dir = new File("schematics");
+ File f = new File("schematics", filename);
+
+ if (!dir.exists()) {
+ if (!dir.mkdir()) {
+ player.sendMessage(Colors.Rose + "A schematics/ folder could not be created.");
+ return true;
+ }
+ }
+
+ try {
+ String filePath = f.getCanonicalPath();
+ String dirPath = dir.getCanonicalPath();
+
+ if (!filePath.substring(0, dirPath.length()).equals(dirPath)) {
+ player.sendMessage(Colors.Rose + "Invalid path for Schematic.");
+ } else {
+ session.getClipboard().saveSchematic(filePath);
+ logger.log(Level.INFO, player.getName() + " saved " + filePath);
+ player.sendMessage(Colors.LightPurple + filename + " saved.");
+ }
+ } catch (IOException e) {
+ player.sendMessage(Colors.Rose + "Schematic could not written.");
+ }
+
+ return true;
+
// Run an editscript
} else if (split[0].equalsIgnoreCase("/editscript")) {
checkArgs(split, 1);
diff --git a/src/com/sk89q/worldedit/SchematicLoadException.java b/src/com/sk89q/worldedit/SchematicLoadException.java
new file mode 100644
index 000000000..bd1a63c79
--- /dev/null
+++ b/src/com/sk89q/worldedit/SchematicLoadException.java
@@ -0,0 +1,30 @@
+// $Id$
+/*
+ * WorldEdit
+ * Copyright (C) 2010 sk89q
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+package com.sk89q.worldedit;
+
+/**
+ *
+ * @author Albert
+ */
+public class SchematicLoadException extends Exception {
+ public SchematicLoadException(String error) {
+ super(error);
+ }
+}