419 lines
16 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|