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'.
This commit is contained in:
Meaglin 2012-03-05 07:05:17 +01:00 committed by TomyLobo
parent 19b353f6b5
commit 8aabfb0c67
17 changed files with 647 additions and 253 deletions

View File

@ -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 <code>TAG_Int_Array</code> 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();
}
}

View File

@ -54,7 +54,7 @@ public final class NBTConstants {
public static final int TYPE_END = 0, TYPE_BYTE = 1, TYPE_SHORT = 2, 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_INT = 3, TYPE_LONG = 4, TYPE_FLOAT = 5, TYPE_DOUBLE = 6,
TYPE_BYTE_ARRAY = 7, TYPE_STRING = 8, TYPE_LIST = 9, TYPE_BYTE_ARRAY = 7, TYPE_STRING = 8, TYPE_LIST = 9,
TYPE_COMPOUND = 10; TYPE_COMPOUND = 10, TYPE_INT_ARRAY = 11;
/** /**
* Default private constructor. * Default private constructor.

View File

@ -199,6 +199,13 @@ public final class NBTInputStream implements Closeable {
} }
return new CompoundTag(name, tagMap); 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: default:
throw new IOException("Invalid tag type: " + type + "."); throw new IOException("Invalid tag type: " + type + ".");
} }

View File

@ -157,6 +157,8 @@ public final class NBTOutputStream implements Closeable {
case NBTConstants.TYPE_COMPOUND: case NBTConstants.TYPE_COMPOUND:
writeCompoundTagPayload((CompoundTag) tag); writeCompoundTagPayload((CompoundTag) tag);
break; break;
case NBTConstants.TYPE_INT_ARRAY:
writeIntArrayTagPayload((IntArrayTag) tag);
default: default:
throw new IOException("Invalid tag type: " + type + "."); throw new IOException("Invalid tag type: " + type + ".");
} }
@ -309,6 +311,14 @@ public final class NBTOutputStream implements Closeable {
/* empty */ /* 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 { public void close() throws IOException {
os.close(); os.close();
} }

View File

@ -1,5 +1,7 @@
package com.sk89q.jnbt; package com.sk89q.jnbt;
import java.util.Map;
import com.sk89q.jnbt.ByteArrayTag; import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.ByteTag; import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.CompoundTag;
@ -13,6 +15,7 @@ import com.sk89q.jnbt.NBTConstants;
import com.sk89q.jnbt.ShortTag; import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag; import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.data.InvalidFormatException;
/* /*
* JNBT License * JNBT License
@ -85,6 +88,8 @@ public final class NBTUtils {
return "TAG_Short"; return "TAG_Short";
} else if (clazz.equals(StringTag.class)) { } else if (clazz.equals(StringTag.class)) {
return "TAG_String"; return "TAG_String";
} else if (clazz.equals(IntArrayTag.class)) {
return "TAG_Int_Array";
} else { } else {
throw new IllegalArgumentException("Invalid tag classs (" throw new IllegalArgumentException("Invalid tag classs ("
+ clazz.getName() + ")."); + clazz.getName() + ").");
@ -123,6 +128,8 @@ public final class NBTUtils {
return NBTConstants.TYPE_SHORT; return NBTConstants.TYPE_SHORT;
} else if (clazz.equals(StringTag.class)) { } else if (clazz.equals(StringTag.class)) {
return NBTConstants.TYPE_STRING; return NBTConstants.TYPE_STRING;
} else if (clazz.equals(IntArrayTag.class)) {
return NBTConstants.TYPE_INT_ARRAY;
} else { } else {
throw new IllegalArgumentException("Invalid tag classs (" throw new IllegalArgumentException("Invalid tag classs ("
+ clazz.getName() + ")."); + clazz.getName() + ").");
@ -162,6 +169,8 @@ public final class NBTUtils {
return ListTag.class; return ListTag.class;
case NBTConstants.TYPE_COMPOUND: case NBTConstants.TYPE_COMPOUND:
return CompoundTag.class; return CompoundTag.class;
case NBTConstants.TYPE_INT_ARRAY:
return IntArrayTag.class;
default: default:
throw new IllegalArgumentException("Invalid tag type : " + type throw new IllegalArgumentException("Invalid tag type : " + type
+ "."); + ".");
@ -175,4 +184,25 @@ public final class NBTUtils {
} }
/**
* Get child tag of a NBT structure.
*
* @param items
* @param key
* @param expected
* @return child tag
* @throws InvalidFormatException
*/
public static <T extends Tag> T getChildTag(Map<String,Tag> items, String key,
Class<T> 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);
}
} }

