mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-23 09:47:38 +00:00
Add new Clipboard-compatible .schematic reader/writer.
This commit is contained in:
parent
7b0e5a977f
commit
47ad03a013
@ -19,11 +19,14 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.command;
|
package com.sk89q.worldedit.command;
|
||||||
|
|
||||||
|
import com.google.common.io.Closer;
|
||||||
import com.sk89q.minecraft.util.commands.Command;
|
import com.sk89q.minecraft.util.commands.Command;
|
||||||
import com.sk89q.minecraft.util.commands.CommandContext;
|
import com.sk89q.minecraft.util.commands.CommandContext;
|
||||||
import com.sk89q.minecraft.util.commands.CommandException;
|
import com.sk89q.minecraft.util.commands.CommandException;
|
||||||
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
import com.sk89q.minecraft.util.commands.CommandPermissions;
|
||||||
import com.sk89q.worldedit.EditSession;
|
import com.sk89q.worldedit.EditSession;
|
||||||
|
import com.sk89q.worldedit.EmptyClipboardException;
|
||||||
|
import com.sk89q.worldedit.FilenameException;
|
||||||
import com.sk89q.worldedit.FilenameResolutionException;
|
import com.sk89q.worldedit.FilenameResolutionException;
|
||||||
import com.sk89q.worldedit.LocalConfiguration;
|
import com.sk89q.worldedit.LocalConfiguration;
|
||||||
import com.sk89q.worldedit.LocalSession;
|
import com.sk89q.worldedit.LocalSession;
|
||||||
@ -31,12 +34,25 @@ import com.sk89q.worldedit.WorldEdit;
|
|||||||
import com.sk89q.worldedit.WorldEditException;
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
import com.sk89q.worldedit.entity.Player;
|
import com.sk89q.worldedit.entity.Player;
|
||||||
import com.sk89q.worldedit.extension.platform.Actor;
|
import com.sk89q.worldedit.extension.platform.Actor;
|
||||||
import com.sk89q.worldedit.schematic.SchematicFormat;
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
|
||||||
|
import com.sk89q.worldedit.session.ClipboardHolder;
|
||||||
|
import com.sk89q.worldedit.util.command.parametric.Optional;
|
||||||
|
import com.sk89q.worldedit.world.registry.WorldData;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileFilter;
|
import java.io.FileFilter;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
@ -45,6 +61,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
*/
|
*/
|
||||||
public class SchematicCommands {
|
public class SchematicCommands {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(SchematicCommands.class.getCanonicalName());
|
||||||
private final WorldEdit worldEdit;
|
private final WorldEdit worldEdit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,36 +75,114 @@ public class SchematicCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
aliases = { "load", "l" },
|
aliases = { "load" },
|
||||||
usage = "[format] <filename>",
|
usage = "[<format>] <filename>",
|
||||||
desc = "Load a file into your clipboard",
|
desc = "Load a schematic into your clipboard",
|
||||||
help = "Load a schematic file into your clipboard\n" +
|
min = 0,
|
||||||
"Format is a format from \"//schematic formats\"\n" +
|
max = 1
|
||||||
"If the format is not provided, WorldEdit will\n" +
|
|
||||||
"attempt to automatically detect the format of the schematic",
|
|
||||||
flags = "f",
|
|
||||||
min = 1,
|
|
||||||
max = 2
|
|
||||||
)
|
)
|
||||||
@CommandPermissions({"worldedit.clipboard.load", "worldedit.schematic.load"}) // TODO: Remove 'clipboard' perm
|
@Deprecated
|
||||||
public void load(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException, CommandException {
|
@CommandPermissions({ "worldedit.clipboard.load", "worldedit.schematic.load" })
|
||||||
// TODO: Update for new clipboard
|
public void load(Player player, LocalSession session, @Optional("schematic") String formatName, String filename) throws FilenameException {
|
||||||
throw new CommandException("Needs to be re-written again");
|
LocalConfiguration config = worldEdit.getConfiguration();
|
||||||
|
|
||||||
|
File dir = worldEdit.getWorkingDirectoryFile(config.saveDir);
|
||||||
|
File f = worldEdit.getSafeOpenFile(player, dir, filename, "schematic", "schematic");
|
||||||
|
|
||||||
|
if (!f.exists()) {
|
||||||
|
player.printError("Schematic " + filename + " does not exist!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipboardFormat format = ClipboardFormat.findByAlias(formatName);
|
||||||
|
if (format == null) {
|
||||||
|
player.printError("Unknown schematic format: " + formatName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Closer closer = Closer.create();
|
||||||
|
try {
|
||||||
|
String filePath = f.getCanonicalPath();
|
||||||
|
String dirPath = dir.getCanonicalPath();
|
||||||
|
|
||||||
|
if (!filePath.substring(0, dirPath.length()).equals(dirPath)) {
|
||||||
|
player.printError("Clipboard file could not read or it does not exist.");
|
||||||
|
} else {
|
||||||
|
FileInputStream fis = closer.register(new FileInputStream(f));
|
||||||
|
BufferedInputStream bis = closer.register(new BufferedInputStream(fis));
|
||||||
|
ClipboardReader reader = format.getReader(bis);
|
||||||
|
|
||||||
|
WorldData worldData = player.getWorld().getWorldData();
|
||||||
|
Clipboard clipboard = reader.read(player.getWorld().getWorldData());
|
||||||
|
session.setClipboard(new ClipboardHolder(clipboard, worldData));
|
||||||
|
|
||||||
|
log.info(player.getName() + " loaded " + filePath);
|
||||||
|
player.print(filename + " loaded. Paste it with //paste");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
player.printError("Schematic could not read or it does not exist: " + e.getMessage());
|
||||||
|
log.log(Level.WARNING, "Failed to load a saved clipboard", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
closer.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
aliases = { "save", "s" },
|
aliases = { "save" },
|
||||||
usage = "[format] <filename>",
|
usage = "[<format>] <filename>",
|
||||||
desc = "Save your clipboard to file",
|
desc = "Save a schematic into your clipboard",
|
||||||
help = "Save your clipboard to file\n" +
|
min = 0,
|
||||||
"Format is a format from \"//schematic formats\"\n",
|
max = 1
|
||||||
min = 1,
|
|
||||||
max = 2
|
|
||||||
)
|
)
|
||||||
@CommandPermissions({"worldedit.clipboard.save", "worldedit.schematic.save"}) // TODO: Remove 'clipboard' perm
|
@Deprecated
|
||||||
public void save(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException, CommandException {
|
@CommandPermissions({ "worldedit.clipboard.save", "worldedit.schematic.save" })
|
||||||
// TODO: Update for new clipboard
|
public void save(Player player, LocalSession session, @Optional("schematic") String formatName, String filename) throws FilenameException, CommandException, EmptyClipboardException {
|
||||||
throw new CommandException("Needs to be re-written again");
|
LocalConfiguration config = worldEdit.getConfiguration();
|
||||||
|
|
||||||
|
File dir = worldEdit.getWorkingDirectoryFile(config.saveDir);
|
||||||
|
File f = worldEdit.getSafeSaveFile(player, dir, filename, "schematic", "schematic");
|
||||||
|
|
||||||
|
if (!f.exists()) {
|
||||||
|
player.printError("Schematic " + filename + " does not exist!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipboardFormat format = ClipboardFormat.findByAlias(formatName);
|
||||||
|
if (format == null) {
|
||||||
|
player.printError("Unknown schematic format: " + formatName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipboardHolder holder = session.getClipboard();
|
||||||
|
|
||||||
|
Closer closer = Closer.create();
|
||||||
|
try {
|
||||||
|
// Create parent directories
|
||||||
|
File parent = f.getParentFile();
|
||||||
|
if (parent != null && !parent.exists()) {
|
||||||
|
if (!parent.mkdirs()) {
|
||||||
|
throw new CommandException("Could not create folder for schematics!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOutputStream fos = closer.register(new FileOutputStream(f));
|
||||||
|
BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos));
|
||||||
|
ClipboardWriter writer = closer.register(format.getWriter(bos));
|
||||||
|
writer.write(holder.getClipboard(), holder.getWorldData());
|
||||||
|
log.info(player.getName() + " saved " + f.getCanonicalPath());
|
||||||
|
player.print(filename + " saved.");
|
||||||
|
} catch (IOException e) {
|
||||||
|
player.printError("Schematic could not written: " + e.getMessage());
|
||||||
|
log.log(Level.WARNING, "Failed to write a saved clipboard", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
closer.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -127,13 +222,13 @@ public class SchematicCommands {
|
|||||||
)
|
)
|
||||||
@CommandPermissions("worldedit.schematic.formats")
|
@CommandPermissions("worldedit.schematic.formats")
|
||||||
public void formats(Actor actor) throws WorldEditException {
|
public void formats(Actor actor) throws WorldEditException {
|
||||||
actor.print("Available schematic formats (Name: Lookup names)");
|
actor.print("Available clipboard formats (Name: Lookup names)");
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
for (SchematicFormat format : SchematicFormat.getFormats()) {
|
for (ClipboardFormat format : ClipboardFormat.values()) {
|
||||||
builder = new StringBuilder();
|
builder = new StringBuilder();
|
||||||
builder.append(format.getName()).append(": ");
|
builder.append(format.name()).append(": ");
|
||||||
for (String lookupName : format.getLookupNames()) {
|
for (String lookupName : format.getAliases()) {
|
||||||
if (!first) {
|
if (!first) {
|
||||||
builder.append(", ");
|
builder.append(", ");
|
||||||
}
|
}
|
||||||
@ -204,9 +299,8 @@ public class SchematicCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
build.append("\n\u00a79");
|
build.append("\n\u00a79");
|
||||||
SchematicFormat format = SchematicFormat.getFormat(file);
|
ClipboardFormat format = ClipboardFormat.findByFile(file);
|
||||||
build.append(prefix).append(file.getName())
|
build.append(prefix).append(file.getName()).append(": ").append(format == null ? "Unknown" : format.name());
|
||||||
.append(": ").append(format == null ? "Unknown" : format.getName());
|
|
||||||
}
|
}
|
||||||
return build.toString();
|
return build.toString();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.extent.clipboard.io;
|
||||||
|
|
||||||
|
import com.sk89q.jnbt.NBTConstants;
|
||||||
|
import com.sk89q.jnbt.NBTInputStream;
|
||||||
|
import com.sk89q.jnbt.NBTOutputStream;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of supported clipboard formats.
|
||||||
|
*/
|
||||||
|
public enum ClipboardFormat {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Schematic format used by many software.
|
||||||
|
*/
|
||||||
|
SCHEMATIC("mcedit", "mce", "schematic") {
|
||||||
|
@Override
|
||||||
|
public ClipboardReader getReader(InputStream inputStream) throws IOException {
|
||||||
|
NBTInputStream nbtStream = new NBTInputStream(new GZIPInputStream(inputStream));
|
||||||
|
return new SchematicReader(nbtStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClipboardWriter getWriter(OutputStream outputStream) throws IOException {
|
||||||
|
NBTOutputStream nbtStream = new NBTOutputStream(new GZIPOutputStream(outputStream));
|
||||||
|
return new SchematicWriter(nbtStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFormat(File file) {
|
||||||
|
DataInputStream str = null;
|
||||||
|
try {
|
||||||
|
str = new DataInputStream(new GZIPInputStream(new FileInputStream(file)));
|
||||||
|
if ((str.readByte() & 0xFF) != NBTConstants.TYPE_COMPOUND) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
byte[] nameBytes = new byte[str.readShort() & 0xFFFF];
|
||||||
|
str.readFully(nameBytes);
|
||||||
|
String name = new String(nameBytes, NBTConstants.CHARSET);
|
||||||
|
return name.equals("Schematic");
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (str != null) {
|
||||||
|
try {
|
||||||
|
str.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final Map<String, ClipboardFormat> aliasMap = new HashMap<String, ClipboardFormat>();
|
||||||
|
|
||||||
|
private final String[] aliases;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance.
|
||||||
|
*
|
||||||
|
* @param aliases an array of aliases by which this format may be referred to
|
||||||
|
*/
|
||||||
|
private ClipboardFormat(String ... aliases) {
|
||||||
|
this.aliases = aliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a set of aliases.
|
||||||
|
*
|
||||||
|
* @return a set of aliases
|
||||||
|
*/
|
||||||
|
public Set<String> getAliases() {
|
||||||
|
return Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(aliases)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a reader.
|
||||||
|
*
|
||||||
|
* @param inputStream the input stream
|
||||||
|
* @return a reader
|
||||||
|
* @throws IOException thrown on I/O error
|
||||||
|
*/
|
||||||
|
public abstract ClipboardReader getReader(InputStream inputStream) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a writer.
|
||||||
|
*
|
||||||
|
* @param outputStream the output stream
|
||||||
|
* @return a writer
|
||||||
|
* @throws IOException thrown on I/O error
|
||||||
|
*/
|
||||||
|
public abstract ClipboardWriter getWriter(OutputStream outputStream) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the given file is of this format.
|
||||||
|
*
|
||||||
|
* @param file the file
|
||||||
|
* @return true if the given file is of this format
|
||||||
|
*/
|
||||||
|
public abstract boolean isFormat(File file);
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (ClipboardFormat format : EnumSet.allOf(ClipboardFormat.class)) {
|
||||||
|
for (String key : format.aliases) {
|
||||||
|
aliasMap.put(key, format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the clipboard format named by the given alias.
|
||||||
|
*
|
||||||
|
* @param alias the alias
|
||||||
|
* @return the format, otherwise null if none is matched
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static ClipboardFormat findByAlias(String alias) {
|
||||||
|
checkNotNull(alias);
|
||||||
|
return aliasMap.get(alias.toLowerCase().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect the format given a file.
|
||||||
|
*
|
||||||
|
* @param file the file
|
||||||
|
* @return the format, otherwise null if one cannot be detected
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static ClipboardFormat findByFile(File file) {
|
||||||
|
checkNotNull(file);
|
||||||
|
|
||||||
|
for (ClipboardFormat format : EnumSet.allOf(ClipboardFormat.class)) {
|
||||||
|
if (format.isFormat(file)) {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.extent.clipboard.io;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
|
import com.sk89q.worldedit.world.registry.WorldData;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads {@code Clipboard}s.
|
||||||
|
*
|
||||||
|
* @see Clipboard
|
||||||
|
*/
|
||||||
|
public interface ClipboardReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a {@code Clipboard}.
|
||||||
|
*
|
||||||
|
* @param data the world data space to convert the blocks to
|
||||||
|
* @return the read clipboard
|
||||||
|
* @throws IOException thrown on I/O error
|
||||||
|
*/
|
||||||
|
Clipboard read(WorldData data) throws IOException;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.extent.clipboard.io;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
|
import com.sk89q.worldedit.world.registry.WorldData;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes {@code Clipboard}s.
|
||||||
|
*
|
||||||
|
* @see Clipboard
|
||||||
|
*/
|
||||||
|
public interface ClipboardWriter extends Closeable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a clipboard.
|
||||||
|
*
|
||||||
|
* @param clipboard the clipboard
|
||||||
|
* @param data the world data instance
|
||||||
|
* @throws IOException thrown on I/O error
|
||||||
|
*/
|
||||||
|
void write(Clipboard clipboard, WorldData data) throws IOException;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.extent.clipboard.io;
|
||||||
|
|
||||||
|
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.ShortTag;
|
||||||
|
import com.sk89q.jnbt.StringTag;
|
||||||
|
import com.sk89q.jnbt.Tag;
|
||||||
|
import com.sk89q.worldedit.BlockVector;
|
||||||
|
import com.sk89q.worldedit.Vector;
|
||||||
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
|
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
|
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
import com.sk89q.worldedit.world.registry.WorldData;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads schematic files based that are compatible with MCEdit and other editors.
|
||||||
|
*/
|
||||||
|
public class SchematicReader implements ClipboardReader {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(SchematicReader.class.getCanonicalName());
|
||||||
|
private final NBTInputStream inputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance.
|
||||||
|
*
|
||||||
|
* @param inputStream the input stream to read from
|
||||||
|
*/
|
||||||
|
public SchematicReader(NBTInputStream inputStream) {
|
||||||
|
checkNotNull(inputStream);
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Clipboard read(WorldData data) throws IOException {
|
||||||
|
// Schematic tag
|
||||||
|
CompoundTag schematicTag = (CompoundTag) inputStream.readTag();
|
||||||
|
if (!schematicTag.getName().equals("Schematic")) {
|
||||||
|
throw new IOException("Tag 'Schematic' does not exist or is not first");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check
|
||||||
|
Map<String, Tag> schematic = schematicTag.getValue();
|
||||||
|
if (!schematic.containsKey("Blocks")) {
|
||||||
|
throw new IOException("Schematic file is missing a 'Blocks' tag");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check type of Schematic
|
||||||
|
String materials = getChildTag(schematic, "Materials", StringTag.class).getValue();
|
||||||
|
if (!materials.equals("Alpha")) {
|
||||||
|
throw new IOException("Schematic file is not an Alpha schematic");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse origin and region from WEOrigin and WEOffset
|
||||||
|
Vector origin;
|
||||||
|
Region region;
|
||||||
|
|
||||||
|
// Get information
|
||||||
|
short width = getChildTag(schematic, "Width", ShortTag.class).getValue();
|
||||||
|
short height = getChildTag(schematic, "Height", ShortTag.class).getValue();
|
||||||
|
short length = getChildTag(schematic, "Length", 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();
|
||||||
|
Vector min = new Vector(originX, originY, originZ);
|
||||||
|
|
||||||
|
int offsetX = getChildTag(schematic, "WEOffsetX", IntTag.class).getValue();
|
||||||
|
int offsetY = getChildTag(schematic, "WEOffsetY", IntTag.class).getValue();
|
||||||
|
int offsetZ = getChildTag(schematic, "WEOffsetZ", IntTag.class).getValue();
|
||||||
|
Vector offset = new Vector(offsetX, offsetY, offsetZ);
|
||||||
|
|
||||||
|
origin = min.subtract(offset);
|
||||||
|
region = new CuboidRegion(min, min.add(width, height, length).subtract(Vector.ONE));
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
origin = new Vector(0, 0, 0);
|
||||||
|
region = new CuboidRegion(origin, origin.add(width, height, length).subtract(Vector.ONE));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get blocks
|
||||||
|
byte[] blockId = getChildTag(schematic, "Blocks", ByteArrayTag.class).getValue();
|
||||||
|
byte[] blockData = getChildTag(schematic, "Data", ByteArrayTag.class).getValue();
|
||||||
|
byte[] addId = new byte[0];
|
||||||
|
short[] blocks = new short[blockId.length]; // Have to later combine IDs
|
||||||
|
|
||||||
|
// We support 4096 block IDs using the same method as vanilla Minecraft, where
|
||||||
|
// the highest 4 bits are stored in a separate byte array.
|
||||||
|
if (schematic.containsKey("AddBlocks")) {
|
||||||
|
addId = getChildTag(schematic, "AddBlocks", ByteArrayTag.class).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine the AddBlocks data with the first 8-bit block ID
|
||||||
|
for (int index = 0; index < blockId.length; index++) {
|
||||||
|
if ((index >> 1) >= addId.length) { // No corresponding AddBlocks index
|
||||||
|
blocks[index] = (short) (blockId[index] & 0xFF);
|
||||||
|
} else {
|
||||||
|
if ((index & 1) == 0) {
|
||||||
|
blocks[index] = (short) (((addId[index >> 1] & 0x0F) << 8) + (blockId[index] & 0xFF));
|
||||||
|
} else {
|
||||||
|
blocks[index] = (short) (((addId[index >> 1] & 0xF0) << 4) + (blockId[index] & 0xFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockArrayClipboard clipboard = new BlockArrayClipboard(region);
|
||||||
|
clipboard.setOrigin(origin);
|
||||||
|
|
||||||
|
// Don't log a torrent of errors
|
||||||
|
int failedBlockSets = 0;
|
||||||
|
|
||||||
|
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 = new BaseBlock(blocks[index], blockData[index]);
|
||||||
|
|
||||||
|
if (tileEntitiesMap.containsKey(pt)) {
|
||||||
|
block.setNbtData(new CompoundTag("", tileEntitiesMap.get(pt)));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
clipboard.setBlock(region.getMinimumPoint().add(pt), block);
|
||||||
|
} catch (WorldEditException e) {
|
||||||
|
switch (failedBlockSets) {
|
||||||
|
case 0:
|
||||||
|
log.log(Level.WARNING, "Failed to set block on a Clipboard", e);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
log.log(Level.WARNING, "Failed to set block on a Clipboard (again) -- no more messages will be logged", e);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
failedBlockSets++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 IOException 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 IOException {
|
||||||
|
if (!items.containsKey(key)) {
|
||||||
|
throw new IOException("Schematic file is missing a \"" + key + "\" tag");
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag tag = items.get(key);
|
||||||
|
if (!expected.isInstance(tag)) {
|
||||||
|
throw new IOException(key + " tag is not of tag type " + expected.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return expected.cast(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.extent.clipboard.io;
|
||||||
|
|
||||||
|
import com.sk89q.jnbt.ByteArrayTag;
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.jnbt.IntTag;
|
||||||
|
import com.sk89q.jnbt.ListTag;
|
||||||
|
import com.sk89q.jnbt.NBTOutputStream;
|
||||||
|
import com.sk89q.jnbt.ShortTag;
|
||||||
|
import com.sk89q.jnbt.StringTag;
|
||||||
|
import com.sk89q.jnbt.Tag;
|
||||||
|
import com.sk89q.worldedit.Vector;
|
||||||
|
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
import com.sk89q.worldedit.world.registry.WorldData;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes schematic files based that are compatible with MCEdit and other editors.
|
||||||
|
*/
|
||||||
|
public class SchematicWriter implements ClipboardWriter {
|
||||||
|
|
||||||
|
private static final int MAX_SIZE = Short.MAX_VALUE - Short.MIN_VALUE;
|
||||||
|
private final NBTOutputStream outputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new schematic writer.
|
||||||
|
*
|
||||||
|
* @param outputStream the output stream to write to
|
||||||
|
*/
|
||||||
|
public SchematicWriter(NBTOutputStream outputStream) {
|
||||||
|
checkNotNull(outputStream);
|
||||||
|
this.outputStream = outputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(Clipboard clipboard, WorldData data) throws IOException {
|
||||||
|
Region region = clipboard.getRegion();
|
||||||
|
Vector origin = clipboard.getOrigin();
|
||||||
|
Vector min = region.getMinimumPoint();
|
||||||
|
Vector offset = min.subtract(origin);
|
||||||
|
int width = region.getWidth();
|
||||||
|
int height = region.getHeight();
|
||||||
|
int length = region.getLength();
|
||||||
|
|
||||||
|
if (width > MAX_SIZE) {
|
||||||
|
throw new IllegalArgumentException("Width of region too large for a .schematic");
|
||||||
|
}
|
||||||
|
if (height > MAX_SIZE) {
|
||||||
|
throw new IllegalArgumentException("Height of region too large for a .schematic");
|
||||||
|
}
|
||||||
|
if (length > MAX_SIZE) {
|
||||||
|
throw new IllegalArgumentException("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", min.getBlockX()));
|
||||||
|
schematic.put("WEOriginY", new IntTag("WEOriginY", min.getBlockY()));
|
||||||
|
schematic.put("WEOriginZ", new IntTag("WEOriginZ", min.getBlockZ()));
|
||||||
|
schematic.put("WEOffsetX", new IntTag("WEOffsetX", offset.getBlockX()));
|
||||||
|
schematic.put("WEOffsetY", new IntTag("WEOffsetY", offset.getBlockY()));
|
||||||
|
schematic.put("WEOffsetZ", new IntTag("WEOffsetZ", offset.getBlockZ()));
|
||||||
|
|
||||||
|
// Copy
|
||||||
|
byte[] blocks = new byte[width * height * length];
|
||||||
|
byte[] addBlocks = null;
|
||||||
|
byte[] blockData = new byte[width * height * length];
|
||||||
|
ArrayList<Tag> tileEntities = new ArrayList<Tag>();
|
||||||
|
|
||||||
|
for (Vector point : region) {
|
||||||
|
Vector relative = point.subtract(min);
|
||||||
|
int x = relative.getBlockX();
|
||||||
|
int y = relative.getBlockY();
|
||||||
|
int z = relative.getBlockZ();
|
||||||
|
|
||||||
|
int index = y * width * length + z * width + x;
|
||||||
|
BaseBlock block = clipboard.getBlock(point);
|
||||||
|
|
||||||
|
// Save 4096 IDs in an AddBlocks section
|
||||||
|
if (block.getType() > 255) {
|
||||||
|
if (addBlocks == null) { // Lazily create section
|
||||||
|
addBlocks = new byte[(blocks.length >> 1) + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
addBlocks[index >> 1] = (byte) (((index & 1) == 0) ?
|
||||||
|
addBlocks[index >> 1] & 0xF0 | (block.getType() >> 8) & 0xF
|
||||||
|
: addBlocks[index >> 1] & 0xF | ((block.getType() >> 8) & 0xF) << 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks[index] = (byte) block.getType();
|
||||||
|
blockData[index] = (byte) block.getData();
|
||||||
|
|
||||||
|
// Store TileEntity data
|
||||||
|
CompoundTag rawTag = block.getNbtData();
|
||||||
|
if (rawTag != null) {
|
||||||
|
Map<String, Tag> values = new HashMap<String, Tag>();
|
||||||
|
for (Entry<String, Tag> entry : rawTag.getValue().entrySet()) {
|
||||||
|
values.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
values.put("id", new StringTag("id", block.getNbtId()));
|
||||||
|
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));
|
||||||
|
|
||||||
|
if (addBlocks != null) {
|
||||||
|
schematic.put("AddBlocks", new ByteArrayTag("AddBlocks", addBlocks));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build and output
|
||||||
|
CompoundTag schematicTag = new CompoundTag("Schematic", schematic);
|
||||||
|
outputStream.writeTag(schematicTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user