Biome generator

This commit is contained in:
paulevsGitch 2020-09-20 23:20:43 +03:00
parent ce76ed9c7a
commit 8f08c310aa
14 changed files with 3173 additions and 44 deletions

View file

@ -0,0 +1,312 @@
package ru.betterend.world.biome;
import java.util.List;
import com.google.common.collect.Lists;
import net.minecraft.client.sound.MusicType;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.SpawnGroup;
import net.minecraft.sound.BiomeAdditionsSound;
import net.minecraft.sound.BiomeMoodSound;
import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.Biome.Category;
import net.minecraft.world.biome.Biome.Precipitation;
import net.minecraft.world.biome.BiomeEffects.Builder;
import net.minecraft.world.biome.BiomeParticleConfig;
import net.minecraft.world.biome.GenerationSettings;
import net.minecraft.world.biome.SpawnSettings;
import net.minecraft.world.gen.GenerationStep.Feature;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.ConfiguredStructureFeature;
import net.minecraft.world.gen.surfacebuilder.ConfiguredSurfaceBuilders;
import ru.betterend.BetterEnd;
import ru.betterend.MHelper;
public class BiomeDefinition
{
private final List<ConfiguredStructureFeature<?, ?>> structures = Lists.newArrayList();
private final List<FeatureInfo> features = Lists.newArrayList();
private final List<SpawnInfo> mobs = Lists.newArrayList();
private BiomeParticleConfig particleConfig;
private BiomeAdditionsSound additions;
private BiomeMoodSound mood;
private SoundEvent music;
private SoundEvent loop;
private int waterFogColor = 329011;
private int waterColor = 4159204;
private int fogColor = 3344392;
private boolean stalactites = true;
private boolean bnStructures = true;
private final Identifier id;
public BiomeDefinition(String name)
{
this.id = new Identifier(BetterEnd.MOD_ID, name);
}
public BiomeDefinition setStalactites(boolean value)
{
stalactites = value;
return this;
}
public BiomeDefinition setBNStructures(boolean value)
{
bnStructures = value;
return this;
}
/**
* Set default ores generation
* @param value - if true (default) then default ores will be generated
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition setDefaultOres(boolean value)
{
return this;
}
/**
* Set default nether structure features to be added
* @param value - if true (default) then default structure features (nether fortresses, caves, etc.) will be added into biome
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition setDefaultStructureFeatures(boolean value)
{
return this;
}
/**
* Set default nether features to be added
* @param value - if true (default) then default features (small structures) will be added into biome
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition setDefaultFeatures(boolean value)
{
return this;
}
/**
* Set default Nether Wastes mobs to be added
* @param value - if true (default) then default mobs will be added into biome
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition setDefaultMobs(boolean value)
{
return this;
}
public BiomeDefinition setParticleConfig(BiomeParticleConfig config)
{
this.particleConfig = config;
return this;
}
/**
* Adds mob into biome
* @param type - {@link EntityType}
* @param group - {@link SpawnGroup}
* @param weight - cumulative spawning weight
* @param minGroupSize - minimum count of mobs in the group
* @param maxGroupSize - maximum count of mobs in the group
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition addMobSpawn(EntityType<?> type, int weight, int minGroupSize, int maxGroupSize)
{
Identifier eID = Registry.ENTITY_TYPE.getId(type);
if (eID != Registry.ENTITY_TYPE.getDefaultId())
{
SpawnInfo info = new SpawnInfo();
info.type = type;
info.weight = weight;
info.minGroupSize = minGroupSize;
info.maxGroupSize = maxGroupSize;
mobs.add(info);
}
return this;
}
/**
* Adds feature (small structure) into biome - plants, ores, small buildings, etc.
* @param feature - {@link ConfiguredStructureFeature} to add
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition addStructureFeature(ConfiguredStructureFeature<?, ?> feature)
{
System.out.println("Structure " + feature);
structures.add(feature);
return this;
}
public BiomeDefinition addFeature(Feature featureStep, ConfiguredFeature<?, ?> feature)
{
FeatureInfo info = new FeatureInfo();
info.featureStep = featureStep;
info.feature = feature;
features.add(info);
return this;
}
/**
* Sets biome fog color
* @param r - Red [0 - 255]
* @param g - Green [0 - 255]
* @param b - Blue [0 - 255]
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition setFogColor(int r, int g, int b)
{
r = MathHelper.clamp(r, 0, 255);
g = MathHelper.clamp(g, 0, 255);
b = MathHelper.clamp(b, 0, 255);
this.fogColor = MHelper.color(r, g, b);
return this;
}
/**
* Sets biome water color
* @param r - Red [0 - 255]
* @param g - Green [0 - 255]
* @param b - Blue [0 - 255]
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition setWaterColor(int r, int g, int b)
{
r = MathHelper.clamp(r, 0, 255);
g = MathHelper.clamp(g, 0, 255);
b = MathHelper.clamp(b, 0, 255);
this.waterColor = MHelper.color(r, g, b);
return this;
}
/**
* Sets biome underwater fog color
* @param r - Red [0 - 255]
* @param g - Green [0 - 255]
* @param b - Blue [0 - 255]
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition setWaterFogColor(int r, int g, int b)
{
r = MathHelper.clamp(r, 0, 255);
g = MathHelper.clamp(g, 0, 255);
b = MathHelper.clamp(b, 0, 255);
this.waterFogColor = MHelper.color(r, g, b);
return this;
}
/**
* Plays in never-ending loop for as long as player is in the biome
* @param loop - SoundEvent
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition setLoop(SoundEvent loop)
{
this.loop = loop;
return this;
}
/**
* Plays commonly while the player is in the biome
* @param mood - SoundEvent
* @return this {@link BiomeDefinition}
*/
public BiomeDefinition setMood(SoundEvent mood)
{
this.mood = new BiomeMoodSound(mood, 6000, 8, 2.0D);
return this;
}
/**
* Set additional sounds. They plays once every 6000-17999 ticks while the player is in the biome
* @param additions - SoundEvent
* @return this BiomeDefenition
*/
public BiomeDefinition setAdditions(SoundEvent additions)
{
this.additions = new BiomeAdditionsSound(additions, 0.0111);
return this;
}
/**
* Set background music for biome
* @param music
* @return
*/
public BiomeDefinition setMusic(SoundEvent music)
{
this.music = music;
return this;
}
public Biome build()
{
SpawnSettings.Builder spawnSettings = new SpawnSettings.Builder();
GenerationSettings.Builder generationSettings = new GenerationSettings.Builder();
Builder effects = new Builder();
mobs.forEach((spawn) -> { spawnSettings.spawn(spawn.type.getSpawnGroup(), new SpawnSettings.SpawnEntry(spawn.type, spawn.weight, spawn.minGroupSize, spawn.maxGroupSize)); });
generationSettings.surfaceBuilder(ConfiguredSurfaceBuilders.END);
structures.forEach((structure) -> generationSettings.structureFeature(structure));
features.forEach((info) -> generationSettings.feature(info.featureStep, info.feature));
effects.skyColor(fogColor).waterColor(waterColor).waterFogColor(waterFogColor).fogColor(fogColor);
if (loop != null) effects.loopSound(loop);
if (mood != null) effects.moodSound(mood);
if (additions != null) effects.additionsSound(additions);
if (particleConfig != null) effects.particleConfig(particleConfig);
effects.music(MusicType.createIngameMusic(music != null ? music : SoundEvents.MUSIC_END));
return new Biome.Builder()
.precipitation(Precipitation.NONE)
.category(Category.THEEND)
.depth(0.1F)
.scale(0.2F)
.temperature(2.0F)
.downfall(0.0F)
.effects(effects.build())
.spawnSettings(spawnSettings.build())
.generationSettings(generationSettings.build())
.build();
}
private static final class SpawnInfo
{
EntityType<?> type;
int weight;
int minGroupSize;
int maxGroupSize;
}
private static final class FeatureInfo
{
Feature featureStep;
ConfiguredFeature<?, ?> feature;
}
public Identifier getID()
{
return id;
}
public boolean hasStalactites()
{
return stalactites;
}
public boolean hasBNStructures()
{
return bnStructures;
}
}