View File

@ -150,7 +150,7 @@ public class ChestBlock extends BaseBlock implements TileEntityBlock, ContainerB
throw new DataException("'Chest' tile entity expected"); 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]; BaseItemStack[] newItems = new BaseItemStack[27];
for (Tag tag : items.getValue()) { for (Tag tag : items.getValue()) {
@ -161,20 +161,20 @@ public class ChestBlock extends BaseBlock implements TileEntityBlock, ContainerB
CompoundTag item = (CompoundTag) tag; CompoundTag item = (CompoundTag) tag;
Map<String, Tag> itemValues = item.getValue(); Map<String, Tag> itemValues = item.getValue();
short id = Chunk.getChildTag(itemValues, "id", ShortTag.class).getValue(); short id = NBTUtils.getChildTag(itemValues, "id", ShortTag.class).getValue();
short damage = Chunk.getChildTag(itemValues, "Damage", ShortTag.class).getValue(); short damage = NBTUtils.getChildTag(itemValues, "Damage", ShortTag.class).getValue();
byte count = Chunk.getChildTag(itemValues, "Count", ByteTag.class).getValue(); byte count = NBTUtils.getChildTag(itemValues, "Count", ByteTag.class).getValue();
byte slot = Chunk.getChildTag(itemValues, "Slot", ByteTag.class).getValue(); byte slot = NBTUtils.getChildTag(itemValues, "Slot", ByteTag.class).getValue();
if (slot >= 0 && slot <= 26) { if (slot >= 0 && slot <= 26) {
BaseItemStack itemstack = new BaseItemStack(id, count, damage); BaseItemStack itemstack = new BaseItemStack(id, count, damage);
if(itemValues.containsKey("tag")) { 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()) { for(Tag e : ench.getValue()) {
Map<String, Tag> vars = ((CompoundTag) e).getValue(); Map<String, Tag> vars = ((CompoundTag) e).getValue();
short enchid = Chunk.getChildTag(vars, "id", ShortTag.class).getValue(); short enchid = NBTUtils.getChildTag(vars, "id", ShortTag.class).getValue();
short enchlvl = Chunk.getChildTag(vars, "lvl", ShortTag.class).getValue(); short enchlvl = NBTUtils.getChildTag(vars, "lvl", ShortTag.class).getValue();
itemstack.getEnchantments().put((int) enchid, (int)enchlvl); itemstack.getEnchantments().put((int) enchid, (int)enchlvl);
} }
} }

View File

@ -150,7 +150,7 @@ public class DispenserBlock extends BaseBlock implements TileEntityBlock, Contai
throw new DataException("'Trap' tile entity expected"); 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]; BaseItemStack[] newItems = new BaseItemStack[9];
for (Tag tag : items.getValue()) { for (Tag tag : items.getValue()) {
@ -161,20 +161,20 @@ public class DispenserBlock extends BaseBlock implements TileEntityBlock, Contai
CompoundTag item = (CompoundTag) tag; CompoundTag item = (CompoundTag) tag;
Map<String, Tag> itemValues = item.getValue(); Map<String, Tag> itemValues = item.getValue();
short id = Chunk.getChildTag(itemValues, "id", ShortTag.class).getValue(); short id = NBTUtils.getChildTag(itemValues, "id", ShortTag.class).getValue();
short damage = Chunk.getChildTag(itemValues, "Damage", ShortTag.class).getValue(); short damage = NBTUtils.getChildTag(itemValues, "Damage", ShortTag.class).getValue();
byte count = Chunk.getChildTag(itemValues, "Count", ByteTag.class).getValue(); byte count = NBTUtils.getChildTag(itemValues, "Count", ByteTag.class).getValue();
byte slot = Chunk.getChildTag(itemValues, "Slot", ByteTag.class).getValue(); byte slot = NBTUtils.getChildTag(itemValues, "Slot", ByteTag.class).getValue();
if (slot >= 0 && slot <= 8) { if (slot >= 0 && slot <= 8) {
BaseItemStack itemstack = new BaseItemStack(id, count, damage); BaseItemStack itemstack = new BaseItemStack(id, count, damage);
if(itemValues.containsKey("tag")) { 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()) { for(Tag e : ench.getValue()) {
Map<String, Tag> vars = ((CompoundTag) e).getValue(); Map<String, Tag> vars = ((CompoundTag) e).getValue();
short enchid = Chunk.getChildTag(vars, "id", ShortTag.class).getValue(); short enchid = NBTUtils.getChildTag(vars, "id", ShortTag.class).getValue();
short enchlvl = Chunk.getChildTag(vars, "lvl", ShortTag.class).getValue(); short enchlvl = NBTUtils.getChildTag(vars, "lvl", ShortTag.class).getValue();
itemstack.getEnchantments().put((int) enchid, (int)enchlvl); itemstack.getEnchantments().put((int) enchid, (int)enchlvl);
} }
} }

View File

@ -194,7 +194,7 @@ public class FurnaceBlock extends BaseBlock implements TileEntityBlock, Containe
throw new DataException("'Furnace' tile entity expected"); 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]; BaseItemStack[] newItems = new BaseItemStack[3];
for (Tag tag : items.getValue()) { for (Tag tag : items.getValue()) {
@ -205,20 +205,20 @@ public class FurnaceBlock extends BaseBlock implements TileEntityBlock, Containe
CompoundTag item = (CompoundTag) tag; CompoundTag item = (CompoundTag) tag;
Map<String, Tag> itemValues = item.getValue(); Map<String, Tag> itemValues = item.getValue();
short id = Chunk.getChildTag(itemValues, "id", ShortTag.class).getValue(); short id = NBTUtils.getChildTag(itemValues, "id", ShortTag.class).getValue();
short damage = Chunk.getChildTag(itemValues, "Damage", ShortTag.class).getValue(); short damage = NBTUtils.getChildTag(itemValues, "Damage", ShortTag.class).getValue();
byte count = Chunk.getChildTag(itemValues, "Count", ByteTag.class).getValue(); byte count = NBTUtils.getChildTag(itemValues, "Count", ByteTag.class).getValue();
byte slot = Chunk.getChildTag(itemValues, "Slot", ByteTag.class).getValue(); byte slot = NBTUtils.getChildTag(itemValues, "Slot", ByteTag.class).getValue();
if (slot >= 0 && slot <= 2) { if (slot >= 0 && slot <= 2) {
BaseItemStack itemstack = new BaseItemStack(id, count, damage); BaseItemStack itemstack = new BaseItemStack(id, count, damage);
if(itemValues.containsKey("tag")) { 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()) { for(Tag e : ench.getValue()) {
Map<String, Tag> vars = ((CompoundTag) e).getValue(); Map<String, Tag> vars = ((CompoundTag) e).getValue();
short enchid = Chunk.getChildTag(vars, "id", ShortTag.class).getValue(); short enchid = NBTUtils.getChildTag(vars, "id", ShortTag.class).getValue();
short enchlvl = Chunk.getChildTag(vars, "lvl", ShortTag.class).getValue(); short enchlvl = NBTUtils.getChildTag(vars, "lvl", ShortTag.class).getValue();
itemstack.getEnchantments().put((int) enchid, (int)enchlvl); itemstack.getEnchantments().put((int) enchid, (int)enchlvl);
} }
} }

View File

@ -151,8 +151,8 @@ public class MobSpawnerBlock extends BaseBlock implements TileEntityBlock {
throw new DataException("'MobSpawner' tile entity expected"); throw new DataException("'MobSpawner' tile entity expected");
} }
StringTag mobTypeTag = (StringTag) Chunk.getChildTag(values, "EntityId", StringTag.class); StringTag mobTypeTag = (StringTag) NBTUtils.getChildTag(values, "EntityId", StringTag.class);
ShortTag delayTag = (ShortTag) Chunk.getChildTag(values, "Delay", ShortTag.class); ShortTag delayTag = (ShortTag) NBTUtils.getChildTag(values, "Delay", ShortTag.class);
this.mobType = mobTypeTag.getValue(); this.mobType = mobTypeTag.getValue();
this.delay = delayTag.getValue(); this.delay = delayTag.getValue();

View File

@ -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<BlockVector, Map<String,Tag>> 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<Tag> 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<Tag> tags = NBTUtils.getChildTag(
rootTag.getValue(), "TileEntities", ListTag.class)
.getValue();
tileEntities = new HashMap<BlockVector, Map<String, Tag>>();
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<String, Tag> values = new HashMap<String, Tag>();
for (Map.Entry<String, Tag> 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<String, Tag> 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<String, Tag> tileEntity = getBlockTileEntity(pos);
((TileEntityBlock) block).fromTileEntityNBT(tileEntity);
}
return block;
}
}

View File

@ -1,75 +1,9 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010 sk89q <http://www.sk89q.com> 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.data; package com.sk89q.worldedit.data;
import java.util.List; import com.sk89q.worldedit.Vector;
import java.util.Map; import com.sk89q.worldedit.blocks.BaseBlock;
import java.util.HashMap;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.blocks.*;
/** public interface Chunk {
* 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<BlockVector, Map<String,Tag>> 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);
}
}
/** /**
* Get the block ID of a block. * Get the block ID of a block.
@ -78,18 +12,7 @@ public class Chunk {
* @return * @return
* @throws DataException * @throws DataException
*/ */
public int getBlockID(Vector pos) 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);
}
}
/** /**
* Get the block data of a block. * Get the block data of a block.
@ -98,89 +21,8 @@ public class Chunk {
* @return * @return
* @throws DataException * @throws DataException
*/ */
public int getBlockData(Vector pos) 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<Tag> tags = getChildTag(
rootTag.getValue(), "TileEntities", ListTag.class)
.getValue();
tileEntities = new HashMap<BlockVector, Map<String, Tag>>();
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<String, Tag> values = new HashMap<String, Tag>();
for (Map.Entry<String, Tag> 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<String, Tag> getBlockTileEntity(Vector pos) throws DataException {
if (tileEntities == null) {
populateTileEntities();
}
return tileEntities.get(new BlockVector(pos));
}
/** /**
* Get a block; * Get a block;
@ -189,54 +31,5 @@ public class Chunk {
* @return block * @return block
* @throws DataException * @throws DataException
*/ */
public BaseBlock getBlock(Vector pos) 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<String, Tag> 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 extends Tag> T getChildTag(Map<String,Tag> items, String key,
Class<T> 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;
}
} }

