tectonicus.raw.RawChunk.java Source code

Java tutorial

Introduction

Here is the source code for tectonicus.raw.RawChunk.java

Source

/*
 * Copyright (c) 2012-2016, John Campbell and other contributors.  All rights reserved.
 *
 * This file is part of Tectonicus. It is subject to the license terms in the LICENSE file found in
 * the top-level directory of this distribution.  The full list of project contributors is contained
 * in the AUTHORS file found in the same location.
 *
 */

package tectonicus.raw;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.DatatypeConverter;

import org.jnbt.ByteArrayTag;
import org.jnbt.ByteTag;
import org.jnbt.CompoundTag;
import org.jnbt.IntTag;
import org.jnbt.ListTag;
import org.jnbt.NBTInputStream;
import org.jnbt.NBTInputStream.Compression;
import org.jnbt.ShortTag;
import org.jnbt.StringTag;
import org.jnbt.Tag;
import org.json.JSONObject;
import tectonicus.BlockIds;
import tectonicus.ChunkCoord;
import tectonicus.util.FileUtils;

public class RawChunk {
    public static final int WIDTH = 16;
    public static final int HEIGHT = 256;
    public static final int DEPTH = 16;

    public static final int MC_REGION_HEIGHT = 128;

    public static final int SECTION_WIDTH = 16;
    public static final int SECTION_HEIGHT = 16;
    public static final int SECTION_DEPTH = 16;

    public static final int MAX_LIGHT = 16;

    private static final int MAX_SECTIONS = HEIGHT / SECTION_HEIGHT;

    private byte[][] biomes;

    private Section[] sections;

    private int blockX, blockY, blockZ;

    private ArrayList<RawSign> signs;
    private ArrayList<TileEntity> flowerPots;
    private ArrayList<TileEntity> paintings;
    private ArrayList<TileEntity> skulls;
    private ArrayList<TileEntity> beacons;
    private ArrayList<TileEntity> banners;
    private ArrayList<TileEntity> itemFrames;

    private Map<String, Object> filterData = new HashMap<String, Object>();

    public RawChunk() {
        clear();
    }

    public RawChunk(File file) throws Exception {
        FileInputStream fileIn = new FileInputStream(file);
        init(fileIn, Compression.Gzip);
    }

    public RawChunk(InputStream in, Compression compression) throws Exception {
        init(in, compression);
    }

    public void setFilterMetadata(String id, Object data) {
        this.filterData.put(id, data);
    }

    public void removeFilterMetadata(String id) {
        this.filterData.remove(id);
    }

    public Object getFilterMetadata(String id) {
        return this.filterData.get(id);
    }

    private void clear() {
        signs = new ArrayList<RawSign>();
        flowerPots = new ArrayList<TileEntity>();
        paintings = new ArrayList<TileEntity>();
        skulls = new ArrayList<TileEntity>();
        beacons = new ArrayList<TileEntity>();
        banners = new ArrayList<TileEntity>();
        itemFrames = new ArrayList<TileEntity>();

        sections = new Section[MAX_SECTIONS];
    }

