// Generate a chunk
  @Override
  public byte[] generate(World world, Random rand, int chunkx, int chunkz) {
    // Create a byte variable to write the chunk inside and return this variable
    byte[] result = new byte[32768];

    // This will set the whole floor to stone (the floor of each chunk)
    for (int y = 30 + 3; y > 0; y--)
      for (int x = 0; x < 16; x++)
        for (int z = 0; z < 16; z++) result[xyzToByte(x, y, z)] = (byte) Material.STONE.getId();

    // Set the lowest layer to bedrock
    for (int x = 0; x < 16; x++)
      for (int z = 0; z < 16; z++) result[xyzToByte(x, 0, z)] = (byte) Material.BEDROCK.getId();

    // The layers for each 5 rooms in the variable y
    for (int y = 30; y < 30 + (7 * 6); y += 6) {

      // The 4 rooms on each layer saved in the variables x and z
      for (int x = 0; x < 16; x += 8) {
        for (int z = 0; z < 16; z += 8) {

          int xr = (rand.nextInt(3) - 1) * (x + 7);
          int zr = (rand.nextInt(3) - 1) * (z + 7);

          int yfloor = rand.nextInt(2);

          // All the y of the room in the variable y2
          for (int y2 = y + yfloor; y2 < y + 8; y2++) {

            // All the x of the room in the variable x2
            for (int x2 = x; x2 < x + 8; x2++) {

              // All the z of the room in the variable z2
              for (int z2 = z; z2 < z + 8; z2++) {

                // Make the bottom of the room
                if (y2 == y + yfloor)
                  for (int xb = x; xb < x + 8; xb++)
                    for (int zb = z; zb < z + 8; zb++)
                      result[xyzToByte(xb, y2, zb)] = (byte) Material.COBBLESTONE.getId();

                // Fill the walls of the place with cobblestone
                if ((x2 == x || x2 == x + 7) && (z2 == z || z2 == z + 7))
                  result[xyzToByte(x2, y2, z2)] = (byte) 98;
                else if (xr == x2) result[xyzToByte(x2, y2, z2)] = (byte) 98;
                else if (zr == z2) result[xyzToByte(x2, y2, z2)] = (byte) 98;
                else result[xyzToByte(x2, y2, z2)] = (byte) Material.AIR.getId();
              }
            }
          }
        }
      }
    }

    // Create the nose generator which generates wave formes to use for the surface.
    Random random = new Random(world.getSeed());
    SimplexOctaveGenerator octave = new SimplexOctaveGenerator(random, 8);
    octave.setScale(1 / 48.0);

    // Generate the ceiling and the grass land
    for (int x = 0; x < 16; x++) {
      for (int z = 0; z < 16; z++) {
        /*int height = getHeight(world, chunkx + x * 0.0625, chunkz + z * 0.0625, 2) + 30+(7*6) + 7;*/
        double height = octave.noise(x + chunkx * 16, z + chunkz * 16, 0.5, 0.5) * 4 + 9;

        result[xyzToByte(x, 30 + (7 * 6), z)] = (byte) Material.COBBLESTONE.getId();
        for (int y = 30 + (7 * 6) + 1; y < 30 + (7 * 6) + 4; y++)
          result[xyzToByte(x, y, z)] = (byte) Material.STONE.getId();

        // Get the current biome
        Biome biome = world.getBiome((chunkx * 16) + x, (chunkz * 16) + z);

        if (biome.equals(Biome.DESERT) || biome.equals(Biome.DESERT_HILLS)) {
          for (int y = 30 + (7 * 6) + 4; y < 30 + (7 * 6) + 2 + height; y++)
            result[xyzToByte(x, y, z)] = (byte) Material.SAND.getId();

        } else if (biome.equals(Biome.MUSHROOM_ISLAND) || biome.equals(Biome.MUSHROOM_ISLAND)) {
          for (int y = 30 + (7 * 6) + 4; y < 30 + (7 * 6) + 2 + height; y++)
            result[xyzToByte(x, y, z)] = (byte) Material.DIRT.getId();
          result[xyzToByte(x, (int) (30 + (7 * 6) + 2 + height), z)] =
              (byte) Material.MYCEL.getId();

        } else {
          for (int y = 30 + (7 * 6) + 4; y < 30 + (7 * 6) + 2 + height; y++)
            result[xyzToByte(x, y, z)] = (byte) Material.DIRT.getId();
          result[xyzToByte(x, (int) (30 + (7 * 6) + 2 + height), z)] =
              (byte) Material.GRASS.getId();
        }
      }
    }

    return result;
  }
public abstract class PlatLot {

  // extremes
  private CachedYs blockYs;
  protected int averageHeight;
  protected int minHeight = Integer.MAX_VALUE;
  protected int minHeightX = 0;
  protected int minHeightZ = 0;
  protected int maxHeight = Integer.MIN_VALUE;
  protected int maxHeightX = 0;
  protected int maxHeightZ = 0;

  protected Random platmapRandom;
  protected Random chunkRandom;

  // styling!
  public enum LotStyle {
    NATURE,
    STRUCTURE,
    ROAD,
    ROUNDABOUT
  };

  public LotStyle style;

  public PlatLot(PlatMap platmap, int chunkX, int chunkZ) {
    super();
    initializeDice(platmap, chunkX, chunkZ);

    style = LotStyle.NATURE;
  }

