Added support for multiple schematic formats

This commit is contained in:
zml2008 2012-03-28 11:05:52 -07:00
parent 1bd8764090
commit 7812dd6a09
6 changed files with 629 additions and 326 deletions

View File

@ -19,19 +19,13 @@
package com.sk89q.worldedit; package com.sk89q.worldedit;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.blocks.*; import com.sk89q.worldedit.blocks.*;
import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.data.*;
import java.io.File; import com.sk89q.worldedit.schematic.SchematicFormat;
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 java.io.File;
import java.io.IOException;
/** /**
* The clipboard remembers the state of a cuboid region. * The clipboard remembers the state of a cuboid region.
* *
@ -316,6 +310,18 @@ public class CuboidClipboard {
return data[pos.getBlockX()][pos.getBlockY()][pos.getBlockZ()]; 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. * Get the size of the copy.
* *
@ -332,77 +338,9 @@ public class CuboidClipboard {
* @throws IOException * @throws IOException
* @throws DataException * @throws DataException
*/ */
@Deprecated
public void saveSchematic(File path) throws IOException, DataException { public void saveSchematic(File path) throws IOException, DataException {
int width = getWidth(); SchematicFormat.MCEDIT.save(this, path);
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<String, Tag> schematic = new HashMap<String, Tag>();
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<Tag> tileEntities = new ArrayList<Tag>();
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<String, Tag> 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<Tag>()));
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();
} }
/** /**
@ -413,177 +351,10 @@ public class CuboidClipboard {
* @throws DataException * @throws DataException
* @throws IOException * @throws IOException
*/ */
@Deprecated
public static CuboidClipboard loadSchematic(File path) public static CuboidClipboard loadSchematic(File path)
throws DataException, IOException { throws DataException, IOException {
FileInputStream stream = new FileInputStream(path); return SchematicFormat.MCEDIT.load(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<String, Tag> 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<Tag> tileEntities = (List<Tag>)getChildTag(schematic, "TileEntities", ListTag.class)
.getValue();
Map<BlockVector, Map<String, Tag>> tileEntitiesMap =
new HashMap<BlockVector, Map<String, Tag>>();
for (Tag tag : tileEntities) {
if (!(tag instanceof CompoundTag)) continue;
CompoundTag t = (CompoundTag) tag;
int x = 0;
int y = 0;
int z = 0;
Map<String, Tag> values = new HashMap<String, Tag>();
for (Map.Entry<String, Tag> 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 extends Tag> T getChildTag(Map<String, Tag> items, String key,
Class<T> 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);
} }
/** /**

View File

@ -72,17 +72,17 @@ public class WorldEdit {
/** /**
* Interface to the server. * Interface to the server.
*/ */
private ServerInterface server; private final ServerInterface server;
/** /**
* Configuration. This is a subclass. * Configuration. This is a subclass.
*/ */
private LocalConfiguration config; private final LocalConfiguration config;
/** /**
* List of commands. * List of commands.
*/ */
private CommandsManager<LocalPlayer> commands; private final CommandsManager<LocalPlayer> commands;
/** /**
* Stores a list of WorldEdit sessions, keyed by players' names. Sessions * 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 * without any WorldEdit abilities or never use WorldEdit in a session will
* not have a session object generated for them. * not have a session object generated for them.
*/ */
private HashMap<String, LocalSession> sessions = new HashMap<String, LocalSession>(); private final HashMap<String, LocalSession> sessions = new HashMap<String, LocalSession>();
/** /**
* Initialize statically. * Initialize statically.
@ -185,20 +185,24 @@ public class WorldEdit {
commands.setInjector(new SimpleInjector(this)); commands.setInjector(new SimpleInjector(this));
server.onCommandRegistration(commands.registerAndReturn(BiomeCommands.class), commands); reg(BiomeCommands.class);
server.onCommandRegistration(commands.registerAndReturn(ChunkCommands.class), commands); reg(ChunkCommands.class);
server.onCommandRegistration(commands.registerAndReturn(ClipboardCommands.class), commands); reg(ClipboardCommands.class);
server.onCommandRegistration(commands.registerAndReturn(GeneralCommands.class), commands); reg(GeneralCommands.class);
server.onCommandRegistration(commands.registerAndReturn(GenerationCommands.class), commands); reg(GenerationCommands.class);
server.onCommandRegistration(commands.registerAndReturn(HistoryCommands.class), commands); reg(HistoryCommands.class);
server.onCommandRegistration(commands.registerAndReturn(NavigationCommands.class), commands); reg(NavigationCommands.class);
server.onCommandRegistration(commands.registerAndReturn(RegionCommands.class), commands); reg(RegionCommands.class);
server.onCommandRegistration(commands.registerAndReturn(ScriptingCommands.class), commands); reg(ScriptingCommands.class);
server.onCommandRegistration(commands.registerAndReturn(SelectionCommands.class), commands); reg(SelectionCommands.class);
server.onCommandRegistration(commands.registerAndReturn(SnapshotUtilCommands.class), commands); reg(SnapshotUtilCommands.class);
server.onCommandRegistration(commands.registerAndReturn(ToolUtilCommands.class), commands); reg(ToolUtilCommands.class);
server.onCommandRegistration(commands.registerAndReturn(ToolCommands.class), commands); reg(ToolCommands.class);
server.onCommandRegistration(commands.registerAndReturn(UtilityCommands.class), commands); reg(UtilityCommands.class);
}
private void reg(Class<?> clazz) {
server.onCommandRegistration(commands.registerAndReturn(clazz), commands);
} }
/** /**

View File

@ -26,6 +26,8 @@ import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.Logging; import com.sk89q.minecraft.util.commands.Logging;
import static com.sk89q.minecraft.util.commands.Logging.LogMode.*; import static com.sk89q.minecraft.util.commands.Logging.LogMode.*;
import com.sk89q.minecraft.util.commands.NestedCommand;
import com.sk89q.worldedit.*; import com.sk89q.worldedit.*;
import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BlockID; import com.sk89q.worldedit.blocks.BlockID;
@ -186,78 +188,36 @@ public class ClipboardCommands {
aliases = { "/load" }, aliases = { "/load" },
usage = "<filename>", usage = "<filename>",
desc = "Load a schematic into your clipboard", desc = "Load a schematic into your clipboard",
min = 1, min = 0,
max = 1 max = 1
) )
@Deprecated
@CommandPermissions("worldedit.clipboard.load") @CommandPermissions("worldedit.clipboard.load")
public void load(CommandContext args, LocalSession session, LocalPlayer player, public void load(CommandContext args, LocalSession session, LocalPlayer player,
EditSession editSession) throws WorldEditException { EditSession editSession) throws WorldEditException {
player.printError("This command is no longer used. See //schematic load.");
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());
}
} }
@Command( @Command(
aliases = { "/save" }, aliases = { "/save" },
usage = "<filename>", usage = "<filename>",
desc = "Save a schematic into your clipboard", desc = "Save a schematic into your clipboard",
min = 1, min = 0,
max = 1 max = 1
) )
@Deprecated
@CommandPermissions("worldedit.clipboard.save") @CommandPermissions("worldedit.clipboard.save")
public void save(CommandContext args, LocalSession session, LocalPlayer player, public void save(CommandContext args, LocalSession session, LocalPlayer player,
EditSession editSession) throws WorldEditException { EditSession editSession) throws WorldEditException {
player.printError("This command is no longer used. See //schematic save.");
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 { @Command(
// Create parent directories aliases = { "/schematic", "/schem"},
File parent = f.getParentFile(); desc = "Schematic-related commands"
if (parent != null && !parent.exists()) { )
parent.mkdirs(); @NestedCommand(SchematicCommands.class)
} public void schematic() {}
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());
}
}
@Command( @Command(
aliases = { "clearclipboard" }, aliases = { "clearclipboard" },

View File

@ -0,0 +1,159 @@
/*
* WorldEdit
* Copyright (C) 2012 sk89q <http://www.sk89q.com> 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 <http://www.gnu.org/licenses/>.
*/
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 = "<format> <filename>",
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 = "<format> <filename>",
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());
}
}
}