    private void init(InputStream in, Compression compression) throws Exception {
        clear();

        NBTInputStream nbtIn = null;
        try {
            nbtIn = new NBTInputStream(in, compression);

            Tag tag = nbtIn.readTag();
            if (tag instanceof CompoundTag) {
                CompoundTag root = (CompoundTag) tag;

                CompoundTag level = NbtUtil.getChild(root, "Level", CompoundTag.class);
                if (level != null) {
                    blockX = blockY = blockZ = 0;

                    IntTag xPosTag = NbtUtil.getChild(level, "xPos", IntTag.class);
                    if (xPosTag != null)
                        blockX = xPosTag.getValue().intValue();

                    IntTag zPosTag = NbtUtil.getChild(level, "zPos", IntTag.class);
                    if (zPosTag != null)
                        blockZ = zPosTag.getValue().intValue();

                    ListTag sections = NbtUtil.getChild(level, "Sections", ListTag.class);
                    if (sections != null) {
                        // Parse as anvil format
                        parseAnvilData(level);
                    } else {
                        // Parse as McRegion format
                        parseMcRegionData(level);
                    }

                    ListTag entitiesTag = NbtUtil.getChild(level, "Entities", ListTag.class);
                    if (entitiesTag != null) {
                        for (Tag t : entitiesTag.getValue()) {
                            if (t instanceof CompoundTag) {
                                CompoundTag entity = (CompoundTag) t;

                                StringTag idTag = NbtUtil.getChild(entity, "id", StringTag.class);
                                if (idTag.getValue().endsWith("Painting")) {
                                    StringTag motiveTag = NbtUtil.getChild(entity, "Motive", StringTag.class);
                                    IntTag xTag = NbtUtil.getChild(entity, "TileX", IntTag.class);
                                    IntTag yTag = NbtUtil.getChild(entity, "TileY", IntTag.class);
                                    IntTag zTag = NbtUtil.getChild(entity, "TileZ", IntTag.class);
                                    ByteTag oldDir = NbtUtil.getChild(entity, "Dir", ByteTag.class);
                                    ByteTag dir = NbtUtil.getChild(entity, "Direction", ByteTag.class);
                                    if (oldDir != null && dir == null) {
                                        dir = oldDir;
                                    }

                                    boolean is18 = false;
                                    if (dir == null) {
                                        dir = NbtUtil.getChild(entity, "Facing", ByteTag.class);
                                        is18 = true;
                                    }

                                    int direction = dir.getValue(); // Have to reverse 0 and 2 for the old Dir tag
                                    if (oldDir != null && direction == 0) {
                                        direction = 2;
                                    } else if (oldDir != null && direction == 2) {
                                        direction = 0;
                                    }

                                    int x = xTag.getValue();
                                    final int y = yTag.getValue();
                                    int z = zTag.getValue();

                                    if (is18 && direction == 0) {
                                        z = zTag.getValue() - 1;
                                    } else if (is18 && direction == 1) {
                                        x = xTag.getValue() + 1;
                                    } else if (is18 && direction == 2) {
                                        z = zTag.getValue() + 1;
                                    } else if (is18 && direction == 3) {
                                        x = xTag.getValue() - 1;
                                    }

                                    final int localX = x - (blockX * WIDTH);
                                    final int localY = y - (blockY * HEIGHT);
                                    final int localZ = z - (blockZ * DEPTH);

                                    //System.out.println("Motive: " + motiveTag.getValue() + " Direction: " + dir.getValue() + " XYZ: " + x + ", " + y + ", " + z + " Local XYZ: " + localX +
                                    //", " + localY + ", " + localZ);
                                    paintings.add(new TileEntity(-1, 0, x, y, z, localX, localY, localZ,
                                            motiveTag.getValue(), direction));
                                } else if (idTag.getValue().equals("ItemFrame")) {
                                    IntTag xTag = NbtUtil.getChild(entity, "TileX", IntTag.class);
                                    IntTag yTag = NbtUtil.getChild(entity, "TileY", IntTag.class);
                                    IntTag zTag = NbtUtil.getChild(entity, "TileZ", IntTag.class);
                                    ByteTag dir = NbtUtil.getChild(entity, "Direction", ByteTag.class);
                                    boolean is18 = false;
                                    if (dir == null) {
                                        dir = NbtUtil.getChild(entity, "Facing", ByteTag.class);
                                        is18 = true;
                                    }

                                    String item = "";
                                    Map<String, Tag> map = entity.getValue();
                                    CompoundTag itemTag = (CompoundTag) map.get("Item");
                                    if (itemTag != null) {
                                        ShortTag itemIdTag = NbtUtil.getChild(itemTag, "id", ShortTag.class);
                                        if (itemIdTag == null) {
                                            StringTag stringItemIdTag = NbtUtil.getChild(itemTag, "id",
                                                    StringTag.class);
                                            item = stringItemIdTag.getValue();
                                        } else {
                                            if (itemIdTag.getValue() == 358)
                                                item = "minecraft:filled_map";
                                        }
                                    }

                                    int x = xTag.getValue();
                                    final int y = yTag.getValue();
                                    int z = zTag.getValue();

                                    if (is18 && dir.getValue() == 0) {
                                        z = zTag.getValue() - 1;
                                    } else if (is18 && dir.getValue() == 1) {
                                        x = xTag.getValue() + 1;
                                    } else if (is18 && dir.getValue() == 2) {
                                        z = zTag.getValue() + 1;
                                    } else if (is18 && dir.getValue() == 3) {
                                        x = xTag.getValue() - 1;
                                    }

                                    final int localX = x - (blockX * WIDTH);
                                    final int localY = y - (blockY * HEIGHT);
                                    final int localZ = z - (blockZ * DEPTH);

                                    //System.out.println(" Direction: " + dir.getValue() + " XYZ: " + x + ", " + y + ", " + z + " Local XYZ: " + localX +
                                    //", " + localY + ", " + localZ);

                                    itemFrames.add(new TileEntity(-2, 0, x, y, z, localX, localY, localZ, item,
                                            dir.getValue()));
                                }
                            }
                        }
                    }

                    ListTag tileEntitiesTag = NbtUtil.getChild(level, "TileEntities", ListTag.class);
                    if (tileEntitiesTag != null) {
                        for (Tag t : tileEntitiesTag.getValue()) {
                            if (t instanceof CompoundTag) {
                                CompoundTag entity = (CompoundTag) t;

                                StringTag idTag = NbtUtil.getChild(entity, "id", StringTag.class);
                                IntTag xTag = NbtUtil.getChild(entity, "x", IntTag.class);
                                IntTag yTag = NbtUtil.getChild(entity, "y", IntTag.class);
                                IntTag zTag = NbtUtil.getChild(entity, "z", IntTag.class);

                                if (idTag != null && xTag != null && yTag != null && zTag != null) {
                                    String id = idTag.getValue();
                                    if (id.equals("Sign")) {
                                        String text1 = NbtUtil.getChild(entity, "Text1", StringTag.class)
                                                .getValue();
                                        String text2 = NbtUtil.getChild(entity, "Text2", StringTag.class)
                                                .getValue();
                                        String text3 = NbtUtil.getChild(entity, "Text3", StringTag.class)
                                                .getValue();
                                        String text4 = NbtUtil.getChild(entity, "Text4", StringTag.class)
                                                .getValue();

                                        if (!text1.isEmpty() && FileUtils.isJSONValid(text1)) {
                                            text1 = text1.replaceAll("^[{]|[}]$", "").split(":", 2)[1];
                                            text2 = text2.replaceAll("^[{]|[}]$", "").split(":", 2)[1];
                                            text3 = text3.replaceAll("^[{]|[}]$", "").split(":", 2)[1];
                                            text4 = text4.replaceAll("^[{]|[}]$", "").split(":", 2)[1];
                                        }

                                        text1 = text1.replaceAll("^\"|\"$", ""); //This regex removes begin and end double quotes
                                        if (text1 == null || text1.equals("null"))
                                            text1 = "";

                                        text2 = text2.replaceAll("^\"|\"$", "");
                                        if (text2 == null || text2.equals("null"))
                                            text2 = "";

                                        text3 = text3.replaceAll("^\"|\"$", "");
                                        if (text3 == null || text3.equals("null"))
                                            text3 = "";

                                        text4 = text4.replaceAll("^\"|\"$", "");
                                        if (text4 == null || text4.equals("null"))
                                            text4 = "";

                                        final int x = xTag.getValue();
                                        final int y = yTag.getValue();
                                        final int z = zTag.getValue();

                                        final int localX = x - (blockX * WIDTH);
                                        final int localY = y - (blockY * HEIGHT);
                                        final int localZ = z - (blockZ * DEPTH);

                                        final int blockId = getBlockId(localX, localY, localZ);
                                        final int data = getBlockData(localX, localY, localZ);

                                        signs.add(new RawSign(blockId, data, x, y, z, localX, localY, localZ, text1,
                                                text2, text3, text4));
                                    } else if (id.equals("FlowerPot")) {
                                        IntTag dataTag = NbtUtil.getChild(entity, "Data", IntTag.class);
                                        IntTag itemTag = NbtUtil.getChild(entity, "Item", IntTag.class);
                                        final int item;
                                        if (itemTag == null) {
                                            StringTag stringIdTag = NbtUtil.getChild(entity, "Item",
                                                    StringTag.class);
                                            if (stringIdTag.getValue().equals("minecraft:sapling"))
                                                item = 6;
                                            else if (stringIdTag.getValue().equals("minecraft:red_flower"))
                                                item = 38;
                                            else
                                                item = 0;
                                        } else {
                                            item = itemTag.getValue();
                                        }

                                        final int x = xTag.getValue();
                                        final int y = yTag.getValue();
                                        final int z = zTag.getValue();

                                        final int localX = x - (blockX * WIDTH);
                                        final int localY = y - (blockY * HEIGHT);
                                        final int localZ = z - (blockZ * DEPTH);

                                        final int blockData = getBlockData(localX, localY, localZ);

                                        final int itemData = dataTag.getValue();

                                        flowerPots.add(new TileEntity(0, blockData, x, y, z, localX, localY, localZ,
                                                itemData, item));
                                    } else if (id.equals("Skull")) {
                                        ByteTag skullType = NbtUtil.getChild(entity, "SkullType", ByteTag.class);
                                        ByteTag rot = NbtUtil.getChild(entity, "Rot", ByteTag.class);

                                        StringTag nameTag = null;
                                        StringTag playerId = null;
                                        String name = "";
                                        String UUID = "";
                                        String textureURL = "";
                                        StringTag extraType = NbtUtil.getChild(entity, "ExtraType",
                                                StringTag.class);
                                        CompoundTag owner = NbtUtil.getChild(entity, "Owner", CompoundTag.class);
                                        if (owner != null) {
                                            nameTag = NbtUtil.getChild(owner, "Name", StringTag.class);
                                            name = nameTag.getValue();
                                            playerId = NbtUtil.getChild(owner, "Id", StringTag.class);
                                            UUID = playerId.getValue().replace("-", "");

                                            // Get skin URL
                                            CompoundTag properties = NbtUtil.getChild(owner, "Properties",
                                                    CompoundTag.class);
                                            ListTag textures = NbtUtil.getChild(properties, "textures",
                                                    ListTag.class);
                                            CompoundTag tex = NbtUtil.getChild(textures, 0, CompoundTag.class);
                                            StringTag value = NbtUtil.getChild(tex, "Value", StringTag.class);
                                            byte[] decoded = DatatypeConverter.parseBase64Binary(value.getValue());
                                            JSONObject obj = new JSONObject(new String(decoded, "UTF-8"));
                                            textureURL = obj.getJSONObject("textures").getJSONObject("SKIN")
                                                    .getString("url");
                                        } else if (extraType != null && !(extraType.getValue().equals(""))) {
                                            name = UUID = extraType.getValue();
                                            textureURL = "http://www.minecraft.net/skin/" + extraType.getValue()
                                                    + ".png";
                                        }

                                        final int x = xTag.getValue();
                                        final int y = yTag.getValue();
                                        final int z = zTag.getValue();

                                        final int localX = x - (blockX * WIDTH);
                                        final int localY = y - (blockY * HEIGHT);
                                        final int localZ = z - (blockZ * DEPTH);

                                        skulls.add(new TileEntity(skullType.getValue(), rot.getValue(), x, y, z,
                                                localX, localY, localZ, name, UUID, textureURL, null));
                                    } else if (id.equals("Beacon")) {
                                        IntTag levels = NbtUtil.getChild(entity, "Levels", IntTag.class);

                                        final int x = xTag.getValue();
                                        final int y = yTag.getValue();
                                        final int z = zTag.getValue();

                                        final int localX = x - (blockX * WIDTH);
                                        final int localY = y - (blockY * HEIGHT);
                                        final int localZ = z - (blockZ * DEPTH);

                                        beacons.add(new TileEntity(0, levels.getValue(), x, y, z, localX, localY,
                                                localZ, 0, 0));
                                    } else if (id.equals("Banner")) {
                                        IntTag base = NbtUtil.getChild(entity, "Base", IntTag.class);
                                        ListTag patternList = NbtUtil.getChild(entity, "Patterns", ListTag.class);

                                        final int x = xTag.getValue();
                                        final int y = yTag.getValue();
                                        final int z = zTag.getValue();

                                        final int localX = x - (blockX * WIDTH);
                                        final int localY = y - (blockY * HEIGHT);
                                        final int localZ = z - (blockZ * DEPTH);

                                        String patterns = "";
                                        final int numPatterns = patternList.getValue().size();
                                        if (numPatterns > 0) {
                                            //System.out.println(patternList + "\n");
                                            patterns += "{";
                                            for (int i = 0; i < numPatterns; i++) {
                                                CompoundTag p = NbtUtil.getChild(patternList, i, CompoundTag.class);
                                                StringTag pattern = NbtUtil.getChild(p, "Pattern", StringTag.class);
                                                IntTag color = NbtUtil.getChild(p, "Color", IntTag.class);
                                                patterns += "\"" + pattern.getValue() + "\"" + ": "
                                                        + color.getValue().toString();
                                                if (i < numPatterns - 1)
                                                    patterns += ", ";
                                            }
                                            patterns += "}";
                                            //System.out.println(patterns);
                                        }
                                        banners.add(new TileEntity(0, base.getValue(), x, y, z, localX, localY,
                                                localZ, patterns, 0));
                                        //banners.add(new TileEntity(0, base.getValue(), x, y, z, localX, localY, localZ, 0, 0));
                                    }
                                    //   else if (id.equals("Furnace"))
                                    //   {
                                    //      
                                    //   }
                                    //   else if (id.equals("MobSpawner"))
                                    //   {
                                    //      
                                    //   }
                                    //   else if (id.equals("Chest"))
                                    //   {
                                    //      
                                    //   }
                                }
                            }
                        }
                    }

                    //   LongTag lastUpdateTag =
                    //      NbtUtil.getChild(level, "LastUpdate", LongTag.class);

                    //   ByteTag terrainPopulatedTag = 
                    //      NbtUtil.getChild(level, "TerrainPopulated", ByteTag.class);
                }
            }
        } finally {
            if (nbtIn != null)
                nbtIn.close();
            if (in != null)
                in.close();
        }

        /* Old debug: put bricks in the corner of every chunk
        for (int y=0; y<HEIGHT; y++)
        {
           if (blockIds[0][y][0] != BlockIds.AIR)
           {
        if (signs.size() > 0)
           blockIds[0][y][0] = BlockIds.DIAMOND_BLOCK;
        else
           blockIds[0][y][0] = BlockIds.BRICK;
           }
        }
        */
    }