  protected static final byte airId = (byte) Material.AIR.getId();
  protected static final byte stoneId = (byte) Material.STONE.getId();
  protected static final byte dirtId = (byte) Material.DIRT.getId();
  protected static final byte grassId = (byte) Material.GRASS.getId();
  protected static final byte snowId = (byte) Material.SNOW_BLOCK.getId();
  protected static final byte sandId = (byte) Material.SAND.getId();
  protected static final byte sandstoneId = (byte) Material.SANDSTONE.getId();
  protected static final byte bedrockId = (byte) Material.BEDROCK.getId();
  protected static final byte fenceId = (byte) Material.FENCE.getId();
  protected static final byte cobbleId = (byte) Material.COBBLESTONE.getId();
  protected static final byte stillWaterId = (byte) Material.STATIONARY_WATER.getId();
  protected static final byte stillLavaId = (byte) Material.STATIONARY_LAVA.getId();
  protected static final byte waterId = (byte) Material.WATER.getId();
  protected static final byte lavaId = (byte) Material.LAVA.getId();
  protected static final byte leavesId = (byte) Material.LEAVES.getId();
  protected static final byte glassId = (byte) Material.GLASS.getId();
  protected static final byte paneId = (byte) Material.THIN_GLASS.getId();
  protected static final byte logId = (byte) Material.LOG.getId();
  protected static final byte glowId = (byte) Material.GLOWSTONE.getId();
  protected static final byte stepId = (byte) Material.STEP.getId();
  protected static final byte clayId = (byte) Material.CLAY.getId();
  protected static final byte ironFenceId = (byte) Material.IRON_FENCE.getId();
  protected static final byte endId = (byte) Material.ENDER_STONE.getId();
  protected static final byte netherrackId = (byte) Material.NETHERRACK.getId();
  protected static final byte soulsandId = (byte) Material.SOUL_SAND.getId();

  protected static final int snowMaterialId = Material.SNOW.getId();
  protected static final Material snowMaterial = Material.SNOW;
  protected static final Material airMaterial = Material.AIR;
  protected static final Material stoneMaterial = Material.STONE;
  protected static final Material rootMaterial = Material.GRASS;

  public abstract long getConnectedKey();

  public abstract boolean makeConnected(PlatLot relative);

  public abstract boolean isConnectable(PlatLot relative);

  public abstract boolean isConnected(PlatLot relative);

  protected abstract void generateActualChunk(
      WorldGenerator generator,
      PlatMap platmap,
      ByteChunk chunk,
      BiomeGrid biomes,
      DataContext context,
      int platX,
      int platZ);

  protected abstract void generateActualBlocks(
      WorldGenerator generator,
      PlatMap platmap,
      RealChunk chunk,
      DataContext context,
      int platX,
      int platZ);

  public Biome getChunkBiome() {
    return Biome.PLAINS;
  }

  public boolean isPlaceableAt(WorldGenerator generator, int chunkX, int chunkZ) {
    return generator.settings.inCityRange(chunkX, chunkZ);
  }

  private void initializeDice(PlatMap platmap, int chunkX, int chunkZ) {

    // reset and pick up the dice
    platmapRandom = platmap.getRandomGenerator();
    chunkRandom = platmap.getChunkRandomGenerator(chunkX, chunkZ);
  }

  protected void initializeContext(WorldGenerator generator, SupportChunk chunk) {
    if (blockYs == null) {
      blockYs = new CachedYs(generator, chunk);

      // what was the average height
      minHeight = blockYs.minHeight;
      minHeightX = blockYs.minHeightX;
      minHeightZ = blockYs.minHeightZ;
      maxHeight = blockYs.maxHeight;
      maxHeightX = blockYs.maxHeightX;
      maxHeightZ = blockYs.maxHeightZ;
      averageHeight = blockYs.averageHeight;
    }
  }

  private void deinitializeContext() {
    blockYs = null;
  }

  protected int getBlockY(int x, int z) {
    return blockYs == null ? 0 : blockYs.getBlockY(x, z);
  }

  protected double getPerciseY(int x, int z) {
    return blockYs == null ? 0 : blockYs.getPerciseY(x, z);
  }

  public abstract int getBottomY(WorldGenerator generator);

  public void generateChunk(
      WorldGenerator generator,
      PlatMap platmap,
      ByteChunk chunk,
      BiomeGrid biomes,
      DataContext context,
      int platX,
      int platZ) {
    initializeDice(platmap, chunk.chunkX, chunk.chunkZ);
    initializeContext(generator, chunk);

    // what do we need to first?
    generator.shapeProvider.preGenerateChunk(generator, this, chunk, biomes, blockYs);

    // let the specialized platlot do it's thing
    generateActualChunk(generator, platmap, chunk, biomes, context, platX, platZ);

    // polish things off
    generator.shapeProvider.postGenerateChunk(generator, this, chunk, blockYs);
  }

  public void generateBlocks(
      WorldGenerator generator,
      PlatMap platmap,
      RealChunk chunk,
      DataContext context,
      int platX,
      int platZ) {
    initializeDice(platmap, chunk.chunkX, chunk.chunkZ);
    initializeContext(generator, chunk);

    // what do we need to first?
    generator.shapeProvider.preGenerateBlocks(generator, this, chunk, blockYs);

    // let the specialized platlot do it's thing
    generateActualBlocks(generator, platmap, chunk, context, platX, platZ);

    // polish things off
    generator.shapeProvider.postGenerateBlocks(generator, this, chunk, blockYs);

    // all done
    deinitializeContext();
  }

