f4653d0522
Improved DungeonSchematic to the point that it can replace SchematicLoader as our method of loading dungeons. Some of the code was copied over rather than refactored to save time. It's been subdivided so make it much more readable. Also, we can reimplement portions of it as WorldOperations later on further remove redudancy. Testing shows that there is one problem left to fix: door blocks are not being set up correctly at the moment. Rifts are being set up properly and attaching doors to rifts will show that dungeons continue to generate fine. Added classes to support DungeonSchematic and its additional filtering logic on import and export. SpecialBlockFinder handles listing the entrance, other doors in the dungeon, and end portal frames. FillContainersOperation is a WorldOperation that fills chests and dispensers. BlockRotator is a temporary class to hold transformMetadata() and transformPoint() until we rewrite that code later. Stripped out most of the code from SchematicLoader to ensure it's no longer used. The only remaining function sets up dungeon pockets. We can phase it out in a later version so as not to delay our release. Removed references to SchematicLoader in mod_pocketDim since it no longer needs to be instantiated.
434 lines
13 KiB
Java
434 lines
13 KiB
Java
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.block.Block;
|
|
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 net.minecraft.world.chunk.Chunk;
|
|
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
|
|
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;
|
|
|
|
protected Schematic(short width, short height, short length, short[] blocks, byte[] metadata, NBTTagList tileEntities)
|
|
{
|
|
this.width = width;
|
|
this.height = height;
|
|
this.length = length;
|
|
this.blocks = blocks;
|
|
this.metadata = metadata;
|
|
this.tileEntities = tileEntities;
|
|
}
|
|
|
|
protected Schematic(Schematic source)
|
|
{
|
|
//Shallow copy constructor - critical for code reuse in derived classes since
|
|
//source's fields will be inaccessible if the derived class is in another package.
|
|
this.width = source.width;
|
|
this.height = source.height;
|
|
this.length = source.length;
|
|
this.blocks = source.blocks;
|
|
this.metadata = source.metadata;
|
|
this.tileEntities = source.tileEntities;
|
|
}
|
|
|
|
public int calculateIndex(int x, int y, int z)
|
|
{
|
|
if (x < 0 || x >= width)
|
|
throw new IndexOutOfBoundsException("x must be non-negative and less than width");
|
|
if (y < 0 || y >= height)
|
|
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 Point3D calculatePoint(int index)
|
|
{
|
|
int y = index / (width * length);
|
|
index -= y * width * length;
|
|
int z = index / width;
|
|
index -= z * width;
|
|
int x = index;
|
|
|
|
return new Point3D(x, y, z);
|
|
}
|
|
|
|
public int calculateIndexBelow(int index)
|
|
{
|
|
return index - (width * length);
|
|
}
|
|
|
|
public short getWidth()
|
|
{
|
|
return width;
|
|
}
|
|
|
|
public short getHeight()
|
|
{
|
|
return height;
|
|
}
|
|
|
|
public short getLength()
|
|
{
|
|
return length;
|
|
}
|
|
|
|
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(String schematicPath) throws FileNotFoundException, InvalidSchematicException
|
|
{
|
|
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);
|
|
}
|
|
|
|
public 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 && hasExtendedBlockIDs)
|
|
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] & 0xFF));
|
|
blocks[index + 1] = (short) (((highBits[index >> 1] & 0xF0) << 4) + (lowBits[index + 1] & 0xFF));
|
|
}
|
|
if (index < volume)
|
|
{
|
|
blocks[index] = lowBits[index >> 1];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Copy the blockIDs
|
|
for (int index = 0; index < volume; index++)
|
|
{
|
|
blocks[index] = (short) (lowBits[index] & 0xFF);
|
|
}
|
|
}
|
|
|
|
//Get the list of tile entities
|
|
tileEntities = schematicTag.getTagList("TileEntities");
|
|
|
|
Schematic result = new Schematic(width, height, length, blocks, metadata, tileEntities);
|
|
return result;
|
|
}
|
|
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)
|
|
{
|
|
//Adjust the vertical bounds to reasonable values if necessary
|
|
int worldHeight = world.getHeight();
|
|
int fixedY = (y < 0) ? 0 : y;
|
|
int fixedHeight = height + y - fixedY;
|
|
|
|
if (fixedHeight + fixedY >= worldHeight)
|
|
{
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
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 ^_^
|
|
WorldCopyOperation copier = new WorldCopyOperation();
|
|
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;
|
|
int length = blocks.length - (blocks.length & 1);
|
|
boolean hasHighBits = false;
|
|
for (index = 0; index < length; index += 2)
|
|
{
|
|
highBits[index >> 1] = (byte) (((blocks[index] >> 8) & 0x0F) + ((blocks[index + 1] >> 4) & 0xF0));
|
|
hasHighBits |= (highBits[index >> 1] != 0);
|
|
}
|
|
if (index < blocks.length)
|
|
{
|
|
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);
|
|
}
|
|
|
|
protected NBTTagCompound writeToNBT(boolean copyTileEntities)
|
|
{
|
|
return writeToNBT(width, height, length, blocks, metadata, tileEntities, copyTileEntities);
|
|
}
|
|
|
|
protected static NBTTagCompound writeToNBT(short width, short height, short length, short[] blocks, byte[] metadata,
|
|
NBTTagList tileEntities, 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[] 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", highBits);
|
|
}
|
|
|
|
if (copyTileEntities)
|
|
{
|
|
//Used when the result of this function will be passed outside this class.
|
|
//Avoids exposing the private field to external modifications.
|
|
schematicTag.setTag("TileEntities", (NBTTagList) tileEntities.copy());
|
|
}
|
|
else
|
|
{
|
|
//Used when the result of this function is for internal use.
|
|
//It's more efficient not to copy the tags unless it's needed.
|
|
schematicTag.setTag("TileEntities", tileEntities);
|
|
}
|
|
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);
|
|
CompressedStreamTools.writeCompressed(writeToNBT(false), outputStream);
|
|
//writeCompressed() probably closes the stream on its own - call close again just in case.
|
|
//Closing twice will not throw an exception.
|
|
outputStream.close();
|
|
}
|
|
|
|
public boolean applyFilter(SchematicFilter filter)
|
|
{
|
|
return filter.apply(this, this.blocks, this.metadata);
|
|
}
|
|
|
|
public void copyToWorld(World world, int x, int y, int z)
|
|
{
|
|
//This isn't implemented as a WorldOperation because it doesn't quite fit the structure of those operations.
|
|
//It's not worth the trouble in this case.
|
|
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++)
|
|
{
|
|
//In the future, we might want to make this more efficient by building whole chunks at a time
|
|
setBlockDirectly(world, x + dx, y + dy, z + dz, blocks[index], metadata[index]);
|
|
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));
|
|
}
|
|
}
|
|
|
|
protected static void setBlockDirectly(World world, int x, int y, int z, int blockID, int metadata)
|
|
{
|
|
if (blockID != 0 && Block.blocksList[blockID] == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int cX = x >> 4;
|
|
int cZ = z >> 4;
|
|
int cY = y >> 4;
|
|
Chunk chunk;
|
|
|
|
int localX = (x % 16) < 0 ? (x % 16) + 16 : (x % 16);
|
|
int localZ = (z % 16) < 0 ? (z % 16) + 16 : (z % 16);
|
|
ExtendedBlockStorage extBlockStorage;
|
|
|
|
try
|
|
{
|
|
chunk = world.getChunkFromChunkCoords(cX, cZ);
|
|
extBlockStorage = chunk.getBlockStorageArray()[cY];
|
|
if (extBlockStorage == null)
|
|
{
|
|
extBlockStorage = new ExtendedBlockStorage(cY << 4, !world.provider.hasNoSky);
|
|
chunk.getBlockStorageArray()[cY] = extBlockStorage;
|
|
}
|
|
extBlockStorage.setExtBlockID(localX, y & 15, localZ, blockID);
|
|
extBlockStorage.setExtBlockMetadata(localX, y & 15, localZ, metadata);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|