View File

@ -20,6 +20,8 @@
package com.sk89q.worldedit.data; package com.sk89q.worldedit.data;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import com.sk89q.jnbt.*; import com.sk89q.jnbt.*;
import com.sk89q.worldedit.*; import com.sk89q.worldedit.*;
@ -64,7 +66,13 @@ public abstract class ChunkStore {
*/ */
public Chunk getChunk(Vector2D pos, LocalWorld world) public Chunk getChunk(Vector2D pos, LocalWorld world)
throws DataException, IOException { throws DataException, IOException {
return new Chunk(world, getChunkTag(pos, world));
CompoundTag tag = getChunkTag(pos, world);
Map<String, Tag> tags = tag.getValue();
if(tags.containsKey("Sections")) {
return new AnvilChunk(world, tag);
}
return new OldChunk(world, tag);
} }
/** /**

View File

@ -45,7 +45,13 @@ public class FileMcRegionChunkStore extends McRegionChunkStore {
protected InputStream getInputStream(String name, String world) throws IOException, protected InputStream getInputStream(String name, String world) throws IOException,
DataException { DataException {
String fileName = "region" + File.separator + name; 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()) { if (!file.exists()) {
file = new File(path, "DIM-1" + File.separator + fileName); file = new File(path, "DIM-1" + File.separator + fileName);
} }

View File

@ -0,0 +1,205 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010 sk89q <http://www.sk89q.com> 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 <http://www.gnu.org/licenses/>.
*/
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<BlockVector, Map<String,Tag>> 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<Tag> tags = NBTUtils.getChildTag(
rootTag.getValue(), "TileEntities", ListTag.class)
.getValue();
tileEntities = new HashMap<BlockVector, Map<String, Tag>>();
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<String, Tag> values = new HashMap<String, Tag>();
for (Map.Entry<String, Tag> 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<String, Tag> 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<String, Tag> tileEntity = getBlockTileEntity(pos);
((TileEntityBlock) block).fromTileEntityNBT(tileEntity);
}
return block;
}
}