  private static final int lowestMineSegment = 16;

  public void generateMines(WorldGenerator generator, ByteChunk chunk) {

    // get shafted! (this builds down to keep the support poles happy)
    if (generator.settings.includeMines)
      for (int y = (minHeight / 16 - 1) * 16; y >= lowestMineSegment; y -= 16) {
        if (isShaftableLevel(generator, y)) generateHorizontalMineLevel(generator, chunk, y);
      }
  }

  protected int findHighestShaftableLevel(
      WorldGenerator generator, DataContext context, SupportChunk chunk) {

    // keep going down until we find what we are looking for
    for (int y = (minHeight / 16 - 1) * 16; y >= lowestMineSegment; y -= 16) {
      if (isShaftableLevel(generator, y)
          && generator.shapeProvider.isHorizontalWEShaft(chunk.chunkX, y, chunk.chunkZ))
        return y + 7;
    }

    // nothing found
    return 0;
  }

  protected boolean isShaftableLevel(WorldGenerator generator, int y) {
    return y >= lowestMineSegment && y < minHeight && minHeight > generator.seaLevel;
  }

  private void generateHorizontalMineLevel(WorldGenerator generator, ByteChunk chunk, int y) {
    int y1 = y + 6;
    int y2 = y1 + 1;

    // draw the shafts/walkways
    boolean pathFound = false;
    if (generator.shapeProvider.isHorizontalNSShaft(chunk.chunkX, y, chunk.chunkZ)) {
      generateMineShaftSpace(chunk, 6, 10, y1, y1 + 4, 0, 6);
      generateMineNSSupport(chunk, 6, y2, 1);
      generateMineNSSupport(chunk, 6, y2, 4);
      generateMineShaftSpace(chunk, 6, 10, y1, y1 + 4, 10, 16);
      generateMineNSSupport(chunk, 6, y2, 11);
      generateMineNSSupport(chunk, 6, y2, 14);
      pathFound = true;
    }
    if (generator.shapeProvider.isHorizontalWEShaft(chunk.chunkX, y, chunk.chunkZ)) {
      generateMineShaftSpace(chunk, 0, 6, y1, y1 + 4, 6, 10);
      generateMineWESupport(chunk, 1, y2, 6);
      generateMineWESupport(chunk, 4, y2, 6);
      generateMineShaftSpace(chunk, 10, 16, y1, y1 + 4, 6, 10);
      generateMineWESupport(chunk, 11, y2, 6);
      generateMineWESupport(chunk, 14, y2, 6);
      pathFound = true;
    }

    // draw the center bit
    if (pathFound) generateMineShaftSpace(chunk, 6, 10, y1, y1 + 4, 6, 10);
  }

  private static final byte shaftBridgeId = (byte) Material.WOOD.getId();
  private static final byte shaftSupportId = (byte) Material.FENCE.getId();
  private static final byte shaftBeamId = (byte) Material.WOOD.getId();

  private void generateMineShaftSpace(
      ByteChunk chunk, int x1, int x2, int y1, int y2, int z1, int z2) {
    chunk.setEmptyBlocks(x1, x2, y1, z1, z2, shaftBridgeId);
    chunk.setBlocks(x1, x2, y1 + 1, y2, z1, z2, airId);
  }

  private void generateMineNSSupport(ByteChunk chunk, int x, int y, int z) {

    // on a bridge
    if (chunk.getBlock(x, y - 1, z) == shaftBridgeId
        && chunk.getBlock(x + 3, y - 1, z) == shaftBridgeId) {

      // place supports
      generateMineSupport(chunk, x, y - 1, z);
      generateMineSupport(chunk, x + 3, y - 1, z);

      // in a tunnel
    } else {
      chunk.setBlock(x, y, z, shaftSupportId);
      chunk.setBlock(x, y + 1, z, shaftSupportId);
      chunk.setBlock(x + 3, y, z, shaftSupportId);
      chunk.setBlock(x + 3, y + 1, z, shaftSupportId);
      chunk.setBlocks(x, x + 4, y + 2, z, z + 1, shaftBeamId);
    }
  }

  private void generateMineWESupport(ByteChunk chunk, int x, int y, int z) {
    // on a bridge
    if (chunk.getBlock(x, y - 1, z) == shaftBridgeId
        && chunk.getBlock(x, y - 1, z + 3) == shaftBridgeId) {

      // place supports
      generateMineSupport(chunk, x, y - 1, z);
      generateMineSupport(chunk, x, y - 1, z + 3);

      // in a tunnel
    } else {
      chunk.setBlock(x, y, z, shaftSupportId);
      chunk.setBlock(x, y + 1, z, shaftSupportId);
      chunk.setBlock(x, y, z + 3, shaftSupportId);
      chunk.setBlock(x, y + 1, z + 3, shaftSupportId);
      chunk.setBlocks(x, x + 1, y + 2, z, z + 4, shaftBeamId);
    }
  }

  private void generateMineSupport(ByteChunk chunk, int x, int y, int z) {
    int aboveSupport = chunk.findLastEmptyAbove(x, y, z);
    if (aboveSupport < maxHeight) chunk.setBlocks(x, y + 1, aboveSupport + 1, z, shaftSupportId);
  }

