package net.minecraft.world.level.biome; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.longs.Long2FloatLinkedOpenHashMap; import java.util.Optional; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.core.RegistryCodecs; import net.minecraft.core.registries.Registries; import net.minecraft.resources.RegistryFileCodec; import net.minecraft.sounds.Music; import net.minecraft.sounds.SoundEvent; import net.minecraft.util.Mth; import net.minecraft.util.StringRepresentable; import net.minecraft.world.level.FoliageColor; import net.minecraft.world.level.GrassColor; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.LiquidBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.LegacyRandomSource; import net.minecraft.world.level.levelgen.WorldgenRandom; import net.minecraft.world.level.levelgen.synth.PerlinSimplexNoise; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.Fluids; import org.jetbrains.annotations.Nullable; public final class Biome { public static final Codec DIRECT_CODEC = RecordCodecBuilder.create( instance -> instance.group( Biome.ClimateSettings.CODEC.forGetter(biome -> biome.climateSettings), BiomeSpecialEffects.CODEC.fieldOf("effects").forGetter(biome -> biome.specialEffects), BiomeGenerationSettings.CODEC.forGetter(biome -> biome.generationSettings), MobSpawnSettings.CODEC.forGetter(biome -> biome.mobSettings) ) .apply(instance, Biome::new) ); public static final Codec NETWORK_CODEC = RecordCodecBuilder.create( instance -> instance.group( Biome.ClimateSettings.CODEC.forGetter(biome -> biome.climateSettings), BiomeSpecialEffects.CODEC.fieldOf("effects").forGetter(biome -> biome.specialEffects) ) .apply( instance, (climateSettings, biomeSpecialEffects) -> new Biome(climateSettings, biomeSpecialEffects, BiomeGenerationSettings.EMPTY, MobSpawnSettings.EMPTY) ) ); public static final Codec> CODEC = RegistryFileCodec.create(Registries.BIOME, DIRECT_CODEC); public static final Codec> LIST_CODEC = RegistryCodecs.homogeneousList(Registries.BIOME, DIRECT_CODEC); private static final PerlinSimplexNoise TEMPERATURE_NOISE = new PerlinSimplexNoise(new WorldgenRandom(new LegacyRandomSource(1234L)), ImmutableList.of(0)); static final PerlinSimplexNoise FROZEN_TEMPERATURE_NOISE = new PerlinSimplexNoise( new WorldgenRandom(new LegacyRandomSource(3456L)), ImmutableList.of(-2, -1, 0) ); @Deprecated( forRemoval = true ) public static final PerlinSimplexNoise BIOME_INFO_NOISE = new PerlinSimplexNoise(new WorldgenRandom(new LegacyRandomSource(2345L)), ImmutableList.of(0)); private static final int TEMPERATURE_CACHE_SIZE = 1024; private final Biome.ClimateSettings climateSettings; private final BiomeGenerationSettings generationSettings; private final MobSpawnSettings mobSettings; private final BiomeSpecialEffects specialEffects; private final ThreadLocal temperatureCache = ThreadLocal.withInitial(() -> Util.make(() -> { Long2FloatLinkedOpenHashMap long2FloatLinkedOpenHashMap = new Long2FloatLinkedOpenHashMap(1024, 0.25F) { @Override protected void rehash(int i) { } }; long2FloatLinkedOpenHashMap.defaultReturnValue(Float.NaN); return long2FloatLinkedOpenHashMap; })); Biome(Biome.ClimateSettings climateSettings, BiomeSpecialEffects specialEffects, BiomeGenerationSettings generationSettings, MobSpawnSettings mobSettings) { this.climateSettings = climateSettings; this.generationSettings = generationSettings; this.mobSettings = mobSettings; this.specialEffects = specialEffects; } public int getSkyColor() { return this.specialEffects.getSkyColor(); } public MobSpawnSettings getMobSettings() { return this.mobSettings; } public boolean hasPrecipitation() { return this.climateSettings.hasPrecipitation(); } public Biome.Precipitation getPrecipitationAt(BlockPos pos, int seaLevel) { if (!this.hasPrecipitation()) { return Biome.Precipitation.NONE; } else { return this.coldEnoughToSnow(pos, seaLevel) ? Biome.Precipitation.SNOW : Biome.Precipitation.RAIN; } } private float getHeightAdjustedTemperature(BlockPos pos, int seaLevel) { float f = this.climateSettings.temperatureModifier.modifyTemperature(pos, this.getBaseTemperature()); int i = seaLevel + 17; if (pos.getY() > i) { float g = (float)(TEMPERATURE_NOISE.getValue(pos.getX() / 8.0F, pos.getZ() / 8.0F, false) * 8.0); return f - (g + pos.getY() - i) * 0.05F / 40.0F; } else { return f; } } @Deprecated private float getTemperature(BlockPos pos, int seaLevel) { long l = pos.asLong(); Long2FloatLinkedOpenHashMap long2FloatLinkedOpenHashMap = (Long2FloatLinkedOpenHashMap)this.temperatureCache.get(); float f = long2FloatLinkedOpenHashMap.get(l); if (!Float.isNaN(f)) { return f; } else { float g = this.getHeightAdjustedTemperature(pos, seaLevel); if (long2FloatLinkedOpenHashMap.size() == 1024) { long2FloatLinkedOpenHashMap.removeFirstFloat(); } long2FloatLinkedOpenHashMap.put(l, g); return g; } } public boolean shouldFreeze(LevelReader level, BlockPos pos) { return this.shouldFreeze(level, pos, true); } public boolean shouldFreeze(LevelReader level, BlockPos water, boolean mustBeAtEdge) { if (this.warmEnoughToRain(water, level.getSeaLevel())) { return false; } else { if (level.isInsideBuildHeight(water.getY()) && level.getBrightness(LightLayer.BLOCK, water) < 10) { BlockState blockState = level.getBlockState(water); FluidState fluidState = level.getFluidState(water); if (fluidState.getType() == Fluids.WATER && blockState.getBlock() instanceof LiquidBlock) { if (!mustBeAtEdge) { return true; } boolean bl = level.isWaterAt(water.west()) && level.isWaterAt(water.east()) && level.isWaterAt(water.north()) && level.isWaterAt(water.south()); if (!bl) { return true; } } } return false; } } public boolean coldEnoughToSnow(BlockPos pos, int seaLevel) { return !this.warmEnoughToRain(pos, seaLevel); } public boolean warmEnoughToRain(BlockPos pos, int seaLevel) { return this.getTemperature(pos, seaLevel) >= 0.15F; } public boolean shouldMeltFrozenOceanIcebergSlightly(BlockPos pos, int seaLevel) { return this.getTemperature(pos, seaLevel) > 0.1F; } public boolean shouldSnow(LevelReader level, BlockPos pos) { if (this.warmEnoughToRain(pos, level.getSeaLevel())) { return false; } else { if (level.isInsideBuildHeight(pos.getY()) && level.getBrightness(LightLayer.BLOCK, pos) < 10) { BlockState blockState = level.getBlockState(pos); if ((blockState.isAir() || blockState.is(Blocks.SNOW)) && Blocks.SNOW.defaultBlockState().canSurvive(level, pos)) { return true; } } return false; } } public BiomeGenerationSettings getGenerationSettings() { return this.generationSettings; } public int getFogColor() { return this.specialEffects.getFogColor(); } public int getGrassColor(double posX, double posZ) { int i = (Integer)this.specialEffects.getGrassColorOverride().orElseGet(this::getGrassColorFromTexture); return this.specialEffects.getGrassColorModifier().modifyColor(posX, posZ, i); } private int getGrassColorFromTexture() { double d = Mth.clamp(this.climateSettings.temperature, 0.0F, 1.0F); double e = Mth.clamp(this.climateSettings.downfall, 0.0F, 1.0F); return GrassColor.get(d, e); } public int getFoliageColor() { return (Integer)this.specialEffects.getFoliageColorOverride().orElseGet(this::getFoliageColorFromTexture); } private int getFoliageColorFromTexture() { double d = Mth.clamp(this.climateSettings.temperature, 0.0F, 1.0F); double e = Mth.clamp(this.climateSettings.downfall, 0.0F, 1.0F); return FoliageColor.get(d, e); } public float getBaseTemperature() { return this.climateSettings.temperature; } public BiomeSpecialEffects getSpecialEffects() { return this.specialEffects; } public int getWaterColor() { return this.specialEffects.getWaterColor(); } public int getWaterFogColor() { return this.specialEffects.getWaterFogColor(); } public Optional getAmbientParticle() { return this.specialEffects.getAmbientParticleSettings(); } public Optional> getAmbientLoop() { return this.specialEffects.getAmbientLoopSoundEvent(); } public Optional getAmbientMood() { return this.specialEffects.getAmbientMoodSettings(); } public Optional getAmbientAdditions() { return this.specialEffects.getAmbientAdditionsSettings(); } public Optional getBackgroundMusic() { return this.specialEffects.getBackgroundMusic(); } public static class BiomeBuilder { private boolean hasPrecipitation = true; @Nullable private Float temperature; private Biome.TemperatureModifier temperatureModifier = Biome.TemperatureModifier.NONE; @Nullable private Float downfall; @Nullable private BiomeSpecialEffects specialEffects; @Nullable private MobSpawnSettings mobSpawnSettings; @Nullable private BiomeGenerationSettings generationSettings; public Biome.BiomeBuilder hasPrecipitation(boolean hasPercipitation) { this.hasPrecipitation = hasPercipitation; return this; } public Biome.BiomeBuilder temperature(float temperature) { this.temperature = temperature; return this; } public Biome.BiomeBuilder downfall(float downfall) { this.downfall = downfall; return this; } public Biome.BiomeBuilder specialEffects(BiomeSpecialEffects effects) { this.specialEffects = effects; return this; } public Biome.BiomeBuilder mobSpawnSettings(MobSpawnSettings mobSpawnSettings) { this.mobSpawnSettings = mobSpawnSettings; return this; } public Biome.BiomeBuilder generationSettings(BiomeGenerationSettings generationSettings) { this.generationSettings = generationSettings; return this; } public Biome.BiomeBuilder temperatureAdjustment(Biome.TemperatureModifier temperatureSettings) { this.temperatureModifier = temperatureSettings; return this; } public Biome build() { if (this.temperature != null && this.downfall != null && this.specialEffects != null && this.mobSpawnSettings != null && this.generationSettings != null) { return new Biome( new Biome.ClimateSettings(this.hasPrecipitation, this.temperature, this.temperatureModifier, this.downfall), this.specialEffects, this.generationSettings, this.mobSpawnSettings ); } else { throw new IllegalStateException("You are missing parameters to build a proper biome\n" + this); } } public String toString() { return "BiomeBuilder{\nhasPrecipitation=" + this.hasPrecipitation + ",\ntemperature=" + this.temperature + ",\ntemperatureModifier=" + this.temperatureModifier + ",\ndownfall=" + this.downfall + ",\nspecialEffects=" + this.specialEffects + ",\nmobSpawnSettings=" + this.mobSpawnSettings + ",\ngenerationSettings=" + this.generationSettings + ",\n}"; } } record ClimateSettings(boolean hasPrecipitation, float temperature, Biome.TemperatureModifier temperatureModifier, float downfall) { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group( Codec.BOOL.fieldOf("has_precipitation").forGetter(climateSettings -> climateSettings.hasPrecipitation), Codec.FLOAT.fieldOf("temperature").forGetter(climateSettings -> climateSettings.temperature), Biome.TemperatureModifier.CODEC .optionalFieldOf("temperature_modifier", Biome.TemperatureModifier.NONE) .forGetter(climateSettings -> climateSettings.temperatureModifier), Codec.FLOAT.fieldOf("downfall").forGetter(climateSettings -> climateSettings.downfall) ) .apply(instance, Biome.ClimateSettings::new) ); } public static enum Precipitation implements StringRepresentable { NONE("none"), RAIN("rain"), SNOW("snow"); public static final Codec CODEC = StringRepresentable.fromEnum(Biome.Precipitation::values); private final String name; private Precipitation(final String name) { this.name = name; } @Override public String getSerializedName() { return this.name; } } public static enum TemperatureModifier implements StringRepresentable { NONE("NONE", 0, "none"), FROZEN("FROZEN", 1, "frozen"); private final String name; public static final Codec CODEC = StringRepresentable.fromEnum(Biome.TemperatureModifier::values); public abstract float modifyTemperature(BlockPos pos, float temperature); TemperatureModifier(final String name) { this.name = name; } public String getName() { return this.name; } @Override public String getSerializedName() { return this.name; } } }