From 877678c945577c31d6fc766997696e33dd991bb9 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 29 Jul 2013 09:58:47 -0400 Subject: [PATCH] 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. --- .../commands/CommandCreateDungeonRift.java | 23 ++++ .../dungeon/DungeonSchematic.java | 112 ++++++++++++++++++ .../mod_pocketDim/dungeon/ModBlockFilter.java | 51 ++++++++ .../mod_pocketDim/helpers/DungeonHelper.java | 7 +- .../schematic/CompoundFilter.java | 58 +++++++++ .../schematic/ReplacementFilter.java | 75 ++++++++++++ .../mod_pocketDim/schematic/Schematic.java | 75 ++++++++++-- .../schematic/SchematicFilter.java | 55 +++++++++ 8 files changed, 446 insertions(+), 10 deletions(-) create mode 100644 StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java create mode 100644 StevenDimDoors/mod_pocketDim/dungeon/ModBlockFilter.java create mode 100644 StevenDimDoors/mod_pocketDim/schematic/CompoundFilter.java create mode 100644 StevenDimDoors/mod_pocketDim/schematic/ReplacementFilter.java create mode 100644 StevenDimDoors/mod_pocketDim/schematic/SchematicFilter.java diff --git a/StevenDimDoors/mod_pocketDim/commands/CommandCreateDungeonRift.java b/StevenDimDoors/mod_pocketDim/commands/CommandCreateDungeonRift.java index 84f2c06a..281df903 100644 --- a/StevenDimDoors/mod_pocketDim/commands/CommandCreateDungeonRift.java +++ b/StevenDimDoors/mod_pocketDim/commands/CommandCreateDungeonRift.java @@ -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 { diff --git a/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java b/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java new file mode 100644 index 00000000..b364cbff --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/dungeon/DungeonSchematic.java @@ -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 mapping = getAssignedToStandardIDMapping(properties); + + for (Entry 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 mapping = getAssignedToStandardIDMapping(properties); + + for (Entry 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 getAssignedToStandardIDMapping(DDProperties properties) + { + //If we ever need this broadly or support other mods, this should be moved to a separate class + TreeMap mapping = new TreeMap(); + 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)); + } +} diff --git a/StevenDimDoors/mod_pocketDim/dungeon/ModBlockFilter.java b/StevenDimDoors/mod_pocketDim/dungeon/ModBlockFilter.java new file mode 100644 index 00000000..c0ab0990 --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/dungeon/ModBlockFilter.java @@ -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; + } +} diff --git a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java index f9cfa8df..93903399 100644 --- a/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java +++ b/StevenDimDoors/mod_pocketDim/helpers/DungeonHelper.java @@ -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) diff --git a/StevenDimDoors/mod_pocketDim/schematic/CompoundFilter.java b/StevenDimDoors/mod_pocketDim/schematic/CompoundFilter.java new file mode 100644 index 00000000..2de3ec6e --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/schematic/CompoundFilter.java @@ -0,0 +1,58 @@ +package StevenDimDoors.mod_pocketDim.schematic; + +import java.util.ArrayList; + +public class CompoundFilter extends SchematicFilter { + + private ArrayList filters; + + public CompoundFilter() + { + super("CompoundFilter"); + filters = new ArrayList(); + } + + 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; + } +} diff --git a/StevenDimDoors/mod_pocketDim/schematic/ReplacementFilter.java b/StevenDimDoors/mod_pocketDim/schematic/ReplacementFilter.java new file mode 100644 index 00000000..ba993047 --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/schematic/ReplacementFilter.java @@ -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; + } +} diff --git a/StevenDimDoors/mod_pocketDim/schematic/Schematic.java b/StevenDimDoors/mod_pocketDim/schematic/Schematic.java index a7baeb90..4f00dab6 100644 --- a/StevenDimDoors/mod_pocketDim/schematic/Schematic.java +++ b/StevenDimDoors/mod_pocketDim/schematic/Schematic.java @@ -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(); + } + } } diff --git a/StevenDimDoors/mod_pocketDim/schematic/SchematicFilter.java b/StevenDimDoors/mod_pocketDim/schematic/SchematicFilter.java new file mode 100644 index 00000000..db3b3746 --- /dev/null +++ b/StevenDimDoors/mod_pocketDim/schematic/SchematicFilter.java @@ -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; + } +}