View file

@ -0,0 +1,163 @@
package ru.betterend.world.biome;
import java.util.List;
import java.util.Random;
import com.google.common.collect.Lists;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.world.WorldAccess;
import net.minecraft.world.biome.Biome;
public class EndBiome
{
protected List<Subbiome> subbiomes = Lists.newArrayList();
protected final Biome biome;
protected final Identifier mcID;
protected EndBiome edge;
protected int edgeSize;
protected EndBiome biomeParent;
protected float maxSubBiomeChance = 1;
protected float genChance = 1;
protected float noiseDensity = 0.3F;
protected float plantDensity = 1.0001F;
public EndBiome(BiomeDefinition definition)
{
biome = definition.build();
mcID = definition.getID();
}
public EndBiome(Biome biome)
{
this.biome = biome;
mcID = BuiltinRegistries.BIOME.getId(biome);
}
public void setPlantDensity(float density)
{
this.plantDensity = density * 1.0001F;
}
public float getPlantDensity()
{
return plantDensity;
}
public void setNoiseDensity(float density)
{
this.noiseDensity = 1 - density * 2;
}
public float getNoiseDensity()
{
return (1F - this.noiseDensity) / 2F;
}
public void genSurfColumn(WorldAccess world, BlockPos pos, Random random) {}
public EndBiome getEdge()
{
return edge == null ? this : edge;
}
public void setEdge(EndBiome edge)
{
this.edge = edge;
edge.biomeParent = this;
}
public int getEdgeSize()
{
return edgeSize;
}
public void setEdgeSize(int size)
{
edgeSize = size;
}
public void addSubBiome(EndBiome biome, float chance)
{
maxSubBiomeChance += chance;
biome.biomeParent = this;
subbiomes.add(new Subbiome(biome, maxSubBiomeChance));
}
public EndBiome getSubBiome(Random random)
{
float chance = random.nextFloat() * maxSubBiomeChance;
for (Subbiome biome: subbiomes)
if (biome.canGenerate(chance))
return biome.biome;
return this;
}
public EndBiome getParentBiome()
{
return this.biomeParent;
}
public boolean hasEdge()
{
return edge != null;
}
public boolean hasParentBiome()
{
return biomeParent != null;
}
public boolean isSame(EndBiome biome)
{
return biome == this || (biome.hasParentBiome() && biome.getParentBiome() == this);
}
protected final class Subbiome
{
EndBiome biome;
float chance;
Subbiome(EndBiome biome, float chance)
{
this.biome = biome;
this.chance = chance;
}
public boolean canGenerate(float chance)
{
return chance < this.chance;
}
}
public boolean canGenerate(float chance)
{
return chance <= this.genChance;
}
public float setGenChance(float chance)
{
genChance += chance;
return genChance;
}
public Biome getBiome()
{
return biome;
}
@Override
public String toString()
{
return mcID.toString();
}
public Identifier getID()
{
return mcID;
}
}

