minecraft-src/net/minecraft/world/level/biome/Biome.java
2025-07-04 03:45:38 +03:00

409 lines
14 KiB
Java

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.util.random.WeightedList;
import net.minecraft.world.level.DryFoliageColor;
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<Biome> 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<Biome> 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<Holder<Biome>> CODEC = RegistryFileCodec.create(Registries.BIOME, DIRECT_CODEC);
public static final Codec<HolderSet<Biome>> 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<Long2FloatLinkedOpenHashMap> 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 = this.getBaseGrassColor();
return this.specialEffects.getGrassColorModifier().modifyColor(posX, posZ, i);
}
private int getBaseGrassColor() {
Optional<Integer> optional = this.specialEffects.getGrassColorOverride();
return optional.isPresent() ? (Integer)optional.get() : this.getGrassColorFromTexture();
}
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 int getDryFoliageColor() {
return (Integer)this.specialEffects.getDryFoliageColorOverride().orElseGet(this::getDryFoliageColorFromTexture);
}
private int getDryFoliageColorFromTexture() {
double d = Mth.clamp(this.climateSettings.temperature, 0.0F, 1.0F);
double e = Mth.clamp(this.climateSettings.downfall, 0.0F, 1.0F);
return DryFoliageColor.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<AmbientParticleSettings> getAmbientParticle() {
return this.specialEffects.getAmbientParticleSettings();
}
public Optional<Holder<SoundEvent>> getAmbientLoop() {
return this.specialEffects.getAmbientLoopSoundEvent();
}
public Optional<AmbientMoodSettings> getAmbientMood() {
return this.specialEffects.getAmbientMoodSettings();
}
public Optional<AmbientAdditionsSettings> getAmbientAdditions() {
return this.specialEffects.getAmbientAdditionsSettings();
}
public Optional<WeightedList<Music>> getBackgroundMusic() {
return this.specialEffects.getBackgroundMusic();
}
public float getBackgroundMusicVolume() {
return this.specialEffects.getBackgroundMusicVolume();
}
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<Biome.ClimateSettings> 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<Biome.Precipitation> 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<Biome.TemperatureModifier> 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;
}
}
}