private void setupFromNBT(NBTTagCompound tagCompound) {
    terrainType = TerrainType.values()[tagCompound.getInteger("terrain")];
    featureTypes = toEnumSet(getIntArraySafe(tagCompound, "features"), FeatureType.values());
    structureTypes = toEnumSet(getIntArraySafe(tagCompound, "structures"), StructureType.values());
    effectTypes = toEnumSet(getIntArraySafe(tagCompound, "effects"), EffectType.values());

    biomes.clear();
    for (int a : getIntArraySafe(tagCompound, "biomes")) {
      BiomeGenBase biome = BiomeGenBase.getBiome(a);
      if (biome != null) {
        biomes.add(biome);
      } else {
        // Protect against deleted biomes (i.e. a mod with biomes gets removed and this dimension
        // still uses it).
        // We will pick a replacement biome here.
        biomes.add(BiomeGenBase.plains);
      }
    }
    if (tagCompound.hasKey("controller")) {
      controllerType = ControllerType.values()[tagCompound.getInteger("controller")];
    } else {
      // Support for old type.
      if (biomes.isEmpty()) {
        controllerType = ControllerType.CONTROLLER_DEFAULT;
      } else {
        controllerType = ControllerType.CONTROLLER_SINGLE;
      }
    }

    digitString = tagCompound.getString("digits");

    forcedDimensionSeed = tagCompound.getLong("forcedSeed");
    baseSeed = tagCompound.getLong("baseSeed");
    worldVersion = tagCompound.getInteger("worldVersion");

    baseBlockForTerrain = getBlockMeta(tagCompound, "baseBlock");
    tendrilBlock = getBlockMeta(tagCompound, "tendrilBlock");
    canyonBlock = getBlockMeta(tagCompound, "canyonBlock");
    fluidForTerrain =
        (Block) Block.blockRegistry.getObjectById(tagCompound.getInteger("fluidBlock"));

    hugeLiquidSphereFluids = readFluidsFromNBT(tagCompound, "hugeLiquidSphereFluids");
    hugeLiquidSphereBlocks = readBlockArrayFromNBT(tagCompound, "hugeLiquidSphereBlocks");

    // Support for the old format with only one liquid block.
    Block oldLiquidSphereFluid =
        (Block) Block.blockRegistry.getObjectById(tagCompound.getInteger("liquidSphereFluid"));
    liquidSphereFluids = readFluidsFromNBT(tagCompound, "liquidSphereFluids");
    if (liquidSphereFluids.length == 0) {
      liquidSphereFluids = new Block[] {oldLiquidSphereFluid};
    }

    // Support for the old format with only one sphere block.
    BlockMeta oldLiquidSphereBlock = getBlockMeta(tagCompound, "liquidSphereBlock");
    liquidSphereBlocks = readBlockArrayFromNBT(tagCompound, "liquidSphereBlocks");
    if (liquidSphereBlocks.length == 0) {
      liquidSphereBlocks = new BlockMeta[] {oldLiquidSphereBlock};
    }

    pyramidBlocks = readBlockArrayFromNBT(tagCompound, "pyramidBlocks");
    if (pyramidBlocks.length == 0) {
      pyramidBlocks = new BlockMeta[] {BlockMeta.STONE};
    }

    // Support for the old format with only one sphere block.
    BlockMeta oldSphereBlock = getBlockMeta(tagCompound, "sphereBlock");
    sphereBlocks = readBlockArrayFromNBT(tagCompound, "sphereBlocks");
    if (sphereBlocks.length == 0) {
      sphereBlocks = new BlockMeta[] {oldSphereBlock};
    }

    hugeSphereBlocks = readBlockArrayFromNBT(tagCompound, "hugeSphereBlocks");

    extraOregen = readBlockArrayFromNBT(tagCompound, "extraOregen");
    fluidsForLakes = readFluidsFromNBT(tagCompound, "lakeFluids");

    peaceful = tagCompound.getBoolean("peaceful");
    noanimals = tagCompound.getBoolean("noanimals");
    shelter = tagCompound.getBoolean("shelter");
    respawnHere = tagCompound.getBoolean("respawnHere");
    if (tagCompound.hasKey("celestialAngle")) {
      celestialAngle = tagCompound.getFloat("celestialAngle");
    } else {
      celestialAngle = null;
    }
    if (tagCompound.hasKey("timeSpeed")) {
      timeSpeed = tagCompound.getFloat("timeSpeed");
    } else {
      timeSpeed = null;
    }
    probeCounter = tagCompound.getInteger("probes");
    actualRfCost = tagCompound.getInteger("actualCost");

    skyDescriptor = new SkyDescriptor.Builder().fromNBT(tagCompound).build();
    calculateCelestialBodyDescriptors();

    patreon1 = tagCompound.getLong("patreon1");

    weatherDescriptor = new WeatherDescriptor.Builder().fromNBT(tagCompound).build();

    extraMobs.clear();
    NBTTagList list = tagCompound.getTagList("mobs", Constants.NBT.TAG_COMPOUND);
    for (int i = 0; i < list.tagCount(); i++) {
      NBTTagCompound tc = list.getCompoundTagAt(i);
      String className = tc.getString("class");
      int chance = tc.getInteger("chance");
      int minGroup = tc.getInteger("minGroup");
      int maxGroup = tc.getInteger("maxGroup");
      int maxLoaded = tc.getInteger("maxLoaded");
      Class<? extends EntityLiving> c = null;
      try {
        c = (Class<? extends EntityLiving>) Class.forName(className);
      } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
      }
      MobDescriptor mob = new MobDescriptor(null, c, chance, minGroup, maxGroup, maxLoaded);
      extraMobs.add(mob);
    }

    String ds = tagCompound.getString("dimensionTypes");
    dimensionTypes = StringUtils.split(ds, ",");
    if (dimensionTypes == null) {
      dimensionTypes = new String[0];
    }
  }
  public DimensionInformation(String name, DimensionDescriptor descriptor, ByteBuf buf) {
    this.name = name;
    this.descriptor = descriptor;

    terrainType = NetworkTools.readEnum(buf, TerrainType.values());
    NetworkTools.readEnumCollection(buf, featureTypes, FeatureType.values());
    NetworkTools.readEnumCollection(buf, structureTypes, StructureType.values());
    NetworkTools.readEnumCollection(buf, effectTypes, EffectType.values());

    biomes.clear();
    int size = buf.readInt();
    for (int i = 0; i < size; i++) {
      BiomeGenBase biome = BiomeGenBase.getBiome(buf.readInt());
      if (biome != null) {
        biomes.add(biome);
      } else {
        biomes.add(BiomeGenBase.plains);
      }
    }
    controllerType = NetworkTools.readEnum(buf, ControllerType.values());
    digitString = NetworkTools.readString(buf);

    forcedDimensionSeed = buf.readLong();
    baseSeed = buf.readLong();
    worldVersion = buf.readInt();

    Block block = (Block) Block.blockRegistry.getObjectById(buf.readInt());
    int meta = buf.readInt();
    baseBlockForTerrain = new BlockMeta(block, meta);
    block = (Block) Block.blockRegistry.getObjectById(buf.readInt());
    meta = buf.readInt();
    tendrilBlock = new BlockMeta(block, meta);

    pyramidBlocks = readBlockArrayFromBuf(buf);
    sphereBlocks = readBlockArrayFromBuf(buf);
    hugeSphereBlocks = readBlockArrayFromBuf(buf);
    liquidSphereBlocks = readBlockArrayFromBuf(buf);
    liquidSphereFluids = readFluidArrayFromBuf(buf);
    hugeLiquidSphereBlocks = readBlockArrayFromBuf(buf);
    hugeLiquidSphereFluids = readFluidArrayFromBuf(buf);

    block = (Block) Block.blockRegistry.getObjectById(buf.readInt());
    meta = buf.readInt();
    canyonBlock = new BlockMeta(block, meta);
    fluidForTerrain = (Block) Block.blockRegistry.getObjectById(buf.readInt());

    extraOregen = readBlockArrayFromBuf(buf);

    fluidsForLakes = readFluidArrayFromBuf(buf);

    peaceful = buf.readBoolean();
    noanimals = buf.readBoolean();
    shelter = buf.readBoolean();
    respawnHere = buf.readBoolean();

    celestialAngle = NetworkTools.readFloat(buf);
    timeSpeed = NetworkTools.readFloat(buf);

    probeCounter = buf.readInt();
    actualRfCost = buf.readInt();

    skyDescriptor = new SkyDescriptor.Builder().fromBytes(buf).build();
    calculateCelestialBodyDescriptors();

    weatherDescriptor = new WeatherDescriptor.Builder().fromBytes(buf).build();

    patreon1 = buf.readLong();

    extraMobs.clear();
    size = buf.readInt();
    for (int i = 0; i < size; i++) {
      String className = NetworkTools.readString(buf);
      try {
        Class<? extends EntityLiving> c = (Class<? extends EntityLiving>) Class.forName(className);
        int chance = buf.readInt();
        int minGroup = buf.readInt();
        int maxGroup = buf.readInt();
        int maxLoaded = buf.readInt();
        MobDescriptor mob = new MobDescriptor(null, c, chance, minGroup, maxGroup, maxLoaded);
        extraMobs.add(mob);
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      }
    }

    size = buf.readInt();
    dimensionTypes = new String[size];
    for (int i = 0; i < size; i++) {
      dimensionTypes[i] = NetworkTools.readString(buf);
    }

    setupBiomeMapping();
  }