View File

@ -101,12 +101,20 @@ public class TrueZipMcRegionChunkStore extends McRegionChunkStore {
} }
} else { } else {
Pattern pattern = Pattern.compile(".*\\.mcr$"); Pattern pattern = Pattern.compile(".*\\.mcr$");
Pattern patternmca = Pattern.compile(".*\\.mca$"); // TODO: does this need a separate class?
// World pattern // World pattern
Pattern worldPattern = Pattern.compile(worldname + "\\$"); Pattern worldPattern = Pattern.compile(worldname + "\\$");
for (Enumeration<? extends ZipEntry> e = zip.entries(); e.hasMoreElements(); ) { for (Enumeration<? extends ZipEntry> e = zip.entries(); e.hasMoreElements(); ) {
ZipEntry testEntry = (ZipEntry) e.nextElement(); ZipEntry testEntry = (ZipEntry) e.nextElement();
// Check for world // Check for world
if (worldPattern.matcher(worldname).matches()) { 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 // Check for file
if (pattern.matcher(testEntry.getName()).matches()) { if (pattern.matcher(testEntry.getName()).matches()) {
folder = testEntry.getName().substring(0, testEntry.getName().lastIndexOf("/")); folder = testEntry.getName().substring(0, testEntry.getName().lastIndexOf("/"));
@ -164,7 +172,7 @@ public class TrueZipMcRegionChunkStore extends McRegionChunkStore {
ZipEntry testEntry = e.nextElement(); 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; return true;
} }
} }

View File

@ -99,10 +99,17 @@ public class ZippedMcRegionChunkStore extends McRegionChunkStore {
} }
} else { } else {
Pattern pattern = Pattern.compile(".*\\.mcr$"); Pattern pattern = Pattern.compile(".*\\.mcr$");
Pattern patternmca = Pattern.compile(".*\\.mca$"); // TODO: does this need a separate class?
for (Enumeration<? extends ZipEntry> e = zip.entries(); e.hasMoreElements(); ) { for (Enumeration<? extends ZipEntry> e = zip.entries(); e.hasMoreElements(); ) {
ZipEntry testEntry = (ZipEntry) e.nextElement(); ZipEntry testEntry = (ZipEntry) e.nextElement();
// Check for world // Check for world
if (testEntry.getName().startsWith(worldname + "/")) { 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()) { if (pattern.matcher(testEntry.getName()).matches()) {
folder = testEntry.getName().substring(0, testEntry.getName().lastIndexOf("/")); folder = testEntry.getName().substring(0, testEntry.getName().lastIndexOf("/"));
name = folder + "/" + name; name = folder + "/" + name;
@ -159,7 +166,7 @@ public class ZippedMcRegionChunkStore extends McRegionChunkStore {
ZipEntry testEntry = e.nextElement(); 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; return true;
} }
} }

View File

@ -19,15 +19,25 @@
package com.sk89q.worldedit.snapshots; 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.io.IOException;
import java.util.Map; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; 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;
/** /**
* *