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 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 settings; private final Supplier globalFluidPicker; public NoiseBasedChunkGenerator(BiomeSource biomeSource, Holder 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 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 codec() { return CODEC; } public Holder generatorSettings() { return this.settings; } public boolean stable(ResourceKey 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 mutableObject = new MutableObject<>(); this.iterateNoiseColumn(height, random, x, z, mutableObject, null); return mutableObject.getValue(); } @Override public void addDebugScreenInfo(List 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 column, @Nullable Predicate 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 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>> iterable = biomeGenerationSettings.getCarvers(); int l = 0; for (Holder> 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 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 set = Sets.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 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); } } }