From 7812dd6a09cb20bc48814715fb7135f1c4f8e657 Mon Sep 17 00:00:00 2001 From: zml2008 Date: Wed, 28 Mar 2012 11:05:52 -0700 Subject: [PATCH] Added support for multiple schematic formats --- .../com/sk89q/worldedit/CuboidClipboard.java | 269 ++---------------- .../java/com/sk89q/worldedit/WorldEdit.java | 40 +-- .../worldedit/commands/ClipboardCommands.java | 78 ++--- .../worldedit/commands/SchematicCommands.java | 159 +++++++++++ .../schematic/MCEditSchematicFormat.java | 269 ++++++++++++++++++ .../worldedit/schematic/SchematicFormat.java | 140 +++++++++ 6 files changed, 629 insertions(+), 326 deletions(-) create mode 100644 src/main/java/com/sk89q/worldedit/commands/SchematicCommands.java create mode 100644 src/main/java/com/sk89q/worldedit/schematic/MCEditSchematicFormat.java create mode 100644 src/main/java/com/sk89q/worldedit/schematic/SchematicFormat.java diff --git a/src/main/java/com/sk89q/worldedit/CuboidClipboard.java b/src/main/java/com/sk89q/worldedit/CuboidClipboard.java index 04707a2b7..2099528e3 100644 --- a/src/main/java/com/sk89q/worldedit/CuboidClipboard.java +++ b/src/main/java/com/sk89q/worldedit/CuboidClipboard.java @@ -19,19 +19,13 @@ package com.sk89q.worldedit; -import com.sk89q.jnbt.*; + import com.sk89q.worldedit.blocks.*; import com.sk89q.worldedit.data.*; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Map; -import java.util.HashMap; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.GZIPInputStream; +import com.sk89q.worldedit.schematic.SchematicFormat; +import java.io.File; +import java.io.IOException; /** * The clipboard remembers the state of a cuboid region. * @@ -316,6 +310,18 @@ public class CuboidClipboard { return data[pos.getBlockX()][pos.getBlockY()][pos.getBlockZ()]; } + /** + * Get one point in the copy. The point is relative to the origin + * of the copy (0, 0, 0) and not to the actual copy origin. + * + * @param pos + * @return null + * @throws ArrayIndexOutOfBoundsException + */ + public void setBlock(Vector pt, BaseBlock block) { + data[pt.getBlockX()][pt.getBlockY()][pt.getBlockZ()] = block; + } + /** * Get the size of the copy. * @@ -332,77 +338,9 @@ public class CuboidClipboard { * @throws IOException * @throws DataException */ + @Deprecated public void saveSchematic(File path) throws IOException, DataException { - int width = getWidth(); - int height = getHeight(); - int length = getLength(); - - if (width > 65535) { - throw new DataException("Width of region too large for a .schematic"); - } - if (height > 65535) { - throw new DataException("Height of region too large for a .schematic"); - } - if (length > 65535) { - throw new DataException("Length of region too large for a .schematic"); - } - - HashMap schematic = new HashMap(); - schematic.put("Width", new ShortTag("Width", (short) width)); - schematic.put("Length", new ShortTag("Length", (short) length)); - schematic.put("Height", new ShortTag("Height", (short) height)); - schematic.put("Materials", new StringTag("Materials", "Alpha")); - schematic.put("WEOriginX", new IntTag("WEOriginX", getOrigin().getBlockX())); - schematic.put("WEOriginY", new IntTag("WEOriginY", getOrigin().getBlockY())); - schematic.put("WEOriginZ", new IntTag("WEOriginZ", getOrigin().getBlockZ())); - schematic.put("WEOffsetX", new IntTag("WEOffsetX", getOffset().getBlockX())); - schematic.put("WEOffsetY", new IntTag("WEOffsetY", getOffset().getBlockY())); - schematic.put("WEOffsetZ", new IntTag("WEOffsetZ", getOffset().getBlockZ())); - - // Copy - byte[] blocks = new byte[width * height * length]; - byte[] blockData = new byte[width * height * length]; - ArrayList tileEntities = new ArrayList(); - - for (int x = 0; x < width; ++x) { - for (int y = 0; y < height; ++y) { - for (int z = 0; z < length; ++z) { - int index = y * width * length + z * width + x; - blocks[index] = (byte) data[x][y][z].getType(); - blockData[index] = (byte) data[x][y][z].getData(); - - // Store TileEntity data - if (data[x][y][z] instanceof TileEntityBlock) { - TileEntityBlock tileEntityBlock = - (TileEntityBlock) data[x][y][z]; - - // Get the list of key/values from the block - Map values = tileEntityBlock.toTileEntityNBT(); - if (values != null) { - values.put("id", new StringTag("id", - tileEntityBlock.getTileEntityID())); - values.put("x", new IntTag("x", x)); - values.put("y", new IntTag("y", y)); - values.put("z", new IntTag("z", z)); - CompoundTag tileEntityTag = - new CompoundTag("TileEntity", values); - tileEntities.add(tileEntityTag); - } - } - } - } - } - - schematic.put("Blocks", new ByteArrayTag("Blocks", blocks)); - schematic.put("Data", new ByteArrayTag("Data", blockData)); - schematic.put("Entities", new ListTag("Entities", CompoundTag.class, new ArrayList())); - schematic.put("TileEntities", new ListTag("TileEntities", CompoundTag.class, tileEntities)); - - // Build and output - CompoundTag schematicTag = new CompoundTag("Schematic", schematic); - NBTOutputStream stream = new NBTOutputStream(new FileOutputStream(path)); - stream.writeTag(schematicTag); - stream.close(); + SchematicFormat.MCEDIT.save(this, path); } /** @@ -413,177 +351,10 @@ public class CuboidClipboard { * @throws DataException * @throws IOException */ + @Deprecated public static CuboidClipboard loadSchematic(File path) throws DataException, IOException { - FileInputStream stream = new FileInputStream(path); - NBTInputStream nbtStream = new NBTInputStream( - new GZIPInputStream(stream)); - - Vector origin = new Vector(); - Vector offset = new Vector(); - - // Schematic tag - CompoundTag schematicTag = (CompoundTag) nbtStream.readTag(); - if (!schematicTag.getName().equals("Schematic")) { - throw new DataException("Tag \"Schematic\" does not exist or is not first"); - } - - // Check - Map schematic = schematicTag.getValue(); - if (!schematic.containsKey("Blocks")) { - throw new DataException("Schematic file is missing a \"Blocks\" tag"); - } - - // Get information - short width = getChildTag(schematic, "Width", ShortTag.class).getValue(); - short length = getChildTag(schematic, "Length", ShortTag.class).getValue(); - short height = getChildTag(schematic, "Height", ShortTag.class).getValue(); - - try { - int originX = getChildTag(schematic, "WEOriginX", IntTag.class).getValue(); - int originY = getChildTag(schematic, "WEOriginY", IntTag.class).getValue(); - int originZ = getChildTag(schematic, "WEOriginZ", IntTag.class).getValue(); - origin = new Vector(originX, originY, originZ); - } catch (DataException e) { - // No origin data - } - - try { - int offsetX = getChildTag(schematic, "WEOffsetX", IntTag.class).getValue(); - int offsetY = getChildTag(schematic, "WEOffsetY", IntTag.class).getValue(); - int offsetZ = getChildTag(schematic, "WEOffsetZ", IntTag.class).getValue(); - offset = new Vector(offsetX, offsetY, offsetZ); - } catch (DataException e) { - // No offset data - } - - // Check type of Schematic - String materials = getChildTag(schematic, "Materials", StringTag.class).getValue(); - if (!materials.equals("Alpha")) { - throw new DataException("Schematic file is not an Alpha schematic"); - } - - // Get blocks - byte[] blocks = getChildTag(schematic, "Blocks", ByteArrayTag.class).getValue(); - byte[] blockData = getChildTag(schematic, "Data", ByteArrayTag.class).getValue(); - - // Need to pull out tile entities - List tileEntities = (List)getChildTag(schematic, "TileEntities", ListTag.class) - .getValue(); - Map> tileEntitiesMap = - new HashMap>(); - - for (Tag tag : tileEntities) { - if (!(tag instanceof CompoundTag)) continue; - CompoundTag t = (CompoundTag) tag; - - int x = 0; - int y = 0; - int z = 0; - - Map values = new HashMap(); - - for (Map.Entry entry : t.getValue().entrySet()) { - if (entry.getKey().equals("x")) { - if (entry.getValue() instanceof IntTag) { - x = ((IntTag) entry.getValue()).getValue(); - } - } else if (entry.getKey().equals("y")) { - if (entry.getValue() instanceof IntTag) { - y = ((IntTag) entry.getValue()).getValue(); - } - } else if (entry.getKey().equals("z")) { - if (entry.getValue() instanceof IntTag) { - z = ((IntTag) entry.getValue()).getValue(); - } - } - - values.put(entry.getKey(), entry.getValue()); - } - - BlockVector vec = new BlockVector(x, y, z); - tileEntitiesMap.put(vec, values); - } - - Vector size = new Vector(width, height, length); - CuboidClipboard clipboard = new CuboidClipboard(size); - clipboard.setOrigin(origin); - clipboard.setOffset(offset); - - for (int x = 0; x < width; ++x) { - for (int y = 0; y < height; ++y) { - for (int z = 0; z < length; ++z) { - int index = y * width * length + z * width + x; - BlockVector pt = new BlockVector(x, y, z); - BaseBlock block; - - switch (blocks[index]) { - case BlockID.WALL_SIGN: - case BlockID.SIGN_POST: - block = new SignBlock(blocks[index], blockData[index]); - break; - - case BlockID.CHEST: - block = new ChestBlock(blockData[index]); - break; - - case BlockID.FURNACE: - case BlockID.BURNING_FURNACE: - block = new FurnaceBlock(blocks[index], blockData[index]); - break; - - case BlockID.DISPENSER: - block = new DispenserBlock(blockData[index]); - break; - - case BlockID.MOB_SPAWNER: - block = new MobSpawnerBlock(blockData[index]); - break; - - case BlockID.NOTE_BLOCK: - block = new NoteBlock(blockData[index]); - break; - - default: - block = new BaseBlock(blocks[index], blockData[index]); - break; - } - - if (block instanceof TileEntityBlock - && tileEntitiesMap.containsKey(pt)) { - ((TileEntityBlock) block).fromTileEntityNBT( - tileEntitiesMap.get(pt)); - } - - clipboard.data[x][y][z] = block; - } - } - } - - return clipboard; - } - - /** - * Get child tag of a NBT structure. - * - * @param items - * @param key - * @param expected - * @return child tag - * @throws DataException - */ - private static T getChildTag(Map items, String key, - Class expected) throws DataException { - - if (!items.containsKey(key)) { - throw new DataException("Schematic file is missing a \"" + key + "\" tag"); - } - Tag tag = items.get(key); - if (!expected.isInstance(tag)) { - throw new DataException( - key + " tag is not of tag type " + expected.getName()); - } - return expected.cast(tag); + return SchematicFormat.MCEDIT.load(path); } /** diff --git a/src/main/java/com/sk89q/worldedit/WorldEdit.java b/src/main/java/com/sk89q/worldedit/WorldEdit.java index afeae4af5..827d4ec98 100644 --- a/src/main/java/com/sk89q/worldedit/WorldEdit.java +++ b/src/main/java/com/sk89q/worldedit/WorldEdit.java @@ -72,17 +72,17 @@ public class WorldEdit { /** * Interface to the server. */ - private ServerInterface server; + private final ServerInterface server; /** * Configuration. This is a subclass. */ - private LocalConfiguration config; + private final LocalConfiguration config; /** * List of commands. */ - private CommandsManager commands; + private final CommandsManager commands; /** * Stores a list of WorldEdit sessions, keyed by players' names. Sessions @@ -91,7 +91,7 @@ public class WorldEdit { * without any WorldEdit abilities or never use WorldEdit in a session will * not have a session object generated for them. */ - private HashMap sessions = new HashMap(); + private final HashMap sessions = new HashMap(); /** * Initialize statically. @@ -185,20 +185,24 @@ public class WorldEdit { commands.setInjector(new SimpleInjector(this)); - server.onCommandRegistration(commands.registerAndReturn(BiomeCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(ChunkCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(ClipboardCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(GeneralCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(GenerationCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(HistoryCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(NavigationCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(RegionCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(ScriptingCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(SelectionCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(SnapshotUtilCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(ToolUtilCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(ToolCommands.class), commands); - server.onCommandRegistration(commands.registerAndReturn(UtilityCommands.class), commands); + reg(BiomeCommands.class); + reg(ChunkCommands.class); + reg(ClipboardCommands.class); + reg(GeneralCommands.class); + reg(GenerationCommands.class); + reg(HistoryCommands.class); + reg(NavigationCommands.class); + reg(RegionCommands.class); + reg(ScriptingCommands.class); + reg(SelectionCommands.class); + reg(SnapshotUtilCommands.class); + reg(ToolUtilCommands.class); + reg(ToolCommands.class); + reg(UtilityCommands.class); + } + + private void reg(Class clazz) { + server.onCommandRegistration(commands.registerAndReturn(clazz), commands); } /** diff --git a/src/main/java/com/sk89q/worldedit/commands/ClipboardCommands.java b/src/main/java/com/sk89q/worldedit/commands/ClipboardCommands.java index 4ba2a141c..d7c837a80 100644 --- a/src/main/java/com/sk89q/worldedit/commands/ClipboardCommands.java +++ b/src/main/java/com/sk89q/worldedit/commands/ClipboardCommands.java @@ -26,6 +26,8 @@ import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.Logging; import static com.sk89q.minecraft.util.commands.Logging.LogMode.*; + +import com.sk89q.minecraft.util.commands.NestedCommand; import com.sk89q.worldedit.*; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BlockID; @@ -34,12 +36,12 @@ import com.sk89q.worldedit.regions.Region; /** * Clipboard commands. - * + * * @author sk89q */ public class ClipboardCommands { private final WorldEdit we; - + public ClipboardCommands(WorldEdit we) { this.we = we; } @@ -54,7 +56,7 @@ public class ClipboardCommands { @CommandPermissions("worldedit.clipboard.copy") public void copy(CommandContext args, LocalSession session, LocalPlayer player, EditSession editSession) throws WorldEditException { - + Region region = session.getSelection(player.getWorld()); Vector min = region.getMinimumPoint(); Vector max = region.getMaximumPoint(); @@ -146,7 +148,7 @@ public class ClipboardCommands { @CommandPermissions("worldedit.clipboard.rotate") public void rotate(CommandContext args, LocalSession session, LocalPlayer player, EditSession editSession) throws WorldEditException { - + int angle = args.getInteger(0); if (angle % 90 == 0) { @@ -186,79 +188,37 @@ public class ClipboardCommands { aliases = { "/load" }, usage = "", desc = "Load a schematic into your clipboard", - min = 1, + min = 0, max = 1 ) + @Deprecated @CommandPermissions("worldedit.clipboard.load") public void load(CommandContext args, LocalSession session, LocalPlayer player, EditSession editSession) throws WorldEditException { - - LocalConfiguration config = we.getConfiguration(); - - String filename = args.getString(0); - File dir = we.getWorkingDirectoryFile(config.saveDir); - File f = we.getSafeOpenFile(player, dir, filename, "schematic", "schematic"); - - try { - String filePath = f.getCanonicalPath(); - String dirPath = dir.getCanonicalPath(); - - if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { - player.printError("Schematic could not read or it does not exist."); - } else { - session.setClipboard(CuboidClipboard.loadSchematic(f)); - WorldEdit.logger.info(player.getName() + " loaded " + filePath); - player.print(filename + " loaded. Paste it with //paste"); - } - } catch (DataException e) { - player.printError("Load error: " + e.getMessage()); - } catch (IOException e) { - player.printError("Schematic could not read or it does not exist: " + e.getMessage()); - } + player.printError("This command is no longer used. See //schematic load."); } @Command( aliases = { "/save" }, usage = "", desc = "Save a schematic into your clipboard", - min = 1, + min = 0, max = 1 ) + @Deprecated @CommandPermissions("worldedit.clipboard.save") public void save(CommandContext args, LocalSession session, LocalPlayer player, EditSession editSession) throws WorldEditException { - - LocalConfiguration config = we.getConfiguration(); - - String filename = args.getString(0); - - File dir = we.getWorkingDirectoryFile(config.saveDir); - File f = we.getSafeSaveFile(player, dir, filename, "schematic", "schematic"); - - if (!dir.exists()) { - if (!dir.mkdir()) { - player.printError("The storage folder could not be created."); - return; - } - } - - try { - // Create parent directories - File parent = f.getParentFile(); - if (parent != null && !parent.exists()) { - parent.mkdirs(); - } - - session.getClipboard().saveSchematic(f); - WorldEdit.logger.info(player.getName() + " saved " + f.getCanonicalPath()); - player.print(filename + " saved."); - } catch (DataException se) { - player.printError("Save error: " + se.getMessage()); - } catch (IOException e) { - player.printError("Schematic could not written: " + e.getMessage()); - } + player.printError("This command is no longer used. See //schematic save."); } + @Command( + aliases = { "/schematic", "/schem"}, + desc = "Schematic-related commands" + ) + @NestedCommand(SchematicCommands.class) + public void schematic() {} + @Command( aliases = { "clearclipboard" }, usage = "", diff --git a/src/main/java/com/sk89q/worldedit/commands/SchematicCommands.java b/src/main/java/com/sk89q/worldedit/commands/SchematicCommands.java new file mode 100644 index 000000000..2a5aa9e53 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/commands/SchematicCommands.java @@ -0,0 +1,159 @@ +/* + * WorldEdit + * Copyright (C) 2012 sk89q and contributors + * + * 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.commands; + +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LocalPlayer; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.data.DataException; +import com.sk89q.worldedit.schematic.SchematicFormat; + +import java.io.File; +import java.io.IOException; + +/** + * Commands related to schematics + * + * @see com.sk89q.worldedit.commands.ClipboardCommands#schematic() + */ +public class SchematicCommands { + private final WorldEdit we; + + public SchematicCommands(WorldEdit we) { + this.we = we; + } + + @Command( + aliases = { "load" }, + usage = " ", + desc = "Load a schematic into your clipboard", + min = 2, + max = 2 + ) + @CommandPermissions("worldedit.clipboard.load") + public void load(CommandContext args, LocalSession session, LocalPlayer player, + EditSession editSession) throws WorldEditException { + + LocalConfiguration config = we.getConfiguration(); + SchematicFormat format = SchematicFormat.getFormat(args.getString(0)); + if (format == null) { + player.printError("Unknown schematic format: " + args.getString(0)); + return; + } + + String filename = args.getString(1); + File dir = we.getWorkingDirectoryFile(config.saveDir); + File f = we.getSafeOpenFile(player, dir, filename, "schematic", "schematic"); + + try { + String filePath = f.getCanonicalPath(); + String dirPath = dir.getCanonicalPath(); + + if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { + player.printError("Schematic could not read or it does not exist."); + } else { + session.setClipboard(format.load(f)); + WorldEdit.logger.info(player.getName() + " loaded " + filePath); + player.print(filename + " loaded. Paste it with //paste"); + } + } catch (DataException e) { + player.printError("Load error: " + e.getMessage()); + } catch (IOException e) { + player.printError("Schematic could not read or it does not exist: " + e.getMessage()); + } + } + + @Command( + aliases = { "save" }, + usage = " ", + desc = "Save a schematic into your clipboard", + min = 2, + max = 2 + ) + @CommandPermissions("worldedit.clipboard.save") + public void save(CommandContext args, LocalSession session, LocalPlayer player, + EditSession editSession) throws WorldEditException { + + LocalConfiguration config = we.getConfiguration(); + SchematicFormat format = SchematicFormat.getFormat(args.getString(0)); + if (format == null) { + player.printError("Unknown schematic format: " + args.getString(0)); + return; + } + + String filename = args.getString(1); + + File dir = we.getWorkingDirectoryFile(config.saveDir); + File f = we.getSafeSaveFile(player, dir, filename, "schematic", "schematic"); + + if (!dir.exists()) { + if (!dir.mkdir()) { + player.printError("The storage folder could not be created."); + return; + } + } + + try { + // Create parent directories + File parent = f.getParentFile(); + if (parent != null && !parent.exists()) { + parent.mkdirs(); + } + + format.save(session.getClipboard(), f); + WorldEdit.logger.info(player.getName() + " saved " + f.getCanonicalPath()); + player.print(filename + " saved."); + } catch (DataException se) { + player.printError("Save error: " + se.getMessage()); + } catch (IOException e) { + player.printError("Schematic could not written: " + e.getMessage()); + } + } + + @Command( + aliases = {"formats", "listformats"}, + desc = "List available schematic formats", + max = 0 + ) + public void formats(CommandContext args, LocalSession session, LocalPlayer player, + EditSession editSession) throws WorldEditException { + player.print("Available schematic formats (Name: Lookup names)"); + StringBuilder builder; + boolean first = true; + for (SchematicFormat format : SchematicFormat.getFormats()) { + builder = new StringBuilder(); + builder.append(format.getName()).append(": "); + for (String lookupName : format.getLookupNames()) { + if (!first) { + builder.append(", "); + } + builder.append(lookupName); + first = false; + } + first = true; + player.print(builder.toString()); + } + } +} diff --git a/src/main/java/com/sk89q/worldedit/schematic/MCEditSchematicFormat.java b/src/main/java/com/sk89q/worldedit/schematic/MCEditSchematicFormat.java new file mode 100644 index 000000000..787ec9c30 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/schematic/MCEditSchematicFormat.java @@ -0,0 +1,269 @@ +/* + * WorldEdit + * Copyright (C) 2012 sk89q and contributors + * + * 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.schematic; + +import com.sk89q.jnbt.ByteArrayTag; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.IntTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.jnbt.NBTOutputStream; +import com.sk89q.jnbt.ShortTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.CuboidClipboard; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.TileEntityBlock; +import com.sk89q.worldedit.data.DataException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +/** + * @author zml2008 + */ +public class MCEditSchematicFormat extends SchematicFormat { + private static final int MAX_SIZE = Short.MAX_VALUE - Short.MIN_VALUE; + + protected MCEditSchematicFormat() { + super("MCEdit", "mcedit", "mce"); + } + + @Override + public CuboidClipboard load(File file) throws IOException, DataException { + FileInputStream stream = new FileInputStream(file); + NBTInputStream nbtStream = new NBTInputStream( + new GZIPInputStream(stream)); + + Vector origin = new Vector(); + Vector offset = new Vector(); + + // Schematic tag + CompoundTag schematicTag = (CompoundTag) nbtStream.readTag(); + if (!schematicTag.getName().equals("Schematic")) { + throw new DataException("Tag \"Schematic\" does not exist or is not first"); + } + + // Check + Map schematic = schematicTag.getValue(); + if (!schematic.containsKey("Blocks")) { + throw new DataException("Schematic file is missing a \"Blocks\" tag"); + } + + // Get information + short width = getChildTag(schematic, "Width", ShortTag.class).getValue(); + short length = getChildTag(schematic, "Length", ShortTag.class).getValue(); + short height = getChildTag(schematic, "Height", ShortTag.class).getValue(); + + try { + int originX = getChildTag(schematic, "WEOriginX", IntTag.class).getValue(); + int originY = getChildTag(schematic, "WEOriginY", IntTag.class).getValue(); + int originZ = getChildTag(schematic, "WEOriginZ", IntTag.class).getValue(); + origin = new Vector(originX, originY, originZ); + } catch (DataException e) { + // No origin data + } + + try { + int offsetX = getChildTag(schematic, "WEOffsetX", IntTag.class).getValue(); + int offsetY = getChildTag(schematic, "WEOffsetY", IntTag.class).getValue(); + int offsetZ = getChildTag(schematic, "WEOffsetZ", IntTag.class).getValue(); + offset = new Vector(offsetX, offsetY, offsetZ); + } catch (DataException e) { + // No offset data + } + + // Check type of Schematic + String materials = getChildTag(schematic, "Materials", StringTag.class).getValue(); + if (!materials.equals("Alpha")) { + throw new DataException("Schematic file is not an Alpha schematic"); + } + + // Get blocks + byte[] blocks = getChildTag(schematic, "Blocks", ByteArrayTag.class).getValue(); + byte[] blockData = getChildTag(schematic, "Data", ByteArrayTag.class).getValue(); + + // Need to pull out tile entities + List tileEntities = getChildTag(schematic, "TileEntities", ListTag.class) + .getValue(); + Map> tileEntitiesMap = + new HashMap>(); + + for (Tag tag : tileEntities) { + if (!(tag instanceof CompoundTag)) continue; + CompoundTag t = (CompoundTag) tag; + + int x = 0; + int y = 0; + int z = 0; + + Map values = new HashMap(); + + for (Map.Entry entry : t.getValue().entrySet()) { + if (entry.getKey().equals("x")) { + if (entry.getValue() instanceof IntTag) { + x = ((IntTag) entry.getValue()).getValue(); + } + } else if (entry.getKey().equals("y")) { + if (entry.getValue() instanceof IntTag) { + y = ((IntTag) entry.getValue()).getValue(); + } + } else if (entry.getKey().equals("z")) { + if (entry.getValue() instanceof IntTag) { + z = ((IntTag) entry.getValue()).getValue(); + } + } + + values.put(entry.getKey(), entry.getValue()); + } + + BlockVector vec = new BlockVector(x, y, z); + tileEntitiesMap.put(vec, values); + } + + Vector size = new Vector(width, height, length); + CuboidClipboard clipboard = new CuboidClipboard(size); + clipboard.setOrigin(origin); + clipboard.setOffset(offset); + + for (int x = 0; x < width; ++x) { + for (int y = 0; y < height; ++y) { + for (int z = 0; z < length; ++z) { + int index = y * width * length + z * width + x; + BlockVector pt = new BlockVector(x, y, z); + BaseBlock block = getBlockForId(blocks[index], blockData[index]); + + if (block instanceof TileEntityBlock && tileEntitiesMap.containsKey(pt)) { + ((TileEntityBlock) block).fromTileEntityNBT(tileEntitiesMap.get(pt)); + } + clipboard.setBlock(pt, block); + } + } + } + + return clipboard; + } + + @Override + public void save(CuboidClipboard clipboard, File file) throws IOException, DataException { + int width = clipboard.getWidth(); + int height = clipboard.getHeight(); + int length = clipboard.getLength(); + + if (width > MAX_SIZE) { + throw new DataException("Width of region too large for a .schematic"); + } + if (height > MAX_SIZE) { + throw new DataException("Height of region too large for a .schematic"); + } + if (length > MAX_SIZE) { + throw new DataException("Length of region too large for a .schematic"); + } + + HashMap schematic = new HashMap(); + schematic.put("Width", new ShortTag("Width", (short) width)); + schematic.put("Length", new ShortTag("Length", (short) length)); + schematic.put("Height", new ShortTag("Height", (short) height)); + schematic.put("Materials", new StringTag("Materials", "Alpha")); + schematic.put("WEOriginX", new IntTag("WEOriginX", clipboard.getOrigin().getBlockX())); + schematic.put("WEOriginY", new IntTag("WEOriginY", clipboard.getOrigin().getBlockY())); + schematic.put("WEOriginZ", new IntTag("WEOriginZ", clipboard.getOrigin().getBlockZ())); + schematic.put("WEOffsetX", new IntTag("WEOffsetX", clipboard.getOffset().getBlockX())); + schematic.put("WEOffsetY", new IntTag("WEOffsetY", clipboard.getOffset().getBlockY())); + schematic.put("WEOffsetZ", new IntTag("WEOffsetZ", clipboard.getOffset().getBlockZ())); + + // Copy + byte[] blocks = new byte[width * height * length]; + byte[] blockData = new byte[width * height * length]; + ArrayList tileEntities = new ArrayList(); + + for (int x = 0; x < width; ++x) { + for (int y = 0; y < height; ++y) { + for (int z = 0; z < length; ++z) { + int index = y * width * length + z * width + x; + BaseBlock block = clipboard.getPoint(new BlockVector(x, y, z)); + blocks[index] = (byte) block.getType(); + blockData[index] = (byte) block.getData(); + + // Store TileEntity data + if (block instanceof TileEntityBlock) { + TileEntityBlock tileEntityBlock = + (TileEntityBlock) block; + + // Get the list of key/values from the block + Map values = tileEntityBlock.toTileEntityNBT(); + if (values != null) { + values.put("id", new StringTag("id", + tileEntityBlock.getTileEntityID())); + values.put("x", new IntTag("x", x)); + values.put("y", new IntTag("y", y)); + values.put("z", new IntTag("z", z)); + CompoundTag tileEntityTag = + new CompoundTag("TileEntity", values); + tileEntities.add(tileEntityTag); + } + } + } + } + } + + schematic.put("Blocks", new ByteArrayTag("Blocks", blocks)); + schematic.put("Data", new ByteArrayTag("Data", blockData)); + schematic.put("Entities", new ListTag("Entities", CompoundTag.class, new ArrayList())); + schematic.put("TileEntities", new ListTag("TileEntities", CompoundTag.class, tileEntities)); + + // Build and output + CompoundTag schematicTag = new CompoundTag("Schematic", schematic); + NBTOutputStream stream = new NBTOutputStream(new FileOutputStream(file)); + stream.writeTag(schematicTag); + stream.close(); + } + + /** + * Get child tag of a NBT structure. + * + * @param items The parent tag map + * @param key The name of the tag to get + * @param expected The expected type of the tag + * @return child tag casted to the expected type + * @throws DataException if the tag does not exist or the tag is not of the expected type + */ + private static T getChildTag(Map items, String key, + Class expected) throws DataException { + + if (!items.containsKey(key)) { + throw new DataException("Schematic file is missing a \"" + key + "\" tag"); + } + Tag tag = items.get(key); + if (!expected.isInstance(tag)) { + throw new DataException( + key + " tag is not of tag type " + expected.getName()); + } + return expected.cast(tag); + } +} diff --git a/src/main/java/com/sk89q/worldedit/schematic/SchematicFormat.java b/src/main/java/com/sk89q/worldedit/schematic/SchematicFormat.java new file mode 100644 index 000000000..f9ff9717f --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/schematic/SchematicFormat.java @@ -0,0 +1,140 @@ +/* + * WorldEdit + * Copyright (C) 2012 sk89q and contributors + * + * 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.schematic; + +import com.sk89q.worldedit.CuboidClipboard; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.blocks.ChestBlock; +import com.sk89q.worldedit.blocks.DispenserBlock; +import com.sk89q.worldedit.blocks.FurnaceBlock; +import com.sk89q.worldedit.blocks.MobSpawnerBlock; +import com.sk89q.worldedit.blocks.NoteBlock; +import com.sk89q.worldedit.blocks.SignBlock; +import com.sk89q.worldedit.data.DataException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Represents a format that a schematic can be stored as + * @author zml2008 + */ +public abstract class SchematicFormat { + private static final Map SCHEMATIC_FORMATS = new HashMap(); + public static final SchematicFormat MCEDIT = new MCEditSchematicFormat(); + + public static Set getFormats() { + return Collections.unmodifiableSet(new HashSet(SCHEMATIC_FORMATS.values())); + } + + public static SchematicFormat getFormat(String lookupName) { + return SCHEMATIC_FORMATS.get(lookupName.toLowerCase()); + } + + private final String name; + private final String[] lookupNames; + + protected SchematicFormat(String name, String... lookupNames) { + this.name = name; + List registeredLookupNames = new ArrayList(lookupNames.length); + for (int i = 0; i < lookupNames.length; ++i) { + if (i == 0 || !SCHEMATIC_FORMATS.containsKey(lookupNames[i].toLowerCase())) { + SCHEMATIC_FORMATS.put(lookupNames[i].toLowerCase(), this); + registeredLookupNames.add(lookupNames[i].toLowerCase()); + } + } + this.lookupNames = registeredLookupNames.toArray(new String[registeredLookupNames.size()]); + } + + /** + * Gets the official/display name for this schematic format + * + * @return The display name for this schematic format + */ + public String getName() { + return name; + } + + public String[] getLookupNames() { + return lookupNames; + } + + public BaseBlock getBlockForId(int id, short data) { + BaseBlock block; + switch (id) { + case BlockID.WALL_SIGN: + case BlockID.SIGN_POST: + block = new SignBlock(id, data); + break; + + case BlockID.CHEST: + block = new ChestBlock(data); + break; + + case BlockID.FURNACE: + case BlockID.BURNING_FURNACE: + block = new FurnaceBlock(id, data); + break; + + case BlockID.DISPENSER: + block = new DispenserBlock(data); + break; + + case BlockID.MOB_SPAWNER: + block = new MobSpawnerBlock(id); + break; + + case BlockID.NOTE_BLOCK: + block = new NoteBlock(data); + break; + + default: + block = new BaseBlock(id, data); + break; + } + return block; + } + + /** + * Loads a schematic from the given file into a CuboidClipboard + * @param file The file to load from + * @return The CuboidClipboard containing the contents of this schematic + * @throws IOException If an error occurs while reading data + * @throws DataException if data is not in the correct format + */ + public abstract CuboidClipboard load(File file) throws IOException, DataException; + + /** + * Saves the data from the specified CuboidClipboard to the given file, overwriting any + * existing data in the file + * @param clipboard The clipboard to get data from + * @param file The file to save to + * @throws IOException If an error occurs while writing data + * @throws DataException If the clipboard has data which cannot be stored + */ + public abstract void save(CuboidClipboard clipboard, File file) throws IOException, DataException; +}