  public void generateMines(WorldGenerator generator, RealChunk chunk) {

    // get shafted!
    if (generator.settings.includeMines)
      for (int y = 0; y + 16 < minHeight; y += 16) {
        if (isShaftableLevel(generator, y)) generateVerticalMineLevel(generator, chunk, y);
      }
  }

  private void generateVerticalMineLevel(WorldGenerator generator, RealChunk chunk, int y) {
    int y1 = y + 6;
    boolean stairsFound = false;

    // going down?
    if (isShaftableLevel(generator, y - 16)) {
      if (generator.shapeProvider.isHorizontalNSShaft(chunk.chunkX, y, chunk.chunkZ)
          && generator.shapeProvider.isHorizontalNSShaft(chunk.chunkX, y - 16, chunk.chunkZ)) {

        // draw the going down bit
        placeMineStairBase(chunk, 10, y1, 15);
        placeMineStairStep(chunk, 10, y1, 14, Stair.SOUTH, Stair.NORTHFLIP);
        placeMineStairStep(chunk, 10, y1 - 1, 13, Stair.SOUTH, Stair.NORTHFLIP);
        placeMineStairStep(chunk, 10, y1 - 2, 12, Stair.SOUTH, Stair.NORTHFLIP);
        placeMineStairStep(chunk, 10, y1 - 3, 11, Stair.SOUTH, Stair.NORTHFLIP);
        placeMineStairStep(chunk, 10, y1 - 4, 10, Stair.SOUTH, Stair.NORTHFLIP);
        placeMineStairStep(chunk, 10, y1 - 5, 9, Stair.SOUTH, Stair.NORTHFLIP);
        placeMineStairStep(chunk, 10, y1 - 6, 8, Stair.SOUTH, Stair.NORTHFLIP);
        stairsFound = true;
      }

      if (!stairsFound
          && generator.shapeProvider.isHorizontalWEShaft(chunk.chunkX, y, chunk.chunkZ)
          && generator.shapeProvider.isHorizontalWEShaft(chunk.chunkX, y - 16, chunk.chunkZ)) {

        // draw the going down bit
        placeMineStairBase(chunk, 15, y1, 10);
        placeMineStairStep(chunk, 14, y1, 10, Stair.EAST, Stair.WESTFLIP);
        placeMineStairStep(chunk, 13, y1 - 1, 10, Stair.EAST, Stair.WESTFLIP);
        placeMineStairStep(chunk, 12, y1 - 2, 10, Stair.EAST, Stair.WESTFLIP);
        placeMineStairStep(chunk, 11, y1 - 3, 10, Stair.EAST, Stair.WESTFLIP);
        placeMineStairStep(chunk, 10, y1 - 4, 10, Stair.EAST, Stair.WESTFLIP);
        placeMineStairStep(chunk, 9, y1 - 5, 10, Stair.EAST, Stair.WESTFLIP);
        placeMineStairStep(chunk, 8, y1 - 6, 10, Stair.EAST, Stair.WESTFLIP);
      }
    }

    // reset the stairs flag
    stairsFound = false;

    // going up?
    if (isShaftableLevel(generator, y + 32)) {
      if (generator.shapeProvider.isHorizontalNSShaft(chunk.chunkX, y, chunk.chunkZ)
          && generator.shapeProvider.isHorizontalNSShaft(chunk.chunkX, y + 16, chunk.chunkZ)) {

        // draw the going up bit
        placeMineStairBase(chunk, 5, y1, 15);
        placeMineStairStep(chunk, 5, y1 + 1, 14, Stair.NORTH, Stair.SOUTHFLIP);
        placeMineStairStep(chunk, 5, y1 + 2, 13, Stair.NORTH, Stair.SOUTHFLIP);
        placeMineStairStep(chunk, 5, y1 + 3, 12, Stair.NORTH, Stair.SOUTHFLIP);
        placeMineStairStep(chunk, 5, y1 + 4, 11, Stair.NORTH, Stair.SOUTHFLIP);
        placeMineStairStep(chunk, 5, y1 + 5, 10, Stair.NORTH, Stair.SOUTHFLIP);
        placeMineStairStep(chunk, 5, y1 + 6, 9, Stair.NORTH, Stair.SOUTHFLIP);
        placeMineStairStep(chunk, 5, y1 + 7, 8, Stair.NORTH, Stair.SOUTHFLIP);
        placeMineStairStep(chunk, 5, y1 + 8, 7, Stair.NORTH, Stair.SOUTHFLIP);
        placeMineStairBase(chunk, 5, y1 + 8, 6);
        placeMineStairBase(chunk, 6, y1 + 8, 6);
        placeMineStairBase(chunk, 7, y1 + 8, 6);
        placeMineStairBase(chunk, 8, y1 + 8, 6);
        placeMineStairBase(chunk, 9, y1 + 8, 6);
        placeMineStairBase(chunk, 10, y1 + 8, 6);
        placeMineStairStep(chunk, 10, y1 + 9, 7, Stair.SOUTH, Stair.NORTHFLIP);

        generateMineSupport(chunk, 6, y1 + 7, 7);
        generateMineSupport(chunk, 9, y1 + 7, 7);

        stairsFound = true;
      }

      if (!stairsFound
          && generator.shapeProvider.isHorizontalWEShaft(chunk.chunkX, y, chunk.chunkZ)
          && generator.shapeProvider.isHorizontalWEShaft(chunk.chunkX, y + 16, chunk.chunkZ)) {

        // draw the going up bit
        placeMineStairBase(chunk, 15, y1, 5);
        placeMineStairStep(chunk, 14, y1 + 1, 5, Stair.WEST, Stair.EASTFLIP);
        placeMineStairStep(chunk, 13, y1 + 2, 5, Stair.WEST, Stair.EASTFLIP);
        placeMineStairStep(chunk, 12, y1 + 3, 5, Stair.WEST, Stair.EASTFLIP);
        placeMineStairStep(chunk, 11, y1 + 4, 5, Stair.WEST, Stair.EASTFLIP);
        placeMineStairStep(chunk, 10, y1 + 5, 5, Stair.WEST, Stair.EASTFLIP);
        placeMineStairStep(chunk, 9, y1 + 6, 5, Stair.WEST, Stair.EASTFLIP);
        placeMineStairStep(chunk, 8, y1 + 7, 5, Stair.WEST, Stair.EASTFLIP);
        placeMineStairStep(chunk, 7, y1 + 8, 5, Stair.WEST, Stair.EASTFLIP);
        placeMineStairBase(chunk, 6, y1 + 8, 5);
        placeMineStairBase(chunk, 6, y1 + 8, 6);
        placeMineStairBase(chunk, 6, y1 + 8, 7);
        placeMineStairBase(chunk, 6, y1 + 8, 8);
        placeMineStairBase(chunk, 6, y1 + 8, 9);
        placeMineStairBase(chunk, 6, y1 + 8, 10);
        placeMineStairStep(chunk, 7, y1 + 9, 10, Stair.EAST, Stair.WESTFLIP);

        generateMineSupport(chunk, 7, y1 + 7, 6);
        generateMineSupport(chunk, 7, y1 + 7, 9);
      }
    }

    // make the ceiling pretty
    boolean pathFound = false;
    if (generator.shapeProvider.isHorizontalNSShaft(chunk.chunkX, y, chunk.chunkZ)) {
      generateMineCeiling(chunk, 6, 10, y1 + 3, 0, 6);
      generateMineCeiling(chunk, 6, 10, y1 + 3, 10, 16);

      generateMineAlcove(generator, chunk, 4, y1, 2, 4, 2);
      generateMineAlcove(generator, chunk, 10, y1, 2, 11, 3);

      pathFound = true;
    }
    if (generator.shapeProvider.isHorizontalWEShaft(chunk.chunkX, y, chunk.chunkZ)) {
      generateMineCeiling(chunk, 0, 6, y1 + 3, 6, 10);
      generateMineCeiling(chunk, 10, 16, y1 + 3, 6, 10);

      generateMineAlcove(generator, chunk, 2, y1, 4, 2, 4);
      generateMineAlcove(generator, chunk, 2, y1, 10, 3, 11);

      pathFound = true;
    }

    // draw the center bit
    if (pathFound) generateMineCeiling(chunk, 6, 10, y1 + 3, 6, 10);
  }