View file

@ -0,0 +1,62 @@
package ru.betterend.world.generator;
import java.util.Collections;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryLookupCodec;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.BiomeSource;
import ru.betterend.BetterEnd;
import ru.betterend.registry.BiomeRegistry;
import ru.betterend.world.biome.EndBiome;
public class BetterEndBiomeSource extends BiomeSource {
public static final Codec<BetterEndBiomeSource> CODEC = RecordCodecBuilder.create((instance) -> {
return instance.group(RegistryLookupCodec.of(Registry.BIOME_KEY).forGetter((theEndBiomeSource) -> {
return theEndBiomeSource.biomeRegistry;
}), Codec.LONG.fieldOf("seed").stable().forGetter((theEndBiomeSource) -> {
return theEndBiomeSource.seed;
})).apply(instance, instance.stable(BetterEndBiomeSource::new));
});
private BiomeMap map;
private final long seed;
private final Registry<Biome> biomeRegistry;
public BetterEndBiomeSource(Registry<Biome> biomeRegistry, long seed) {
super(Collections.emptyList());
this.seed = seed;
this.map = new BiomeMap(seed, 50);
this.biomeRegistry = biomeRegistry;
BiomeRegistry.MUTABLE.clear();
for (EndBiome biome : BiomePicker.getBiomes())
BiomeRegistry.MUTABLE.put(biomeRegistry.getOrThrow(BiomeRegistry.getBiomeKey(biome)), biome);
}
@Override
public Biome getBiomeForNoiseGen(int biomeX, int biomeY, int biomeZ) {
EndBiome netherBiome = map.getBiome(biomeX << 2, biomeZ << 2);
if (biomeX == 0 && biomeZ == 0) {
map.clearCache();
}
return biomeRegistry.getOrThrow(BiomeRegistry.getBiomeKey(netherBiome));
}
@Override
public BiomeSource withSeed(long seed) {
return new BetterEndBiomeSource(biomeRegistry, seed);
}
@Override
protected Codec<? extends BiomeSource> getCodec() {
return CODEC;
}
public static void register() {
Registry.register(Registry.BIOME_SOURCE, new Identifier(BetterEnd.MOD_ID, "better_end_biome_source"), CODEC);
}
}

