Created DungeonSchematic Class

Created DungeonSchematic, a class that extends Schematic and adds
DD-specific functionality such as methods for filtering the schematic's
block before importing and exporting. Testing confirms that
DungeonSchematic works well so far, although it still lacks critical
features.

Made some minor changes to Schematic and copied
SchematicLoader.setBlockDirectly(). I realized during testing that
setting blocks without using that function could cause problems. First,
it's possible that the blocks would not be placed if chunks weren't
created. Second, there are issues with blocks updating if we use World
to set them, even if block updates are disabled. That causes some
torches to break and other weird problems.

Added classes to support block filtering operations applied to
Schematic. These are used to implement ID standardization and removing
mod blocks when importing.
This commit is contained in:
SenseiKiwi 2013-07-29 09:58:47 -04:00
parent 8b64165e2e
commit 877678c945
8 changed files with 446 additions and 10 deletions

View file

@ -1,13 +1,17 @@
package StevenDimDoors.mod_pocketDim.commands;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Collection;
import net.minecraft.entity.player.EntityPlayer;
import StevenDimDoors.mod_pocketDim.DDProperties;
import StevenDimDoors.mod_pocketDim.DungeonGenerator;
import StevenDimDoors.mod_pocketDim.LinkData;
import StevenDimDoors.mod_pocketDim.dungeon.DungeonSchematic;
import StevenDimDoors.mod_pocketDim.helpers.DungeonHelper;
import StevenDimDoors.mod_pocketDim.helpers.dimHelper;
import StevenDimDoors.mod_pocketDim.schematic.InvalidSchematicException;
public class CommandCreateDungeonRift extends DDCommandBase
{
@ -81,6 +85,25 @@ public class CommandCreateDungeonRift extends DDCommandBase
link = dimHelper.instance.createPocket(link, true, true);
dimHelper.dimList.get(link.destDimID).dungeonGenerator = result;
sender.sendChatToPlayer("Created a rift to \"" + getSchematicName(result) + "\" dungeon (Dimension ID = " + link.destDimID + ").");
/*try {
DungeonSchematic dungeon;
if ((new File(result.schematicPath)).exists())
{
dungeon = DungeonSchematic.readFromFile(result.schematicPath);
}
else
{
dungeon = DungeonSchematic.readFromResource(result.schematicPath);
}
dungeon.ApplyImportFilters(DDProperties.instance());
dungeon.copyToWorld(sender.worldObj, x, y, z);
} catch (InvalidSchematicException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}*/
}
else
{

View file

@ -0,0 +1,112 @@
package StevenDimDoors.mod_pocketDim.dungeon;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import net.minecraft.world.World;
import StevenDimDoors.mod_pocketDim.DDProperties;
import StevenDimDoors.mod_pocketDim.schematic.CompoundFilter;
import StevenDimDoors.mod_pocketDim.schematic.InvalidSchematicException;
import StevenDimDoors.mod_pocketDim.schematic.ReplacementFilter;
import StevenDimDoors.mod_pocketDim.schematic.Schematic;
public class DungeonSchematic extends Schematic {
private static final short MAX_VANILLA_BLOCK_ID = 158;
private static final short STANDARD_FABRIC_OF_REALITY_ID = 1973;
private static final short STANDARD_ETERNAL_FABRIC_ID = 220;
private static final short[] MOD_BLOCK_FILTER_EXCEPTIONS = new short[] {
STANDARD_FABRIC_OF_REALITY_ID,
STANDARD_ETERNAL_FABRIC_ID
};
private DungeonSchematic(Schematic source)
{
super(source);
}
private DungeonSchematic()
{
//Used to create a dummy instance for readFromResource()
super((short) 0, (short) 0, (short) 0, null, null, null);
}
public static DungeonSchematic readFromFile(String schematicPath) throws FileNotFoundException, InvalidSchematicException
{
return readFromFile(new File(schematicPath));
}
public static DungeonSchematic readFromFile(File schematicFile) throws FileNotFoundException, InvalidSchematicException
{
return readFromStream(new FileInputStream(schematicFile));
}
public static DungeonSchematic readFromResource(String resourcePath) throws InvalidSchematicException
{
//We need an instance of a class in the mod to retrieve a resource
DungeonSchematic empty = new DungeonSchematic();
InputStream schematicStream = empty.getClass().getResourceAsStream(resourcePath);
return readFromStream(schematicStream);
}
public static DungeonSchematic readFromStream(InputStream schematicStream) throws InvalidSchematicException
{
return new DungeonSchematic(Schematic.readFromStream(schematicStream));
}
public void ApplyImportFilters(DDProperties properties)
{
//Filter out mod blocks except some of our own
CompoundFilter standardizer = new CompoundFilter();
standardizer.addFilter(new ModBlockFilter(MAX_VANILLA_BLOCK_ID, MOD_BLOCK_FILTER_EXCEPTIONS,
(short) properties.FabricBlockID, (byte) 0));
//Also convert standard DD block IDs to local versions
Map<Short, Short> mapping = getAssignedToStandardIDMapping(properties);
for (Entry<Short, Short> entry : mapping.entrySet())
{
if (entry.getKey() != entry.getValue())
{
standardizer.addFilter(new ReplacementFilter(entry.getValue(), entry.getKey()));
}
}
standardizer.apply(this, this.blocks, this.metadata);
}
public void ApplyExportFilters(DDProperties properties)
{
//Check if some block IDs assigned by Forge differ from our standard IDs
//If so, change the IDs to standard values
CompoundFilter standardizer = new CompoundFilter();
Map<Short, Short> mapping = getAssignedToStandardIDMapping(properties);
for (Entry<Short, Short> entry : mapping.entrySet())
{
if (entry.getKey() != entry.getValue())
{
standardizer.addFilter(new ReplacementFilter(entry.getKey(), entry.getValue()));
}
}
standardizer.apply(this, this.blocks, this.metadata);
}
private Map<Short, Short> getAssignedToStandardIDMapping(DDProperties properties)
{
//If we ever need this broadly or support other mods, this should be moved to a separate class
TreeMap<Short, Short> mapping = new TreeMap<Short, Short>();
mapping.put((short) properties.FabricBlockID, STANDARD_FABRIC_OF_REALITY_ID);
mapping.put((short) properties.PermaFabricBlockID, STANDARD_ETERNAL_FABRIC_ID);
return mapping;
}
public static DungeonSchematic copyFromWorld(World world, int x, int y, int z, short width, short height, short length, boolean doCompactBounds)
{
return new DungeonSchematic(Schematic.copyFromWorld(world, x, y, z, width, height, length, doCompactBounds));
}
}

View file

@ -0,0 +1,51 @@
package StevenDimDoors.mod_pocketDim.dungeon;
import net.minecraft.block.Block;
import StevenDimDoors.mod_pocketDim.schematic.SchematicFilter;
public class ModBlockFilter extends SchematicFilter {
private short maxVanillaBlockID;
private short[] exceptions;
private short replacementBlockID;
private byte replacementMetadata;
public ModBlockFilter(short maxVanillaBlockID, short[] exceptions, short replacementBlockID, byte replacementMetadata)
{
super("ModBlockFilter");
this.maxVanillaBlockID = maxVanillaBlockID;
this.exceptions = exceptions;
this.replacementBlockID = replacementBlockID;
this.replacementMetadata = replacementMetadata;
}
@Override
protected boolean applyToBlock(int index, short[] blocks, byte[] metadata)
{
int k;
short currentID = blocks[index];
if (currentID > maxVanillaBlockID || (currentID != 0 && Block.blocksList[currentID] == null))
{
//This might be a mod block. Check if an exception exists.
for (k = 0; k < exceptions.length; k++)
{
if (currentID == exceptions[k])
{
//Exception found, not considered a mod block
return false;
}
}
//No matching exception found. Replace the block.
blocks[index] = replacementBlockID;
metadata[index] = replacementMetadata;
return true;
}
return false;
}
@Override
protected boolean terminates()
{
return false;
}
}

View file

@ -17,8 +17,8 @@ import StevenDimDoors.mod_pocketDim.DimData;
import StevenDimDoors.mod_pocketDim.DungeonGenerator;
import StevenDimDoors.mod_pocketDim.LinkData;
import StevenDimDoors.mod_pocketDim.mod_pocketDim;
import StevenDimDoors.mod_pocketDim.dungeon.DungeonSchematic;
import StevenDimDoors.mod_pocketDim.items.itemDimDoor;
import StevenDimDoors.mod_pocketDim.schematic.Schematic;
import StevenDimDoors.mod_pocketDim.util.WeightedContainer;
public class DungeonHelper
@ -383,9 +383,10 @@ public class DungeonHelper
try
{
short size = (short) 2 * MAX_EXPORT_RADIUS + 1;
Schematic schematic = Schematic.copyFromWorld(world,
DungeonSchematic dungeon = DungeonSchematic.copyFromWorld(world,
centerX - MAX_EXPORT_RADIUS, centerY - MAX_EXPORT_RADIUS, centerZ - MAX_EXPORT_RADIUS, size, size, size, true);
schematic.writeToFile(exportPath);
dungeon.ApplyExportFilters(properties);
dungeon.writeToFile(exportPath);
return true;
}
catch(Exception e)

View file

@ -0,0 +1,58 @@
package StevenDimDoors.mod_pocketDim.schematic;
import java.util.ArrayList;
public class CompoundFilter extends SchematicFilter {
private ArrayList<SchematicFilter> filters;
public CompoundFilter()
{
super("CompoundFilter");
filters = new ArrayList<SchematicFilter>();
}
public void addFilter(SchematicFilter filter)
{
filters.add(filter);
}
@Override
protected boolean initialize(Schematic schematic, short[] blocks, byte[] metadata)
{
for (SchematicFilter filter : filters)
{
if (!filter.initialize(schematic, blocks, metadata))
{
return false;
}
}
return !filters.isEmpty();
}
@Override
protected boolean finish()
{
for (SchematicFilter filter : filters)
{
if (!filter.finish())
{
return false;
}
}
return true;
}
@Override
protected boolean applyToBlock(int index, short[] blocks, byte[] metadata)
{
for (SchematicFilter filter : filters)
{
if (!filter.applyToBlock(index, blocks, metadata))
{
return !filter.terminates();
}
}
return true;
}
}

View file

@ -0,0 +1,75 @@
package StevenDimDoors.mod_pocketDim.schematic;
public class ReplacementFilter extends SchematicFilter {
private short targetBlock;
private byte targetMetadata;
private boolean matchMetadata;
private short replacementBlock;
private byte replacementMetadata;
private boolean changeMetadata;
public ReplacementFilter(short targetBlock, byte targetMetadata, short replacementBlock, byte replacementMetadata)
{
super("ReplacementFilter");
this.targetBlock = targetBlock;
this.targetMetadata = targetMetadata;
this.matchMetadata = true;
this.replacementBlock = replacementBlock;
this.replacementMetadata = replacementMetadata;
this.changeMetadata = true;
}
public ReplacementFilter(short targetBlock, short replacementBlock, byte replacementMetadata)
{
super("ReplacementFilter");
this.targetBlock = targetBlock;
this.matchMetadata = false;
this.replacementBlock = replacementBlock;
this.replacementMetadata = replacementMetadata;
this.changeMetadata = true;
}
public ReplacementFilter(short targetBlock, byte targetMetadata, short replacementBlock)
{
super("ReplacementFilter");
this.targetBlock = targetBlock;
this.targetMetadata = targetMetadata;
this.matchMetadata = true;
this.replacementBlock = replacementBlock;
this.changeMetadata = false;
}
public ReplacementFilter(short targetBlock, short replacementBlock)
{
super("ReplacementFilter");
this.targetBlock = targetBlock;
this.matchMetadata = false;
this.replacementBlock = replacementBlock;
this.changeMetadata = false;
}
@Override
protected boolean applyToBlock(int index, short[] blocks, byte[] metadata)
{
if (blocks[index] == targetBlock)
{
if ((matchMetadata && metadata[index] == targetMetadata) || !matchMetadata)
{
blocks[index] = replacementBlock;
if (changeMetadata)
{
metadata[index] = replacementMetadata;
}
return true;
}
}
return false;
}
@Override
protected boolean terminates()
{
return false;
}
}

View file

@ -7,11 +7,14 @@ 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;
/**
@ -26,7 +29,7 @@ public class Schematic {
protected short[] blocks;
protected byte[] metadata;
protected NBTTagList tileEntities = new NBTTagList();
protected NBTTagList tileEntities;
protected Schematic(short width, short height, short length, short[] blocks, byte[] metadata, NBTTagList tileEntities)
{
@ -37,8 +40,20 @@ public class Schematic {
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;
}
private int calculateIndex(int x, int y, int z)
protected 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");
@ -83,7 +98,7 @@ public class Schematic {
return readFromStream(schematicStream);
}
private static Schematic readFromStream(InputStream schematicStream) throws InvalidSchematicException
public static Schematic readFromStream(InputStream schematicStream) throws InvalidSchematicException
{
short width;
short height;
@ -165,7 +180,8 @@ public class Schematic {
//Get the list of tile entities
tileEntities = schematicTag.getTagList("TileEntities");
return new Schematic(width, height, length, blocks, metadata, tileEntities);
Schematic result = new Schematic(width, height, length, blocks, metadata, tileEntities);
return result;
}
catch (InvalidSchematicException ex)
{
@ -246,7 +262,13 @@ public class Schematic {
return writeToNBT(true);
}
private NBTTagCompound writeToNBT(boolean copyTileEntities)
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.
@ -301,6 +323,11 @@ public class Schematic {
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.
@ -317,8 +344,8 @@ public class Schematic {
{
for (dx = 0; dx < width; dx++)
{
//Don't cause block updates!
world.setBlock(x + dx, y + dy, z + dz, blocks[index], metadata[index], 2);
//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++;
}
}
@ -339,4 +366,38 @@ public class Schematic {
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();
}
}
}

View file

@ -0,0 +1,55 @@
package StevenDimDoors.mod_pocketDim.schematic;
public class SchematicFilter {
private String name;
protected SchematicFilter(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public boolean apply(Schematic schematic, short[] blocks, byte[] metadata)
{
if (!initialize(schematic, blocks, metadata))
return false;
for (int index = 0; index < blocks.length; index++)
{
if (!applyToBlock(index, blocks, metadata) && terminates())
return false;
}
return finish();
}
protected boolean initialize(Schematic schematic, short[] blocks, byte[] metadata)
{
return true;
}
protected boolean applyToBlock(int index, short[] blocks, byte[] metadata)
{
return true;
}
protected boolean finish()
{
return true;
}
protected boolean terminates()
{
return true;
}
public String toString()
{
return name;
}
}