    private void parseAnvilData(CompoundTag level) {
        ListTag sectionsList = NbtUtil.getChild(level, "Sections", ListTag.class);
        // sections shouldn't be null here

        List<Tag> list = sectionsList.getValue();
        for (Tag t : list) {
            if (!(t instanceof CompoundTag))
                continue;

            CompoundTag compound = (CompoundTag) t;

            final int sectionY = NbtUtil.getByte(compound, "Y", (byte) 0);

            if (sectionY < 0 || sectionY >= MAX_SECTIONS)
                continue;

            Section newSection = new Section();
            sections[sectionY] = newSection;

            ByteArrayTag blocksTag = NbtUtil.getChild(compound, "Blocks", ByteArrayTag.class);
            if (blocksTag != null) {
                for (int x = 0; x < SECTION_WIDTH; x++) {
                    for (int y = 0; y < SECTION_HEIGHT; y++) {
                        for (int z = 0; z < SECTION_DEPTH; z++) {
                            final int index = calcAnvilIndex(x, y, z);
                            final int id = blocksTag.getValue()[index] & 0xFF;
                            newSection.blockIds[x][y][z] = id;
                        }
                    }
                }
            }

            ByteArrayTag addTag = NbtUtil.getChild(compound, "Add", ByteArrayTag.class);
            if (addTag != null) {
                for (int x = 0; x < SECTION_WIDTH; x++) {
                    for (int y = 0; y < SECTION_HEIGHT; y++) {
                        for (int z = 0; z < SECTION_DEPTH; z++) {
                            final int addValue = getAnvil4Bit(addTag, x, y, z);
                            newSection.blockIds[x][y][z] = newSection.blockIds[x][y][z] | (addValue << 8);
                        }
                    }
                }
            }

            ByteArrayTag dataTag = NbtUtil.getChild(compound, "Data", ByteArrayTag.class);
            if (dataTag != null) {
                for (int x = 0; x < SECTION_WIDTH; x++) {
                    for (int y = 0; y < SECTION_HEIGHT; y++) {
                        for (int z = 0; z < SECTION_DEPTH; z++) {
                            final byte half = getAnvil4Bit(dataTag, x, y, z);
                            newSection.blockData[x][y][z] = half;
                        }
                    }
                }
            }

            ByteArrayTag skylightTag = NbtUtil.getChild(compound, "SkyLight", ByteArrayTag.class);
            if (skylightTag != null) {
                for (int x = 0; x < SECTION_WIDTH; x++) {
                    for (int y = 0; y < SECTION_HEIGHT; y++) {
                        for (int z = 0; z < SECTION_DEPTH; z++) {
                            final byte half = getAnvil4Bit(skylightTag, x, y, z);
                            newSection.skylight[x][y][z] = half;
                        }
                    }
                }
            }

            ByteArrayTag blocklightTag = NbtUtil.getChild(compound, "BlockLight", ByteArrayTag.class);
            if (blocklightTag != null) {
                for (int x = 0; x < SECTION_WIDTH; x++) {
                    for (int y = 0; y < SECTION_HEIGHT; y++) {
                        for (int z = 0; z < SECTION_DEPTH; z++) {
                            final byte half = getAnvil4Bit(blocklightTag, x, y, z);
                            newSection.blocklight[x][y][z] = half;
                        }
                    }
                }
            }
        }

        // Parse "Biomes" data (16x16)
        ByteArrayTag biomeDataTag = NbtUtil.getChild(level, "Biomes", ByteArrayTag.class);
        if (biomeDataTag != null) {
            biomes = new byte[SECTION_WIDTH][SECTION_DEPTH];

            for (int x = 0; x < SECTION_WIDTH; x++) {
                for (int z = 0; z < SECTION_DEPTH; z++) {
                    final int index = x * SECTION_WIDTH + z;
                    biomes[x][z] = biomeDataTag.getValue()[index];
                }
            }
        }
    }