View file

@ -0,0 +1,39 @@
package ru.betterend.world.generator;
import java.util.Random;
import ru.betterend.world.biome.EndBiome;
public class BiomeChunk
{
protected static final int WIDTH = 16;
private static final int SM_WIDTH = WIDTH >> 1;
private static final int MASK_A = SM_WIDTH - 1;
private static final int MASK_C = WIDTH - 1;
private final EndBiome[][] biomes;
public BiomeChunk(BiomeMap map, Random random)
{
EndBiome[][] PreBio = new EndBiome[SM_WIDTH][SM_WIDTH];
biomes = new EndBiome[WIDTH][WIDTH];
for (int x = 0; x < SM_WIDTH; x++)
for (int z = 0; z < SM_WIDTH; z++)
PreBio[x][z] = BiomePicker.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 EndBiome getBiome(int x, int z)
{
return biomes[x & MASK_C][z & MASK_C];
}
private int offsetXZ(int x, Random random)
{
return ((x + random.nextInt(2)) >> 1) & MASK_A;
}
}

View file

@ -0,0 +1,100 @@
package ru.betterend.world.generator;
import java.util.HashMap;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.gen.ChunkRandom;
import ru.betterend.MHelper;
import ru.betterend.noise.OpenSimplexNoise;
import ru.betterend.world.biome.EndBiome;
public class BiomeMap
{
private static final HashMap<ChunkPos, BiomeChunk> MAPS = new HashMap<ChunkPos, BiomeChunk>();
private static final ChunkRandom RANDOM = new ChunkRandom();
private final int size;
private final int sizeXZ;
private final int depth;
private final OpenSimplexNoise noiseX;;
private final OpenSimplexNoise noiseZ;
public BiomeMap(long seed, int size)
{
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;
}
public void clearCache()
{
if (MAPS.size() > 16)
MAPS.clear();
}
private EndBiome 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;
}
ChunkPos cpos = new ChunkPos(MHelper.floor((double) x / BiomeChunk.WIDTH), MHelper.floor((double) z / BiomeChunk.WIDTH));
BiomeChunk chunk = MAPS.get(cpos);
if (chunk == null)
{
RANDOM.setTerrainSeed(cpos.x, cpos.z);
chunk = new BiomeChunk(this, RANDOM);
MAPS.put(cpos, chunk);
}
return chunk.getBiome(MHelper.floor(x), MHelper.floor(z));
}
public EndBiome getBiome(int x, int z)
{
EndBiome biome = getRawBiome(x, z);
if (biome.hasEdge() || (biome.hasParentBiome() && biome.getParentBiome().hasEdge()))
{
EndBiome 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;
}
}

View file

@ -0,0 +1,30 @@
package ru.betterend.world.generator;
import java.util.List;
import java.util.Random;
import com.google.common.collect.Lists;
import ru.betterend.world.biome.EndBiome;
public class BiomePicker {
private static final List<EndBiome> BIOMES = Lists.newArrayList();
private static float maxChance = 0;
public static void addBiome(EndBiome biome) {
BIOMES.add(biome);
maxChance = biome.setGenChance(maxChance);
}
public static EndBiome getBiome(Random random) {
float chance = random.nextFloat() * maxChance;
for (EndBiome biome: BIOMES)
if (biome.canGenerate(chance))
return biome;
return null;
}
public static List<EndBiome> getBiomes() {
return BIOMES;
}
}