/**
 * ProfileData acts as a data store facility for the results of the test performed by the Profile
 * object. This data includes the number of trials and number of successes among those trials (i.e.,
 * the number of times the correct answer was selected) broken down by algorithm type and question
 * profile (i.e., by combination of features).
 *
 * @author jjohnson346
 */
public class ProfileData {

  private int[][] successCounts; // stores the number of successes for each
  // question profile,
  // for each algo.

  public final int[] trialCounts; // stores the number of trials for each
  // question profile.

  public final double[][] successProbs; // the prob of success for each algo,
  // for each question profile
  // IMPORTANT: note that it is PUBLIC

  private final int NUM_ALGOS = AlgorithmType.values().length; // the number
  // of algos
  // to store
  // data for

  private final int NUM_FEATURES = FeatureType.values().length; // the number
  // of
  // features
  // identified

  private final int NUM_FEATURE_COMBOS = (int) Math.pow(2.0, NUM_FEATURES); // the
  // number
  // of
  // combos
  // of
  // features,
  // assume
  // true/false
  // value
  // for
  // each.

  /**
   * constructor initializes the successCounts, trialCounts and successProbs arrays according to the
   * number of algos, number of features, and the consequent number of feature combinations.
   */
  public ProfileData() {
    successCounts = new int[NUM_FEATURE_COMBOS][NUM_ALGOS];
    trialCounts = new int[NUM_FEATURE_COMBOS];
    successProbs = new double[NUM_FEATURE_COMBOS][NUM_ALGOS];
  }

