diff --git a/StevenDimDoors/mod_pocketDim/schematic/CompactBoundsOperation.java b/StevenDimDoors/mod_pocketDim/schematic/CompactBoundsOperation.java index 9b3ad881..7bb11bc3 100644 --- a/StevenDimDoors/mod_pocketDim/schematic/CompactBoundsOperation.java +++ b/StevenDimDoors/mod_pocketDim/schematic/CompactBoundsOperation.java @@ -18,7 +18,7 @@ public class CompactBoundsOperation extends WorldOperation } @Override - protected boolean start(World world, int x, int y, int z, int width, int height, int length) + protected boolean initialize(World world, int x, int y, int z, int width, int height, int length) { minX = Integer.MAX_VALUE; minY = Integer.MAX_VALUE; diff --git a/StevenDimDoors/mod_pocketDim/schematic/InvalidSchematicException.java b/StevenDimDoors/mod_pocketDim/schematic/InvalidSchematicException.java new file mode 100644 index 00000000..d1307a75 --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/schematic/InvalidSchematicException.java @@ -0,0 +1,21 @@ +package StevenDimDoors.mod_pocketDim.schematic; + +public class InvalidSchematicException extends Exception { + + private static final long serialVersionUID = -1011044077455149932L; + + public InvalidSchematicException() + { + super(); + } + + public InvalidSchematicException(String message) + { + super(message); + } + + public InvalidSchematicException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/StevenDimDoors/mod_pocketDim/schematic/Schematic.java b/StevenDimDoors/mod_pocketDim/schematic/Schematic.java index 6e8b7806..1c4c4d5b 100644 --- a/StevenDimDoors/mod_pocketDim/schematic/Schematic.java +++ b/StevenDimDoors/mod_pocketDim/schematic/Schematic.java @@ -1,21 +1,29 @@ package StevenDimDoors.mod_pocketDim.schematic; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import net.minecraft.tileentity.TileEntity; import net.minecraft.world.World; import StevenDimDoors.mod_pocketDim.Point3D; +/** + * Represents an MC schematic and provides functions for loading, storing, and manipulating schematics. + * This functionality has no dependencies to Dimensional Doors. + */ public class Schematic { - + protected short width; protected short height; protected short length; - + protected short[] blocks; protected byte[] metadata; protected NBTTagList tileEntities = new NBTTagList(); @@ -29,7 +37,7 @@ public class Schematic { this.metadata = metadata; this.tileEntities = tileEntities; } - + private int calculateIndex(int x, int y, int z) { if (x < 0 || x >= width) @@ -38,30 +46,136 @@ public class Schematic { throw new IndexOutOfBoundsException("y must be non-negative and less than height"); if (z < 0 || z >= length) throw new IndexOutOfBoundsException("z must be non-negative and less than length"); - + return (y * width * length + z * width + x); } - + public short getBlockID(int x, int y, int z) { return blocks[calculateIndex(x, y, z)]; } - + public byte getBlockMetadata(int x, int y, int z) { return metadata[calculateIndex(x, y, z)]; } - + public NBTTagList getTileEntities() { return (NBTTagList) tileEntities.copy(); } - - public static Schematic readFromFile() + + public static Schematic readFromFile(String schematicPath) throws FileNotFoundException, InvalidSchematicException { - throw new UnsupportedOperationException(); + return readFromFile(new File(schematicPath)); } - + + public static Schematic readFromFile(File schematicFile) throws FileNotFoundException, InvalidSchematicException + { + return readFromStream(new FileInputStream(schematicFile)); + } + + public static Schematic readFromResource(String resourcePath) throws InvalidSchematicException + { + //We need an instance of a class in the mod to retrieve a resource + Schematic empty = new Schematic((short) 0, (short) 0, (short) 0, null, null, null); + InputStream schematicStream = empty.getClass().getResourceAsStream(resourcePath); + return readFromStream(schematicStream); + } + + private static Schematic readFromStream(InputStream schematicStream) throws InvalidSchematicException + { + short width; + short height; + short length; + int volume; + int pairs; + + byte[] metadata = null; //block metadata + byte[] lowBits = null; //first 8 bits of the block IDs + byte[] highBits = null; //additional 4 bits of the block IDs + short[] blocks = null; //list of combined block IDs + NBTTagList tileEntities = null; //storage for tile entities in NBT form + NBTTagCompound schematicTag; //the NBT data extracted from the schematic file + boolean hasExtendedBlockIDs; //indicates whether the schematic contains extended block IDs + + try + { + try + { + schematicTag = CompressedStreamTools.readCompressed(schematicStream); + schematicStream.close(); //readCompressed() probably closes the stream anyway, but close again to be sure. + } + catch (Exception ex) + { + throw new InvalidSchematicException("The schematic could not be decoded."); + } + + //load size of schematic to generate + width = schematicTag.getShort("Width"); + height = schematicTag.getShort("Height"); + length = schematicTag.getShort("Length"); + volume = width * length * height; + + if (width < 0) + throw new InvalidSchematicException("The schematic cannot have a negative width."); + if (height < 0) + throw new InvalidSchematicException("The schematic cannot have a negative height."); + if (length < 0) + throw new InvalidSchematicException("The schematic cannot have a negative length."); + + //load block info + lowBits = schematicTag.getByteArray("Blocks"); + highBits = schematicTag.getByteArray("AddBlocks"); + metadata = schematicTag.getByteArray("Data"); + hasExtendedBlockIDs = (highBits.length != 0); + + if (volume != lowBits.length) + throw new InvalidSchematicException("The schematic has data for fewer blocks than its dimensions indicate."); + if (volume != metadata.length) + throw new InvalidSchematicException("The schematic has metadata for fewer blocks than its dimensions indicate."); + if (volume > 2 * highBits.length && highBits.length != 0) + throw new InvalidSchematicException("The schematic has extended block IDs for fewer blocks than its dimensions indicate."); + + blocks = new short[volume]; + if (hasExtendedBlockIDs) + { + //Combine the split block IDs into a single value + pairs = volume - (volume & 1); + int index; + for (index = 0; index < pairs; index += 2) + { + blocks[index] = (short) (((highBits[index >> 1] & 0x0F) << 8) + lowBits[index]); + blocks[index + 1] = (short) (((highBits[index >> 1] & 0xF0) << 4) + lowBits[index + 1]); + } + if (index < volume) + { + blocks[index] = lowBits[index >> 1]; + } + } + else + { + //Copy the blockIDs + for (int index = 0; index < volume; index++) + blocks[index] = lowBits[index]; + } + + //Get the list of tile entities + tileEntities = schematicTag.getTagList("TileEntities"); + + return new Schematic(width, height, length, blocks, metadata, tileEntities); + } + catch (InvalidSchematicException ex) + { + //Throw the exception again to pass it to the caller. + throw ex; + } + catch (Exception ex) + { + throw new InvalidSchematicException("An unexpected error occurred while trying to decode the schematic.", ex); + } + } + public static Schematic copyFromWorld(World world, int x, int y, int z, short width, short height, short length, boolean doCompactBounds) { if (doCompactBounds) @@ -75,17 +189,17 @@ public class Schematic { { fixedHeight = worldHeight - fixedY; } - + //Compact the area to be copied to remove empty borders CompactBoundsOperation compactor = new CompactBoundsOperation(); compactor.apply(world, x, fixedY, z, width, fixedHeight, length); Point3D minCorner = compactor.getMinCorner(); Point3D maxCorner = compactor.getMaxCorner(); - + short compactWidth = (short) (maxCorner.getX() - minCorner.getX() + 1); short compactHeight = (short) (maxCorner.getY() - minCorner.getY() + 1); short compactLength = (short) (maxCorner.getZ() - minCorner.getZ() + 1); - + return copyFromWorld(world, minCorner.getX(), minCorner.getY(), minCorner.getZ(), compactWidth, compactHeight, compactLength); } @@ -94,7 +208,7 @@ public class Schematic { return copyFromWorld(world, x, y, z, width, height, length); } } - + private static Schematic copyFromWorld(World world, int x, int y, int z, short width, short height, short length) { //Short and sweet ^_^ @@ -102,7 +216,7 @@ public class Schematic { copier.apply(world, x, y, z, width, height, length); return new Schematic(width, height, length, copier.getBlockIDs(), copier.getMetadata(), copier.getTileEntities()); } - + private static boolean encodeBlockIDs(short[] blocks, byte[] lowBits, byte[] highBits) { int index; @@ -118,40 +232,44 @@ public class Schematic { highBits[index >> 1] = (byte) ((blocks[index] >> 8) & 0x0F); hasHighBits |= (highBits[index >> 1] != 0); } + for (index = 0; index < blocks.length; index++) + { + lowBits[index] = (byte) (blocks[index] & 0xFF); + } return hasHighBits; } - + public NBTTagCompound writeToNBT() { return writeToNBT(true); } - + private NBTTagCompound writeToNBT(boolean copyTileEntities) { //This is the main storage function. Schematics are really compressed NBT tags, so if we can generate //the tags, most of the work is done. All the other storage functions will rely on this one. - + NBTTagCompound schematicTag = new NBTTagCompound("Schematic"); schematicTag.setShort("Width", width); schematicTag.setShort("Length", length); schematicTag.setShort("Height", height); - + schematicTag.setTag("Entities", new NBTTagList()); schematicTag.setString("Materials", "Alpha"); - - byte[] lowBytes = new byte[blocks.length]; - byte[] highBytes = new byte[(blocks.length >> 1) + 1]; - boolean hasExtendedIDs = encodeBlockIDs(blocks, lowBytes, highBytes); - - schematicTag.setByteArray("Blocks", lowBytes); + + byte[] lowBits = new byte[blocks.length]; + byte[] highBits = new byte[(blocks.length >> 1) + 1]; + boolean hasExtendedIDs = encodeBlockIDs(blocks, lowBits, highBits); + + schematicTag.setByteArray("Blocks", lowBits); schematicTag.setByteArray("Data", metadata); - + if (hasExtendedIDs) { - schematicTag.setByteArray("AddBlocks", highBytes); + schematicTag.setByteArray("AddBlocks", highBits); } - + if (copyTileEntities) { //Used when the result of this function will be passed outside this class. @@ -166,12 +284,12 @@ public class Schematic { } return schematicTag; } - + public void writeToFile(String schematicPath) throws IOException { writeToFile(new File(schematicPath)); } - + public void writeToFile(File schematicFile) throws IOException { FileOutputStream outputStream = new FileOutputStream(schematicFile); @@ -180,4 +298,40 @@ public class Schematic { //Closing twice will not throw an exception. outputStream.close(); } + + public void copyToWorld(World world, int x, int y, int z) + { + int index; + int count; + int dx, dy, dz; + + //Copy blocks and metadata into the world + index = 0; + for (dy = 0; dy < height; dy++) + { + for (dz = 0; dz < length; dz++) + { + for (dx = 0; dx < width; dx++) + { + world.setBlock(x + dx, y + dy, z + dz, blocks[index], metadata[index], 3); + index++; + } + } + } + //Copy tile entities into the world + count = tileEntities.tagCount(); + for (index = 0; index < count; index++) + { + NBTTagCompound tileTag = (NBTTagCompound) tileEntities.tagAt(index); + //Rewrite its location to be in world coordinates + dx = tileTag.getInteger("x") + x; + dy = tileTag.getInteger("y") + y; + dz = tileTag.getInteger("z") + z; + tileTag.setInteger("x", dx); + tileTag.setInteger("y", dy); + tileTag.setInteger("z", dz); + //Load the tile entity and put it in the world + world.setBlockTileEntity(dx, dy, dz, TileEntity.createAndLoadEntity(tileTag)); + } + } } diff --git a/StevenDimDoors/mod_pocketDim/schematic/WorldCopyOperation.java b/StevenDimDoors/mod_pocketDim/schematic/WorldCopyOperation.java index d0c53d20..fe49a785 100644 --- a/StevenDimDoors/mod_pocketDim/schematic/WorldCopyOperation.java +++ b/StevenDimDoors/mod_pocketDim/schematic/WorldCopyOperation.java @@ -20,11 +20,11 @@ public class WorldCopyOperation extends WorldOperation super("WorldCopyOperation"); blockIDs = null; metadata = null; - tileEntities = new NBTTagList(); + tileEntities = null; } @Override - protected boolean start(World world, int x, int y, int z, int width, int height, int length) + protected boolean initialize(World world, int x, int y, int z, int width, int height, int length) { index = 0; originX = x; @@ -32,6 +32,7 @@ public class WorldCopyOperation extends WorldOperation originZ = z; blockIDs = new short[width * height * length]; metadata = new byte[width * height * length]; + tileEntities = new NBTTagList(); return true; } diff --git a/StevenDimDoors/mod_pocketDim/schematic/WorldOperation.java b/StevenDimDoors/mod_pocketDim/schematic/WorldOperation.java index 3d6f7fae..35798a56 100644 --- a/StevenDimDoors/mod_pocketDim/schematic/WorldOperation.java +++ b/StevenDimDoors/mod_pocketDim/schematic/WorldOperation.java @@ -11,7 +11,7 @@ public abstract class WorldOperation { this.name = name; } - protected boolean start(World world, int x, int y, int z, int width, int height, int length) + protected boolean initialize(World world, int x, int y, int z, int width, int height, int length) { return true; } @@ -25,7 +25,7 @@ public abstract class WorldOperation { public boolean apply(World world, int x, int y, int z, int width, int height, int length) { - if (!start(world, x, y, z, width, height, length)) + if (!initialize(world, x, y, z, width, height, length)) return false; int cx, cy, cz;