  private void generateMineAlcove(
      WorldGenerator generator, RealChunk chunk, int x, int y, int z, int prizeX, int prizeZ) {
    if (chunkRandom.nextDouble() < 0.66) {
      if (!chunk.isEmpty(x, y, z)
          && !chunk.isEmpty(x + 1, y, z)
          && !chunk.isEmpty(x, y, z + 1)
          && !chunk.isEmpty(x + 1, y, z + 1)) {
        chunk.setBlocks(x, x + 2, y + 1, y + 4, z, z + 2, Material.AIR);
        generateMineCeiling(chunk, x, x + 2, y + 3, z, z + 2);
        if (chunkRandom.nextDouble() < 0.66) {
          if (chunkRandom.nextDouble() < 0.33)
            generateMineTreat(generator, chunk, prizeX, y + 1, prizeZ);
          else generateMineTrick(generator, chunk, prizeX, y + 1, prizeZ);
        }
      }
    }
  }

  private void generateMineCeiling(RealChunk chunk, int x1, int x2, int y, int z1, int z2) {
    for (int x = x1; x < x2; x++) {
      for (int z = z1; z < z2; z++) {
        if (chunkRandom.nextBoolean())
          if (!chunk.isEmpty(x, y + 1, z) && chunk.isEmpty(x, y, z))
            chunk.setStoneSlab(x, y, z, Direction.StoneSlab.COBBLESTONEFLIP);
      }
    }
  }

  private void generateMineSupport(RealChunk chunk, int x, int y, int z) {
    int aboveSupport = chunk.findLastEmptyAbove(x, y, z);
    if (aboveSupport < maxHeight) chunk.setBlocks(x, y + 1, aboveSupport + 1, z, Material.FENCE);
  }

  private void placeMineStairBase(RealChunk chunk, int x, int y, int z) {
    chunk.setBlocks(x, y + 1, y + 4, z, Material.AIR);
    chunk.setEmptyBlock(x, y, z, Material.WOOD);
  }

  private void placeMineStairStep(
      RealChunk chunk, int x, int y, int z, Stair direction, Stair flipDirection) {
    chunk.setBlocks(x, y + 1, y + 4, z, Material.AIR);
    chunk.setStair(x, y, z, Material.WOOD_STAIRS, direction);
    if (chunk.isEmpty(x, y - 1, z))
      chunk.setStair(x, y - 1, z, Material.WOOD_STAIRS, flipDirection);
  }

