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

419 lines
16 KiB
Java

package net.minecraft.world.level.levelgen;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.collect.Sets;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.text.DecimalFormat;
import java.util.List;
import java.util.OptionalInt;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import java.util.function.Supplier;
import net.minecraft.SharedConstants;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.QuartPos;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.util.Mth;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NaturalSpawner;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeGenerationSettings;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.BiomeResolver;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.CarvingMask;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.levelgen.Aquifer.FluidPicker;
import net.minecraft.world.level.levelgen.Aquifer.FluidStatus;
import net.minecraft.world.level.levelgen.blending.Blender;
import net.minecraft.world.level.levelgen.carver.CarvingContext;
import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;
import org.apache.commons.lang3.mutable.MutableObject;
import org.jetbrains.annotations.Nullable;
public final class NoiseBasedChunkGenerator extends ChunkGenerator {
public static final MapCodec<NoiseBasedChunkGenerator> CODEC = RecordCodecBuilder.mapCodec(
instance -> instance.group(
BiomeSource.CODEC.fieldOf("biome_source").forGetter(noiseBasedChunkGenerator -> noiseBasedChunkGenerator.biomeSource),
NoiseGeneratorSettings.CODEC.fieldOf("settings").forGetter(noiseBasedChunkGenerator -> noiseBasedChunkGenerator.settings)
)
.apply(instance, instance.stable(NoiseBasedChunkGenerator::new))
);
private static final BlockState AIR = Blocks.AIR.defaultBlockState();
private final Holder<NoiseGeneratorSettings> settings;
private final Supplier<FluidPicker> globalFluidPicker;
public NoiseBasedChunkGenerator(BiomeSource biomeSource, Holder<NoiseGeneratorSettings> settings) {
super(biomeSource);
this.settings = settings;
this.globalFluidPicker = Suppliers.memoize(() -> createFluidPicker(settings.value()));
}
private static FluidPicker createFluidPicker(NoiseGeneratorSettings settings) {
FluidStatus fluidStatus = new FluidStatus(-54, Blocks.LAVA.defaultBlockState());
int i = settings.seaLevel();
FluidStatus fluidStatus2 = new FluidStatus(i, settings.defaultFluid());
FluidStatus fluidStatus3 = new FluidStatus(DimensionType.MIN_Y * 2, Blocks.AIR.defaultBlockState());
return (j, k, l) -> k < Math.min(-54, i) ? fluidStatus : fluidStatus2;
}
@Override
public CompletableFuture<ChunkAccess> createBiomes(RandomState randomState, Blender blender, StructureManager structureManager, ChunkAccess chunk) {
return CompletableFuture.supplyAsync(() -> {
this.doCreateBiomes(blender, randomState, structureManager, chunk);
return chunk;
}, Util.backgroundExecutor().forName("init_biomes"));
}
private void doCreateBiomes(Blender blender, RandomState random, StructureManager structureManager, ChunkAccess chunk) {
NoiseChunk noiseChunk = chunk.getOrCreateNoiseChunk(chunkAccess -> this.createNoiseChunk(chunkAccess, structureManager, blender, random));
BiomeResolver biomeResolver = BelowZeroRetrogen.getBiomeResolver(blender.getBiomeResolver(this.biomeSource), chunk);
chunk.fillBiomesFromNoise(biomeResolver, noiseChunk.cachedClimateSampler(random.router(), this.settings.value().spawnTarget()));
}
private NoiseChunk createNoiseChunk(ChunkAccess chunk, StructureManager structureManager, Blender blender, RandomState random) {
return NoiseChunk.forChunk(
chunk, random, Beardifier.forStructuresInChunk(structureManager, chunk.getPos()), this.settings.value(), (FluidPicker)this.globalFluidPicker.get(), blender
);
}
@Override
protected MapCodec<? extends ChunkGenerator> codec() {
return CODEC;
}
public Holder<NoiseGeneratorSettings> generatorSettings() {
return this.settings;
}
public boolean stable(ResourceKey<NoiseGeneratorSettings> settings) {
return this.settings.is(settings);
}
@Override
public int getBaseHeight(int x, int z, Heightmap.Types type, LevelHeightAccessor level, RandomState random) {
return this.iterateNoiseColumn(level, random, x, z, null, type.isOpaque()).orElse(level.getMinY());
}
@Override
public NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor height, RandomState random) {
MutableObject<NoiseColumn> mutableObject = new MutableObject<>();
this.iterateNoiseColumn(height, random, x, z, mutableObject, null);
return mutableObject.getValue();
}
@Override
public void addDebugScreenInfo(List<String> info, RandomState random, BlockPos pos) {
DecimalFormat decimalFormat = new DecimalFormat("0.000");
NoiseRouter noiseRouter = random.router();
DensityFunction.SinglePointContext singlePointContext = new DensityFunction.SinglePointContext(pos.getX(), pos.getY(), pos.getZ());
double d = noiseRouter.ridges().compute(singlePointContext);
info.add(
"NoiseRouter T: "
+ decimalFormat.format(noiseRouter.temperature().compute(singlePointContext))
+ " V: "
+ decimalFormat.format(noiseRouter.vegetation().compute(singlePointContext))
+ " C: "
+ decimalFormat.format(noiseRouter.continents().compute(singlePointContext))
+ " E: "
+ decimalFormat.format(noiseRouter.erosion().compute(singlePointContext))
+ " D: "
+ decimalFormat.format(noiseRouter.depth().compute(singlePointContext))
+ " W: "
+ decimalFormat.format(d)
+ " PV: "
+ decimalFormat.format(NoiseRouterData.peaksAndValleys((float)d))
+ " AS: "
+ decimalFormat.format(noiseRouter.initialDensityWithoutJaggedness().compute(singlePointContext))
+ " N: "
+ decimalFormat.format(noiseRouter.finalDensity().compute(singlePointContext))
);
}
private OptionalInt iterateNoiseColumn(
LevelHeightAccessor level, RandomState random, int x, int z, @Nullable MutableObject<NoiseColumn> column, @Nullable Predicate<BlockState> stoppingState
) {
NoiseSettings noiseSettings = this.settings.value().noiseSettings().clampToHeightAccessor(level);
int i = noiseSettings.getCellHeight();
int j = noiseSettings.minY();
int k = Mth.floorDiv(j, i);
int l = Mth.floorDiv(noiseSettings.height(), i);
if (l <= 0) {
return OptionalInt.empty();
} else {
BlockState[] blockStates;
if (column == null) {
blockStates = null;
} else {
blockStates = new BlockState[noiseSettings.height()];
column.setValue(new NoiseColumn(j, blockStates));
}
int m = noiseSettings.getCellWidth();
int n = Math.floorDiv(x, m);
int o = Math.floorDiv(z, m);
int p = Math.floorMod(x, m);
int q = Math.floorMod(z, m);
int r = n * m;
int s = o * m;
double d = (double)p / m;
double e = (double)q / m;
NoiseChunk noiseChunk = new NoiseChunk(
1,
random,
r,
s,
noiseSettings,
DensityFunctions.BeardifierMarker.INSTANCE,
this.settings.value(),
(FluidPicker)this.globalFluidPicker.get(),
Blender.empty()
);
noiseChunk.initializeForFirstCellX();
noiseChunk.advanceCellX(0);
for (int t = l - 1; t >= 0; t--) {
noiseChunk.selectCellYZ(t, 0);
for (int u = i - 1; u >= 0; u--) {
int v = (k + t) * i + u;
double f = (double)u / i;
noiseChunk.updateForY(v, f);
noiseChunk.updateForX(x, d);
noiseChunk.updateForZ(z, e);
BlockState blockState = noiseChunk.getInterpolatedState();
BlockState blockState2 = blockState == null ? this.settings.value().defaultBlock() : blockState;
if (blockStates != null) {
int w = t * i + u;
blockStates[w] = blockState2;
}
if (stoppingState != null && stoppingState.test(blockState2)) {
noiseChunk.stopInterpolation();
return OptionalInt.of(v + 1);
}
}
}
noiseChunk.stopInterpolation();
return OptionalInt.empty();
}
}
@Override
public void buildSurface(WorldGenRegion level, StructureManager structureManager, RandomState random, ChunkAccess chunk) {
if (!SharedConstants.debugVoidTerrain(chunk.getPos())) {
WorldGenerationContext worldGenerationContext = new WorldGenerationContext(this, level);
this.buildSurface(
chunk, worldGenerationContext, random, structureManager, level.getBiomeManager(), level.registryAccess().lookupOrThrow(Registries.BIOME), Blender.of(level)
);
}
}
@VisibleForTesting
public void buildSurface(
ChunkAccess chunk,
WorldGenerationContext context,
RandomState random,
StructureManager structureManager,
BiomeManager biomeManager,
Registry<Biome> biomes,
Blender blender
) {
NoiseChunk noiseChunk = chunk.getOrCreateNoiseChunk(chunkAccess -> this.createNoiseChunk(chunkAccess, structureManager, blender, random));
NoiseGeneratorSettings noiseGeneratorSettings = this.settings.value();
random.surfaceSystem()
.buildSurface(random, biomeManager, biomes, noiseGeneratorSettings.useLegacyRandomSource(), context, chunk, noiseChunk, noiseGeneratorSettings.surfaceRule());
}
@Override
public void applyCarvers(WorldGenRegion level, long seed, RandomState random, BiomeManager biomeManager, StructureManager structureManager, ChunkAccess chunk) {
BiomeManager biomeManager2 = biomeManager.withDifferentSource((ix, jx, kx) -> this.biomeSource.getNoiseBiome(ix, jx, kx, random.sampler()));
WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
int i = 8;
ChunkPos chunkPos = chunk.getPos();
NoiseChunk noiseChunk = chunk.getOrCreateNoiseChunk(chunkAccessx -> this.createNoiseChunk(chunkAccessx, structureManager, Blender.of(level), random));
Aquifer aquifer = noiseChunk.aquifer();
CarvingContext carvingContext = new CarvingContext(
this, level.registryAccess(), chunk.getHeightAccessorForGeneration(), noiseChunk, random, this.settings.value().surfaceRule()
);
CarvingMask carvingMask = ((ProtoChunk)chunk).getOrCreateCarvingMask();
for (int j = -8; j <= 8; j++) {
for (int k = -8; k <= 8; k++) {
ChunkPos chunkPos2 = new ChunkPos(chunkPos.x + j, chunkPos.z + k);
ChunkAccess chunkAccess = level.getChunk(chunkPos2.x, chunkPos2.z);
BiomeGenerationSettings biomeGenerationSettings = chunkAccess.carverBiome(
() -> this.getBiomeGenerationSettings(
this.biomeSource.getNoiseBiome(QuartPos.fromBlock(chunkPos2.getMinBlockX()), 0, QuartPos.fromBlock(chunkPos2.getMinBlockZ()), random.sampler())
)
);
Iterable<Holder<ConfiguredWorldCarver<?>>> iterable = biomeGenerationSettings.getCarvers();
int l = 0;
for (Holder<ConfiguredWorldCarver<?>> holder : iterable) {
ConfiguredWorldCarver<?> configuredWorldCarver = holder.value();
worldgenRandom.setLargeFeatureSeed(seed + l, chunkPos2.x, chunkPos2.z);
if (configuredWorldCarver.isStartChunk(worldgenRandom)) {
configuredWorldCarver.carve(carvingContext, chunk, biomeManager2::getBiome, worldgenRandom, aquifer, chunkPos2, carvingMask);
}
l++;
}
}
}
}
@Override
public CompletableFuture<ChunkAccess> fillFromNoise(Blender blender, RandomState randomState, StructureManager structureManager, ChunkAccess chunk) {
NoiseSettings noiseSettings = this.settings.value().noiseSettings().clampToHeightAccessor(chunk.getHeightAccessorForGeneration());
int i = noiseSettings.minY();
int j = Mth.floorDiv(i, noiseSettings.getCellHeight());
int k = Mth.floorDiv(noiseSettings.height(), noiseSettings.getCellHeight());
return k <= 0 ? CompletableFuture.completedFuture(chunk) : CompletableFuture.supplyAsync(() -> {
int l = chunk.getSectionIndex(k * noiseSettings.getCellHeight() - 1 + i);
int m = chunk.getSectionIndex(i);
Set<LevelChunkSection> set = Sets.<LevelChunkSection>newHashSet();
for (int n = l; n >= m; n--) {
LevelChunkSection levelChunkSection = chunk.getSection(n);
levelChunkSection.acquire();
set.add(levelChunkSection);
}
ChunkAccess var20;
try {
var20 = this.doFill(blender, structureManager, randomState, chunk, j, k);
} finally {
for (LevelChunkSection levelChunkSection3 : set) {
levelChunkSection3.release();
}
}
return var20;
}, Util.backgroundExecutor().forName("wgen_fill_noise"));
}
private ChunkAccess doFill(Blender blender, StructureManager structureManager, RandomState random, ChunkAccess chunk, int minCellY, int cellCountY) {
NoiseChunk noiseChunk = chunk.getOrCreateNoiseChunk(chunkAccess -> this.createNoiseChunk(chunkAccess, structureManager, blender, random));
Heightmap heightmap = chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
Heightmap heightmap2 = chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
ChunkPos chunkPos = chunk.getPos();
int i = chunkPos.getMinBlockX();
int j = chunkPos.getMinBlockZ();
Aquifer aquifer = noiseChunk.aquifer();
noiseChunk.initializeForFirstCellX();
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
int k = noiseChunk.cellWidth();
int l = noiseChunk.cellHeight();
int m = 16 / k;
int n = 16 / k;
for (int o = 0; o < m; o++) {
noiseChunk.advanceCellX(o);
for (int p = 0; p < n; p++) {
int q = chunk.getSectionsCount() - 1;
LevelChunkSection levelChunkSection = chunk.getSection(q);
for (int r = cellCountY - 1; r >= 0; r--) {
noiseChunk.selectCellYZ(r, p);
for (int s = l - 1; s >= 0; s--) {
int t = (minCellY + r) * l + s;
int u = t & 15;
int v = chunk.getSectionIndex(t);
if (q != v) {
q = v;
levelChunkSection = chunk.getSection(v);
}
double d = (double)s / l;
noiseChunk.updateForY(t, d);
for (int w = 0; w < k; w++) {
int x = i + o * k + w;
int y = x & 15;
double e = (double)w / k;
noiseChunk.updateForX(x, e);
for (int z = 0; z < k; z++) {
int aa = j + p * k + z;
int ab = aa & 15;
double f = (double)z / k;
noiseChunk.updateForZ(aa, f);
BlockState blockState = noiseChunk.getInterpolatedState();
if (blockState == null) {
blockState = this.settings.value().defaultBlock();
}
blockState = this.debugPreliminarySurfaceLevel(noiseChunk, x, t, aa, blockState);
if (blockState != AIR && !SharedConstants.debugVoidTerrain(chunk.getPos())) {
levelChunkSection.setBlockState(y, u, ab, blockState, false);
heightmap.update(y, t, ab, blockState);
heightmap2.update(y, t, ab, blockState);
if (aquifer.shouldScheduleFluidUpdate() && !blockState.getFluidState().isEmpty()) {
mutableBlockPos.set(x, t, aa);
chunk.markPosForPostprocessing(mutableBlockPos);
}
}
}
}
}
}
}
noiseChunk.swapSlices();
}
noiseChunk.stopInterpolation();
return chunk;
}
private BlockState debugPreliminarySurfaceLevel(NoiseChunk chunk, int x, int y, int z, BlockState state) {
return state;
}
@Override
public int getGenDepth() {
return this.settings.value().noiseSettings().height();
}
@Override
public int getSeaLevel() {
return this.settings.value().seaLevel();
}
@Override
public int getMinY() {
return this.settings.value().noiseSettings().minY();
}
@Override
public void spawnOriginalMobs(WorldGenRegion level) {
if (!this.settings.value().disableMobGeneration()) {
ChunkPos chunkPos = level.getCenter();
Holder<Biome> holder = level.getBiome(chunkPos.getWorldPosition().atY(level.getMaxY()));
WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
worldgenRandom.setDecorationSeed(level.getSeed(), chunkPos.getMinBlockX(), chunkPos.getMinBlockZ());
NaturalSpawner.spawnMobsForChunkGeneration(level, holder, chunkPos, worldgenRandom);
}
}
}