    private void parseMcRegionData(CompoundTag level) {
        // McRegion chunks are only 128 high, so just create the lower half of the sections
        for (int i = 0; i < 8; i++) {
            sections[i] = new Section();
        }

        ByteArrayTag blocks = NbtUtil.getChild(level, "Blocks", ByteArrayTag.class);
        if (blocks != null) {
            for (int x = 0; x < WIDTH; x++) {
                for (int y = 0; y < MC_REGION_HEIGHT; y++) {
                    for (int z = 0; z < DEPTH; z++) {
                        final int index = calcIndex(x, y, z);
                        final byte blockId = blocks.getValue()[index];
                        setBlockId(x, y, z, blockId);
                    }
                }
            }
        }

        ByteArrayTag dataTag = NbtUtil.getChild(level, "Data", ByteArrayTag.class);
        if (dataTag != null) {
            for (int x = 0; x < WIDTH; x++) {
                for (int y = 0; y < MC_REGION_HEIGHT; y++) {
                    for (int z = 0; z < DEPTH; z++) {
                        final byte half = get4Bit(dataTag, x, y, z);
                        setBlockData(x, y, z, half);
                    }
                }
            }
        }

        ByteArrayTag skylightTag = NbtUtil.getChild(level, "SkyLight", ByteArrayTag.class);
        if (skylightTag != null) {
            for (int x = 0; x < WIDTH; x++) {
                for (int y = 0; y < MC_REGION_HEIGHT; y++) {
                    for (int z = 0; z < DEPTH; z++) {
                        final byte half = get4Bit(skylightTag, x, y, z);
                        setSkyLight(x, y, z, half);
                    }
                }
            }
        }

        ByteArrayTag blockLightTag = NbtUtil.getChild(level, "BlockLight", ByteArrayTag.class);
        if (blockLightTag != null) {
            for (int x = 0; x < WIDTH; x++) {
                for (int y = 0; y < MC_REGION_HEIGHT; y++) {
                    for (int z = 0; z < DEPTH; z++) {
                        final byte half = get4Bit(blockLightTag, x, y, z);
                        setBlockLight(x, y, z, half);
                    }
                }
            }
        }
    }

