From 8aabfb0c67d05d5e71c12061fbd0f683fbcff02a Mon Sep 17 00:00:00 2001 From: Meaglin Date: Mon, 5 Mar 2012 07:05:17 +0100 Subject: [PATCH] Implemented new Anvil saving format, fixed old chunk saving format. - Added 'Chunk' Interface. - Moved old 'Chunk' to 'OldChunk' and replaced dynamic world height reference with '128. - Added 'AnvilChunk' implementing the new anvil chunk format. - Added temp fixes to FileMcRegionChunkStore.java, TrueZipMcRegionChunkStore.java and ZippedMcRegionChunkStore.java too allow them to read .mca files. - Added the new 'IntArrayTag' since the new heightmap tag wasn't recognized. - Moved 'getChildTag' to 'NBTUtils'. --- src/main/java/com/sk89q/jnbt/IntArrayTag.java | 87 +++++++ .../java/com/sk89q/jnbt/NBTConstants.java | 2 +- .../java/com/sk89q/jnbt/NBTInputStream.java | 7 + .../java/com/sk89q/jnbt/NBTOutputStream.java | 10 + src/main/java/com/sk89q/jnbt/NBTUtils.java | 30 +++ .../sk89q/worldedit/blocks/ChestBlock.java | 16 +- .../worldedit/blocks/DispenserBlock.java | 16 +- .../sk89q/worldedit/blocks/FurnaceBlock.java | 16 +- .../worldedit/blocks/MobSpawnerBlock.java | 4 +- .../com/sk89q/worldedit/data/AnvilChunk.java | 223 +++++++++++++++++ .../java/com/sk89q/worldedit/data/Chunk.java | 225 +----------------- .../com/sk89q/worldedit/data/ChunkStore.java | 10 +- .../data/FileMcRegionChunkStore.java | 8 +- .../com/sk89q/worldedit/data/OldChunk.java | 205 ++++++++++++++++ .../data/TrueZipMcRegionChunkStore.java | 10 +- .../data/ZippedMcRegionChunkStore.java | 9 +- .../worldedit/snapshots/SnapshotRestore.java | 22 +- 17 files changed, 647 insertions(+), 253 deletions(-) create mode 100644 src/main/java/com/sk89q/jnbt/IntArrayTag.java create mode 100644 src/main/java/com/sk89q/worldedit/data/AnvilChunk.java create mode 100644 src/main/java/com/sk89q/worldedit/data/OldChunk.java diff --git a/src/main/java/com/sk89q/jnbt/IntArrayTag.java b/src/main/java/com/sk89q/jnbt/IntArrayTag.java new file mode 100644 index 000000000..4a3365bd5 --- /dev/null +++ b/src/main/java/com/sk89q/jnbt/IntArrayTag.java @@ -0,0 +1,87 @@ +package com.sk89q.jnbt; + +import com.sk89q.jnbt.Tag; + +/* + * JNBT License + * + * Copyright (c) 2010 Graham Edgecombe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the JNBT team nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The TAG_Int_Array tag. + * + * @author Graham Edgecombe + * + */ +public final class IntArrayTag extends Tag { + + /** + * The value. + */ + private final int[] value; + + /** + * Creates the tag. + * + * @param name + * The name. + * @param value + * The value. + */ + public IntArrayTag(String name, int[] value) { + super(name); + this.value = value; + } + + @Override + public int[] getValue() { + return value; + } + + @Override + public String toString() { + StringBuilder hex = new StringBuilder(); + for (int b : value) { + String hexDigits = Integer.toHexString(b).toUpperCase(); + if (hexDigits.length() == 1) { + hex.append("0"); + } + hex.append(hexDigits).append(" "); + } + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Int_Array" + append + ": " + hex.toString(); + } + +} diff --git a/src/main/java/com/sk89q/jnbt/NBTConstants.java b/src/main/java/com/sk89q/jnbt/NBTConstants.java index b8fe8d105..b7b3adb19 100644 --- a/src/main/java/com/sk89q/jnbt/NBTConstants.java +++ b/src/main/java/com/sk89q/jnbt/NBTConstants.java @@ -54,7 +54,7 @@ public final class NBTConstants { public static final int TYPE_END = 0, TYPE_BYTE = 1, TYPE_SHORT = 2, TYPE_INT = 3, TYPE_LONG = 4, TYPE_FLOAT = 5, TYPE_DOUBLE = 6, TYPE_BYTE_ARRAY = 7, TYPE_STRING = 8, TYPE_LIST = 9, - TYPE_COMPOUND = 10; + TYPE_COMPOUND = 10, TYPE_INT_ARRAY = 11; /** * Default private constructor. diff --git a/src/main/java/com/sk89q/jnbt/NBTInputStream.java b/src/main/java/com/sk89q/jnbt/NBTInputStream.java index 6ed486163..6dc92ada5 100644 --- a/src/main/java/com/sk89q/jnbt/NBTInputStream.java +++ b/src/main/java/com/sk89q/jnbt/NBTInputStream.java @@ -199,6 +199,13 @@ public final class NBTInputStream implements Closeable { } return new CompoundTag(name, tagMap); + case NBTConstants.TYPE_INT_ARRAY: + length = is.readInt(); + int[] data = new int[length]; + for (int i = 0; i < length; i++) { + data[i] = is.readInt(); + } + return new IntArrayTag(name, data); default: throw new IOException("Invalid tag type: " + type + "."); } diff --git a/src/main/java/com/sk89q/jnbt/NBTOutputStream.java b/src/main/java/com/sk89q/jnbt/NBTOutputStream.java index e4f81da18..4cd9935dd 100644 --- a/src/main/java/com/sk89q/jnbt/NBTOutputStream.java +++ b/src/main/java/com/sk89q/jnbt/NBTOutputStream.java @@ -157,6 +157,8 @@ public final class NBTOutputStream implements Closeable { case NBTConstants.TYPE_COMPOUND: writeCompoundTagPayload((CompoundTag) tag); break; + case NBTConstants.TYPE_INT_ARRAY: + writeIntArrayTagPayload((IntArrayTag) tag); default: throw new IOException("Invalid tag type: " + type + "."); } @@ -308,6 +310,14 @@ public final class NBTOutputStream implements Closeable { private void writeEndTagPayload(EndTag tag) { /* empty */ } + + private void writeIntArrayTagPayload(IntArrayTag tag) throws IOException { + int[] data = tag.getValue(); + os.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + os.writeInt(data[i]); + } + } public void close() throws IOException { os.close(); diff --git a/src/main/java/com/sk89q/jnbt/NBTUtils.java b/src/main/java/com/sk89q/jnbt/NBTUtils.java index 8c217497a..c2c8fc9aa 100644 --- a/src/main/java/com/sk89q/jnbt/NBTUtils.java +++ b/src/main/java/com/sk89q/jnbt/NBTUtils.java @@ -1,5 +1,7 @@ package com.sk89q.jnbt; +import java.util.Map; + import com.sk89q.jnbt.ByteArrayTag; import com.sk89q.jnbt.ByteTag; import com.sk89q.jnbt.CompoundTag; @@ -13,6 +15,7 @@ import com.sk89q.jnbt.NBTConstants; import com.sk89q.jnbt.ShortTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.data.InvalidFormatException; /* * JNBT License @@ -85,6 +88,8 @@ public final class NBTUtils { return "TAG_Short"; } else if (clazz.equals(StringTag.class)) { return "TAG_String"; + } else if (clazz.equals(IntArrayTag.class)) { + return "TAG_Int_Array"; } else { throw new IllegalArgumentException("Invalid tag classs (" + clazz.getName() + ")."); @@ -123,6 +128,8 @@ public final class NBTUtils { return NBTConstants.TYPE_SHORT; } else if (clazz.equals(StringTag.class)) { return NBTConstants.TYPE_STRING; + } else if (clazz.equals(IntArrayTag.class)) { + return NBTConstants.TYPE_INT_ARRAY; } else { throw new IllegalArgumentException("Invalid tag classs (" + clazz.getName() + ")."); @@ -162,6 +169,8 @@ public final class NBTUtils { return ListTag.class; case NBTConstants.TYPE_COMPOUND: return CompoundTag.class; + case NBTConstants.TYPE_INT_ARRAY: + return IntArrayTag.class; default: throw new IllegalArgumentException("Invalid tag type : " + type + "."); @@ -174,5 +183,26 @@ public final class NBTUtils { private NBTUtils() { } + + /** + * Get child tag of a NBT structure. + * + * @param items + * @param key + * @param expected + * @return child tag + * @throws InvalidFormatException + */ + public static T getChildTag(Map items, String key, + Class expected) throws InvalidFormatException { + if (!items.containsKey(key)) { + throw new InvalidFormatException("Missing a \"" + key + "\" tag"); + } + Tag tag = items.get(key); + if (!expected.isInstance(tag)) { + throw new InvalidFormatException(key + " tag is not of tag type " + expected.getName()); + } + return expected.cast(tag); + } } diff --git a/src/main/java/com/sk89q/worldedit/blocks/ChestBlock.java b/src/main/java/com/sk89q/worldedit/blocks/ChestBlock.java index 2b78d0d0d..020233d90 100644 --- a/src/main/java/com/sk89q/worldedit/blocks/ChestBlock.java +++ b/src/main/java/com/sk89q/worldedit/blocks/ChestBlock.java @@ -150,7 +150,7 @@ public class ChestBlock extends BaseBlock implements TileEntityBlock, ContainerB throw new DataException("'Chest' tile entity expected"); } - ListTag items = (ListTag) Chunk.getChildTag(values, "Items", ListTag.class); + ListTag items = (ListTag) NBTUtils.getChildTag(values, "Items", ListTag.class); BaseItemStack[] newItems = new BaseItemStack[27]; for (Tag tag : items.getValue()) { @@ -161,20 +161,20 @@ public class ChestBlock extends BaseBlock implements TileEntityBlock, ContainerB CompoundTag item = (CompoundTag) tag; Map itemValues = item.getValue(); - short id = Chunk.getChildTag(itemValues, "id", ShortTag.class).getValue(); - short damage = Chunk.getChildTag(itemValues, "Damage", ShortTag.class).getValue(); - byte count = Chunk.getChildTag(itemValues, "Count", ByteTag.class).getValue(); - byte slot = Chunk.getChildTag(itemValues, "Slot", ByteTag.class).getValue(); + short id = NBTUtils.getChildTag(itemValues, "id", ShortTag.class).getValue(); + short damage = NBTUtils.getChildTag(itemValues, "Damage", ShortTag.class).getValue(); + byte count = NBTUtils.getChildTag(itemValues, "Count", ByteTag.class).getValue(); + byte slot = NBTUtils.getChildTag(itemValues, "Slot", ByteTag.class).getValue(); if (slot >= 0 && slot <= 26) { BaseItemStack itemstack = new BaseItemStack(id, count, damage); if(itemValues.containsKey("tag")) { - ListTag ench = (ListTag) Chunk.getChildTag(itemValues, "tag", CompoundTag.class).getValue().get("ench"); + ListTag ench = (ListTag) NBTUtils.getChildTag(itemValues, "tag", CompoundTag.class).getValue().get("ench"); for(Tag e : ench.getValue()) { Map vars = ((CompoundTag) e).getValue(); - short enchid = Chunk.getChildTag(vars, "id", ShortTag.class).getValue(); - short enchlvl = Chunk.getChildTag(vars, "lvl", ShortTag.class).getValue(); + short enchid = NBTUtils.getChildTag(vars, "id", ShortTag.class).getValue(); + short enchlvl = NBTUtils.getChildTag(vars, "lvl", ShortTag.class).getValue(); itemstack.getEnchantments().put((int) enchid, (int)enchlvl); } } diff --git a/src/main/java/com/sk89q/worldedit/blocks/DispenserBlock.java b/src/main/java/com/sk89q/worldedit/blocks/DispenserBlock.java index 46d210f4a..7b7cc73b2 100644 --- a/src/main/java/com/sk89q/worldedit/blocks/DispenserBlock.java +++ b/src/main/java/com/sk89q/worldedit/blocks/DispenserBlock.java @@ -150,7 +150,7 @@ public class DispenserBlock extends BaseBlock implements TileEntityBlock, Contai throw new DataException("'Trap' tile entity expected"); } - ListTag items = (ListTag) Chunk.getChildTag(values, "Items", ListTag.class); + ListTag items = (ListTag) NBTUtils.getChildTag(values, "Items", ListTag.class); BaseItemStack[] newItems = new BaseItemStack[9]; for (Tag tag : items.getValue()) { @@ -161,20 +161,20 @@ public class DispenserBlock extends BaseBlock implements TileEntityBlock, Contai CompoundTag item = (CompoundTag) tag; Map itemValues = item.getValue(); - short id = Chunk.getChildTag(itemValues, "id", ShortTag.class).getValue(); - short damage = Chunk.getChildTag(itemValues, "Damage", ShortTag.class).getValue(); - byte count = Chunk.getChildTag(itemValues, "Count", ByteTag.class).getValue(); - byte slot = Chunk.getChildTag(itemValues, "Slot", ByteTag.class).getValue(); + short id = NBTUtils.getChildTag(itemValues, "id", ShortTag.class).getValue(); + short damage = NBTUtils.getChildTag(itemValues, "Damage", ShortTag.class).getValue(); + byte count = NBTUtils.getChildTag(itemValues, "Count", ByteTag.class).getValue(); + byte slot = NBTUtils.getChildTag(itemValues, "Slot", ByteTag.class).getValue(); if (slot >= 0 && slot <= 8) { BaseItemStack itemstack = new BaseItemStack(id, count, damage); if(itemValues.containsKey("tag")) { - ListTag ench = (ListTag) Chunk.getChildTag(itemValues, "tag", CompoundTag.class).getValue().get("ench"); + ListTag ench = (ListTag) NBTUtils.getChildTag(itemValues, "tag", CompoundTag.class).getValue().get("ench"); for(Tag e : ench.getValue()) { Map vars = ((CompoundTag) e).getValue(); - short enchid = Chunk.getChildTag(vars, "id", ShortTag.class).getValue(); - short enchlvl = Chunk.getChildTag(vars, "lvl", ShortTag.class).getValue(); + short enchid = NBTUtils.getChildTag(vars, "id", ShortTag.class).getValue(); + short enchlvl = NBTUtils.getChildTag(vars, "lvl", ShortTag.class).getValue(); itemstack.getEnchantments().put((int) enchid, (int)enchlvl); } } diff --git a/src/main/java/com/sk89q/worldedit/blocks/FurnaceBlock.java b/src/main/java/com/sk89q/worldedit/blocks/FurnaceBlock.java index af1e86489..e62d329e8 100644 --- a/src/main/java/com/sk89q/worldedit/blocks/FurnaceBlock.java +++ b/src/main/java/com/sk89q/worldedit/blocks/FurnaceBlock.java @@ -194,7 +194,7 @@ public class FurnaceBlock extends BaseBlock implements TileEntityBlock, Containe throw new DataException("'Furnace' tile entity expected"); } - ListTag items = (ListTag) Chunk.getChildTag(values, "Items", ListTag.class); + ListTag items = (ListTag) NBTUtils.getChildTag(values, "Items", ListTag.class); BaseItemStack[] newItems = new BaseItemStack[3]; for (Tag tag : items.getValue()) { @@ -205,20 +205,20 @@ public class FurnaceBlock extends BaseBlock implements TileEntityBlock, Containe CompoundTag item = (CompoundTag) tag; Map itemValues = item.getValue(); - short id = Chunk.getChildTag(itemValues, "id", ShortTag.class).getValue(); - short damage = Chunk.getChildTag(itemValues, "Damage", ShortTag.class).getValue(); - byte count = Chunk.getChildTag(itemValues, "Count", ByteTag.class).getValue(); - byte slot = Chunk.getChildTag(itemValues, "Slot", ByteTag.class).getValue(); + short id = NBTUtils.getChildTag(itemValues, "id", ShortTag.class).getValue(); + short damage = NBTUtils.getChildTag(itemValues, "Damage", ShortTag.class).getValue(); + byte count = NBTUtils.getChildTag(itemValues, "Count", ByteTag.class).getValue(); + byte slot = NBTUtils.getChildTag(itemValues, "Slot", ByteTag.class).getValue(); if (slot >= 0 && slot <= 2) { BaseItemStack itemstack = new BaseItemStack(id, count, damage); if(itemValues.containsKey("tag")) { - ListTag ench = (ListTag) Chunk.getChildTag(itemValues, "tag", CompoundTag.class).getValue().get("ench"); + ListTag ench = (ListTag) NBTUtils.getChildTag(itemValues, "tag", CompoundTag.class).getValue().get("ench"); for(Tag e : ench.getValue()) { Map vars = ((CompoundTag) e).getValue(); - short enchid = Chunk.getChildTag(vars, "id", ShortTag.class).getValue(); - short enchlvl = Chunk.getChildTag(vars, "lvl", ShortTag.class).getValue(); + short enchid = NBTUtils.getChildTag(vars, "id", ShortTag.class).getValue(); + short enchlvl = NBTUtils.getChildTag(vars, "lvl", ShortTag.class).getValue(); itemstack.getEnchantments().put((int) enchid, (int)enchlvl); } } diff --git a/src/main/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java b/src/main/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java index be0e519c4..2430502bc 100644 --- a/src/main/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java +++ b/src/main/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java @@ -151,8 +151,8 @@ public class MobSpawnerBlock extends BaseBlock implements TileEntityBlock { throw new DataException("'MobSpawner' tile entity expected"); } - StringTag mobTypeTag = (StringTag) Chunk.getChildTag(values, "EntityId", StringTag.class); - ShortTag delayTag = (ShortTag) Chunk.getChildTag(values, "Delay", ShortTag.class); + StringTag mobTypeTag = (StringTag) NBTUtils.getChildTag(values, "EntityId", StringTag.class); + ShortTag delayTag = (ShortTag) NBTUtils.getChildTag(values, "Delay", ShortTag.class); this.mobType = mobTypeTag.getValue(); this.delay = delayTag.getValue(); diff --git a/src/main/java/com/sk89q/worldedit/data/AnvilChunk.java b/src/main/java/com/sk89q/worldedit/data/AnvilChunk.java new file mode 100644 index 000000000..e92e2d4f0 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/data/AnvilChunk.java @@ -0,0 +1,223 @@ +package com.sk89q.worldedit.data; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.sk89q.jnbt.ByteArrayTag; +import com.sk89q.jnbt.ByteTag; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.IntTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.NBTUtils; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.LocalWorld; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.blocks.ChestBlock; +import com.sk89q.worldedit.blocks.DispenserBlock; +import com.sk89q.worldedit.blocks.FurnaceBlock; +import com.sk89q.worldedit.blocks.MobSpawnerBlock; +import com.sk89q.worldedit.blocks.NoteBlock; +import com.sk89q.worldedit.blocks.SignBlock; +import com.sk89q.worldedit.blocks.TileEntityBlock; + +public class AnvilChunk implements Chunk { + + private CompoundTag rootTag; + private byte[][] blocks; + private byte[][] data; + private int rootX; + private int rootZ; + + private Map> tileEntities; + @SuppressWarnings("unused") + private LocalWorld world; // TODO: remove if stays unused. + + /** + * Construct the chunk with a compound tag. + * + * @param tag + * @throws DataException + */ + public AnvilChunk(LocalWorld world, CompoundTag tag) throws DataException { + rootTag = tag; + this.world = world; + + rootX = NBTUtils.getChildTag( rootTag.getValue(), "xPos", IntTag.class).getValue(); + rootZ = NBTUtils.getChildTag( rootTag.getValue(), "zPos", IntTag.class).getValue(); + + blocks = new byte[16][16*16*16]; + data = new byte[16][16*16*8]; + List sections = NBTUtils.getChildTag(rootTag.getValue(), "Sections", ListTag.class).getValue(); + for(Tag section : sections) { + if(!(section instanceof CompoundTag)) continue; + CompoundTag compoundsection = (CompoundTag) section; + if(!compoundsection.getValue().containsKey("Y")) continue; // Empty section. + int y = NBTUtils.getChildTag(compoundsection.getValue(), "Y", ByteTag.class).getValue(); + if(y < 0 || y >= 16) continue; + blocks[y] = NBTUtils.getChildTag(compoundsection.getValue(), "Blocks", ByteArrayTag.class).getValue(); + data[y] = NBTUtils.getChildTag(compoundsection.getValue(), "Data", ByteArrayTag.class).getValue(); + } + + int sectionsize = 16*16*16; + for(int i = 0; i < blocks.length; i++) { + if (blocks[i].length != sectionsize) { + throw new InvalidFormatException("Chunk blocks byte array expected " + + "to be " + sectionsize + " bytes; found " + blocks[i].length); + } + } + + for(int i = 0; i < data.length; i++) { + if (data[i].length != (sectionsize/2)) { + throw new InvalidFormatException("Chunk block data byte array " + + "expected to be " + sectionsize + " bytes; found " + data[i].length); + } + } + } + + @Override + public int getBlockID(Vector pos) throws DataException { + int x = pos.getBlockX() - rootX * 16; + int y = pos.getBlockY(); + int z = pos.getBlockZ() - rootZ * 16; + + int section = y >> 4; + if(section < 0 || section >= blocks.length) throw new DataException("Chunk does not contain position " + pos); + int yindex = y & 0x0F; + if(yindex < 0 || yindex >= 16) throw new DataException("Chunk does not contain position " + pos); + + + int index = x + (z * 16 + (yindex * 16 * 16)); + try { + return blocks[section][index]; + } catch (IndexOutOfBoundsException e) { + throw new DataException("Chunk does not contain position " + pos); + } + } + + @Override + public int getBlockData(Vector pos) throws DataException { + int x = pos.getBlockX() - rootX * 16; + int y = pos.getBlockY(); + int z = pos.getBlockZ() - rootZ * 16; + + int section = y >> 4; + if(section < 0 || section >= blocks.length) throw new DataException("Chunk does not contain position " + pos); + int yindex = y & 0x0F; + if(yindex < 0 || yindex >= 16) throw new DataException("Chunk does not contain position " + pos); + + + int index = x + (z * 16 + (yindex * 16 * 16)); + boolean shift = index % 2 == 0; + index /= 2; + + try { + if (!shift) { + return (data[section][index] & 0xF0) >> 4; + } else { + return data[section][index] & 0xF; + } + } catch (IndexOutOfBoundsException e) { + throw new DataException("Chunk does not contain position " + pos); + } + } + + /** + * Used to load the tile entities. + * + * @throws DataException + */ + private void populateTileEntities() throws DataException { + List tags = NBTUtils.getChildTag( + rootTag.getValue(), "TileEntities", ListTag.class) + .getValue(); + + tileEntities = new HashMap>(); + + for (Tag tag : tags) { + if (!(tag instanceof CompoundTag)) { + throw new InvalidFormatException("CompoundTag expected in TileEntities"); + } + + CompoundTag t = (CompoundTag) tag; + + int x = 0; + int y = 0; + int z = 0; + + Map values = new HashMap(); + + for (Map.Entry entry : t.getValue().entrySet()) { + if (entry.getKey().equals("x")) { + if (entry.getValue() instanceof IntTag) { + x = ((IntTag) entry.getValue()).getValue(); + } + } else if (entry.getKey().equals("y")) { + if (entry.getValue() instanceof IntTag) { + y = ((IntTag) entry.getValue()).getValue(); + } + } else if (entry.getKey().equals("z")) { + if (entry.getValue() instanceof IntTag) { + z = ((IntTag) entry.getValue()).getValue(); + } + } + + values.put(entry.getKey(), entry.getValue()); + } + + BlockVector vec = new BlockVector(x, y, z); + tileEntities.put(vec, values); + } + } + + /** + * Get the map of tags keyed to strings for a block's tile entity data. May + * return null if there is no tile entity data. Not public yet because + * what this function returns isn't ideal for usage. + * + * @param pos + * @return + * @throws DataException + */ + private Map getBlockTileEntity(Vector pos) throws DataException { + if (tileEntities == null) { + populateTileEntities(); + } + + return tileEntities.get(new BlockVector(pos)); + } + + @Override + public BaseBlock getBlock(Vector pos) throws DataException { + int id = getBlockID(pos); + int data = getBlockData(pos); + BaseBlock block; + + if (id == BlockID.WALL_SIGN || id == BlockID.SIGN_POST) { + block = new SignBlock(id, data); + } else if (id == BlockID.CHEST) { + block = new ChestBlock(data); + } else if (id == BlockID.FURNACE || id == BlockID.BURNING_FURNACE) { + block = new FurnaceBlock(id, data); + } else if (id == BlockID.DISPENSER) { + block = new DispenserBlock(data); + } else if (id == BlockID.MOB_SPAWNER) { + block = new MobSpawnerBlock(data); + } else if (id == BlockID.NOTE_BLOCK) { + block = new NoteBlock(data); + } else { + block = new BaseBlock(id, data); + } + + if (block instanceof TileEntityBlock) { + Map tileEntity = getBlockTileEntity(pos); + ((TileEntityBlock) block).fromTileEntityNBT(tileEntity); + } + + return block; + } + +} diff --git a/src/main/java/com/sk89q/worldedit/data/Chunk.java b/src/main/java/com/sk89q/worldedit/data/Chunk.java index a33d5d7f4..e0fd98867 100644 --- a/src/main/java/com/sk89q/worldedit/data/Chunk.java +++ b/src/main/java/com/sk89q/worldedit/data/Chunk.java @@ -1,75 +1,9 @@ -// $Id$ -/* - * WorldEdit - * Copyright (C) 2010 sk89q and contributors - * - * 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 . -*/ - package com.sk89q.worldedit.data; -import java.util.List; -import java.util.Map; -import java.util.HashMap; -import com.sk89q.jnbt.*; -import com.sk89q.worldedit.*; -import com.sk89q.worldedit.blocks.*; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; -/** - * Represents a chunk. - * - * @author sk89q - */ -public class Chunk { - private CompoundTag rootTag; - private byte[] blocks; - private byte[] data; - private int rootX; - private int rootZ; - - private Map> tileEntities; - private LocalWorld world; - - /** - * Construct the chunk with a compound tag. - * - * @param tag - * @throws DataException - */ - public Chunk(LocalWorld world, CompoundTag tag) throws DataException { - rootTag = tag; - this.world = world; - - blocks = getChildTag( - rootTag.getValue(), "Blocks", ByteArrayTag.class).getValue(); - data = getChildTag( - rootTag.getValue(), "Data", ByteArrayTag.class).getValue(); - rootX = getChildTag( - rootTag.getValue(), "xPos", IntTag.class).getValue(); - rootZ = getChildTag( - rootTag.getValue(), "zPos", IntTag.class).getValue(); - - if (blocks.length != 16*16*(world.getMaxY() + 1)) { - throw new InvalidFormatException("Chunk blocks byte array expected " - + "to be " + 16*16*(world.getMaxY() + 1) + " bytes; found " + blocks.length); - } - - if (data.length != 16*16*((world.getMaxY() + 1)/2)) { - throw new InvalidFormatException("Chunk block data byte array " - + "expected to be " + 16*16*((world.getMaxY() + 1)/2) + " bytes; found " + data.length); - } - } +public interface Chunk { /** * Get the block ID of a block. @@ -78,19 +12,8 @@ public class Chunk { * @return * @throws DataException */ - public int getBlockID(Vector pos) throws DataException { - int x = pos.getBlockX() - rootX * 16; - int y = pos.getBlockY(); - int z = pos.getBlockZ() - rootZ * 16; - int index = y + (z * (world.getMaxY() + 1) + (x * (world.getMaxY() + 1) * 16)); - - try { - return blocks[index]; - } catch (IndexOutOfBoundsException e) { - throw new DataException("Chunk does not contain position " + pos); - } - } - + public int getBlockID(Vector pos) throws DataException; + /** * Get the block data of a block. * @@ -98,90 +21,9 @@ public class Chunk { * @return * @throws DataException */ - public int getBlockData(Vector pos) throws DataException { - int x = pos.getBlockX() - rootX * 16; - int y = pos.getBlockY(); - int z = pos.getBlockZ() - rootZ * 16; - int index = y + (z * (world.getMaxY() + 1) + (x * (world.getMaxY() + 1) * 16)); - boolean shift = index % 2 == 0; - index /= 2; - - try { - if (!shift) { - return (data[index] & 0xF0) >> 4; - } else { - return data[index] & 0xF; - } - } catch (IndexOutOfBoundsException e) { - throw new DataException("Chunk does not contain position " + pos); - } - } - - /** - * Used to load the tile entities. - * - * @throws DataException - */ - private void populateTileEntities() throws DataException { - List tags = getChildTag( - rootTag.getValue(), "TileEntities", ListTag.class) - .getValue(); - - tileEntities = new HashMap>(); - - for (Tag tag : tags) { - if (!(tag instanceof CompoundTag)) { - throw new InvalidFormatException("CompoundTag expected in TileEntities"); - } - - CompoundTag t = (CompoundTag) tag; - - int x = 0; - int y = 0; - int z = 0; - - Map values = new HashMap(); - - for (Map.Entry entry : t.getValue().entrySet()) { - if (entry.getKey().equals("x")) { - if (entry.getValue() instanceof IntTag) { - x = ((IntTag) entry.getValue()).getValue(); - } - } else if (entry.getKey().equals("y")) { - if (entry.getValue() instanceof IntTag) { - y = ((IntTag) entry.getValue()).getValue(); - } - } else if (entry.getKey().equals("z")) { - if (entry.getValue() instanceof IntTag) { - z = ((IntTag) entry.getValue()).getValue(); - } - } - - values.put(entry.getKey(), entry.getValue()); - } - - BlockVector vec = new BlockVector(x, y, z); - tileEntities.put(vec, values); - } - } - - /** - * Get the map of tags keyed to strings for a block's tile entity data. May - * return null if there is no tile entity data. Not public yet because - * what this function returns isn't ideal for usage. - * - * @param pos - * @return - * @throws DataException - */ - private Map getBlockTileEntity(Vector pos) throws DataException { - if (tileEntities == null) { - populateTileEntities(); - } - - return tileEntities.get(new BlockVector(pos)); - } - + public int getBlockData(Vector pos) throws DataException; + + /** * Get a block; * @@ -189,54 +31,5 @@ public class Chunk { * @return block * @throws DataException */ - public BaseBlock getBlock(Vector pos) throws DataException { - int id = getBlockID(pos); - int data = getBlockData(pos); - BaseBlock block; - - if (id == BlockID.WALL_SIGN || id == BlockID.SIGN_POST) { - block = new SignBlock(id, data); - } else if (id == BlockID.CHEST) { - block = new ChestBlock(data); - } else if (id == BlockID.FURNACE || id == BlockID.BURNING_FURNACE) { - block = new FurnaceBlock(id, data); - } else if (id == BlockID.DISPENSER) { - block = new DispenserBlock(data); - } else if (id == BlockID.MOB_SPAWNER) { - block = new MobSpawnerBlock(data); - } else if (id == BlockID.NOTE_BLOCK) { - block = new NoteBlock(data); - } else { - block = new BaseBlock(id, data); - } - - if (block instanceof TileEntityBlock) { - Map tileEntity = getBlockTileEntity(pos); - ((TileEntityBlock) block).fromTileEntityNBT(tileEntity); - } - - return block; - } - - /** - * Get child tag of a NBT structure. - * - * @param items - * @param key - * @param expected - * @return child tag - * @throws InvalidFormatException - */ - @SuppressWarnings("unchecked") - public static T getChildTag(Map items, String key, - Class expected) throws InvalidFormatException { - if (!items.containsKey(key)) { - throw new InvalidFormatException("Missing a \"" + key + "\" tag"); - } - Tag tag = items.get(key); - if (!expected.isInstance(tag)) { - throw new InvalidFormatException(key + " tag is not of tag type " + expected.getName()); - } - return (T) tag; - } + public BaseBlock getBlock(Vector pos) throws DataException; } diff --git a/src/main/java/com/sk89q/worldedit/data/ChunkStore.java b/src/main/java/com/sk89q/worldedit/data/ChunkStore.java index 467bd5425..e4ca9e5f2 100644 --- a/src/main/java/com/sk89q/worldedit/data/ChunkStore.java +++ b/src/main/java/com/sk89q/worldedit/data/ChunkStore.java @@ -20,6 +20,8 @@ package com.sk89q.worldedit.data; import java.io.IOException; +import java.util.Map; + import com.sk89q.jnbt.*; import com.sk89q.worldedit.*; @@ -64,7 +66,13 @@ public abstract class ChunkStore { */ public Chunk getChunk(Vector2D pos, LocalWorld world) throws DataException, IOException { - return new Chunk(world, getChunkTag(pos, world)); + + CompoundTag tag = getChunkTag(pos, world); + Map tags = tag.getValue(); + if(tags.containsKey("Sections")) { + return new AnvilChunk(world, tag); + } + return new OldChunk(world, tag); } /** diff --git a/src/main/java/com/sk89q/worldedit/data/FileMcRegionChunkStore.java b/src/main/java/com/sk89q/worldedit/data/FileMcRegionChunkStore.java index f8e2ccde9..d1d645e97 100644 --- a/src/main/java/com/sk89q/worldedit/data/FileMcRegionChunkStore.java +++ b/src/main/java/com/sk89q/worldedit/data/FileMcRegionChunkStore.java @@ -45,7 +45,13 @@ public class FileMcRegionChunkStore extends McRegionChunkStore { protected InputStream getInputStream(String name, String world) throws IOException, DataException { String fileName = "region" + File.separator + name; - File file = new File(path, fileName); + File file = new File(path, fileName.replace("mcr", "mca")); // TODO: does this need a separate class? + if (!file.exists()) { + file = new File(path, fileName); + } + if (!file.exists()) { + file = new File(path, "DIM-1" + File.separator + fileName.replace("mcr", "mca")); // TODO: does this need a separate class? + } if (!file.exists()) { file = new File(path, "DIM-1" + File.separator + fileName); } diff --git a/src/main/java/com/sk89q/worldedit/data/OldChunk.java b/src/main/java/com/sk89q/worldedit/data/OldChunk.java new file mode 100644 index 000000000..23ece3928 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/data/OldChunk.java @@ -0,0 +1,205 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q and contributors + * + * 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 . +*/ + +package com.sk89q.worldedit.data; + +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import com.sk89q.jnbt.*; +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.blocks.*; + +/** + * Represents a chunk. + * + * @author sk89q + */ +public class OldChunk implements Chunk { + private CompoundTag rootTag; + private byte[] blocks; + private byte[] data; + private int rootX; + private int rootZ; + + private Map> tileEntities; + @SuppressWarnings("unused") + private LocalWorld world; // TODO: remove if stays unused. + + /** + * Construct the chunk with a compound tag. + * + * @param tag + * @throws DataException + */ + public OldChunk(LocalWorld world, CompoundTag tag) throws DataException { + rootTag = tag; + this.world = world; + + + blocks = NBTUtils.getChildTag(rootTag.getValue(), "Blocks", ByteArrayTag.class).getValue(); + data = NBTUtils.getChildTag( rootTag.getValue(), "Data", ByteArrayTag.class).getValue(); + rootX = NBTUtils.getChildTag( rootTag.getValue(), "xPos", IntTag.class).getValue(); + rootZ = NBTUtils.getChildTag( rootTag.getValue(), "zPos", IntTag.class).getValue(); + + int size = 16 * 16 * 128; + if (blocks.length != size) { + throw new InvalidFormatException("Chunk blocks byte array expected " + + "to be " + size + " bytes; found " + blocks.length); + } + + if (data.length != (size/2)) { + throw new InvalidFormatException("Chunk block data byte array " + + "expected to be " + size + " bytes; found " + data.length); + } + } + + @Override + public int getBlockID(Vector pos) throws DataException { + if(pos.getBlockY() >= 128) return 0; + + int x = pos.getBlockX() - rootX * 16; + int y = pos.getBlockY(); + int z = pos.getBlockZ() - rootZ * 16; + int index = y + (z * 128 + (x * 128 * 16)); + try { + return blocks[index]; + } catch (IndexOutOfBoundsException e) { + throw new DataException("Chunk does not contain position " + pos); + } + } + + @Override + public int getBlockData(Vector pos) throws DataException { + if(pos.getBlockY() >= 128) return 0; + + int x = pos.getBlockX() - rootX * 16; + int y = pos.getBlockY(); + int z = pos.getBlockZ() - rootZ * 16; + int index = y + (z * 128 + (x * 128 * 16)); + boolean shift = index % 2 == 0; + index /= 2; + + try { + if (!shift) { + return (data[index] & 0xF0) >> 4; + } else { + return data[index] & 0xF; + } + } catch (IndexOutOfBoundsException e) { + throw new DataException("Chunk does not contain position " + pos); + } + } + + /** + * Used to load the tile entities. + * + * @throws DataException + */ + private void populateTileEntities() throws DataException { + List tags = NBTUtils.getChildTag( + rootTag.getValue(), "TileEntities", ListTag.class) + .getValue(); + + tileEntities = new HashMap>(); + + for (Tag tag : tags) { + if (!(tag instanceof CompoundTag)) { + throw new InvalidFormatException("CompoundTag expected in TileEntities"); + } + + CompoundTag t = (CompoundTag) tag; + + int x = 0; + int y = 0; + int z = 0; + + Map values = new HashMap(); + + for (Map.Entry entry : t.getValue().entrySet()) { + if (entry.getKey().equals("x")) { + if (entry.getValue() instanceof IntTag) { + x = ((IntTag) entry.getValue()).getValue(); + } + } else if (entry.getKey().equals("y")) { + if (entry.getValue() instanceof IntTag) { + y = ((IntTag) entry.getValue()).getValue(); + } + } else if (entry.getKey().equals("z")) { + if (entry.getValue() instanceof IntTag) { + z = ((IntTag) entry.getValue()).getValue(); + } + } + + values.put(entry.getKey(), entry.getValue()); + } + + BlockVector vec = new BlockVector(x, y, z); + tileEntities.put(vec, values); + } + } + + /** + * Get the map of tags keyed to strings for a block's tile entity data. May + * return null if there is no tile entity data. Not public yet because + * what this function returns isn't ideal for usage. + * + * @param pos + * @return + * @throws DataException + */ + private Map getBlockTileEntity(Vector pos) throws DataException { + if (tileEntities == null) { + populateTileEntities(); + } + + return tileEntities.get(new BlockVector(pos)); + } + + @Override + public BaseBlock getBlock(Vector pos) throws DataException { + int id = getBlockID(pos); + int data = getBlockData(pos); + BaseBlock block; + + if (id == BlockID.WALL_SIGN || id == BlockID.SIGN_POST) { + block = new SignBlock(id, data); + } else if (id == BlockID.CHEST) { + block = new ChestBlock(data); + } else if (id == BlockID.FURNACE || id == BlockID.BURNING_FURNACE) { + block = new FurnaceBlock(id, data); + } else if (id == BlockID.DISPENSER) { + block = new DispenserBlock(data); + } else if (id == BlockID.MOB_SPAWNER) { + block = new MobSpawnerBlock(data); + } else if (id == BlockID.NOTE_BLOCK) { + block = new NoteBlock(data); + } else { + block = new BaseBlock(id, data); + } + + if (block instanceof TileEntityBlock) { + Map tileEntity = getBlockTileEntity(pos); + ((TileEntityBlock) block).fromTileEntityNBT(tileEntity); + } + + return block; + } + +} diff --git a/src/main/java/com/sk89q/worldedit/data/TrueZipMcRegionChunkStore.java b/src/main/java/com/sk89q/worldedit/data/TrueZipMcRegionChunkStore.java index 9f8166f65..29c14774d 100644 --- a/src/main/java/com/sk89q/worldedit/data/TrueZipMcRegionChunkStore.java +++ b/src/main/java/com/sk89q/worldedit/data/TrueZipMcRegionChunkStore.java @@ -101,12 +101,20 @@ public class TrueZipMcRegionChunkStore extends McRegionChunkStore { } } else { Pattern pattern = Pattern.compile(".*\\.mcr$"); + Pattern patternmca = Pattern.compile(".*\\.mca$"); // TODO: does this need a separate class? // World pattern Pattern worldPattern = Pattern.compile(worldname + "\\$"); for (Enumeration e = zip.entries(); e.hasMoreElements(); ) { ZipEntry testEntry = (ZipEntry) e.nextElement(); // Check for world if (worldPattern.matcher(worldname).matches()) { + // Check for file + // TODO: does this need a separate class? + if (patternmca.matcher(testEntry.getName()).matches()) { + folder = testEntry.getName().substring(0, testEntry.getName().lastIndexOf("/")); + name = folder + "/" + name.replace("mcr", "mca"); + break; + } // Check for file if (pattern.matcher(testEntry.getName()).matches()) { folder = testEntry.getName().substring(0, testEntry.getName().lastIndexOf("/")); @@ -164,7 +172,7 @@ public class TrueZipMcRegionChunkStore extends McRegionChunkStore { ZipEntry testEntry = e.nextElement(); - if (testEntry.getName().matches(".*\\.mcr$")) { + if (testEntry.getName().matches(".*\\.mcr$") || testEntry.getName().matches(".*\\.mca$")) { // TODO: does this need a separate class? return true; } } diff --git a/src/main/java/com/sk89q/worldedit/data/ZippedMcRegionChunkStore.java b/src/main/java/com/sk89q/worldedit/data/ZippedMcRegionChunkStore.java index 473bceb74..f7f85a2ff 100644 --- a/src/main/java/com/sk89q/worldedit/data/ZippedMcRegionChunkStore.java +++ b/src/main/java/com/sk89q/worldedit/data/ZippedMcRegionChunkStore.java @@ -99,10 +99,17 @@ public class ZippedMcRegionChunkStore extends McRegionChunkStore { } } else { Pattern pattern = Pattern.compile(".*\\.mcr$"); + Pattern patternmca = Pattern.compile(".*\\.mca$"); // TODO: does this need a separate class? for (Enumeration e = zip.entries(); e.hasMoreElements(); ) { ZipEntry testEntry = (ZipEntry) e.nextElement(); // Check for world if (testEntry.getName().startsWith(worldname + "/")) { + // TODO: does this need a separate class? + if (patternmca.matcher(testEntry.getName()).matches()) { + folder = testEntry.getName().substring(0, testEntry.getName().lastIndexOf("/")); + name = folder + "/" + name.replace("mcr", "mca"); + break; + } if (pattern.matcher(testEntry.getName()).matches()) { folder = testEntry.getName().substring(0, testEntry.getName().lastIndexOf("/")); name = folder + "/" + name; @@ -159,7 +166,7 @@ public class ZippedMcRegionChunkStore extends McRegionChunkStore { ZipEntry testEntry = e.nextElement(); - if (testEntry.getName().matches(".*\\.mcr$")) { + if (testEntry.getName().matches(".*\\.mcr$") || testEntry.getName().matches(".*\\.mca$")) { // TODO: does this need a separate class? return true; } } diff --git a/src/main/java/com/sk89q/worldedit/snapshots/SnapshotRestore.java b/src/main/java/com/sk89q/worldedit/snapshots/SnapshotRestore.java index 3c2058953..bcdd293e1 100644 --- a/src/main/java/com/sk89q/worldedit/snapshots/SnapshotRestore.java +++ b/src/main/java/com/sk89q/worldedit/snapshots/SnapshotRestore.java @@ -19,15 +19,25 @@ package com.sk89q.worldedit.snapshots; -import com.sk89q.worldedit.*; -import com.sk89q.worldedit.regions.*; -import com.sk89q.worldedit.blocks.*; -import com.sk89q.worldedit.data.*; import java.io.IOException; -import java.util.Map; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; -import java.util.ArrayList; +import java.util.Map; + +import com.sk89q.worldedit.BlockVector2D; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.data.Chunk; +import com.sk89q.worldedit.data.ChunkStore; +import com.sk89q.worldedit.data.DataException; +import com.sk89q.worldedit.data.MissingChunkException; +import com.sk89q.worldedit.data.MissingWorldException; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; /** *