com.wildex999.schematicbuilder.schematic.SchematicLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.wildex999.schematicbuilder.schematic.SchematicLoader.java

Source

package com.wildex999.schematicbuilder.schematic;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.mutable.MutableInt;

import com.wildex999.schematicbuilder.ModSchematicBuilder;
import com.wildex999.schematicbuilder.config.ConfigurationManager;
import com.wildex999.schematicbuilder.config.IConfigListener;
import com.wildex999.schematicbuilder.exceptions.ExceptionInvalid;
import com.wildex999.schematicbuilder.exceptions.ExceptionLoad;
import com.wildex999.schematicbuilder.exceptions.ExceptionSave;
import com.wildex999.utils.ModLog;

import cpw.mods.fml.common.registry.FMLControlledNamespacedRegistry;
import cpw.mods.fml.common.registry.GameData;
import net.minecraft.block.Block;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;

//Schematic Loading/Saving heavily based on 
//https://github.com/Lunatrius/Schematica/blob/master/src/main/java/com/github/lunatrius/schematica/world/schematic/SchematicAlpha.java
//Which uses The MIT License (MIT)
//Copyright (c) 2014 Jadran "Lunatrius" Kotnik
//https://github.com/Lunatrius/Schematica/blob/master/LICENSE

//Modification and addition done by Kai Roar Stjern ( wildex999 ) 

public class SchematicLoader {

    public static final String NBT_NAME = "SchematicName";
    public static final String NBT_AUTHOR = "SchematicAuthor";

    public static final String NBT_MATERIALS = "Materials";
    public static final String NBT_SCHEMATIC_CLASSIC = "Classic";
    public static final String NBT_SCHEMATIC_ALPHA = "Alpha";

    public static final String NBT_SCHEMATICBUILDER_VERSION = "SchematicBuilderVersion";
    public static final int SchematicBuilderVersion = 1;

    public static final String NBT_BLOCKS = "Blocks";
    public static final String NBT_DATA = "Data";
    public static final String NBT_EXTENDED_METADATA = "ExtendedMetadata";

    public static final String NBT_WIDTH = "Width";
    public static final String NBT_HEIGHT = "Height";
    public static final String NBT_LENGTH = "Length";

    public static final String NBT_ADD_BLOCKS = "AddBlocks";
    public static final String NBT_ADD_BLOCKS_SCHEMATICA = "Add";
    public static final String NBT_MAPPING_SCHEMATICA = "SchematicaMapping";

    public static final String NBT_TILE_ENTITIES = "TileEntities";
    public static final String NBT_ENTITIES = "Entities";

    public static final short maxBlockId = 4095;

    public static boolean writeCompressed = true;

    private static final FMLControlledNamespacedRegistry<Block> BlockRegistry = GameData.getBlockRegistry();

    public static Schematic loadSchematic(File file, HashMap<Short, MutableInt> blockCount)
            throws IOException, ExceptionLoad {
        NBTTagCompound tagCompound = readTagCompoundFromFile(file);
        return loadSchematic(tagCompound, blockCount);
    }

    public static Schematic loadSchematicMeta(File file) throws IOException, ExceptionLoad {
        NBTTagCompound tagCompound = readTagCompoundFromFile(file);
        return loadSchematicMeta(tagCompound);
    }

    public static void saveSchematic(File file, Schematic schematic) throws IOException, ExceptionSave {
        NBTTagCompound tag = saveSchematic(schematic);
        writeTagCompoundToFile(file, tag);
    }

    public static NBTTagCompound readTagCompoundFromFile(File file) throws IOException {
        try {
            return CompressedStreamTools.readCompressed(new FileInputStream(file));
        } catch (Exception e) {
            ModLog.logger.debug("Failed to read as compressed NBT, attempting to read as non-compressed...");
            return CompressedStreamTools.read(file);
        }
    }

    public static void writeTagCompoundToFile(File file, NBTTagCompound tag) throws IOException {
        if (writeCompressed)
            CompressedStreamTools.writeCompressed(tag, new FileOutputStream(file));
        else
            CompressedStreamTools.write(tag, file);
    }

    public static Schematic loadSchematicMeta(NBTTagCompound tagCompound) throws ExceptionLoad {
        //Verify that it's a supported format
        final String format = tagCompound.getString(NBT_MATERIALS);
        if (!format.equals(NBT_SCHEMATIC_ALPHA))
            throw new ExceptionLoad("Unsupported Schematic format: " + format);

        String schematicName = tagCompound.getString(NBT_NAME);
        String schematicAuthor = tagCompound.getString(NBT_AUTHOR);

        int width = tagCompound.getShort(NBT_WIDTH);
        int height = tagCompound.getShort(NBT_HEIGHT);
        int length = tagCompound.getShort(NBT_LENGTH);

        if (width == 0 || height == 0 || length == 0)
            throw new ExceptionInvalid("Width, height or length is not set!");

        //Create Schematic from data
        Schematic schematic = new Schematic(width, height, length);
        schematic.name = schematicName;
        schematic.author = schematicAuthor;

        return schematic;
    }