    private static final int calcIndex(final int x, final int y, final int z) {
        // y + ( z * ChunkSizeY(=128) + ( x * ChunkSizeY(=128) * ChunkSizeZ(=16) ) ) ];
        return y + (z * MC_REGION_HEIGHT) + (x * MC_REGION_HEIGHT * DEPTH);
    }

    private static final int calcAnvilIndex(final int x, final int y, final int z) {
        // Note that the old format is XZY ((x * 16 + z) * 128 + y)
        // and the new format is       YZX ((y * 16 + z) * 16 + x)

        return x + (z * SECTION_HEIGHT) + (y * SECTION_HEIGHT * SECTION_DEPTH);
    }

    private static final int calc4BitIndex(final int x, final int y, final int z) {
        // Math.floor is bloody slow!
        // Since calcIndex should always be +ive, we can just cast to int and get the same result 
        return (int) (calcIndex(x, y, z) / 2);
    }

    private static final int calcAnvil4BitIndex(final int x, final int y, final int z) {
        // Math.floor is bloody slow!
        // Since calcIndex should always be +ive, we can just cast to int and get the same result 
        return (int) (calcAnvilIndex(x, y, z) / 2);
    }

    private static byte getAnvil4Bit(ByteArrayTag tag, final int x, final int y, final int z) {
        final int index = calcAnvil4BitIndex(x, y, z);
        if (index == 2048)
            System.out.println();
        ;

        final int doublet = tag.getValue()[index];

        // Upper or lower half?
        final boolean isUpper = (x % 2 == 1);

        byte half;
        if (isUpper) {
            half = (byte) ((doublet >> 4) & 0xF);
        } else {
            half = (byte) (doublet & 0xF);
        }

        return half;
    }