  private void generateMineTreat(WorldGenerator generator, RealChunk chunk, int x, int y, int z) {

    // cool stuff?
    if (generator.settings.treasuresInMines
        && chunkRandom.nextDouble() <= generator.settings.oddsOfTreasureInMines) {
      chunk.setChest(
          x,
          y,
          z,
          Direction.General.SOUTH,
          generator.lootProvider.getItems(generator, chunkRandom, LootLocation.MINE));
    }
  }

  private void generateMineTrick(WorldGenerator generator, RealChunk chunk, int x, int y, int z) {
    // not so cool stuff?
    if (generator.settings.spawnersInMines
        && chunkRandom.nextDouble() <= generator.settings.oddsOfSpawnerInMines) {
      chunk.setSpawner(
          x, y, z, generator.spawnProvider.getEntity(generator, chunkRandom, SpawnerLocation.MINE));
    }
  }

  public boolean isValidStrataY(WorldGenerator generator, int blockX, int blockY, int blockZ) {
    return true;
  }

  public void generateOres(WorldGenerator generator, RealChunk chunk) {

    // shape the world
    if (generator.settings.includeOres || generator.settings.includeUndergroundFluids)
      generator.oreProvider.sprinkleOres(
          generator, this, chunk, blockYs, chunkRandom, OreLocation.CRUST);
  }

  // TODO move this logic to SurroundingLots, add to it the ability to produce SurroundingHeights
  // and SurroundingDepths
  public PlatLot[][] getNeighborPlatLots(
      PlatMap platmap, int platX, int platZ, boolean onlyConnectedNeighbors) {
    PlatLot[][] miniPlatMap = new PlatLot[3][3];

    // populate the results
    for (int x = 0; x < 3; x++) {
      for (int z = 0; z < 3; z++) {

        // which platchunk are we looking at?
        int atX = platX + x - 1;
        int atZ = platZ + z - 1;

        // is it in bounds?
        if (!(atX < 0 || atX > PlatMap.Width - 1 || atZ < 0 || atZ > PlatMap.Width - 1)) {
          PlatLot relative = platmap.getLot(atX, atZ);

          if (!onlyConnectedNeighbors || isConnected(relative)) {
            miniPlatMap[x][z] = relative;
          }
        }
      }
    }

    return miniPlatMap;
  }

  public void generateSurface(WorldGenerator generator, RealChunk chunk, boolean includeTrees) {

    // plant grass or snow
    generator.surfaceProvider.generateSurface(generator, this, chunk, blockYs, includeTrees);
  }
}
public abstract class BananaWellArchetype extends WellArchetype {

  // normal materials
  protected byte byteLiquid = (byte) Material.STATIONARY_WATER.getId();
  protected byte byteStone = (byte) Material.STONE.getId();
  protected byte byteDirt = (byte) Material.DIRT.getId();
  protected byte byteGrass = (byte) Material.GRASS.getId();
  protected byte byteLog = (byte) Material.LOG.getId();
  protected byte byteLeaves = (byte) Material.LEAVES.getId();
  protected byte byteAir = (byte) Material.AIR.getId();
  protected int intOre = byteStone;
  protected int intTreeBase = byteGrass;
  protected int intTreeTrunk = byteLog;
  protected int intTreeLeaves = byteLeaves;

  protected byte byteTreeData = 0;
  protected int intAir = Material.AIR.getId();
  protected int minTreeHeight = 5;
  protected int maxTreeHeight = 5;

  public BananaWellArchetype(World world, long seed, int wellX, int wellZ) {
    super(world, seed, wellX, wellZ);

    // figure out materials
    calculateMaterials();
  }

  // override this if you want something special
  protected void calculateMaterials() {
    randomizeMaterials();

    //  copy over the "seed" materials used by populateOres and populateFoliage
    intOre = byteStone;
    intTreeBase = byteGrass;
  }

  protected void randomizeMaterials() {
    switch (random.nextInt(5)) {
      case 0:
        // lava, sand/sandstone/stone, mushroom stalks
        byteLiquid = (byte) Material.STATIONARY_LAVA.getId();
        byteStone = (byte) Material.STONE.getId();
        byteDirt = (byte) Material.SANDSTONE.getId();
        byteGrass = (byte) Material.SAND.getId();
        intTreeTrunk = Material.HUGE_MUSHROOM_1.getId();
        intTreeLeaves = intAir;
        byteTreeData = (byte) (random.nextInt(2) == 0 ? 10 : 1);
        break;
      case 1:
        // air, sand/sandstone/stone, cactus
        byteLiquid = (byte) Material.AIR.getId();
        byteStone = (byte) Material.STONE.getId();
        byteDirt = (byte) Material.SANDSTONE.getId();
        byteGrass = (byte) Material.SAND.getId();
        intTreeTrunk = Material.CACTUS.getId();
        intTreeLeaves = intAir;
        minTreeHeight = 2;
        maxTreeHeight = 4;
        break;
      case 3:
        // water, glass/glowstone/endstone, crystals
        byteLiquid = (byte) Material.STATIONARY_WATER.getId();
        byteStone = (byte) Material.ENDER_STONE.getId();
        byteDirt = (byte) Material.SOUL_SAND.getId();
        byteGrass = (byte) Material.MYCEL.getId();
        intTreeTrunk = Material.GLOWSTONE.getId();
        intTreeLeaves = Material.THIN_GLASS.getId();
        break;
      default:
        // water, grass/dirt/stone, normal trees are the default
        byteTreeData = (byte) random.nextInt(3);
        break;
    }
  }

