2010-10-02 23:11:44 +00:00
|
|
|
// $Id$
|
|
|
|
/*
|
|
|
|
* WorldEdit
|
|
|
|
* Copyright (C) 2010 sk89q <http://www.sk89q.com>
|
|
|
|
*
|
|
|
|
* 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/>.
|
|
|
|
*/
|
|
|
|
|
2010-10-03 17:44:52 +00:00
|
|
|
import org.jnbt.*;
|
|
|
|
import java.io.*;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.ArrayList;
|
2010-10-02 23:11:44 +00:00
|
|
|
import com.sk89q.worldedit.*;
|
|
|
|
|
|
|
|
/**
|
2010-10-13 17:08:53 +00:00
|
|
|
* The clipboard remembers the state of a cuboid region.
|
2010-10-02 23:11:44 +00:00
|
|
|
*
|
2010-10-13 17:08:53 +00:00
|
|
|
* @author sk89q
|
2010-10-02 23:11:44 +00:00
|
|
|
*/
|
2010-10-11 08:22:47 +00:00
|
|
|
public class CuboidClipboard {
|
2010-10-02 23:11:44 +00:00
|
|
|
private int[][][] data;
|
2010-10-13 17:08:53 +00:00
|
|
|
private Vector offset;
|
2010-10-13 01:03:56 +00:00
|
|
|
private Vector origin;
|
2010-10-13 17:08:53 +00:00
|
|
|
private Vector size;
|
2010-10-02 23:11:44 +00:00
|
|
|
|
|
|
|
/**
|
2010-10-13 17:08:53 +00:00
|
|
|
* Constructs the clipboard.
|
|
|
|
*
|
|
|
|
* @param size
|
|
|
|
*/
|
|
|
|
public CuboidClipboard(Vector size) {
|
|
|
|
this.size = size;
|
|
|
|
data = new int[size.getBlockX()][size.getBlockY()][size.getBlockZ()];
|
|
|
|
origin = new Vector();
|
|
|
|
offset = new Vector();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs the clipboard.
|
|
|
|
*
|
|
|
|
* @param size
|
|
|
|
* @param origin
|
|
|
|
*/
|
|
|
|
public CuboidClipboard(Vector size, Vector origin) {
|
|
|
|
this.size = size;
|
|
|
|
data = new int[size.getBlockX()][size.getBlockY()][size.getBlockZ()];
|
|
|
|
this.origin = origin;
|
|
|
|
offset = new Vector();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs the clipboard.
|
|
|
|
*
|
|
|
|
* @param size
|
2010-10-02 23:11:44 +00:00
|
|
|
* @param origin
|
|
|
|
*/
|
2010-10-13 17:08:53 +00:00
|
|
|
public CuboidClipboard(Vector size, Vector origin, Vector offset) {
|
|
|
|
this.size = size;
|
|
|
|
data = new int[size.getBlockX()][size.getBlockY()][size.getBlockZ()];
|
2010-10-02 23:11:44 +00:00
|
|
|
this.origin = origin;
|
2010-10-13 17:08:53 +00:00
|
|
|
this.offset = offset;
|
2010-10-02 23:11:44 +00:00
|
|
|
}
|
|
|
|
|
2010-10-03 17:44:52 +00:00
|
|
|
/**
|
|
|
|
* Get the width (X-direction) of the clipboard.
|
|
|
|
*
|
2010-10-11 15:56:19 +00:00
|
|
|
* @return width
|
2010-10-03 17:44:52 +00:00
|
|
|
*/
|
|
|
|
public int getWidth() {
|
2010-10-13 17:08:53 +00:00
|
|
|
return size.getBlockX();
|
2010-10-03 17:44:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the length (Z-direction) of the clipboard.
|
|
|
|
*
|
2010-10-11 15:56:19 +00:00
|
|
|
* @return length
|
2010-10-03 17:44:52 +00:00
|
|
|
*/
|
|
|
|
public int getLength() {
|
2010-10-13 17:08:53 +00:00
|
|
|
return size.getBlockZ();
|
2010-10-03 17:44:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the height (Y-direction) of the clipboard.
|
|
|
|
*
|
2010-10-11 15:56:19 +00:00
|
|
|
* @return height
|
2010-10-03 17:44:52 +00:00
|
|
|
*/
|
|
|
|
public int getHeight() {
|
2010-10-13 17:08:53 +00:00
|
|
|
return size.getBlockY();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Rotate the clipboard in 2D. It can only rotate by angles divisible by 90.
|
|
|
|
*
|
|
|
|
* @param angle in degrees
|
|
|
|
*/
|
|
|
|
public void rotate2D(int angle) {
|
|
|
|
angle = angle % 360;
|
|
|
|
if (angle % 90 != 0) { // Can only rotate 90 degrees at the moment
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int width = getWidth();
|
|
|
|
int length = getLength();
|
|
|
|
int height = getHeight();
|
|
|
|
int newWidth = angle % 180 == 0 ? width : length;
|
|
|
|
int newLength = angle % 180 == 0 ? length : width;
|
|
|
|
Vector sizeRotated = size.transform2D(angle, 0, 0, 0, 0);
|
|
|
|
int shiftX = sizeRotated.getX() < 0 ? newWidth - 1 : 0;
|
|
|
|
int shiftZ = sizeRotated.getZ() < 0 ? newLength - 1: 0;
|
|
|
|
|
|
|
|
int newData[][][] = new int[newWidth][getHeight()][newLength];
|
|
|
|
|
|
|
|
for (int x = 0; x < width; x++) {
|
|
|
|
for (int z = 0; z < length; z++) {
|
|
|
|
int newX = (new Vector(x, 0, z)).transform2D(angle, 0, 0, 0, 0)
|
|
|
|
.getBlockX();
|
|
|
|
int newZ = (new Vector(x, 0, z)).transform2D(angle, 0, 0, 0, 0)
|
|
|
|
.getBlockZ();
|
|
|
|
for (int y = 0; y < height; y++) {
|
|
|
|
newData[shiftX + newX][y][shiftZ + newZ] = data[x][y][z];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
data = newData;
|
|
|
|
size = new Vector(newWidth, getHeight(), newLength);
|
|
|
|
offset = offset.transform2D(angle, 0, 0, 0, 0)
|
|
|
|
.subtract(shiftX, 0, shiftZ);
|
2010-10-03 17:44:52 +00:00
|
|
|
}
|
|
|
|
|
2010-10-02 23:11:44 +00:00
|
|
|
/**
|
|
|
|
* Copy to the clipboard.
|
|
|
|
*
|
|
|
|
* @param editSession
|
|
|
|
*/
|
|
|
|
public void copy(EditSession editSession) {
|
2010-10-13 17:08:53 +00:00
|
|
|
for (int x = 0; x < size.getBlockX(); x++) {
|
|
|
|
for (int y = 0; y < size.getBlockY(); y++) {
|
|
|
|
for (int z = 0; z < size.getBlockZ(); z++) {
|
|
|
|
data[x][y][z] =
|
|
|
|
editSession.getBlock(new Vector(x, y, z).add(origin));
|
2010-10-02 23:11:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Paste from the clipboard.
|
|
|
|
*
|
|
|
|
* @param editSession
|
2010-10-11 15:56:19 +00:00
|
|
|
* @param newOrigin Position to paste it from
|
2010-10-02 23:11:44 +00:00
|
|
|
* @param noAir True to not paste air
|
2010-10-04 23:39:35 +00:00
|
|
|
* @throws MaxChangedBlocksException
|
2010-10-02 23:11:44 +00:00
|
|
|
*/
|
2010-10-13 01:03:56 +00:00
|
|
|
public void paste(EditSession editSession, Vector newOrigin, boolean noAir)
|
2010-10-05 20:46:31 +00:00
|
|
|
throws MaxChangedBlocksException {
|
2010-10-13 17:08:53 +00:00
|
|
|
place(editSession, newOrigin.add(offset), noAir);
|
2010-10-05 20:46:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Places the blocks in a position from the minimum corner.
|
|
|
|
*
|
|
|
|
* @param editSession
|
2010-10-13 17:08:53 +00:00
|
|
|
* @param pos
|
2010-10-05 20:46:31 +00:00
|
|
|
* @param noAir
|
|
|
|
* @throws MaxChangedBlocksException
|
|
|
|
*/
|
2010-10-13 17:08:53 +00:00
|
|
|
public void place(EditSession editSession, Vector pos, boolean noAir)
|
2010-10-05 20:46:31 +00:00
|
|
|
throws MaxChangedBlocksException {
|
2010-10-13 17:08:53 +00:00
|
|
|
for (int x = 0; x < size.getBlockX(); x++) {
|
|
|
|
for (int y = 0; y < size.getBlockY(); y++) {
|
|
|
|
for (int z = 0; z < size.getBlockZ(); z++) {
|
|
|
|
if (noAir && data[x][y][z] == 0) continue;
|
|
|
|
|
|
|
|
editSession.setBlock(new Vector(x, y, z).add(pos),
|
|
|
|
data[x][y][z]);
|
2010-10-02 23:11:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-10-03 17:44:52 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves the clipboard data to a .schematic-format file.
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @throws IOException
|
2010-10-05 00:00:54 +00:00
|
|
|
* @throws SchematicException
|
2010-10-03 17:44:52 +00:00
|
|
|
*/
|
2010-10-05 00:00:54 +00:00
|
|
|
public void saveSchematic(String path) throws IOException, SchematicException {
|
2010-10-13 17:08:53 +00:00
|
|
|
int width = getWidth();
|
|
|
|
int height = getHeight();
|
|
|
|
int length = getLength();
|
2010-10-03 17:44:52 +00:00
|
|
|
|
2010-10-13 17:08:53 +00:00
|
|
|
if (width > 65535) {
|
2010-10-05 00:00:54 +00:00
|
|
|
throw new SchematicException("Width of region too large for a .schematic");
|
|
|
|
}
|
2010-10-13 17:08:53 +00:00
|
|
|
if (height > 65535) {
|
2010-10-05 00:00:54 +00:00
|
|
|
throw new SchematicException("Height of region too large for a .schematic");
|
|
|
|
}
|
2010-10-13 17:08:53 +00:00
|
|
|
if (length > 65535) {
|
2010-10-05 00:00:54 +00:00
|
|
|
throw new SchematicException("Length of region too large for a .schematic");
|
|
|
|
}
|
|
|
|
|
2010-10-03 17:44:52 +00:00
|
|
|
HashMap<String,Tag> schematic = new HashMap<String,Tag>();
|
2010-10-13 17:08:53 +00:00
|
|
|
schematic.put("Width", new ShortTag("Width", (short)width));
|
|
|
|
schematic.put("Length", new ShortTag("Length", (short)length));
|
|
|
|
schematic.put("Height", new ShortTag("Height", (short)height));
|
2010-10-03 17:44:52 +00:00
|
|
|
schematic.put("Materials", new StringTag("Materials", "Alpha"));
|
|
|
|
|
|
|
|
// Copy blocks
|
2010-10-13 17:08:53 +00:00
|
|
|
byte[] blocks = new byte[width * height * length];
|
|
|
|
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;
|
2010-10-03 17:44:52 +00:00
|
|
|
blocks[index] = (byte)data[x][y][z];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
schematic.put("Blocks", new ByteArrayTag("Blocks", blocks));
|
|
|
|
|
|
|
|
// Current data is not supported
|
2010-10-13 17:08:53 +00:00
|
|
|
byte[] data = new byte[width * height * length];
|
2010-10-03 17:44:52 +00:00
|
|
|
schematic.put("Data", new ByteArrayTag("Data", data));
|
|
|
|
|
|
|
|
// These are not stored either
|
|
|
|
schematic.put("Entities", new ListTag("Entities", CompoundTag.class, new ArrayList<Tag>()));
|
|
|
|
schematic.put("TileEntities", new ListTag("TileEntities", CompoundTag.class, new ArrayList<Tag>()));
|
|
|
|
|
|
|
|
// Build and output
|
|
|
|
CompoundTag schematicTag = new CompoundTag("Schematic", schematic);
|
|
|
|
NBTOutputStream stream = new NBTOutputStream(new FileOutputStream(path));
|
|
|
|
stream.writeTag(schematicTag);
|
|
|
|
stream.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load a .schematic file into a clipboard.
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @param origin
|
2010-10-11 15:56:19 +00:00
|
|
|
* @return clipboard
|
2010-10-05 00:00:54 +00:00
|
|
|
* @throws SchematicException
|
2010-10-03 17:44:52 +00:00
|
|
|
* @throws IOException
|
|
|
|
*/
|
2010-10-13 17:08:53 +00:00
|
|
|
public static CuboidClipboard loadSchematic(String path)
|
2010-10-05 00:00:54 +00:00
|
|
|
throws SchematicException, IOException {
|
2010-10-03 17:44:52 +00:00
|
|
|
FileInputStream stream = new FileInputStream(path);
|
|
|
|
NBTInputStream nbtStream = new NBTInputStream(stream);
|
|
|
|
CompoundTag schematicTag = (CompoundTag)nbtStream.readTag();
|
|
|
|
if (!schematicTag.getName().equals("Schematic")) {
|
2010-10-05 00:00:54 +00:00
|
|
|
throw new SchematicException("Tag \"Schematic\" does not exist or is not first");
|
2010-10-03 17:44:52 +00:00
|
|
|
}
|
|
|
|
Map<String,Tag> schematic = schematicTag.getValue();
|
|
|
|
if (!schematic.containsKey("Blocks")) {
|
2010-10-05 00:00:54 +00:00
|
|
|
throw new SchematicException("Schematic file is missing a \"Blocks\" tag");
|
2010-10-03 17:44:52 +00:00
|
|
|
}
|
2010-10-13 17:08:53 +00:00
|
|
|
short width = (Short)getChildTag(schematic, "Width", ShortTag.class).getValue();
|
|
|
|
short length = (Short)getChildTag(schematic, "Length", ShortTag.class).getValue();
|
|
|
|
short height = (Short)getChildTag(schematic, "Height", ShortTag.class).getValue();
|
2010-10-03 17:44:52 +00:00
|
|
|
String materials = (String)getChildTag(schematic, "Materials", StringTag.class).getValue();
|
|
|
|
if (!materials.equals("Alpha")) {
|
2010-10-05 00:00:54 +00:00
|
|
|
throw new SchematicException("Schematic file is not an Alpha schematic");
|
2010-10-03 17:44:52 +00:00
|
|
|
}
|
|
|
|
byte[] blocks = (byte[])getChildTag(schematic, "Blocks", ByteArrayTag.class).getValue();
|
|
|
|
|
2010-10-13 17:08:53 +00:00
|
|
|
Vector size = new Vector(width, height, length);
|
2010-10-03 17:44:52 +00:00
|
|
|
|
2010-10-13 17:08:53 +00:00
|
|
|
CuboidClipboard clipboard = new CuboidClipboard(size);
|
|
|
|
|
|
|
|
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;
|
2010-10-03 17:44:52 +00:00
|
|
|
clipboard.data[x][y][z] = blocks[index];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return clipboard;
|
|
|
|
}
|
|
|
|
|
2010-10-13 17:08:53 +00:00
|
|
|
/**
|
|
|
|
* Get child tag of a NBT structure.
|
|
|
|
*
|
|
|
|
* @param items
|
|
|
|
* @param key
|
|
|
|
* @param expected
|
|
|
|
* @return child tag
|
|
|
|
* @throws SchematicException
|
|
|
|
*/
|
2010-10-03 17:44:52 +00:00
|
|
|
private static Tag getChildTag(Map<String,Tag> items, String key, Class expected)
|
2010-10-05 00:00:54 +00:00
|
|
|
throws SchematicException {
|
2010-10-03 17:44:52 +00:00
|
|
|
if (!items.containsKey(key)) {
|
2010-10-05 00:00:54 +00:00
|
|
|
throw new SchematicException("Schematic file is missing a \"" + key + "\" tag");
|
2010-10-03 17:44:52 +00:00
|
|
|
}
|
|
|
|
Tag tag = items.get(key);
|
|
|
|
if (!expected.isInstance(tag)) {
|
2010-10-05 00:00:54 +00:00
|
|
|
throw new SchematicException(
|
2010-10-03 17:44:52 +00:00
|
|
|
key + " tag is not of tag type " + expected.getName());
|
|
|
|
}
|
|
|
|
return tag;
|
|
|
|
}
|
2010-10-02 23:11:44 +00:00
|
|
|
}
|