    private static byte get4Bit(ByteArrayTag tag, final int x, final int y, final int z) {
        final int index = calc4BitIndex(x, y, z);
        final int doublet = tag.getValue()[index];

        // Upper or lower half?
        final boolean isUpper = (y % 2 == 1);

        byte half;
        if (isUpper) {
            half = (byte) ((doublet >> 4) & 0xF);
        } else {
            half = (byte) (doublet & 0xF);
        }

        return half;
    }

    public int getBlockId(final int x, final int y, final int z) {
        final int sectionY = y / MAX_SECTIONS;
        final int localY = y % SECTION_HEIGHT;

        Section s = sections[sectionY];
        if (s != null)
            return s.blockIds[x][localY][z];
        else
            return BlockIds.AIR;
    }

    public void setBlockId(final int x, final int y, final int z, final int blockId) {
        final int sectionY = y / MAX_SECTIONS;
        final int localY = y % SECTION_HEIGHT;

        Section s = sections[sectionY];
        if (s == null) {
            s = new Section();
            sections[sectionY] = s;
        }

        s.blockIds[x][localY][z] = blockId;
    }

    private void setBlockData(final int x, final int y, final int z, final byte val) {
        final int sectionY = y / MAX_SECTIONS;
        final int localY = y % SECTION_HEIGHT;

        Section s = sections[sectionY];
        if (s == null) {
            s = new Section();
            sections[sectionY] = s;
        }

        s.blockData[x][localY][z] = val;
    }

