diff --git a/pom.xml b/pom.xml index f810c474f..c631d0aad 100644 --- a/pom.xml +++ b/pom.xml @@ -83,7 +83,7 @@ org.polydev gaea - 1.3.0 + 1.3.4 diff --git a/src/main/java/com/dfsek/terra/TerraChunkGenerator.java b/src/main/java/com/dfsek/terra/TerraChunkGenerator.java index 48f9a9c8a..3e53e7f69 100644 --- a/src/main/java/com/dfsek/terra/TerraChunkGenerator.java +++ b/src/main/java/com/dfsek/terra/TerraChunkGenerator.java @@ -1,8 +1,11 @@ package com.dfsek.terra; import com.dfsek.terra.biome.TerraBiomeGrid; +import com.dfsek.terra.config.WorldConfig; +import com.dfsek.terra.population.FaunaPopulator; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.generator.BlockPopulator; import org.bukkit.generator.ChunkGenerator.ChunkData; import org.jetbrains.annotations.NotNull; import org.polydev.gaea.biome.BiomeGrid; @@ -11,6 +14,7 @@ import org.polydev.gaea.generation.GenerationPopulator; import org.polydev.gaea.math.FastNoise; import org.polydev.gaea.math.InterpolationType; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; @@ -23,10 +27,12 @@ public class TerraChunkGenerator extends GaeaChunkGenerator { @Override public ChunkData generateBase(@NotNull World world, @NotNull Random random, int chunkX, int chunkZ, FastNoise fastNoise) { ChunkData chunk = createChunkData(world); + int sea = WorldConfig.fromWorld(world).seaLevel; for(byte x = 0; x < 16; x++) { for(int y = 0; y < 256; y++) { for(byte z = 0; z < 16; z++) { if(super.getInterpolatedNoise(x, y, z) > 0) chunk.setBlock(x, y, z, Material.STONE); + else if(y < sea) chunk.setBlock(x, y, z, Material.WATER); } } } @@ -51,4 +57,9 @@ public class TerraChunkGenerator extends GaeaChunkGenerator { public org.polydev.gaea.biome.BiomeGrid getBiomeGrid(World world) { return TerraBiomeGrid.fromWorld(world); } + + @Override + public @NotNull List getDefaultPopulators(@NotNull World world) { + return Arrays.asList(new FaunaPopulator()); + } } diff --git a/src/main/java/com/dfsek/terra/biome/BiomeZone.java b/src/main/java/com/dfsek/terra/biome/BiomeZone.java index 6ff7956cb..2a5ec3e5d 100644 --- a/src/main/java/com/dfsek/terra/biome/BiomeZone.java +++ b/src/main/java/com/dfsek/terra/biome/BiomeZone.java @@ -13,23 +13,26 @@ public class BiomeZone { private final World w; private final FastNoise noise; private static final Map zones = new HashMap<>(); + private static final double[] normalMap = new double[] {-0.35662081837654114D, -0.30661869049072266D, -0.27095329761505127D, -0.24149227142333984D, -0.21537694334983826D, -0.19166918098926544D, -0.16956785321235657D, -0.14864568412303925D, -0.12845154106616974D, -0.10894706845283508D, -0.08996972441673279D, -0.0715663805603981D, -0.053535036742687225D, -0.03580872714519501D, -0.01817353256046772D, -7.577221258543432E-4D, 0.016616813838481903D, 0.03416096046566963D, 0.05187138542532921D, 0.06989025324583054D, 0.08827653527259827D, 0.10723070055246353D, 0.12675245106220245D, 0.14694781601428986D, 0.16793397068977356D, 0.18999846279621124D, 0.2138010412454605D, 0.24002985656261444D, 0.2696261405944824D, 0.30540621280670166D, 0.35551881790161133D, 0.653269350528717D}; + public BiomeZone(World w, float freq) { this.w = w; this.noise = new FastNoise((int) w.getSeed()+2); - noise.setNoiseType(FastNoise.NoiseType.Value); + noise.setNoiseType(FastNoise.NoiseType.SimplexFractal); + noise.setFractalOctaves(5); noise.setFrequency(freq); setZones(WorldConfig.fromWorld(w).definedGrids); zones.put(w, this); } public void setZones(BiomeGrid[] grids) { - if(grids.length != 16) throw new IllegalArgumentException("Illegal number of grids!"); + if(grids.length != 32) throw new IllegalArgumentException("Illegal number of grids!"); this.grids = grids; } public BiomeGrid getGrid(int x, int z) { - return grids[normalize(noise.getValue(x, z))]; + return grids[normalize(noise.getSimplexFractal(x, z))]; } public static BiomeZone fromWorld(World w) { @@ -38,14 +41,15 @@ public class BiomeZone { } /** - * Takes a noise input and normalizes it to a value between 0 and 15 inclusive. + * Takes a noise input and normalizes it to a value between 0 and 31 inclusive. * - * @param i - The noise value to normalize. + * @param d - The noise value to normalize. * @return int - The normalized value. */ - private static int normalize(double i) { - if(i > 0) i = Math.pow(i, 0.8125); // Redistribute - else i = -Math.pow(-i, 0.8125); // Redistribute - return Math.min((int) Math.floor((i+1)*8), 15); + public static int normalize(double d) { + for(int i = 0; i < normalMap.length; i++) { + if(d < normalMap[i]) return i; + } + return normalMap.length-1; } } diff --git a/src/main/java/com/dfsek/terra/biome/UserDefinedBiome.java b/src/main/java/com/dfsek/terra/biome/UserDefinedBiome.java index 7eb6ad508..afdec9f27 100644 --- a/src/main/java/com/dfsek/terra/biome/UserDefinedBiome.java +++ b/src/main/java/com/dfsek/terra/biome/UserDefinedBiome.java @@ -10,18 +10,30 @@ import org.polydev.gaea.math.parsii.eval.Parser; import org.polydev.gaea.math.parsii.eval.Scope; import org.polydev.gaea.math.parsii.tokenizer.ParseException; import org.polydev.gaea.structures.features.Feature; +import org.polydev.gaea.world.BlockPalette; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.TreeMap; public class UserDefinedBiome implements Biome { private final UserDefinedGenerator gen; private final BiomeConfig config; + private final UserDefinedDecorator decorator; public UserDefinedBiome(BiomeConfig config) throws ParseException { this.config = config; Scope s = Scope.create(); - gen = new UserDefinedGenerator(s, Parser.parse(Objects.requireNonNull(config.getString("noise-equation")), s), Collections.emptyList(), ConfigUtil.getPalette(config.getString("palette")).getPalette()); + TreeMap paletteMap = new TreeMap<>(); + for(Map.Entry e : config.getConfigurationSection("palette").getValues(false).entrySet()) { + paletteMap.put((Integer) e.getValue(), ConfigUtil.getPalette(e.getKey()).getPalette()); + } + + + this.decorator = new UserDefinedDecorator(config); + + gen = new UserDefinedGenerator(s, Parser.parse(Objects.requireNonNull(config.getString("noise-equation")), s), Collections.emptyList(), paletteMap); } public BiomeConfig getConfig() { @@ -35,7 +47,7 @@ public class UserDefinedBiome implements Biome { */ @Override public org.bukkit.block.Biome getVanillaBiome() { - return org.bukkit.block.Biome.PLAINS; + return config.getVanillaBiome(); } /** @@ -65,6 +77,6 @@ public class UserDefinedBiome implements Biome { */ @Override public Decorator getDecorator() { - return null; + return decorator; } } diff --git a/src/main/java/com/dfsek/terra/biome/UserDefinedDecorator.java b/src/main/java/com/dfsek/terra/biome/UserDefinedDecorator.java new file mode 100644 index 000000000..ba5448b8f --- /dev/null +++ b/src/main/java/com/dfsek/terra/biome/UserDefinedDecorator.java @@ -0,0 +1,60 @@ +package com.dfsek.terra.biome; + +import com.dfsek.terra.config.BiomeConfig; +import org.bukkit.block.Biome; +import org.polydev.gaea.biome.Decorator; +import org.polydev.gaea.math.ProbabilityCollection; +import org.polydev.gaea.tree.Tree; +import org.polydev.gaea.world.Fauna; + +import java.util.Map; + +public class UserDefinedDecorator extends Decorator { + + private final ProbabilityCollection fauna = new ProbabilityCollection<>(); + private int faunaChance; + + public UserDefinedDecorator(BiomeConfig config) { + if(config.contains("fauna")) { + for(Map.Entry e : config.getConfigurationSection("fauna").getValues(false).entrySet()) { + fauna.add(Fauna.valueOf(e.getKey()), (Integer) e.getValue()); + } + } + faunaChance = config.getInt("fauna-chance", 0); + } + + @Override + public ProbabilityCollection getTrees() { + return null; + } + + @Override + public int getTreeDensity() { + return 0; + } + + @Override + public boolean overrideStructureChance() { + return false; + } + + @Override + public boolean shouldGenerateSnow() { + return false; + } + + @Override + public Biome getVanillaBiome() { + return null; + } + + @Override + public ProbabilityCollection getFauna() { + return fauna; + } + + @Override + public int getFaunaChance() { + return faunaChance; + } +} diff --git a/src/main/java/com/dfsek/terra/biome/UserDefinedGenerator.java b/src/main/java/com/dfsek/terra/biome/UserDefinedGenerator.java index 3db15a7f9..5e73cf150 100644 --- a/src/main/java/com/dfsek/terra/biome/UserDefinedGenerator.java +++ b/src/main/java/com/dfsek/terra/biome/UserDefinedGenerator.java @@ -10,6 +10,8 @@ import org.polydev.gaea.math.parsii.eval.Variable; import org.polydev.gaea.world.BlockPalette; import java.util.List; +import java.util.Map; +import java.util.TreeMap; public class UserDefinedGenerator extends BiomeTerrain { private final Expression noiseExp; @@ -17,13 +19,13 @@ public class UserDefinedGenerator extends BiomeTerrain { private final Variable xVar; private final Variable yVar; private final Variable zVar; - private final BlockPalette p; + private final TreeMap paletteMap; - public UserDefinedGenerator(Scope s, Expression e, List v, BlockPalette p) { + public UserDefinedGenerator(Scope s, Expression e, List v, TreeMap p) { this.noiseExp = e; this.vars = v; - this.p = p; + this.paletteMap = p; this.xVar = s.getVariable("x"); this.yVar = s.getVariable("y"); this.zVar = s.getVariable("z"); @@ -72,6 +74,33 @@ public class UserDefinedGenerator extends BiomeTerrain { */ @Override public BlockPalette getPalette(int y) { - return p; + for(Map.Entry e : paletteMap.entrySet()) { + if(e.getKey() >= y ) return e.getValue(); + } + return null; + } + + private static class Range { + private final int min; + private final int max; + + /** + * Instantiates a Range object with a minimum value (inclusive) and a maximum value (exclusive). + * @param min The minimum value (inclusive). + * @param max The maximum value (exclusive). + */ + public Range(int min, int max) { + this.min = min; + this.max = max; + } + + /** + * Tests if a value is within range. + * @param val The value to test. + * @return boolean - Whether the value is within range. + */ + public boolean isInRange(int val) { + return val >= min && val < max; + } } } diff --git a/src/main/java/com/dfsek/terra/biome/UserDefinedGrid.java b/src/main/java/com/dfsek/terra/biome/UserDefinedGrid.java index 740bd1ba8..e2553de98 100644 --- a/src/main/java/com/dfsek/terra/biome/UserDefinedGrid.java +++ b/src/main/java/com/dfsek/terra/biome/UserDefinedGrid.java @@ -9,4 +9,8 @@ public class UserDefinedGrid extends BiomeGrid { super(w, freq1, freq2); super.setGrid(config.getBiomeGrid()); } + public UserDefinedGrid(World w, float freq1, float freq2, UserDefinedBiome[][] b) { + super(w, freq1, freq2); + super.setGrid(b); + } } diff --git a/src/main/java/com/dfsek/terra/config/BiomeConfig.java b/src/main/java/com/dfsek/terra/config/BiomeConfig.java index 15b76ac4f..8ceab9707 100644 --- a/src/main/java/com/dfsek/terra/config/BiomeConfig.java +++ b/src/main/java/com/dfsek/terra/config/BiomeConfig.java @@ -14,6 +14,7 @@ public class BiomeConfig extends YamlConfiguration { private UserDefinedBiome biome; private String biomeID; private String friendlyName; + private org.bukkit.block.Biome vanillaBiome; private boolean isEnabled = false; public BiomeConfig(File file) throws InvalidConfigurationException, IOException { @@ -37,6 +38,12 @@ public class BiomeConfig extends YamlConfiguration { this.biomeID = getString("id"); if(!contains("name")) throw new InvalidConfigurationException("Biome Name unspecified!"); this.friendlyName = getString("name"); + if(!contains("vanilla")) throw new InvalidConfigurationException("Vanila Biome unspecified!"); + try { + this.vanillaBiome = org.bukkit.block.Biome.valueOf(getString("vanilla")); + } catch(IllegalArgumentException e) { + throw new InvalidConfigurationException("Invalid Vanilla biome: " + getString("vanilla")); + } isEnabled = true; } @@ -55,4 +62,8 @@ public class BiomeConfig extends YamlConfiguration { public String getFriendlyName() { return friendlyName; } + + public org.bukkit.block.Biome getVanillaBiome() { + return vanillaBiome; + } } diff --git a/src/main/java/com/dfsek/terra/config/PaletteConfig.java b/src/main/java/com/dfsek/terra/config/PaletteConfig.java index e3a3ebfee..6a4d26612 100644 --- a/src/main/java/com/dfsek/terra/config/PaletteConfig.java +++ b/src/main/java/com/dfsek/terra/config/PaletteConfig.java @@ -31,7 +31,7 @@ public class PaletteConfig extends YamlConfiguration { ProbabilityCollection layer = new ProbabilityCollection<>(); for(Map.Entry type : ((Map) m.get("materials")).entrySet()) { layer.add(Bukkit.createBlockData(type.getKey()), type.getValue()); - Bukkit.getLogger().info("[Terra] Added" + type.getKey() + " with probability " + type.getValue()); + Bukkit.getLogger().info("[Terra] Added " + type.getKey() + " with probability " + type.getValue()); } Bukkit.getLogger().info("[Terra] Added above materials for " + m.get("layers") + " layers."); palette.addBlockData(layer, (Integer) m.get("layers")); @@ -61,28 +61,4 @@ public class PaletteConfig extends YamlConfiguration { public String getPaletteID() { return paletteID; } - - private static class Range { - private final int min; - private final int max; - - /** - * Instantiates a Range object with a minimum value (inclusive) and a maximum value (exclusive). - * @param min The minimum value (inclusive). - * @param max The maximum value (exclusive). - */ - public Range(int min, int max) { - this.min = min; - this.max = max; - } - - /** - * Tests if a value is within range. - * @param val The value to test. - * @return boolean - Whether the value is within range. - */ - public boolean isInRange(int val) { - return val >= min && val < max; - } - } } diff --git a/src/main/java/com/dfsek/terra/config/WorldConfig.java b/src/main/java/com/dfsek/terra/config/WorldConfig.java index d5a2079ed..6bd2e262f 100644 --- a/src/main/java/com/dfsek/terra/config/WorldConfig.java +++ b/src/main/java/com/dfsek/terra/config/WorldConfig.java @@ -1,8 +1,7 @@ package com.dfsek.terra.config; import com.dfsek.terra.Terra; -import com.dfsek.terra.biome.BiomeZone; -import com.dfsek.terra.biome.TerraBiomeGrid; +import com.dfsek.terra.biome.UserDefinedBiome; import com.dfsek.terra.biome.UserDefinedGrid; import org.bukkit.World; import org.bukkit.configuration.InvalidConfigurationException; @@ -27,7 +26,10 @@ public class WorldConfig { private static final Map configs = new HashMap<>(); private final Map biomeGrids = new HashMap<>(); public float zoneFreq = 1f/1536; - public UserDefinedGrid[] definedGrids = new UserDefinedGrid[16]; + public float freq1 = 1f/256; + public float freq2 = 1f/512; + public int seaLevel; + public UserDefinedGrid[] definedGrids = new UserDefinedGrid[32]; public WorldConfig(World w, JavaPlugin main) { @@ -63,6 +65,7 @@ public class WorldConfig { main.getLogger().severe("Unable to load configuration for world " + w + "."); } + seaLevel = config.getInt("sea-level", 63); try (Stream paths = Files.walk(Paths.get(main.getDataFolder() + File.separator + "grids"))) { paths @@ -85,8 +88,19 @@ public class WorldConfig { - for(int i = 0; i < 16; i++) { - definedGrids[i] = biomeGrids.get(config.getStringList("grids").get(i)).getGrid(); + for(int i = 0; i < 32; i++) { + String partName = config.getStringList("grids").get(i); + if(partName.startsWith("BIOME:")) { + UserDefinedBiome[][] temp = new UserDefinedBiome[16][16]; + UserDefinedBiome b = ConfigUtil.getBiome(partName.substring(6)).getBiome(); + for(int x = 0; x < 16; x++) { + for(int z = 0; z < 16; z++) { + temp[x][z] = b; + } + } + definedGrids[i] = new UserDefinedGrid(w, freq1, freq2, temp); + main.getLogger().info("Loaded single-biome grid " + partName); + } else definedGrids[i] = biomeGrids.get(partName).getGrid(); } diff --git a/src/main/java/com/dfsek/terra/population/FaunaPopulator.java b/src/main/java/com/dfsek/terra/population/FaunaPopulator.java new file mode 100644 index 000000000..a7672abee --- /dev/null +++ b/src/main/java/com/dfsek/terra/population/FaunaPopulator.java @@ -0,0 +1,29 @@ +package com.dfsek.terra.population; + +import com.dfsek.terra.biome.TerraBiomeGrid; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.generator.BlockPopulator; +import org.jetbrains.annotations.NotNull; +import org.polydev.gaea.biome.Biome; +import org.polydev.gaea.world.Fauna; + +import java.util.Random; + +public class FaunaPopulator extends BlockPopulator { + @Override + public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { + for(int x = 0; x < 16; x++) { + for(int z = 0; z < 16; z++) { + Biome biome = TerraBiomeGrid.fromWorld(world).getBiome((chunk.getX() << 4) + x, (chunk.getZ() << 4) + z); + if(biome.getDecorator().getFaunaChance() <= 0 || random.nextInt(100) > biome.getDecorator().getFaunaChance()) + continue; + Block highest = Fauna.getHighestValidSpawnAt(chunk, x, z); + try { + if(highest != null) biome.getDecorator().getFauna().get(random).plant(highest.getLocation()); + } catch(NullPointerException ignored) {} + } + } + } +} diff --git a/src/test/java/DistributionTest.java b/src/test/java/DistributionTest.java new file mode 100644 index 000000000..5836c78e7 --- /dev/null +++ b/src/test/java/DistributionTest.java @@ -0,0 +1,39 @@ +import com.dfsek.terra.biome.BiomeZone; +import org.polydev.gaea.math.FastNoise; + +import java.util.Random; + +public class DistributionTest { + public static void main(String[] args) { + FastNoise noise = new FastNoise(new Random().nextInt()); + noise.setFrequency(0.01f); + noise.setNoiseType(FastNoise.NoiseType.SimplexFractal); + noise.setFractalOctaves(3); + int[] numbers = new int[32]; + double min = Integer.MAX_VALUE; + double max = Integer.MIN_VALUE; + for(int i = 0; i < 32; i++) { + numbers[i] = 0; + } + + for(int i = 0; i < 10000000; i++) { + double n = noise.getSimplexFractal(0, i); + max = Math.max(max, n); + min = Math.min(min, n); + numbers[BiomeZone.normalize(n)]++; + } + + for(int i = 0; i < 32; i++) { + System.out.println(i + ": " + numbers[i]); + } + for(int i = 0; i < 32; i++) { + System.out.print(i + " |"); + for(int j = 0; j < numbers[i]/2500; j++) { + System.out.print("-"); + } + System.out.println("|"); + } + System.out.println("max: " + max); + System.out.println("min: " + min); + } +} diff --git a/src/test/java/LookupGenerator.java b/src/test/java/LookupGenerator.java new file mode 100644 index 000000000..56f7ac5d7 --- /dev/null +++ b/src/test/java/LookupGenerator.java @@ -0,0 +1,92 @@ +import com.dfsek.terra.biome.BiomeZone; +import org.polydev.gaea.math.FastNoise; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +public class LookupGenerator { + private static double[] lookup; + + public static void main(String[] args) { + int dist = 32; + + List vals = new ArrayList<>(); + FastNoise noise = new FastNoise(new Random().nextInt()); + noise.setFrequency(0.01f); + noise.setNoiseType(FastNoise.NoiseType.SimplexFractal); + noise.setFractalOctaves(5); + int[] numbers = new int[dist]; + double min = Integer.MAX_VALUE; + double max = Integer.MIN_VALUE; + for(int i = 0; i < dist; i++) { + numbers[i] = 0; + } + + for(int i = 0; i < 10000000; i++) { + double n = noise.getSimplexFractal(0, i); + max = Math.max(max, n); + min = Math.min(min, n); + vals.add(n); + numbers[normalize(n, dist)]++; + } + + for(int i = 0; i < dist; i++) { + System.out.println(i + ": " + numbers[i]); + } + for(int i = 0; i < dist; i++) { + System.out.print(i + (String.valueOf(i).length() ==1 ? " " : "") + " |"); + for(int j = 0; j < numbers[i]/3000; j++) { + System.out.print("-"); + } + System.out.println("|"); + } + System.out.println("max: " + max); + System.out.println("min: " + min); + Collections.sort(vals); + + lookup = new double[dist]; + StringBuilder s = new StringBuilder("{"); + for(int i = 0; i < dist; i++) { + int current = vals.size()/dist; + System.out.println(i + ", max: " + vals.get(current*(i+1)-1)); + lookup[i] = vals.get(current*(i+1)-1); + s.append(vals.get(current*(i+1)-1) + "D, "); + } + s.delete(s.length()-2, s.length()); + s.append("}"); + numbers = new int[dist]; + vals = new ArrayList<>(); + for(int i = 0; i < 10000000; i++) { + double n = noise.getSimplexFractal(0, i); + vals.add(n); + numbers[normalizeNew(n)]++; + } + + for(int i = 0; i < dist; i++) { + System.out.println(i + ": " + numbers[i]); + } + for(int i = 0; i < dist; i++) { + System.out.print(i + (String.valueOf(i).length() ==1 ? " " : "") + " |"); + for(int j = 0; j < numbers[i]/3000; j++) { + System.out.print("-"); + } + System.out.println("|"); + } + System.out.println(s.toString()); + + } + public static int normalize(double i, int n) { + i*=1.42; // Magic simplex value (sqrt(2) plus a little) + i = Math.min(Math.max(i, -1),1); + return Math.min((int) Math.floor((i+1)*((double)n/2)), n-1); + } + + public static int normalizeNew(double d) { + for(int i = 0; i < lookup.length; i++) { + if(d < lookup[i]) return i; + } + return lookup.length-1; + } +}