  /**
   * inserts the results data for a question into the data store. That is, this function inserts for
   * each algorithm, the results of applying that algorithm on the question, as given by the array,
   * results, passed in as an input argument. This data is inserted the location of the data store
   * corresponding to the profile for the given question.
   *
   * @param question the question for which to insert the data, the profile is the pertinent info,
   *     here.
   * @param results an array of booleans giving whether the corresponding algo was successful on the
   *     question.
   */
  public void insert(CFEExamQuestion question, boolean[] results) {
    // get the profile for the question passed in as an input parm.
    int profileIndex = question.getProfile().getProfileIndex();

    // increment the number of trials for the index corresponding to the
    // question's profile.
    trialCounts[profileIndex]++;

    // increment the success count in the successCounts array for those
    // algos that were successful.
    for (int j = 0; j < successCounts[profileIndex].length; j++) {
      if (results[j]) successCounts[profileIndex][j]++;
    }
  }

  /**
   * calculates the probability of success for a each algorithm on each question profile and stores
   * the results in a public array, successProbs, which is used by the CFEExamAgent to determine on
   * each question it confronts which algo to use. That is, it picks the algo with highest
   * probability of success given the profile of the current question.
   */
  public void calculate() {
    for (int i = 0; i < NUM_FEATURE_COMBOS; i++) {
      for (int j = 0; j < NUM_ALGOS; j++) {
        if (trialCounts[i] != 0) successProbs[i][j] = (double) successCounts[i][j] / trialCounts[i];
        else successProbs[i][j] = 0.0;
      }
    }
  }