    public int getBlockData(final int x, final int y, final int z) {
        final int sectionY = y / MAX_SECTIONS;
        final int localY = y % SECTION_HEIGHT;

        Section s = sections[sectionY];
        if (s != null && x >= 0 && x <= 15 && z >= 0 && z <= 15) //TODO:  Fix this (workaround for painting and stair problems)
            return s.blockData[x][localY][z];
        else
            return 0;
    }

    public void setSkyLight(final int x, final int y, final int z, final byte val) {
        final int sectionY = y / MAX_SECTIONS;
        final int localY = y % SECTION_HEIGHT;

        Section s = sections[sectionY];
        if (s == null) {
            s = new Section();
            sections[sectionY] = s;
        }

        s.skylight[x][localY][z] = val;
    }

    public byte getSkyLight(final int x, final int y, final int z) {
        final int sectionY = y / MAX_SECTIONS;
        final int localY = y % SECTION_HEIGHT;

        Section s = sections[sectionY];
        if (s != null && x >= 0 && localY >= 0 && z >= 0) //TODO: Fix this (workaround for painting and stair problems)
            return s.skylight[x][localY][z];
        else
            return MAX_LIGHT - 1;
    }

    private void setBlockLight(final int x, final int y, final int z, final byte val) {
        final int sectionY = y / MAX_SECTIONS;
        final int localY = y % SECTION_HEIGHT;

        Section s = sections[sectionY];
        if (s == null) {
            s = new Section();
            sections[sectionY] = s;
        }

        s.blocklight[x][localY][z] = val;
    }

    public byte getBlockLight(final int x, final int y, final int z) {
        final int sectionY = y / MAX_SECTIONS;
        final int localY = y % SECTION_HEIGHT;

        Section s = sections[sectionY];
        if (s != null && x >= 0 && localY >= 0 && z >= 0) //TODO: Fix this (workaround for painting and stair problems)
            return s.blocklight[x][localY][z];
        else
            return 0;
    }

    public int getBlockIdClamped(final int x, final int y, final int z, final int defaultId) {
        if (x < 0 || x >= WIDTH)
            return defaultId;
        if (y < 0 || y >= HEIGHT)
            return defaultId;
        if (z < 0 || z >= DEPTH)
            return defaultId;

        return getBlockId(x, y, z);
    }

    public int getBlockX() {
        return blockX;
    }

    public int getBlockY() {
        return blockY;
    }

    public int getBlockZ() {
        return blockZ;
    }