  @Override
  public void populateBlocks(Chunk chunk, int chunkX, int chunkZ) {
    // ores
    populateOres(chunk);

    // foliage
    populateFoliage(chunk);
  }

  /**
   * Populates the world with ores.
   *
   * @author Nightgunner5
   * @author Markus Persson
   */
  private static final int[] iterations = new int[] {10, 20, 20, 2, 8, 1, 1, 1};

  private static final int[] amount = new int[] {32, 16, 8, 8, 7, 7, 6};
  private static final int[] type =
      new int[] {
        Material.GRAVEL.getId(),
        Material.COAL_ORE.getId(),
        Material.IRON_ORE.getId(),
        Material.GOLD_ORE.getId(),
        Material.REDSTONE_ORE.getId(),
        Material.DIAMOND_ORE.getId(),
        Material.LAPIS_ORE.getId()
      };
  private static final int[] maxHeight = new int[] {128, 128, 128, 128, 128, 64, 32, 16, 16, 32};

  protected void populateOres(Chunk chunk) {
    // ores
    for (int i = 0; i < type.length; i++) {
      for (int j = 0; j < iterations[i]; j++) {
        placeOre(
            chunk,
            random.nextInt(16),
            random.nextInt(maxHeight[i]),
            random.nextInt(16),
            amount[i],
            type[i]);
      }
    }
  }

  private void placeOre(Chunk source, int originX, int originY, int originZ, int amount, int type) {
    for (int i = 0; i < amount; i++) {
      int x = originX + random.nextInt(amount / 2) - amount / 4;
      int y = originY + random.nextInt(amount / 4) - amount / 8;
      int z = originZ + random.nextInt(amount / 2) - amount / 4;
      x &= 0xf;
      z &= 0xf;
      if (y > 127 || y < 0) {
        continue;
      }
      Block block = source.getBlock(x, y, z);
      if (block.getTypeId() == intOre) {
        block.setTypeId(type, false);
      }
    }
  }

  private static final int chanceRange = 150;