View File

@ -0,0 +1,269 @@
/*
* WorldEdit
* Copyright (C) 2012 sk89q <http://www.sk89q.com> 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 <http://www.gnu.org/licenses/>.
*/
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<String, Tag> 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<Tag> tileEntities = getChildTag(schematic, "TileEntities", ListTag.class)
.getValue();
Map<BlockVector, Map<String, Tag>> tileEntitiesMap =
new HashMap<BlockVector, Map<String, Tag>>();
for (Tag tag : tileEntities) {
if (!(tag instanceof CompoundTag)) continue;
CompoundTag t = (CompoundTag) tag;
int x = 0;
int y = 0;
int z = 0;
Map<String, Tag> values = new HashMap<String, Tag>();
for (Map.Entry<String, Tag> 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<String, Tag> schematic = new HashMap<String, Tag>();
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<Tag> tileEntities = new ArrayList<Tag>();
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<String, Tag> 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<Tag>()));
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 extends Tag> T getChildTag(Map<String, Tag> items, String key,
Class<T> 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);
}
}

View File

@ -0,0 +1,140 @@
/*
* WorldEdit
* Copyright (C) 2012 sk89q <http://www.sk89q.com> 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 <http://www.gnu.org/licenses/>.
*/
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<String, SchematicFormat> SCHEMATIC_FORMATS = new HashMap<String, SchematicFormat>();
public static final SchematicFormat MCEDIT = new MCEditSchematicFormat();
public static Set<SchematicFormat> getFormats() {
return Collections.unmodifiableSet(new HashSet<SchematicFormat>(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<String> registeredLookupNames = new ArrayList<String>(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;
}