package net.minecraft.world.level.levelgen; import java.util.Arrays; import java.util.Optional; import java.util.function.Function; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.biome.Biomes; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.BlockColumn; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.levelgen.SurfaceRules.Context; import net.minecraft.world.level.levelgen.SurfaceRules.RuleSource; import net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule; import net.minecraft.world.level.levelgen.carver.CarvingContext; import net.minecraft.world.level.levelgen.synth.NormalNoise; public class SurfaceSystem { private static final BlockState WHITE_TERRACOTTA = Blocks.WHITE_TERRACOTTA.defaultBlockState(); private static final BlockState ORANGE_TERRACOTTA = Blocks.ORANGE_TERRACOTTA.defaultBlockState(); private static final BlockState TERRACOTTA = Blocks.TERRACOTTA.defaultBlockState(); private static final BlockState YELLOW_TERRACOTTA = Blocks.YELLOW_TERRACOTTA.defaultBlockState(); private static final BlockState BROWN_TERRACOTTA = Blocks.BROWN_TERRACOTTA.defaultBlockState(); private static final BlockState RED_TERRACOTTA = Blocks.RED_TERRACOTTA.defaultBlockState(); private static final BlockState LIGHT_GRAY_TERRACOTTA = Blocks.LIGHT_GRAY_TERRACOTTA.defaultBlockState(); private static final BlockState PACKED_ICE = Blocks.PACKED_ICE.defaultBlockState(); private static final BlockState SNOW_BLOCK = Blocks.SNOW_BLOCK.defaultBlockState(); private final BlockState defaultBlock; private final int seaLevel; private final BlockState[] clayBands; private final NormalNoise clayBandsOffsetNoise; private final NormalNoise badlandsPillarNoise; private final NormalNoise badlandsPillarRoofNoise; private final NormalNoise badlandsSurfaceNoise; private final NormalNoise icebergPillarNoise; private final NormalNoise icebergPillarRoofNoise; private final NormalNoise icebergSurfaceNoise; private final PositionalRandomFactory noiseRandom; private final NormalNoise surfaceNoise; private final NormalNoise surfaceSecondaryNoise; public SurfaceSystem(RandomState randomState, BlockState defaultBlock, int seaLevel, PositionalRandomFactory noiseRandom) { this.defaultBlock = defaultBlock; this.seaLevel = seaLevel; this.noiseRandom = noiseRandom; this.clayBandsOffsetNoise = randomState.getOrCreateNoise(Noises.CLAY_BANDS_OFFSET); this.clayBands = generateBands(noiseRandom.fromHashOf(ResourceLocation.withDefaultNamespace("clay_bands"))); this.surfaceNoise = randomState.getOrCreateNoise(Noises.SURFACE); this.surfaceSecondaryNoise = randomState.getOrCreateNoise(Noises.SURFACE_SECONDARY); this.badlandsPillarNoise = randomState.getOrCreateNoise(Noises.BADLANDS_PILLAR); this.badlandsPillarRoofNoise = randomState.getOrCreateNoise(Noises.BADLANDS_PILLAR_ROOF); this.badlandsSurfaceNoise = randomState.getOrCreateNoise(Noises.BADLANDS_SURFACE); this.icebergPillarNoise = randomState.getOrCreateNoise(Noises.ICEBERG_PILLAR); this.icebergPillarRoofNoise = randomState.getOrCreateNoise(Noises.ICEBERG_PILLAR_ROOF); this.icebergSurfaceNoise = randomState.getOrCreateNoise(Noises.ICEBERG_SURFACE); } public void buildSurface( RandomState randomState, BiomeManager biomeManager, Registry biomes, boolean useLegacyRandomSource, WorldGenerationContext context, ChunkAccess chunk, NoiseChunk noiseChunk, RuleSource ruleSource ) { final BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); final ChunkPos chunkPos = chunk.getPos(); int i = chunkPos.getMinBlockX(); int j = chunkPos.getMinBlockZ(); BlockColumn blockColumn = new BlockColumn() { @Override public BlockState getBlock(int pos) { return chunk.getBlockState(mutableBlockPos.setY(pos)); } @Override public void setBlock(int pos, BlockState state) { LevelHeightAccessor levelHeightAccessor = chunk.getHeightAccessorForGeneration(); if (levelHeightAccessor.isInsideBuildHeight(pos)) { chunk.setBlockState(mutableBlockPos.setY(pos), state); if (!state.getFluidState().isEmpty()) { chunk.markPosForPostprocessing(mutableBlockPos); } } } public String toString() { return "ChunkBlockColumn " + chunkPos; } }; Context context2 = new Context(this, randomState, chunk, noiseChunk, biomeManager::getBiome, biomes, context); SurfaceRule surfaceRule = (SurfaceRule)ruleSource.apply(context2); BlockPos.MutableBlockPos mutableBlockPos2 = new BlockPos.MutableBlockPos(); for (int k = 0; k < 16; k++) { for (int l = 0; l < 16; l++) { int m = i + k; int n = j + l; int o = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, k, l) + 1; mutableBlockPos.setX(m).setZ(n); Holder holder = biomeManager.getBiome(mutableBlockPos2.set(m, useLegacyRandomSource ? 0 : o, n)); if (holder.is(Biomes.ERODED_BADLANDS)) { this.erodedBadlandsExtension(blockColumn, m, n, o, chunk); } int p = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, k, l) + 1; context2.updateXZ(m, n); int q = 0; int r = Integer.MIN_VALUE; int s = Integer.MAX_VALUE; int t = chunk.getMinY(); for (int u = p; u >= t; u--) { BlockState blockState = blockColumn.getBlock(u); if (blockState.isAir()) { q = 0; r = Integer.MIN_VALUE; } else if (!blockState.getFluidState().isEmpty()) { if (r == Integer.MIN_VALUE) { r = u + 1; } } else { if (s >= u) { s = DimensionType.WAY_BELOW_MIN_Y; for (int v = u - 1; v >= t - 1; v--) { BlockState blockState2 = blockColumn.getBlock(v); if (!this.isStone(blockState2)) { s = v + 1; break; } } } q++; int vx = u - s + 1; context2.updateY(q, vx, r, m, u, n); if (blockState == this.defaultBlock) { BlockState blockState2 = surfaceRule.tryApply(m, u, n); if (blockState2 != null) { blockColumn.setBlock(u, blockState2); } } } } if (holder.is(Biomes.FROZEN_OCEAN) || holder.is(Biomes.DEEP_FROZEN_OCEAN)) { this.frozenOceanExtension(context2.getMinSurfaceLevel(), holder.value(), blockColumn, mutableBlockPos2, m, n, o); } } } } protected int getSurfaceDepth(int x, int z) { double d = this.surfaceNoise.getValue(x, 0.0, z); return (int)(d * 2.75 + 3.0 + this.noiseRandom.at(x, 0, z).nextDouble() * 0.25); } protected double getSurfaceSecondary(int x, int z) { return this.surfaceSecondaryNoise.getValue(x, 0.0, z); } private boolean isStone(BlockState state) { return !state.isAir() && state.getFluidState().isEmpty(); } public int getSeaLevel() { return this.seaLevel; } @Deprecated public Optional topMaterial( RuleSource rule, CarvingContext context, Function> biomeGetter, ChunkAccess chunk, NoiseChunk noiseChunk, BlockPos pos, boolean hasFluid ) { Context context2 = new Context(this, context.randomState(), chunk, noiseChunk, biomeGetter, context.registryAccess().lookupOrThrow(Registries.BIOME), context); SurfaceRule surfaceRule = (SurfaceRule)rule.apply(context2); int i = pos.getX(); int j = pos.getY(); int k = pos.getZ(); context2.updateXZ(i, k); context2.updateY(1, 1, hasFluid ? j + 1 : Integer.MIN_VALUE, i, j, k); BlockState blockState = surfaceRule.tryApply(i, j, k); return Optional.ofNullable(blockState); } private void erodedBadlandsExtension(BlockColumn blockColumn, int x, int z, int height, LevelHeightAccessor level) { double d = 0.2; double e = Math.min(Math.abs(this.badlandsSurfaceNoise.getValue(x, 0.0, z) * 8.25), this.badlandsPillarNoise.getValue(x * 0.2, 0.0, z * 0.2) * 15.0); if (!(e <= 0.0)) { double f = 0.75; double g = 1.5; double h = Math.abs(this.badlandsPillarRoofNoise.getValue(x * 0.75, 0.0, z * 0.75) * 1.5); double i = 64.0 + Math.min(e * e * 2.5, Math.ceil(h * 50.0) + 24.0); int j = Mth.floor(i); if (height <= j) { for (int k = j; k >= level.getMinY(); k--) { BlockState blockState = blockColumn.getBlock(k); if (blockState.is(this.defaultBlock.getBlock())) { break; } if (blockState.is(Blocks.WATER)) { return; } } for (int k = j; k >= level.getMinY() && blockColumn.getBlock(k).isAir(); k--) { blockColumn.setBlock(k, this.defaultBlock); } } } } private void frozenOceanExtension(int minSurfaceLevel, Biome biome, BlockColumn blockColumn, BlockPos.MutableBlockPos topWaterPos, int x, int z, int height) { double d = 1.28; double e = Math.min(Math.abs(this.icebergSurfaceNoise.getValue(x, 0.0, z) * 8.25), this.icebergPillarNoise.getValue(x * 1.28, 0.0, z * 1.28) * 15.0); if (!(e <= 1.8)) { double f = 1.17; double g = 1.5; double h = Math.abs(this.icebergPillarRoofNoise.getValue(x * 1.17, 0.0, z * 1.17) * 1.5); double i = Math.min(e * e * 1.2, Math.ceil(h * 40.0) + 14.0); if (biome.shouldMeltFrozenOceanIcebergSlightly(topWaterPos.set(x, this.seaLevel, z), this.seaLevel)) { i -= 2.0; } double j; if (i > 2.0) { j = this.seaLevel - i - 7.0; i += this.seaLevel; } else { i = 0.0; j = 0.0; } double k = i; RandomSource randomSource = this.noiseRandom.at(x, 0, z); int l = 2 + randomSource.nextInt(4); int m = this.seaLevel + 18 + randomSource.nextInt(10); int n = 0; for (int o = Math.max(height, (int)i + 1); o >= minSurfaceLevel; o--) { if (blockColumn.getBlock(o).isAir() && o < (int)k && randomSource.nextDouble() > 0.01 || blockColumn.getBlock(o).is(Blocks.WATER) && o > (int)j && o < this.seaLevel && j != 0.0 && randomSource.nextDouble() > 0.15) { if (n <= l && o > m) { blockColumn.setBlock(o, SNOW_BLOCK); n++; } else { blockColumn.setBlock(o, PACKED_ICE); } } } } } private static BlockState[] generateBands(RandomSource random) { BlockState[] blockStates = new BlockState[192]; Arrays.fill(blockStates, TERRACOTTA); for (int i = 0; i < blockStates.length; i++) { i += random.nextInt(5) + 1; if (i < blockStates.length) { blockStates[i] = ORANGE_TERRACOTTA; } } makeBands(random, blockStates, 1, YELLOW_TERRACOTTA); makeBands(random, blockStates, 2, BROWN_TERRACOTTA); makeBands(random, blockStates, 1, RED_TERRACOTTA); int ix = random.nextIntBetweenInclusive(9, 15); int j = 0; for (int k = 0; j < ix && k < blockStates.length; k += random.nextInt(16) + 4) { blockStates[k] = WHITE_TERRACOTTA; if (k - 1 > 0 && random.nextBoolean()) { blockStates[k - 1] = LIGHT_GRAY_TERRACOTTA; } if (k + 1 < blockStates.length && random.nextBoolean()) { blockStates[k + 1] = LIGHT_GRAY_TERRACOTTA; } j++; } return blockStates; } private static void makeBands(RandomSource random, BlockState[] output, int minSize, BlockState state) { int i = random.nextIntBetweenInclusive(6, 15); for (int j = 0; j < i; j++) { int k = minSize + random.nextInt(3); int l = random.nextInt(output.length); for (int m = 0; l + m < output.length && m < k; m++) { output[l + m] = state; } } } protected BlockState getBand(int x, int y, int z) { int i = (int)Math.round(this.clayBandsOffsetNoise.getValue(x, 0.0, z) * 4.0); return this.clayBands[(y + i + this.clayBands.length) % this.clayBands.length]; } }