    //If blockCount is provided, blocks will be counted and written to this list by block name
    public static Schematic loadSchematic(NBTTagCompound tagCompound, HashMap<Short, MutableInt> blockCount)
            throws ExceptionLoad {
        boolean extraData = false;

        Schematic schematic = loadSchematicMeta(tagCompound);

        int width = schematic.getWidth();
        int height = schematic.getHeight();
        int length = schematic.getLength();

        byte blocks[] = tagCompound.getByteArray(NBT_BLOCKS);
        byte metaData[] = tagCompound.getByteArray(NBT_DATA);

        if (blocks.length < (width * height * length))
            throw new ExceptionInvalid("Not enough blocks provided to cover the defined size! Got " + blocks.length
                    + " blocks, with size: " + (width * height * length) + "." + " Width: " + width + " Height: "
                    + height + " Length: " + length);

        byte extraBlocks[] = null;
        byte extraBlocksNibble[] = null;
        if (tagCompound.hasKey(NBT_ADD_BLOCKS)) {
            extraData = true;
            extraBlocksNibble = tagCompound.getByteArray(NBT_ADD_BLOCKS);

            if (extraBlocksNibble.length * 2 < (width * height * length))
                throw new ExceptionInvalid("Not enough extra Block data provided to cover the defined size! Got "
                        + extraBlocksNibble.length * 2 + " blocks, with size: " + (width * height * length) + "."
                        + " Width: " + width + " Height: " + height + " Length: " + length);

            //This one has been packed, so we have to spread it to 2 bytes, with 4 bits per byte
            extraBlocks = new byte[extraBlocksNibble.length * 2];
            for (int i = 0; i < extraBlocksNibble.length; i++) {
                extraBlocks[i * 2] = (byte) ((extraBlocksNibble[i] >> 4) & 0xF);
                extraBlocks[i * 2 + 1] = (byte) (extraBlocksNibble[i] & 0xF);
            }
        } else if (tagCompound.hasKey(NBT_ADD_BLOCKS_SCHEMATICA)) {
            extraData = true;
            //This one is already in the format of 4 bits per byte(But uses more space when stored)
            extraBlocks = tagCompound.getByteArray(NBT_ADD_BLOCKS_SCHEMATICA);
        }

        //Read Mapping for name to id if included
        HashMap<Integer, String> nameMap = new HashMap<Integer, String>();
        if (tagCompound.hasKey(NBT_MAPPING_SCHEMATICA)) {
            NBTTagCompound mapping = tagCompound.getCompoundTag(NBT_MAPPING_SCHEMATICA);
            Set<String> names = mapping.func_150296_c();

            if (ModSchematicBuilder.debug)
                ModLog.logger.info("Loading name mapping: ");

            for (String name : names) {
                int schematicBlockId = mapping.getInteger(name);
                int serverBlockId = BlockRegistry.getId(name);

                if (ModSchematicBuilder.debug)
                    ModLog.logger.info("Name Map(Schematic -> Server): " + name + ": " + schematicBlockId + " -> "
                            + serverBlockId);

                schematic.addSchematicMap((short) schematicBlockId, (byte) 0, name, (short) serverBlockId,
                        (byte) 0);
                nameMap.put(schematicBlockId, name);
            }
        }

        //TODO: Read TileEntities
        //TODO: Read Entities

        //Air counting
        MutableInt airCount = null;
        if (blockCount != null) {
            airCount = blockCount.get(0);
            if (airCount == null) {
                airCount = new MutableInt(0);
                blockCount.put((short) 0, airCount);
            }
        }

        //Store blocks
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                for (int z = 0; z < length; z++) {
                    int index = x + (y * length + z) * width;
                    int blockID = blocks[index] & 0xFF;
                    if (extraData)
                        blockID = blockID | ((extraBlocks[index] & 0xFF) << 8);
                    byte meta = (byte) (metaData[index] & 0xF);

                    //Create a mapping for this BlockID & meta if it does not exist
                    SchematicMap map = schematic.getSchematicMap((short) blockID, meta, false);
                    if (map == null) {
                        //Try to get Base block id and copy that
                        map = schematic.getSchematicMap((short) blockID, meta, true);
                        if (map == null) {
                            Block block = BlockRegistry.getRaw(blockID); //Raw will return null if not found(Instead of default)
                            short serverBlockId;
                            String blockName = null;
                            if (block != null) {
                                serverBlockId = (short) blockID;
                                blockName = BlockRegistry.getNameForObject(block);
                            } else
                                serverBlockId = -1;
                            map = schematic.addSchematicMap((short) blockID, meta,
                                    nameMap.size() > 0 ? nameMap.get(blockID) : blockName, serverBlockId, meta);
                        } else
                            map = schematic.addSchematicMap((short) blockID, meta, map.schematicBlockName,
                                    map.blockId, meta);
                    }

                    schematic.setBlock(x, y, z, (short) blockID, meta);

                    if (blockCount != null) {
                        if (blockID == 0) { //Skip lookup for air
                            airCount.increment();
                            continue;
                        }

                        if (blockID < 0 || blockID >= SchematicLoader.maxBlockId)
                            throw new ExceptionInvalid("Invalid block ID: " + blockID + ", is above max Block ID: "
                                    + SchematicLoader.maxBlockId + ". Schematic might be invalid!");

                        short blockIndex = (short) ((blockID << 4) | meta);
                        MutableInt count = blockCount.get(blockIndex);
                        if (count != null)
                            count.increment();
                        else
                            blockCount.put(blockIndex, new MutableInt(1));
                    }
                }
            }
        }

        return schematic;
    }

    //Save Schematic to NBT. Returns null on failure.
    public static NBTTagCompound saveSchematic(Schematic schematic) throws ExceptionSave {
        if (schematic == null)
            throw new ExceptionSave("Null schematic provided!");

        NBTTagCompound nbt = new NBTTagCompound();

        nbt.setString(NBT_MATERIALS, NBT_SCHEMATIC_ALPHA);
        nbt.setInteger(NBT_SCHEMATICBUILDER_VERSION, SchematicBuilderVersion);

        nbt.setString(NBT_NAME, schematic.name);

        if (schematic.getWidth() > Short.MAX_VALUE || schematic.getHeight() > Short.MAX_VALUE
                || schematic.getLength() > Short.MAX_VALUE)
            throw new ExceptionSave("SchematicBuilder failed to save Schematic due to size limits exceeded("
                    + Short.MAX_VALUE + "): " + "Width: " + schematic.getWidth() + " Height: "
                    + schematic.getHeight() + " Length: " + schematic.getLength());

        nbt.setShort(NBT_WIDTH, (short) schematic.getWidth());
        nbt.setShort(NBT_HEIGHT, (short) schematic.getHeight());
        nbt.setShort(NBT_LENGTH, (short) schematic.getLength());

        //Packed means that there are two blocks per byte
        byte blocks[] = new byte[schematic.getWidth() * schematic.getHeight() * schematic.getLength()]; //8 bit
        byte meta[] = new byte[(schematic.getWidth() * schematic.getHeight() * schematic.getLength())]; //8 bit(Not packed yet, TODO)
        byte extra[] = new byte[(schematic.getWidth() * schematic.getHeight() * schematic.getLength()) / 2]; //4 bit packed
        String mapping[] = new String[maxBlockId + 1]; //One name per id
        boolean gotExtraData = false;
        SchematicBlock airBlock = new SchematicBlock(schematic.blockIdAir, (byte) 0);

        for (int x = 0; x < schematic.getWidth(); x++) {
            for (int y = 0; y < schematic.getHeight(); y++) {
                for (int z = 0; z < schematic.getLength(); z++) {
                    int index = x + (y * schematic.getLength() + z) * schematic.getWidth();
                    int part = 1 - (index & 1); //We want the first part to be shifted left
                    SchematicBlock sBlock = schematic.getBlock(x, y, z);
                    if (sBlock == null)
                        sBlock = airBlock;

                    int blockId = sBlock.getSchematicBlockId();

                    blocks[index] = (byte) (blockId & 0xFF);
                    if (blockId > 255) {
                        gotExtraData = true;
                        extra[index / 2] |= (byte) ((blockId >> 8) << (part * 4));
                    }
                    //meta[index/2] |= sBlock.metaData << (part*4);
                    meta[index] = sBlock.getSchematicMeta(); //Currently uses a full byte instead of compressing down to 4 bits

                    if (mapping[blockId] == null) {
                        SchematicMap map = schematic.getSchematicMap(sBlock.getSchematicBlockId(),
                                sBlock.getSchematicMeta(), true);
                        if (map == null || map.schematicBlockName == null)
                            mapping[blockId] = "";
                        else
                            mapping[blockId] = map.schematicBlockName;
                    }
                }
            }
        }

        nbt.setByteArray(NBT_BLOCKS, blocks);
        nbt.setByteArray(NBT_DATA, meta);
        if (gotExtraData)
            nbt.setByteArray(NBT_ADD_BLOCKS, extra);

        //Write Block name/id mapping
        NBTTagCompound mappingTag = new NBTTagCompound();
        for (short blockId = 0; blockId <= maxBlockId; blockId++) {
            if (mapping[blockId] == null || mapping[blockId].isEmpty())
                continue;
            mappingTag.setShort(mapping[blockId], blockId);
        }

        nbt.setTag(NBT_MAPPING_SCHEMATICA, mappingTag);

        //TODO: Write TileEntities
        //TODO: Write Entities

        return nbt;
    }
}