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); + } +}