    public ChunkCoord getChunkCoord() {
        return new ChunkCoord(blockX, blockZ);
    }

    public long getMemorySize() {
        int blockIdTotal = 0;
        int skyLightTotal = 0;
        int blockLightTotal = 0;
        int blockDataTotal = 0;
        for (Section s : sections) {
            if (s != null) {
                blockIdTotal += s.blockIds.length * s.blockIds[0].length * s.blockIds[0][0].length;
                skyLightTotal += s.skylight.length * s.skylight[0].length * s.skylight[0][0].length;
                blockLightTotal += s.blocklight.length * s.blocklight[0].length * s.blocklight[0][0].length;
                blockDataTotal += s.blockData.length * s.blockData[0].length * s.blockData[0][0].length;
            }
        }

        return blockIdTotal + blockDataTotal + skyLightTotal + blockLightTotal;
    }

    public ArrayList<RawSign> getSigns() {
        return new ArrayList<RawSign>(signs);
    }

    public ArrayList<TileEntity> getFlowerPots() {
        return new ArrayList<TileEntity>(flowerPots);
    }

    public ArrayList<TileEntity> getPaintings() {
        return new ArrayList<TileEntity>(paintings);
    }

    public ArrayList<TileEntity> getSkulls() {
        return new ArrayList<TileEntity>(skulls);
    }

    public ArrayList<TileEntity> getBeacons() {
        return new ArrayList<TileEntity>(beacons);
    }

    public ArrayList<TileEntity> getBanners() {
        return new ArrayList<TileEntity>(banners);
    }

    public ArrayList<TileEntity> getItemFrames() {
        return new ArrayList<TileEntity>(itemFrames);
    }

    public byte[] calculateHash(MessageDigest hashAlgorithm) {
        hashAlgorithm.reset();

        for (Section s : sections) {
            if (s != null) {
                update(hashAlgorithm, s.blockIds);
                update(hashAlgorithm, s.blockData);
                update(hashAlgorithm, s.skylight);
                update(hashAlgorithm, s.blocklight);
            } else {
                byte[][][] dummy = new byte[1][1][1];
                update(hashAlgorithm, dummy);
            }
        }

        for (RawSign sign : signs) {
            hashAlgorithm.update(Integer.toString(sign.x).getBytes());
            hashAlgorithm.update(Integer.toString(sign.y).getBytes());
            hashAlgorithm.update(Integer.toString(sign.z).getBytes());
            hashAlgorithm.update(sign.text1.getBytes());
            hashAlgorithm.update(sign.text2.getBytes());
            hashAlgorithm.update(sign.text3.getBytes());
            hashAlgorithm.update(sign.text4.getBytes());
        }

        return hashAlgorithm.digest();
    }

    private static void update(MessageDigest hashAlgorithm, int[][][] data) {
        for (int x = 0; x < data.length; x++) {
            for (int y = 0; y < data[0].length; y++) {
                for (int z = 0; y < data[0][0].length; y++) {
                    final int val = data[x][y][z];

                    hashAlgorithm.update((byte) ((val) & 0xFF));
                    hashAlgorithm.update((byte) ((val >> 8) & 0xFF));
                    hashAlgorithm.update((byte) ((val >> 16) & 0xFF));
                    hashAlgorithm.update((byte) ((val >> 24) & 0xFF));
                }
            }
        }
    }

    private static void update(MessageDigest hashAlgorithm, byte[][][] data) {
        for (int x = 0; x < data.length; x++) {
            for (int y = 0; y < data[0].length; y++) {
                hashAlgorithm.update(data[x][y]);
            }
        }
    }

    public int getBiomeId(final int x, final int y, final int z) {
        if (biomes != null)
            return biomes[x][z];
        else
            return BiomeIds.UNKNOWN;
    }

    private static class Section {
        public int[][][] blockIds;
        public byte[][][] blockData;

        public byte[][][] skylight;
        public byte[][][] blocklight;

        public Section() {
            blockIds = new int[SECTION_WIDTH][SECTION_HEIGHT][SECTION_DEPTH];
            blockData = new byte[SECTION_WIDTH][SECTION_HEIGHT][SECTION_DEPTH];

            skylight = new byte[SECTION_WIDTH][SECTION_HEIGHT][SECTION_DEPTH];
            blocklight = new byte[SECTION_WIDTH][SECTION_HEIGHT][SECTION_DEPTH];
        }
    }

}