  /**
   * loads profile data from file, profile data.txt, and stores the contents in the public array,
   * successProbs. successProbs is the critical array used by the CFEExamAgent for selecting the
   * algo to use on each question it confronts.
   */
  public void load() throws FileNotFoundException {
    // change backslashes to forward slashes for mac version.
    // Scanner scanner = new Scanner(new
    // File("profile data\\profile data.txt"));
    Scanner scanner = new Scanner(new File("profile data//profile data.txt"));

    // skip first 2 header lines.
    scanner.nextLine();
    scanner.nextLine();

    for (int i = 0; i < NUM_FEATURE_COMBOS; i++) {

      // skip first column showing the index.
      scanner.nextInt();

      trialCounts[i] = scanner.nextInt();

      for (int j = 0; j < NUM_ALGOS; j++) {
        successProbs[i][j] = scanner.nextDouble();
      }
    }
    scanner.close();
  }

  /**
   * returns a pretty formatted version of the contents of number of trials and successProb for each
   * question profile.
   */
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append(toStringTrueFalse());
    sb.append("\n\n");
    sb.append(toStringMultipleChoice());
    return new String(sb);
    //		StringBuilder sb = new StringBuilder();
    //
    //		final int COLUMN_WIDTH = 14;
    //		String formatString = "%" + COLUMN_WIDTH + "s";
    //
    //		// make header.
    //		sb.append(String.format(formatString, "Index"));
    //		sb.append(String.format(formatString, "Trials"));
    //
    //		for (AlgorithmType t : AlgorithmType.values()) {
    //			sb.append(String.format(formatString, t));
    //		}
    //		sb.append(String.format(formatString, "Agent"));
    //		sb.append(String.format(formatString, "Description"));
    //		sb.append("\n");
    //		for (int i = 0; i < AlgorithmType.values().length + 4; i++)
    //			sb.append(String.format(formatString, "-------------"));
    //		sb.append("\n");
    //
    //		// display values.
    //		for (int i = 0; i < NUM_FEATURE_COMBOS; i++) {
    //			// 2016/01/05 - version 2.0.0 - added condition for
    //			// trial counts > 0 in order to remove the "zero" rows,
    //			// i.e., rows showing profile indices where there are no
    //			// trials.
    //			if (trialCounts[i] > 0) {
    //				sb.append(String.format(formatString, i));
    //				sb.append(String.format(formatString, trialCounts[i]));
    //
    //				for (int j = 0; j < AlgorithmType.values().length; j++) {
    //					sb.append(String.format("%" + COLUMN_WIDTH + ".3f", successProbs[i][j]));
    //				}
    //				sb.append(String.format("%" + COLUMN_WIDTH + ".3f", maxSuccessProb(successProbs[i])));
    //				sb.append(Profile.getDescription(i));
    //				sb.append("\n");
    //			}
    //		}
    //
    //		// display total trial count
    //		int totalCount = 0;
    //		for (int i = 0; i < trialCounts.length; i++) {
    //			totalCount += trialCounts[i];
    //		}
    //		sb.append(String.format("%" + COLUMN_WIDTH + "s%" + COLUMN_WIDTH
    //				+ "d\n", "Total Count:", totalCount));
    //
    //		return new String(sb);
  }

  private String toStringMultipleChoice() {
    StringBuilder sb = new StringBuilder();

    final int COLUMN_WIDTH = 14;
    String formatString = "%" + COLUMN_WIDTH + "s";

    // make header.
    sb.append("Multiple Choice:\n----------------\n");
    sb.append(String.format(formatString, "Index"));
    sb.append(String.format(formatString, "Trials"));

    for (AlgorithmType t : AlgorithmType.values()) {
      sb.append(String.format(formatString, t));
    }
    sb.append(String.format(formatString, "Agent"));
    sb.append(String.format(formatString, "Description"));
    sb.append("\n");
    for (int i = 0; i < AlgorithmType.values().length + 4; i++)
      sb.append(String.format(formatString, "-------------"));
    sb.append("\n");

    int totalCount = 0; // for displaying the total count at the bottom.
    double weightedAgentAccuracy =
        0; // for calculating weighted accuracy rate across all question profiles.

    // display values.
    for (int i = 0; i < NUM_FEATURE_COMBOS; i++) {
      // 2016/01/05 - version 2.0.0 - added condition for
      // trial counts > 0 in order to remove the "zero" rows,
      // i.e., rows showing profile indices where there are no
      // trials.
      // Also, added the !hasFeature(truefalse) condition.
      if (trialCounts[i] > 0 && !Profile.hasFeature(i, FeatureType.TRUE_FALSE.ordinal())) {
        sb.append(String.format(formatString, i));
        sb.append(String.format(formatString, trialCounts[i]));

        for (int j = 0; j < AlgorithmType.values().length; j++) {
          sb.append(String.format("%" + COLUMN_WIDTH + ".3f", successProbs[i][j]));
        }
        double agentAccuracy = maxSuccessProb(successProbs[i]);
        sb.append(String.format("%" + COLUMN_WIDTH + ".3f", maxSuccessProb(successProbs[i])));
        sb.append(Profile.getDescription(i));
        sb.append("\n");
        totalCount += trialCounts[i];
        weightedAgentAccuracy += trialCounts[i] * agentAccuracy;
      }
    }
    weightedAgentAccuracy /= totalCount;

    // display total trial count
    sb.append(
        String.format(
            "%"
                + COLUMN_WIDTH
                + "s%"
                + COLUMN_WIDTH
                + "d%"
                + COLUMN_WIDTH * 9
                + "s%"
                + COLUMN_WIDTH
                + ".3f\n",
            "Total Count:",
            totalCount,
            " ",
            weightedAgentAccuracy));

    return new String(sb);
  }

  private String toStringTrueFalse() {
    StringBuilder sb = new StringBuilder();

    final int COLUMN_WIDTH = 14;
    String formatString = "%" + COLUMN_WIDTH + "s";

    // make header.
    sb.append("True-False: \n-----------\n");
    sb.append(String.format(formatString, "Index"));
    sb.append(String.format(formatString, "Trials"));

    for (AlgorithmType t : AlgorithmType.values()) {
      sb.append(String.format(formatString, t));
    }
    sb.append(String.format(formatString, "Agent"));
    sb.append(String.format(formatString, "Description"));
    sb.append("\n");
    for (int i = 0; i < AlgorithmType.values().length + 4; i++)
      sb.append(String.format(formatString, "-------------"));
    sb.append("\n");

    int totalCount = 0; // for displaying the total count at the bottom.
    double weightedAgentAccuracy =
        0; // for calculating weighted accuracy rate across all question profiles.

    // display values.
    for (int i = 0; i < NUM_FEATURE_COMBOS; i++) {
      // 2016/01/05 - version 2.0.0 - added condition for
      // trial counts > 0 in order to remove the "zero" rows,
      // i.e., rows showing profile indices where there are no
      // trials.
      // Also, added the hasFeature(truefalse) condition.
      if (trialCounts[i] > 0 && Profile.hasFeature(i, FeatureType.TRUE_FALSE.ordinal())) {
        sb.append(String.format(formatString, i));
        sb.append(String.format(formatString, trialCounts[i]));

        for (int j = 0; j < AlgorithmType.values().length; j++) {
          sb.append(String.format("%" + COLUMN_WIDTH + ".3f", successProbs[i][j]));
        }
        double agentAccuracy = maxSuccessProb(successProbs[i]);
        sb.append(String.format("%" + COLUMN_WIDTH + ".3f", agentAccuracy));
        sb.append(Profile.getDescription(i));
        sb.append("\n");
        totalCount += trialCounts[i];
        weightedAgentAccuracy += trialCounts[i] * agentAccuracy;
      }
    }
    weightedAgentAccuracy /= totalCount;

    // display total trial count
    //		sb.append(String.format("%" + COLUMN_WIDTH + "s%" + COLUMN_WIDTH
    //				+ "d\n", "Total Count:", totalCount));
    sb.append(
        String.format(
            "%"
                + COLUMN_WIDTH
                + "s%"
                + COLUMN_WIDTH
                + "d%"
                + COLUMN_WIDTH * 9
                + "s%"
                + COLUMN_WIDTH
                + ".3f\n",
            "Total Count:",
            totalCount,
            " ",
            weightedAgentAccuracy));

    return new String(sb);
  }

  private double maxSuccessProb(double[] successProbs) {
    double max = successProbs[0];
    for (int i = 1; i < successProbs.length; i++) {
      if (successProbs[i] > max) max = successProbs[i];
    }
    return max;
  }
}