  protected void populateFoliage(Chunk chunk) {

    //		int centerX = (chunk.getX() << 4) + random.nextInt(16);
    //		int centerZ = (chunk.getZ() << 4) + random.nextInt(16);
    //		if (random.nextBoolean()) {
    //			data = 2;
    //			height = 5 + random.nextInt(3);
    //		}
    //
    //		//EC: tweaked the biome logic a bit
    //		switch (getBiome()) {
    //		case FOREST:
    //			chance = 160;
    //			multiplier = 10;
    //			break;
    //		case PLAINS:
    //			chance = 40;
    //			break;
    //		case RAINFOREST:
    //			chance = 160;
    //			multiplier = 10;
    //			break;
    //		case SAVANNA:
    //			chance = 20;
    //			break;
    //		case SEASONAL_FOREST:
    //			chance = 140;
    //			multiplier = 8;
    //			break;
    //		case SHRUBLAND:
    //			chance = 60;
    //			break;
    //		case SWAMPLAND:
    //			chance = 120;
    //			break;
    //		case TAIGA:
    //			chance = 120;
    //			data = 1;
    //			height = 8 + random.nextInt(3);
    //			multiplier = 3;
    //			break;
    //		case TUNDRA:
    //			chance = 10;
    //			data = 1;
    //			height = 7 + random.nextInt(3);
    //			break;
    //		case SKY:
    //		case DESERT:
    //		case HELL:
    //		case ICE_DESERT:
    //			chance = 5;
    //			return;
    //		}

    byte data = (byte) random.nextInt(3);
    int chance = chanceRange / 2;
    int height = minTreeHeight + random.nextInt(maxTreeHeight);
    int multiplier = 3 + random.nextInt(7);

    for (int i = 0; i < multiplier; i++) {
      int centerX = (chunk.getX() << 4) + random.nextInt(16);
      int centerZ = (chunk.getZ() << 4) + random.nextInt(16);
      if (random.nextInt(chanceRange) < chance) {
        int centerY = world.getHighestBlockYAt(centerX, centerZ) - 1;
        Block sourceBlock = world.getBlockAt(centerX, centerY, centerZ);

        // found a place to put it?
        if (sourceBlock.getTypeId() == intTreeBase) {

          // leaves or leave?
          if (intTreeLeaves != intAir) {
            setBlock(centerX, centerY + height + 1, centerZ, intTreeLeaves, data);
            // world.getBlockAt(centerX, centerY + height + 1, centerZ).setTypeIdAndData(intLeaves,
            // data, true);
            for (int j = 0; j < 4; j++) {
              setBlock(centerX, centerY + height + 1 - j, centerZ - 1, intTreeLeaves, data);
              setBlock(centerX, centerY + height + 1 - j, centerZ + 1, intTreeLeaves, data);
              setBlock(centerX - 1, centerY + height + 1 - j, centerZ, intTreeLeaves, data);
              setBlock(centerX + 1, centerY + height + 1 - j, centerZ, intTreeLeaves, data);
              // world.getBlockAt(centerX, centerY + height + 1 - j, centerZ -
              // 1).setTypeIdAndData(intLeaves, data, true);
              // world.getBlockAt(centerX, centerY + height + 1 - j, centerZ +
              // 1).setTypeIdAndData(intLeaves, data, true);
              // world.getBlockAt(centerX - 1, centerY + height + 1 - j,
              // centerZ).setTypeIdAndData(intLeaves, data, true);
              // world.getBlockAt(centerX + 1, centerY + height + 1 - j,
              // centerZ).setTypeIdAndData(intLeaves, data, true);
            }

            if (random.nextBoolean()) {
              setBlock(centerX + 1, centerY + height, centerZ + 1, intTreeLeaves, data);
              // world.getBlockAt(centerX + 1, centerY + height, centerZ +
              // 1).setTypeIdAndData(intLeaves, data, true);
            }
            if (random.nextBoolean()) {
              setBlock(centerX + 1, centerY + height, centerZ - 1, intTreeLeaves, data);
              // world.getBlockAt(centerX + 1, centerY + height, centerZ -
              // 1).setTypeIdAndData(intLeaves, data, true);
            }
            if (random.nextBoolean()) {
              setBlock(centerX - 1, centerY + height, centerZ + 1, intTreeLeaves, data);
              // world.getBlockAt(centerX - 1, centerY + height, centerZ +
              // 1).setTypeIdAndData(intLeaves, data, true);
            }
            if (random.nextBoolean()) {
              setBlock(centerX - 1, centerY + height, centerZ - 1, intTreeLeaves, data);
              // world.getBlockAt(centerX - 1, centerY + height, centerZ -
              // 1).setTypeIdAndData(intLeaves, data, true);
            }

            setBlock(centerX + 1, centerY + height - 1, centerZ + 1, intTreeLeaves, data);
            setBlock(centerX + 1, centerY + height - 1, centerZ - 1, intTreeLeaves, data);
            setBlock(centerX - 1, centerY + height - 1, centerZ + 1, intTreeLeaves, data);
            setBlock(centerX - 1, centerY + height - 1, centerZ - 1, intTreeLeaves, data);
            setBlock(centerX + 1, centerY + height - 2, centerZ + 1, intTreeLeaves, data);
            setBlock(centerX + 1, centerY + height - 2, centerZ - 1, intTreeLeaves, data);
            setBlock(centerX - 1, centerY + height - 2, centerZ + 1, intTreeLeaves, data);
            setBlock(centerX - 1, centerY + height - 2, centerZ - 1, intTreeLeaves, data);
            // world.getBlockAt(centerX + 1, centerY + height - 1, centerZ +
            // 1).setTypeIdAndData(intLeaves, data, true);
            // world.getBlockAt(centerX + 1, centerY + height - 1, centerZ -
            // 1).setTypeIdAndData(intLeaves, data, true);
            // world.getBlockAt(centerX - 1, centerY + height - 1, centerZ +
            // 1).setTypeIdAndData(intLeaves, data, true);
            // world.getBlockAt(centerX - 1, centerY + height - 1, centerZ -
            // 1).setTypeIdAndData(intLeaves, data, true);
            // world.getBlockAt(centerX + 1, centerY + height - 2, centerZ +
            // 1).setTypeIdAndData(intLeaves, data, true);
            // world.getBlockAt(centerX + 1, centerY + height - 2, centerZ -
            // 1).setTypeIdAndData(intLeaves, data, true);
            // world.getBlockAt(centerX - 1, centerY + height - 2, centerZ +
            // 1).setTypeIdAndData(intLeaves, data, true);
            // world.getBlockAt(centerX - 1, centerY + height - 2, centerZ -
            // 1).setTypeIdAndData(intLeaves, data, true);

            for (int j = 0; j < 2; j++) {
              for (int k = -2; k <= 2; k++) {
                for (int l = -2; l <= 2; l++) {
                  setBlock(centerX + k, centerY + height - 1 - j, centerZ + l, intTreeLeaves, data);
                  // world.getBlockAt(centerX + k, centerY + height
                  //	- 1 - j, centerZ + l).setTypeIdAndData(intLeaves, data, true);
                }
              }
            }

            for (int j = 0; j < 2; j++) {
              if (random.nextBoolean())
                clearBlock(centerX + 2, centerY + height - 1 - j, centerZ + 2);
              if (random.nextBoolean())
                clearBlock(centerX + 2, centerY + height - 1 - j, centerZ - 2);
              if (random.nextBoolean())
                clearBlock(centerX - 2, centerY + height - 1 - j, centerZ + 2);
              if (random.nextBoolean())
                clearBlock(centerX - 2, centerY + height - 1 - j, centerZ - 2);
              // if (random.nextBoolean()) {
              //	world.getBlockAt(centerX + 2, centerY + height - 1
              //		- j, centerZ + 2).setTypeIdAndData(intAir, (byte) 0, true);
              // }
              // if (random.nextBoolean()) {
              //	world.getBlockAt(centerX + 2, centerY + height - 1
              //		- j, centerZ - 2).setTypeIdAndData(intAir, (byte) 0, true);
              // }
              // if (random.nextBoolean()) {
              //	world.getBlockAt(centerX - 2, centerY + height - 1
              //		- j, centerZ + 2).setTypeIdAndData(intAir, (byte) 0, true);
              // }
              // if (random.nextBoolean()) {
              //	world.getBlockAt(centerX - 2, centerY + height - 1
              //		- j, centerZ - 2).setTypeIdAndData(intAir, (byte) 0, true);
              // }
            }
          }

          // Trunk
          for (int y = 1; y <= height; y++) {
            world
                .getBlockAt(centerX, centerY + y, centerZ)
                .setTypeIdAndData(intTreeTrunk, data, false);
          }
        }
      }
    }
  }
}