/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.pepsoft.worldpainter.layers.exporters; import org.pepsoft.minecraft.Chunk; import org.pepsoft.util.PerlinNoise; import org.pepsoft.worldpainter.Dimension; import org.pepsoft.worldpainter.Tile; import org.pepsoft.worldpainter.exporting.AbstractLayerExporter; import org.pepsoft.worldpainter.exporting.FirstPassLayerExporter; import org.pepsoft.worldpainter.layers.Chasms; import java.io.IOException; import java.io.ObjectInputStream; import java.util.Random; import static org.pepsoft.minecraft.Block.BLOCKS; import static org.pepsoft.minecraft.Constants.*; import static org.pepsoft.worldpainter.Constants.MEDIUM_BLOBS; import static org.pepsoft.worldpainter.Constants.TILE_SIZE; /** * * @author pepijn */ public class ChasmsExporter extends AbstractLayerExporter<Chasms> implements FirstPassLayerExporter { public ChasmsExporter() { super(Chasms.INSTANCE, new ChasmsSettings()); } @Override public void render(Dimension dimension, Tile tile, Chunk chunk) { final ChasmsSettings settings = (ChasmsSettings) getSettings(); final boolean surfaceBreaking = settings.isSurfaceBreaking(); final boolean floodWithLava = settings.isFloodWithLava(); final boolean glassCeiling = settings.isGlassCeiling(); final boolean leaveWater = settings.isLeaveWater(); final int waterLevel = (settings.getWaterLevel() > 0) ? settings.getWaterLevel() : -1; // To avoid rendering a layer of water when fallThrough is true final int minimumLevel = settings.getChasmsEverywhereLevel(); final long seed = dimension.getSeed(); if ((seed + SEED_OFFSET) != perlinNoise.getSeed()) { perlinNoise.setSeed(seed + SEED_OFFSET); perlinNoise2.setSeed(seed + SEED_OFFSET2); perlinNoise3.setSeed(seed + SEED_OFFSET3); perlinNoise4.setSeed(seed + SEED_OFFSET4); perlinNoise5.setSeed(seed + SEED_OFFSET5); perlinNoise6.setSeed(seed + SEED_OFFSET6); } final int xOffset = (chunk.getxPos() & 7) << 4; final int zOffset = (chunk.getzPos() & 7) << 4; final int minY = settings.getMinimumLevel(); final boolean fallThrough = (minY == 0) && dimension.isBottomless(); final int minYAdjusted = Math.max(minY, 1); final int maxY = Math.min(settings.getMaximumLevel(), dimension.getMaxHeight() - 1); for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { final int localX = xOffset + x, localY = zOffset + z; if (tile.getBitLayerValue(org.pepsoft.worldpainter.layers.Void.INSTANCE, localX, localY)) { continue; } final int chasmsValue = Math.max(minimumLevel, tile.getLayerValue(Chasms.INSTANCE, localX, localY)); if (chasmsValue > 0) { final int worldX = tile.getX() * TILE_SIZE + localX, worldY = tile.getY() * TILE_SIZE + localY; // final float px = worldX / MEDIUM_BLOBS, py = worldY / MEDIUM_BLOBS; // final float px = worldX / SMALL_BLOBS, py = worldY / SMALL_BLOBS; // [2] ravine? final float px = worldX / MEDIUM_BLOBS, py = worldY / MEDIUM_BLOBS; // [3] tunnellike // Maximising it shouldn't be necessary, but there are // worlds in the wild with heights above the maximum: final int terrainheight = Math.min(tile.getIntHeight(localX, localY), maxY); // if ((x == 0) && (z == 0)) { // System.out.println("terrainHeight: " + terrainheight); // } boolean breachedCeiling = false, previousBlockInCavern = false; for (int y = terrainheight; fallThrough ? (y >= 0) : (y >= minYAdjusted); y--) { int existingBlockType = chunk.getBlockType(x, y, z); if (existingBlockType == BLK_AIR) { // There is already a void here; assume that things // like removing water, etc. have already been done // by whatever made the void breachedCeiling = true; previousBlockInCavern = true; continue; } float bias = CHASM_CHANCE * Math.max( 0.1f * (10 - Math.min( Math.min( surfaceBreaking ? Integer.MAX_VALUE : (terrainheight - Math.max(dimension.getTopLayerDepth(worldX, worldY, terrainheight), 3) - y), (fallThrough ? Integer.MAX_VALUE : (y - 1)) - minY), 10)), 1.0f - chasmsValue / 15.0f); // 0.5f - chasmsValue / 15.0f); // TODO: higher than 50% has no effect if (fallThrough && (y < 5)) { // Widen the caverns towards the bottom bias -= (5 - y) * 0.05f; } // final float pz = y / SMALL_BLOBS; // final float pz = y / MEDIUM_BLOBS; // [2] ravine? final float pz = (y + 15) / MEDIUM_BLOBS; // [3] tunnellike // float cavernLikelyhood = Math.min(perlinNoise.getPerlinNoise(px, py, pz), perlinNoise2.getPerlinNoise(px, py, pz)) + 0.5f - bias; // double cavernLikelyhood = Math.min(Math.sin(perlinNoise.getPerlinNoise(px, py, pz) * 10), Math.cos(perlinNoise2.getPerlinNoise(px, py, pz) * 10)) / 2 + 0.5f - bias; // [2] ravine? final double cavernLikelyhood = Math.min(perlinNoise3.getPerlinNoise(px, py, pz) - bias, Math.min(Math.sin(perlinNoise.getPerlinNoise(px, py, pz) * 25), Math.cos(perlinNoise2.getPerlinNoise(px, py, pz) * 25)) / 2) + 0.5f; // [3] tunnellike final double cavernLikelyhood2 = Math.min(perlinNoise6.getPerlinNoise(px, py, pz) - bias, Math.min(Math.sin(perlinNoise4.getPerlinNoise(px, py, pz) * 25), Math.cos(perlinNoise5.getPerlinNoise(px, py, pz) * 25)) / 2) + 0.5f; // [3] tunnellike // double cavernLikelyhood = Math.sin(perlinNoise.getPerlinNoise(px, py, pz) * 5); // double cavernLikelyhood2 = Math.sin(perlinNoise2.getPerlinNoise(px, py) * 5); // if ((x == 0) && (z == 0)) { // System.out.println(y + ": bias: " + bias + ", cavernLikelyHood: " + cavernLikelyhood); // } // if (cavernLikelyhood > CHASM_CHANCE) { if ((cavernLikelyhood > CHASM_CHANCE) || (cavernLikelyhood2 > CHASM_CHANCE)) { // if ((cavernLikelyhood > CHASM_CHANCE) && (cavernLikelyhood2 > CHASM_CHANCE)) { // In a cavern if ((! breachedCeiling) && (y < maxY)) { // System.out.println("Cavern for value: " + chasmsValue); // if (chasmsValue < lowestValueCavern) { // lowestValueCavern = chasmsValue; // System.out.println("Lowest cavern value with caverns: " + lowestValueCavern); // } if (glassCeiling) { for (int yy = y + 1; yy <= terrainheight; yy++) { chunk.setBlockType(x, yy, z, BLK_GLASS); } } if (surfaceBreaking) { final int blockAbove = chunk.getBlockType(x, y + 1, z); if (leaveWater) { if (blockAbove == BLK_STATIONARY_WATER) { chunk.setBlockType(x, y + 1, z, BLK_WATER); } else if (blockAbove == BLK_STATIONARY_LAVA) { chunk.setBlockType(x, y + 1, z, BLK_LAVA); } } else { if ((blockAbove == BLK_WATER) || (blockAbove == BLK_STATIONARY_WATER)) { for (int yy = y + 1; yy <= maxY; yy++) { final int blockType = chunk.getBlockType(x, yy, z); if ((blockType == BLK_WATER) || (blockType == BLK_STATIONARY_WATER)) { chunk.setBlockType(x, yy, z, BLK_AIR); chunk.setDataValue(x, yy, z, 0); // Set the surrounding water, if // any, to non-stationary, so that // it will flow into the cavern if ((x > 0) && (chunk.getBlockType(x - 1, yy, z) == BLK_STATIONARY_WATER)) { chunk.setBlockType(x - 1, yy, z, BLK_WATER); } if ((x < 15) && (chunk.getBlockType(x + 1, yy, z) == BLK_STATIONARY_WATER)) { chunk.setBlockType(x + 1, yy, z, BLK_WATER); } if ((z > 0) && (chunk.getBlockType(x, yy, z - 1) == BLK_STATIONARY_WATER)) { chunk.setBlockType(x, yy, z - 1, BLK_WATER); } if ((z < 15) && (chunk.getBlockType(x, yy, z + 1) == BLK_STATIONARY_WATER)) { chunk.setBlockType(x, yy, z + 1, BLK_WATER); } } else { break; } } } else if ((blockAbove == BLK_LAVA) || (blockAbove == BLK_STATIONARY_LAVA)) { for (int yy = y + 1; yy <= maxY; yy++) { final int blockType = chunk.getBlockType(x, yy, z); if ((blockType == BLK_LAVA) || (blockType == BLK_STATIONARY_LAVA)) { chunk.setBlockType(x, yy, z, BLK_AIR); chunk.setDataValue(x, yy, z, 0); // Set the surrounding water, if // any, to non-stationary, so that // it will flow into the cavern if ((x > 0) && (chunk.getBlockType(x - 1, yy, z) == BLK_STATIONARY_LAVA)) { chunk.setBlockType(x - 1, yy, z, BLK_LAVA); } if ((x < 15) && (chunk.getBlockType(x + 1, yy, z) == BLK_STATIONARY_LAVA)) { chunk.setBlockType(x + 1, yy, z, BLK_LAVA); } if ((z > 0) && (chunk.getBlockType(x, yy, z - 1) == BLK_STATIONARY_LAVA)) { chunk.setBlockType(x, yy, z - 1, BLK_LAVA); } if ((z < 15) && (chunk.getBlockType(x, yy, z + 1) == BLK_STATIONARY_LAVA)) { chunk.setBlockType(x, yy, z + 1, BLK_LAVA); } } else { break; } } } } } } breachedCeiling = true; if ((! previousBlockInCavern) && (y < maxY)) { final int blockAbove = chunk.getBlockType(x, y + 1, z); // Note that the post processor will take care // of supporting sand with sandstone, if that is // not disabled if (blockAbove == BLK_GRAVEL) { // Support gravel with stone chunk.setBlockType(x, y + 1, z, BLK_STONE); } } // System.out.println("Cavern at " + x + ", " + y + ", " + z); if (y > waterLevel) { chunk.setBlockType(x, y, z, BLK_AIR); previousBlockInCavern = true; } else { if (floodWithLava) { chunk.setBlockType(x, y, z, BLK_STATIONARY_LAVA); } else { chunk.setBlockType(x, y, z, BLK_STATIONARY_WATER); } previousBlockInCavern = false; } } else if (previousBlockInCavern && (y >= waterLevel) && (! BLOCKS[existingBlockType].veryInsubstantial)) { // Material material = subsurfaceMaterial.getMaterial(seed, worldX, worldY, 1); // if (material == Material.AIR) { int rnd = new Random(seed + (worldX * 65537) + (worldY * 4099)).nextInt(MUSHROOM_CHANCE); if (rnd == 0) { chunk.setBlockType(x, y + 1, z, BLK_BROWN_MUSHROOM); // System.out.println("Cave shroom @ " + worldX + ", " + worldY + "!"); } // } else { // chunk.setMaterial(x, y + 1, z, material); // } // chunk.setMaterial(x, y, z, subsurfaceMaterial.getMaterial(seed, worldX, worldY, 0)); previousBlockInCavern = false; } } } if (glassCeiling) { chunk.setHeight(x, z, 1); } } } } private final PerlinNoise perlinNoise = new PerlinNoise(0); private final PerlinNoise perlinNoise2 = new PerlinNoise(0); private final PerlinNoise perlinNoise3 = new PerlinNoise(0); private final PerlinNoise perlinNoise4 = new PerlinNoise(0); private final PerlinNoise perlinNoise5 = new PerlinNoise(0); private final PerlinNoise perlinNoise6 = new PerlinNoise(0); // private int lowestValueCavern = Integer.MAX_VALUE; private static final float CHASM_CHANCE = 0.5f; // private static final float CHASM_CHANCE = 0.95f; private static final long SEED_OFFSET = 37; private static final long SEED_OFFSET2 = 41; private static final long SEED_OFFSET3 = 43; private static final long SEED_OFFSET4 = 47; private static final long SEED_OFFSET5 = 49; private static final long SEED_OFFSET6 = 51; private static final int MUSHROOM_CHANCE = 250; public static class ChasmsSettings implements ExporterSettings { @Override public boolean isApplyEverywhere() { return chasmsEverywhereLevel > 0; } @Override public Chasms getLayer() { return Chasms.INSTANCE; } public boolean isFloodWithLava() { return floodWithLava; } public void setFloodWithLava(boolean floodWithLava) { this.floodWithLava = floodWithLava; } public boolean isGlassCeiling() { return glassCeiling; } public void setGlassCeiling(boolean glassCeiling) { this.glassCeiling = glassCeiling; } public boolean isSurfaceBreaking() { return surfaceBreaking; } public void setSurfaceBreaking(boolean surfaceBreaking) { this.surfaceBreaking = surfaceBreaking; } public int getWaterLevel() { return waterLevel; } public void setWaterLevel(int waterLevel) { this.waterLevel = waterLevel; } public int getChasmsEverywhereLevel() { return chasmsEverywhereLevel; } public void setChasmsEverywhereLevel(int chasmsEverywhereLevel) { this.chasmsEverywhereLevel = chasmsEverywhereLevel; } public boolean isLeaveWater() { return leaveWater; } public void setLeaveWater(boolean leaveWater) { this.leaveWater = leaveWater; } public int getMinimumLevel() { return minimumLevel; } public void setMinimumLevel(int minimumLevel) { this.minimumLevel = minimumLevel; } public int getMaximumLevel() { return maximumLevel; } public void setMaximumLevel(int maximumLevel) { this.maximumLevel = maximumLevel; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final ChasmsSettings other = (ChasmsSettings) obj; if (this.waterLevel != other.waterLevel) { return false; } if (this.chasmsEverywhereLevel != other.chasmsEverywhereLevel) { return false; } if (this.floodWithLava != other.floodWithLava) { return false; } if (this.glassCeiling != other.glassCeiling) { return false; } if (this.surfaceBreaking != other.surfaceBreaking) { return false; } if (this.leaveWater != other.leaveWater) { return false; } if (this.minimumLevel != other.minimumLevel) { return false; } if (this.maximumLevel != other.maximumLevel) { return false; } return true; } @Override public int hashCode() { int hash = 7; hash = 29 * hash + this.waterLevel; hash = 29 * hash + this.chasmsEverywhereLevel; hash = 29 * hash + (this.floodWithLava ? 1 : 0); hash = 29 * hash + (this.glassCeiling ? 1 : 0); hash = 29 * hash + (this.surfaceBreaking ? 1 : 0); hash = 29 * hash + (this.leaveWater ? 1 : 0); hash = 29 * hash + this.minimumLevel; hash = 29 * hash + this.maximumLevel; return hash; } @Override public ChasmsSettings clone() { try { return (ChasmsSettings) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // Legacy settings if (maximumLevel == 0) { maximumLevel = Integer.MAX_VALUE; } } private int waterLevel, chasmsEverywhereLevel; private boolean floodWithLava, glassCeiling, surfaceBreaking, leaveWater = true; private int minimumLevel = 0, maximumLevel = Integer.MAX_VALUE; private static final long serialVersionUID = 1L; } }