diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/BiomeSelector.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/BiomeSelector.java index 900ef94..c6c90a1 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/BiomeSelector.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/BiomeSelector.java @@ -11,7 +11,6 @@ import ca.recrown.islandsurvivalcraft.Utilities; public class BiomeSelector { private boolean initialized = false; - private volatile Random random; private final HashMap lands = new HashMap<>(); private HashMap> temperaturesForLand = new HashMap<>(); private final HashMap> temperaturePartitionedLandBiomes = new HashMap<>(); @@ -20,9 +19,7 @@ public class BiomeSelector { private final HashMap> temperaturePartitionedOceanBiomes = new HashMap<>(); - public BiomeSelector(Random random) { - this.random = random; - + public BiomeSelector() { temperaturePartitionedLandBiomes.put(0.05f, new ArrayList<>()); temperaturePartitionedLandBiomes.put(0.30f, new ArrayList<>()); temperaturePartitionedLandBiomes.put(0.95f, new ArrayList<>()); @@ -32,10 +29,6 @@ public class BiomeSelector { temperaturePartitionedOceanBiomes.put(0.5f, new ArrayList<>()); } - public BiomeSelector() { - this(new Random()); - } - public void initialize() { if (initialized) throw new IllegalStateException("Biome selector is already initialized."); @@ -197,7 +190,7 @@ public class BiomeSelector { * @param temperature Minecraft temperature to select biome from. * @return The randomly selected biome. */ - public Biome getLandBiome(float temperature) { + public Biome getLandBiome(float temperature, Random random) { if (!initialized) throw new IllegalStateException("Biome selector is not initialized."); ArrayList biomes = null; if (temperature <= 0.05f) { @@ -218,7 +211,7 @@ public class BiomeSelector { * @param temperature Minecraft temperature to select biome from. * @return The randomly selected biome. */ - public Biome getOceanBiome(float temperature) { + public Biome getOceanBiome(float temperature, Random random) { if (!initialized) throw new IllegalStateException("Biome selector is not initialized."); ArrayList biomes = null; if (temperature <= 0.00f) { @@ -230,7 +223,4 @@ public class BiomeSelector { return biomes.get((int) random.nextFloat() * biomes.size()); } - public void setBiome(Random random) { - this.random = random; - } } \ No newline at end of file diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapper.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapper.java index fdb5693..bb8b736 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapper.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapper.java @@ -13,14 +13,12 @@ public class IslandWorldMapper implements CoordinateValidatable { private final Cache blockValueCache; private final SimplexOctaveGenerator noiseGenerator; - private final int noiseOctaves = 8; + private final int noiseOctaves = 4; private final float islandBlockGenerationPercent = 15f; - private final float exaggerationFactor = 1f; - private final float islandExaggeration = 1f; private final double noiseFrequency = 1.7D; private final double noiseAmplitude = 0.5D; private final double scale = 0.004D; - private final float shoreFactor = 0.05f; + private final float shoreFactor = 0.065f; private final float shallowPortion = 0.01f; private final DepthFirstSearch dfs; @@ -117,20 +115,14 @@ public class IslandWorldMapper implements CoordinateValidatable { Double res = blockValueCache.get(p); if (res == null) { double shift = 2 * (islandBlockGenerationPercent / 100f); - double rawNoise = noiseGenerator.noise(worldX, worldZ, noiseFrequency, noiseAmplitude, true); - double noise = 0; - if (rawNoise < 0) { - noise = ( - Math.pow(- rawNoise, exaggerationFactor) - shift); - } else { - noise = Math.pow(rawNoise, islandExaggeration) - shift; - } + double rawNoise = noiseGenerator.noise(worldX, worldZ, noiseFrequency, noiseAmplitude, true) - shift; double maxNeg = -1 - shift; double maxPos = 1 - shift; - if (noise < 0) { - res = - noise / maxNeg; + if (rawNoise < 0) { + res = - rawNoise / maxNeg; } else { - res = noise / maxPos; + res = rawNoise / maxPos; } blockValueCache.set(p, res); return res; diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/BiomeGenerator.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/BiomeGenerator.java index d877683..0ad4c00 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/BiomeGenerator.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/BiomeGenerator.java @@ -1,5 +1,7 @@ package ca.recrown.islandsurvivalcraft.world.generation; +import java.util.Random; + import org.bukkit.World; import org.bukkit.block.Biome; @@ -26,6 +28,7 @@ public interface BiomeGenerator { * @param tempGenerator The temperature generator to be used. * @param biomeCache Cache for biomes. * @param chunkGenCache Cache for whether or not the chunk is generated. + * @param random A random that bukkit passes. */ - public void generateBiomeColumn(Biome[][] biomesArray, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGenerator, Cache biomeCache, Cache chunkGenCache); + public void generateBiomeColumn(Biome[][] biomesArray, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGenerator, Cache biomeCache, Cache chunkGenCache, Random random); } \ No newline at end of file diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/IslandWorldChunkGenerator.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/IslandWorldChunkGenerator.java index bce5eef..4eb27e3 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/IslandWorldChunkGenerator.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/IslandWorldChunkGenerator.java @@ -46,10 +46,9 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene int seaLevel = world.getSeaLevel(); IslandWorldMapper mapper = new IslandWorldMapper(seed, blockValueCache); TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(seed); - WorldHeightShader heightShader = new WorldHeightShader(mapper, world.getSeaLevel(), world.getMaxHeight(), 3); + WorldHeightShader heightShader = new WorldHeightShader(seed, mapper, seaLevel, world.getMaxHeight(), 3); WorldLayerShader layerShader = new WorldLayerShader(seed, seaLevel, maxHeight, mapper); BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator(); - biomeSelector.setBiome(random); LinkedList> tasks = new LinkedList<>(); ChunkData chunkData = createChunkData(world); @@ -70,7 +69,7 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene final int localZ = z; if (biomes[localX][localZ] == null) { biomeGenerator.generateBiomeColumn(biomes, world, chunkX, chunkZ, localX, localZ, mapper, biomeSelector, - temperatureMapGenerator, biomeCache, chunkExistenceCache); + temperatureMapGenerator, biomeCache, chunkExistenceCache, random); } if (biomes[localX][localZ] == null) throw new IllegalStateException("Biome was null."); tasks.add(exBeta.submit(() -> { @@ -84,7 +83,7 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene final int worldZ = Utilities.CHUNK_SIZE * chunkZ + localZ; Biome currentBiome = biomes[localX][localZ]; int terrainHeight = heightShader.getTerrainHeight(worldX, worldZ, currentBiome); - int currentTerrainHeight = terrainHeight - 1; + int currentTerrainHeight = terrainHeight; int bedrockHeight = random.nextInt(5) + 1; if (layerShader.hasSpecialLayers(currentBiome)) { Material currentMaterial = layerShader.getMaterialForHeight(worldX, worldZ, currentTerrainHeight, currentBiome); @@ -93,14 +92,17 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene currentTerrainHeight --; } } else { - chunkData.setBlock(localX, currentTerrainHeight, localZ, layerShader.getSurfaceMaterial(currentBiome)); - int transitionHeight = layerShader.getTransitionMaterialThickness(worldX, worldZ, currentBiome); - currentTerrainHeight -= transitionHeight; - chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, currentTerrainHeight + transitionHeight, localZ + 1, layerShader.getTransitionMaterial(currentBiome)); + int surfaceThickness = layerShader.getSurfaceThickness(worldX, worldZ, currentBiome); + currentTerrainHeight -= surfaceThickness; + chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, currentTerrainHeight + surfaceThickness, localZ + 1, layerShader.getSurfaceMaterial(currentBiome)); + int transitionThickness = layerShader.getTransitionMaterialThickness(worldX, worldZ, currentBiome); + currentTerrainHeight -= transitionThickness; + chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, currentTerrainHeight + transitionThickness, localZ + 1, layerShader.getTransitionMaterial(currentBiome)); } chunkData.setRegion(localX, bedrockHeight, localZ, localX + 1, currentTerrainHeight + 1, localZ + 1, Material.STONE); + if (terrainHeight < seaLevel) { - chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, seaLevel, localZ + 1, Material.WATER); + chunkData.setRegion(localX, terrainHeight, localZ, localX + 1, seaLevel, localZ + 1, Material.WATER); } chunkData.setRegion(localX, 0, localZ, localX + 1, bedrockHeight, localZ + 1, Material.BEDROCK); } diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGenerator.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGenerator.java index 92900fd..27d8377 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGenerator.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGenerator.java @@ -1,5 +1,7 @@ package ca.recrown.islandsurvivalcraft.world.generation; +import java.util.Random; + import org.bukkit.World; import org.bukkit.block.Biome; @@ -15,7 +17,7 @@ import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper; public class UniBiomeIslandGenerator implements BiomeGenerator { @Override - public void generateBiomeColumn(Biome[][] biomes, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGen, Cache biomeCache, Cache chunkGenCache) { + public void generateBiomeColumn(Biome[][] biomes, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGen, Cache biomeCache, Cache chunkGenCache, Random random) { int worldX = 16 * chunkX + localX; int worldZ = 16 * chunkZ + localZ; Point2 chunkCoords = Utilities.worldToChunkCoordinates(new Point2(worldX, worldZ)); @@ -31,7 +33,7 @@ public class UniBiomeIslandGenerator implements BiomeGenerator { //Fine, check if it's ocean. if (!mapper.isIsland(worldX, worldZ)) { biomeSet = new Biome[4]; - biomeSet[0] = biomeSelector.getOceanBiome(tempGen.getTemperature(worldX, worldZ)); + biomeSet[0] = biomeSelector.getOceanBiome(tempGen.getTemperature(worldX, worldZ), random); setCacheBiome(worldX, worldZ, biomeSet, biomes, biomeCache); return; } @@ -44,9 +46,9 @@ public class UniBiomeIslandGenerator implements BiomeGenerator { search.setStartPosition(worldX, worldZ); if (!search.findTarget(islandInfo)) { float temp = tempGen.getTemperature(worldX, worldZ); - if (islandInfo.main == null) islandInfo.main = biomeSelector.getLandBiome(temp); + if (islandInfo.main == null) islandInfo.main = biomeSelector.getLandBiome(temp, random); if (islandInfo.shore == null) islandInfo.shore = biomeSelector.getShoreBiome(islandInfo.main, temp); - if (islandInfo.shallow == null) islandInfo.shallow = biomeSelector.getOceanBiome(temp); + if (islandInfo.shallow == null) islandInfo.shallow = biomeSelector.getOceanBiome(temp, random); } PropagatorInfo propInfo = new PropagatorInfo(islandInfo, biomes, new Point2(chunkX, chunkZ), mapper, biomeCache); diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldHeightShader.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldHeightShader.java index 9676075..cd6ae9a 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldHeightShader.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldHeightShader.java @@ -1,41 +1,49 @@ package ca.recrown.islandsurvivalcraft.world.shaders; +import java.util.Random; + import org.bukkit.block.Biome; +import org.bukkit.util.noise.SimplexOctaveGenerator; import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper; public class WorldHeightShader { - private final IslandWorldMapper islandLocator; + private final IslandWorldMapper mapper; + private final SimplexOctaveGenerator shader; private final int seaLevel; private final int worldHeight; private final int minimumHeight; - public WorldHeightShader(IslandWorldMapper islandLocator, int seaLevel, int worldHeight, int minimumHeight) { - this.islandLocator = islandLocator; + public WorldHeightShader(long seed, IslandWorldMapper islandLocator, int seaLevel, int worldHeight, int minimumHeight) { + this.mapper = islandLocator; this.seaLevel = seaLevel; this.worldHeight = worldHeight; this.minimumHeight = minimumHeight; + this.shader = new SimplexOctaveGenerator(new Random(seed - 1), 8); + this.shader.setScale(0.05d); } public int getTerrainHeight(int worldX, int worldZ, Biome biome) { int height = 0; String biomeName = biome.name().toLowerCase(); if (biomeName.contains("hills")) { - height = calculateTerrainHeight(worldX, worldZ, 15); + height = calculateTerrainHeight(worldX, worldZ, 30d); } else if (biomeName.contains("mountains")) { - height = calculateTerrainHeight(worldX, worldZ, 120); + height = calculateTerrainHeight(worldX, worldZ, 70d); } else if (biomeName.contains("plateau")) { - height = Math.min(calculateTerrainHeight(worldX, worldZ, 50), 30); + height = (int) Math.min(calculateTerrainHeight(worldX, worldZ, 50d), seaLevel + 30d); } else if (biomeName.contains("modified")) { - height = calculateTerrainHeight(worldX, worldZ, 45); + height = calculateTerrainHeight(worldX, worldZ, 40d); } else if (biomeName.contains("shattered")) { - height = calculateTerrainHeight(worldX, worldZ, 60); + height = calculateTerrainHeight(worldX, worldZ, 40d); } else if (biomeName.contains("tall")) { - height = calculateTerrainHeight(worldX, worldZ, 45); + height = calculateTerrainHeight(worldX, worldZ, 30d); + } else if (biomeName.contains("stone")) { + height = calculateTerrainHeight(worldX, worldZ, 70d); } else if (!biomeName.contains("ocean")) { - height = calculateTerrainHeight(worldX, worldZ, 15); + height = calculateTerrainHeight(worldX, worldZ, 10d); } else { - height = calculateTerrainHeight(worldX, worldZ, seaLevel); + height = calculateTerrainHeight(worldX, worldZ, seaLevel, 1d, 0.25d); } if (height > worldHeight) throw new IllegalStateException("Resulting height is greater than world height! Biome this occurred on: " + biomeName); @@ -43,14 +51,15 @@ public class WorldHeightShader { return height; } - private int calculateTerrainHeight(int worldX, int worldZ, int multiplier) { - int blockHeight = 0; - double islandValue = islandLocator.getWorldValue(worldX, worldZ); - if (!islandLocator.isLand(worldX, worldZ)) { - blockHeight = (int) ((1d + islandValue) * (double) multiplier); - } else { - blockHeight += seaLevel + islandValue * multiplier; - } - return blockHeight; + private int calculateTerrainHeight(int worldX, int worldZ, double heightFactor, double shift, double exaggerationFactor) { + int res = seaLevel; + double shapeValue = (shader.noise(worldX, worldZ, 1.5d, 0.05d, true) + shift) / (shift + 1d); + if (shift == 1d && exaggerationFactor != 1d) shapeValue = Math.pow(shapeValue, exaggerationFactor); + res += mapper.getWorldValue(worldX, worldZ) * (shapeValue * heightFactor); + return res; + } + + private int calculateTerrainHeight(int worldX, int worldZ, double heightFactor) { + return calculateTerrainHeight(worldX, worldZ, heightFactor, 0.75d, 1d); } } \ No newline at end of file diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldLayerShader.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldLayerShader.java index 7f603e2..f3576de 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldLayerShader.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldLayerShader.java @@ -38,7 +38,7 @@ public class WorldLayerShader { /** * Figures out the type of material to be placed at a given world coordinate set. - * + * Returns null when special layering is done and should return to normal generation. * @param worldX The world x coordinate. * @param worldZ The world z coordinate. * @param y The elevation. @@ -74,7 +74,7 @@ public class WorldLayerShader { String biomeName = biome.toString().toLowerCase(); if (biomeName.contains("beach") || biomeName.contains("desert") || biomeName.contains("warm_ocean")) { return Material.SAND; - } else if (biomeName.contains("deep_ocean")) { + } else if (biomeName.contains("ocean")) { return Material.GRAVEL; } else if (biomeName.contains("stone")) { return Material.STONE; @@ -82,6 +82,16 @@ public class WorldLayerShader { return Material.GRASS_BLOCK; } + + public int getSurfaceThickness(int worldX, int worldZ, Biome biome) { + String biomeName = biome.toString().toLowerCase(); + + if (biomeName.contains("beach")) { + return (int) (Math.abs(mapper.getWorldValue(worldX, worldZ)) * 5) + 4; + } + + return 1; + } public Material getTransitionMaterial(Biome biome) { String biomeName = biome.toString().toLowerCase(); diff --git a/src/test/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGeneratorTest.java b/src/test/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGeneratorTest.java index a5c682f..a8d1eed 100644 --- a/src/test/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGeneratorTest.java +++ b/src/test/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGeneratorTest.java @@ -3,6 +3,7 @@ package ca.recrown.islandsurvivalcraft.world.generation; import static org.junit.jupiter.api.Assertions.assertFalse; import java.util.LinkedList; +import java.util.Random; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -11,6 +12,7 @@ import java.util.concurrent.TimeUnit; import org.bukkit.block.Biome; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -30,6 +32,9 @@ public class UniBiomeIslandGeneratorTest { private volatile Cache blockValueCache; private volatile Cache biomeCache; private volatile Cache chunkExistenceCache; + private final BiomeSelector biomeSelector = new BiomeSelector(); + private final Random random = new Random(SEED); + private class BiomeGenTask implements Runnable { private final int amount; @@ -51,8 +56,6 @@ public class UniBiomeIslandGeneratorTest { public void generateBiome(int chunkX, int chunkZ) { IslandWorldMapper mapper = new IslandWorldMapper(SEED, blockValueCache); TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(SEED); - BiomeSelector biomeSelector = new BiomeSelector(); - biomeSelector.initialize(); BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator(); Biome[][] biomes = new Biome[Utilities.CHUNK_SIZE][Utilities.CHUNK_SIZE]; @@ -60,7 +63,7 @@ public class UniBiomeIslandGeneratorTest { for (int localZ = 0; localZ < Utilities.CHUNK_SIZE; localZ++) { if (biomes[localX][localZ] == null) { biomeGenerator.generateBiomeColumn(biomes, dummyWorld, chunkX, chunkZ, localX, localZ, mapper, - biomeSelector, temperatureMapGenerator, biomeCache, chunkExistenceCache); + biomeSelector, temperatureMapGenerator, biomeCache, chunkExistenceCache, random); } if (biomes[localX][localZ] == null) throw new IllegalStateException("Biome was null."); @@ -68,6 +71,11 @@ public class UniBiomeIslandGeneratorTest { } } } + + @BeforeAll + public void setup() { + biomeSelector.initialize(); + } @BeforeEach public void individualSetup() {