diff --git a/src/main/java/ru/bclib/util/BonemealUtil.java b/src/main/java/ru/bclib/util/BonemealUtil.java index 18baa32e..9a97255d 100644 --- a/src/main/java/ru/bclib/util/BonemealUtil.java +++ b/src/main/java/ru/bclib/util/BonemealUtil.java @@ -2,8 +2,10 @@ package ru.bclib.util; import java.util.Map; import java.util.Random; +import java.util.Set; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.Block; @@ -11,6 +13,15 @@ import net.minecraft.world.level.block.Block; public class BonemealUtil { private static final Map>> GRASS_BIOMES = Maps.newHashMap(); private static final Map> GRASS_TYPES = Maps.newHashMap(); + private static final Set SPREADABLE_BLOCKS = Sets.newHashSet(); + + public static void addSpreadableBlock(Block block) { + SPREADABLE_BLOCKS.add(block); + } + + public static boolean isSpreadable(Block block) { + return SPREADABLE_BLOCKS.contains(block); + } public static void addBonemealGrass(Block terrain, Block plant) { addBonemealGrass(terrain, plant, 1F); diff --git a/src/main/java/ru/bclib/util/WeighTree.java b/src/main/java/ru/bclib/util/WeighTree.java new file mode 100644 index 00000000..28527dca --- /dev/null +++ b/src/main/java/ru/bclib/util/WeighTree.java @@ -0,0 +1,74 @@ +package ru.bclib.util; + +import java.util.Random; + +public class WeighTree { + private final float maxWeight; + private final Node root; + + public WeighTree(WeightedList list) { + maxWeight = list.getMaxWeight(); + root = getNode(list); + } + + /** + * Get eandom value from tree. + * @param random - {@link Random}. + * @return {@link T} value. + */ + public T get(Random random) { + return root.get(random.nextFloat() * maxWeight); + } + + private Node getNode(WeightedList biomes) { + int size = biomes.size(); + if (size == 1) { + return new Leaf(biomes.get(0)); + } + else if (size == 2) { + T first = biomes.get(0); + return new Branch(biomes.getWeight(0), new Leaf(first), new Leaf(biomes.get(1))); + } + else { + int index = size >> 1; + float separator = biomes.getWeight(index); + Node a = getNode(biomes.subList(0, index + 1)); + Node b = getNode(biomes.subList(index, size)); + return new Branch(separator, a, b); + } + } + + private abstract class Node { + abstract T get(float value); + } + + private class Branch extends Node { + final float separator; + final Node min; + final Node max; + + public Branch(float separator, Node min, Node max) { + this.separator = separator; + this.min = min; + this.max = max; + } + + @Override + T get(float value) { + return value < separator ? min.get(value) : max.get(value); + } + } + + private class Leaf extends Node { + final T biome; + + Leaf(T biome) { + this.biome = biome; + } + + @Override + T get(float value) { + return biome; + } + } +} diff --git a/src/main/java/ru/bclib/util/WeightedList.java b/src/main/java/ru/bclib/util/WeightedList.java index eeb856a9..e8ad35ef 100644 --- a/src/main/java/ru/bclib/util/WeightedList.java +++ b/src/main/java/ru/bclib/util/WeightedList.java @@ -3,18 +3,29 @@ package ru.bclib.util; import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.function.Consumer; public class WeightedList { private final List weights = new ArrayList(); private final List values = new ArrayList(); private float maxWeight; + /** + * Adds value with specified weight to the list + * @param value + * @param weight + */ public void add(T value, float weight) { maxWeight += weight; weights.add(maxWeight); values.add(value); } + /** + * Get random value. + * @param random - {@link Random}. + * @return {@link T} value. + */ public T get(Random random) { if (maxWeight < 1) { return null; @@ -27,8 +38,79 @@ public class WeightedList { } return null; } + + /** + * Get value by index. + * @param index - {@code int} index. + * @return {@link T} value. + */ + public T get(int index) { + return values.get(index); + } + + /** + * Get value weight. Weight is summed with all previous values weights. + * @param index - {@code int} index. + * @return {@code float} weight. + */ + public float getWeight(int index) { + return weights.get(index); + } + /** + * Chech if the list is empty. + * @return {@code true} if list is empty and {@code false} if not. + */ public boolean isEmpty() { return maxWeight == 0; } + + /** + * Get the list size. + * @return {@code int} list size. + */ + public int size() { + return values.size(); + } + + /** + * Makes a sublist of this list with same weights. Used only in {@link WeighTree} + * @param start - {@code int} start index (inclusive). + * @param end - {@code int} end index (exclusive). + * @return {@link WeightedList}. + */ + protected WeightedList subList(int start, int end) { + WeightedList list = new WeightedList(); + for (int i = start; i < end; i++) { + list.weights.add(weights.get(i)); + list.values.add(values.get(i)); + } + list.maxWeight = list.weights.get(end - 1); + return list; + } + + /** + * Check if list contains certain value. + * @param value - {@link T} value. + * @return {@code true} if value is in list and {@code false} if not. + */ + public boolean contains(T value) { + return values.contains(value); + } + + /** + * Applies {@link Consumer} to all values in list. + * @param function - {@link Consumer}. + */ + public void forEach(Consumer function) { + values.forEach(function); + } + + /** + * Get the maximum weight of the tree. + * @return {@code float} maximum weight. + */ + public float getMaxWeight() { + return maxWeight; + } } diff --git a/src/main/java/ru/bclib/world/biomes/BCLBiome.java b/src/main/java/ru/bclib/world/biomes/BCLBiome.java index 025cdceb..65f9289b 100644 --- a/src/main/java/ru/bclib/world/biomes/BCLBiome.java +++ b/src/main/java/ru/bclib/world/biomes/BCLBiome.java @@ -11,16 +11,16 @@ import com.google.gson.JsonObject; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.biome.Biome; -import ru.bclib.config.IdConfig; import ru.bclib.util.JsonFactory; import ru.bclib.util.StructureHelper; +import ru.bclib.util.WeightedList; import ru.bclib.world.features.BCLFeature; import ru.bclib.world.features.ListFeature; import ru.bclib.world.features.ListFeature.StructureInfo; import ru.bclib.world.features.NBTStructureFeature.TerrainMerge; public class BCLBiome { - protected List subbiomes = Lists.newArrayList(); + protected WeightedList subbiomes = new WeightedList(); protected final Biome biome; protected final ResourceLocation mcID; @@ -29,32 +29,29 @@ public class BCLBiome { protected BCLBiome biomeParent; protected float maxSubBiomeChance = 1; - protected final float genChanceUnmutable; - protected float genChance = 1; + protected final float genChance; private final float fogDensity; private BCLFeature structuresFeature; private Biome actualBiome; - public BCLBiome(BiomeDefinition definition, IdConfig config) { + public BCLBiome(BiomeDefinition definition) { this.mcID = definition.getID(); this.readStructureList(); if (structuresFeature != null) { definition.addFeature(structuresFeature); } this.biome = definition.build(); - this.fogDensity = config.getFloat(mcID, "fog_density", definition.getFodDensity()); - this.genChanceUnmutable = config.getFloat(mcID, "generation_chance", definition.getGenChance()); - this.edgeSize = config.getInt(mcID, "edge_size", 32); + this.genChance = definition.getGenChance(); + this.fogDensity = definition.getFodDensity(); } - public BCLBiome(ResourceLocation id, Biome biome, float fogDensity, float genChance, boolean hasCaves, IdConfig config) { + public BCLBiome(ResourceLocation id, Biome biome, float fogDensity, float genChance) { this.mcID = id; - this.readStructureList(); this.biome = biome; - this.fogDensity = config.getFloat(mcID, "fog_density", fogDensity); - this.genChanceUnmutable = config.getFloat(mcID, "generation_chance", genChance); - this.edgeSize = config.getInt(mcID, "edge_size", 32); + this.genChance = genChance; + this.fogDensity = fogDensity; + this.readStructureList(); } public BCLBiome getEdge() { @@ -75,9 +72,8 @@ public class BCLBiome { } public void addSubBiome(BCLBiome biome) { - maxSubBiomeChance += biome.mutateGenChance(maxSubBiomeChance); biome.biomeParent = this; - subbiomes.add(biome); + subbiomes.add(biome, biome.getGenChance()); } public boolean containsSubBiome(BCLBiome biome) { @@ -85,11 +81,7 @@ public class BCLBiome { } public BCLBiome getSubBiome(Random random) { - float chance = random.nextFloat() * maxSubBiomeChance; - for (BCLBiome biome : subbiomes) - if (biome.canGenerate(chance)) - return biome; - return this; + return subbiomes.get(random); } public BCLBiome getParentBiome() { @@ -108,16 +100,6 @@ public class BCLBiome { return biome == this || (biome.hasParentBiome() && biome.getParentBiome() == this); } - public boolean canGenerate(float chance) { - return chance <= this.genChance; - } - - public float mutateGenChance(float chance) { - genChance = genChanceUnmutable; - genChance += chance; - return genChance; - } - public Biome getBiome() { return biome; } @@ -172,10 +154,6 @@ public class BCLBiome { return this.genChance; } - public float getGenChanceImmutable() { - return this.genChanceUnmutable; - } - public void updateActualBiomes(Registry biomeRegistry) { subbiomes.forEach((sub) -> { if (sub != this) { diff --git a/src/main/java/ru/bclib/world/biomes/BiomeDefinition.java b/src/main/java/ru/bclib/world/biomes/BiomeDefinition.java index ffa3075d..02cff669 100644 --- a/src/main/java/ru/bclib/world/biomes/BiomeDefinition.java +++ b/src/main/java/ru/bclib/world/biomes/BiomeDefinition.java @@ -34,6 +34,7 @@ import net.minecraft.world.level.levelgen.feature.configurations.ProbabilityFeat import net.minecraft.world.level.levelgen.surfacebuilders.ConfiguredSurfaceBuilder; import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilder; import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilderBaseConfiguration; +import ru.bclib.config.IdConfig; import ru.bclib.util.ColorUtil; import ru.bclib.world.features.BCLFeature; import ru.bclib.world.structures.BCLStructureFeature; @@ -85,8 +86,8 @@ public class BiomeDefinition { /** * Create default definition for The Nether biome. - * @param id - * @return + * @param id - {@ResourceLocation}. + * @return {@link BiomeDefinition}. */ public static BiomeDefinition netherBiome(ResourceLocation id) { BiomeDefinition def = new BiomeDefinition(id); @@ -98,8 +99,8 @@ public class BiomeDefinition { /** * Create default definition for The End biome. - * @param id - * @return + * @param id - {@ResourceLocation}. + * @return {@link BiomeDefinition}. */ public static BiomeDefinition endBiome(ResourceLocation id) { BiomeDefinition def = new BiomeDefinition(id); @@ -109,6 +110,23 @@ public class BiomeDefinition { return def; } + /** + * Used to load biome settings from config. + * @param config - {@link IdConfig}. + * @return this {@link BiomeDefinition}. + */ + public BiomeDefinition loadConfigValues(IdConfig config) { + this.fogDensity = config.getFloat(id, "fog_density", this.fogDensity); + this.genChance = config.getFloat(id, "generation_chance", this.genChance); + this.edgeSize = config.getInt(id, "edge_size", this.edgeSize); + return this; + } + + /** + * Set category of the biome. + * @param category - {@link BiomeCategory}. + * @return this {@link BiomeDefinition}. + */ public BiomeDefinition setCategory(BiomeCategory category) { this.category = category; return this; diff --git a/src/main/java/ru/bclib/world/generator/BiomeChunk.java b/src/main/java/ru/bclib/world/generator/BiomeChunk.java new file mode 100644 index 00000000..3fdb6358 --- /dev/null +++ b/src/main/java/ru/bclib/world/generator/BiomeChunk.java @@ -0,0 +1,35 @@ +package ru.bclib.world.generator; + +import java.util.Random; + +import ru.bclib.world.biomes.BCLBiome; + +public class BiomeChunk { + protected static final int WIDTH = 16; + private static final int SM_WIDTH = WIDTH >> 1; + private static final int MASK_OFFSET = SM_WIDTH - 1; + protected static final int MASK_WIDTH = WIDTH - 1; + + private final BCLBiome[][] biomes; + + public BiomeChunk(BiomeMap map, Random random, BiomePicker picker) { + BCLBiome[][] PreBio = new BCLBiome[SM_WIDTH][SM_WIDTH]; + biomes = new BCLBiome[WIDTH][WIDTH]; + + for (int x = 0; x < SM_WIDTH; x++) + for (int z = 0; z < SM_WIDTH; z++) + PreBio[x][z] = picker.getBiome(random); + + for (int x = 0; x < WIDTH; x++) + for (int z = 0; z < WIDTH; z++) + biomes[x][z] = PreBio[offsetXZ(x, random)][offsetXZ(z, random)].getSubBiome(random); + } + + public BCLBiome getBiome(int x, int z) { + return biomes[x & MASK_WIDTH][z & MASK_WIDTH]; + } + + private int offsetXZ(int x, Random random) { + return ((x + random.nextInt(2)) >> 1) & MASK_OFFSET; + } +} diff --git a/src/main/java/ru/bclib/world/generator/BiomeMap.java b/src/main/java/ru/bclib/world/generator/BiomeMap.java new file mode 100644 index 00000000..088fda63 --- /dev/null +++ b/src/main/java/ru/bclib/world/generator/BiomeMap.java @@ -0,0 +1,113 @@ +package ru.bclib.world.generator; + +import java.util.Map; + +import com.google.common.collect.Maps; + +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.levelgen.WorldgenRandom; +import ru.bclib.noise.OpenSimplexNoise; +import ru.bclib.util.MHelper; +import ru.bclib.world.biomes.BCLBiome; + +public class BiomeMap { + private static final WorldgenRandom RANDOM = new WorldgenRandom(); + + private final Map maps = Maps.newHashMap(); + private final int size; + private final int sizeXZ; + private final int depth; + private final OpenSimplexNoise noiseX; + private final OpenSimplexNoise noiseZ; + private final BiomePicker picker; + private final long seed; + + public BiomeMap(long seed, int size, BiomePicker picker) { + maps.clear(); + RANDOM.setSeed(seed); + noiseX = new OpenSimplexNoise(RANDOM.nextLong()); + noiseZ = new OpenSimplexNoise(RANDOM.nextLong()); + this.sizeXZ = size; + depth = (int) Math.ceil(Math.log(size) / Math.log(2)) - 2; + this.size = 1 << depth; + this.picker = picker; + this.seed = seed; + } + + public long getSeed() { + return seed; + } + + public void clearCache() { + if (maps.size() > 32) { + maps.clear(); + } + } + + private BCLBiome getRawBiome(int bx, int bz) { + double x = (double) bx * size / sizeXZ; + double z = (double) bz * size / sizeXZ; + double nx = x; + double nz = z; + + double px = bx * 0.2; + double pz = bz * 0.2; + + for (int i = 0; i < depth; i++) { + nx = (x + noiseX.eval(px, pz)) / 2F; + nz = (z + noiseZ.eval(px, pz)) / 2F; + + x = nx; + z = nz; + + px = px / 2 + i; + pz = pz / 2 + i; + } + + bx = MHelper.floor(x); + bz = MHelper.floor(z); + if ((bx & BiomeChunk.MASK_WIDTH) == BiomeChunk.MASK_WIDTH) { + x += (bz / 2) & 1; + } + if ((bz & BiomeChunk.MASK_WIDTH) == BiomeChunk.MASK_WIDTH) { + z += (bx / 2) & 1; + } + + ChunkPos cpos = new ChunkPos(MHelper.floor(x / BiomeChunk.WIDTH), MHelper.floor(z / BiomeChunk.WIDTH)); + BiomeChunk chunk = maps.get(cpos); + if (chunk == null) { + RANDOM.setBaseChunkSeed(cpos.x, cpos.z); + chunk = new BiomeChunk(this, RANDOM, picker); + maps.put(cpos, chunk); + } + + return chunk.getBiome(MHelper.floor(x), MHelper.floor(z)); + } + + public BCLBiome getBiome(int x, int z) { + BCLBiome biome = getRawBiome(x, z); + + if (biome.hasEdge() || (biome.hasParentBiome() && biome.getParentBiome().hasEdge())) { + BCLBiome search = biome; + if (biome.hasParentBiome()) { + search = biome.getParentBiome(); + } + int d = (int) Math.ceil(search.getEdgeSize() / 4F) << 2; + + boolean edge = !search.isSame(getRawBiome(x + d, z)); + edge = edge || !search.isSame(getRawBiome(x - d, z)); + edge = edge || !search.isSame(getRawBiome(x, z + d)); + edge = edge || !search.isSame(getRawBiome(x, z - d)); + edge = edge || !search.isSame(getRawBiome(x - 1, z - 1)); + edge = edge || !search.isSame(getRawBiome(x - 1, z + 1)); + edge = edge || !search.isSame(getRawBiome(x + 1, z - 1)); + edge = edge || !search.isSame(getRawBiome(x + 1, z + 1)); + + if (edge) { + biome = search.getEdge(); + } + } + + return biome; + } +} diff --git a/src/main/java/ru/bclib/world/generator/BiomePicker.java b/src/main/java/ru/bclib/world/generator/BiomePicker.java new file mode 100644 index 00000000..57eb36dd --- /dev/null +++ b/src/main/java/ru/bclib/world/generator/BiomePicker.java @@ -0,0 +1,69 @@ +package ru.bclib.world.generator; + +import java.util.List; +import java.util.Random; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import net.minecraft.resources.ResourceLocation; +import ru.bclib.util.WeighTree; +import ru.bclib.util.WeightedList; +import ru.bclib.world.biomes.BCLBiome; + +public class BiomePicker { + private final Set immutableIDs = Sets.newHashSet(); + private final List biomes = Lists.newArrayList(); + private int biomeCount = 0; + private WeighTree tree; + + public void addBiome(BCLBiome biome) { + immutableIDs.add(biome.getID()); + biomes.add(biome); + biomeCount ++; + } + + public void addBiomeMutable(BCLBiome biome) { + biomes.add(biome); + } + + public void clearMutables() { + for (int i = biomes.size() - 1; i >= biomeCount; i--) { + biomes.remove(i); + } + } + + public BCLBiome getBiome(Random random) { + return biomes.isEmpty() ? null : tree.get(random); + } + + public List getBiomes() { + return biomes; + } + + public boolean containsImmutable(ResourceLocation id) { + return immutableIDs.contains(id); + } + + public void removeMutableBiome(ResourceLocation id) { + for (int i = biomeCount; i < biomes.size(); i++) { + BCLBiome biome = biomes.get(i); + if (biome.getID().equals(id)) { + biomes.remove(i); + break; + } + } + } + + public void rebuild() { + if (biomes.isEmpty()) { + return; + } + WeightedList list = new WeightedList(); + biomes.forEach((biome) -> { + list.add(biome, biome.getGenChance()); + }); + tree = new WeighTree(list); + } +} diff --git a/src/main/java/ru/bclib/world/generator/BiomeType.java b/src/main/java/ru/bclib/world/generator/BiomeType.java new file mode 100644 index 00000000..6e40eb2c --- /dev/null +++ b/src/main/java/ru/bclib/world/generator/BiomeType.java @@ -0,0 +1,6 @@ +package ru.bclib.world.generator; + +public enum BiomeType { + LAND, + VOID; +}