/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.base.Suppliers * com.google.common.collect.ImmutableList * com.google.common.collect.ImmutableList$Builder * com.mojang.datafixers.kinds.App * com.mojang.datafixers.kinds.Applicative * com.mojang.serialization.Codec * com.mojang.serialization.MapCodec * com.mojang.serialization.codecs.RecordCodecBuilder * org.jspecify.annotations.Nullable */ package net.minecraft.world.level.levelgen; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.kinds.App; import com.mojang.datafixers.kinds.Applicative; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; import net.minecraft.util.KeyDispatchDataCodec; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.NoiseChunk; import net.minecraft.world.level.levelgen.PositionalRandomFactory; import net.minecraft.world.level.levelgen.RandomState; import net.minecraft.world.level.levelgen.SurfaceSystem; import net.minecraft.world.level.levelgen.VerticalAnchor; import net.minecraft.world.level.levelgen.WorldGenerationContext; import net.minecraft.world.level.levelgen.placement.CaveSurface; import net.minecraft.world.level.levelgen.synth.NormalNoise; import org.jspecify.annotations.Nullable; public class SurfaceRules { public static final ConditionSource ON_FLOOR = SurfaceRules.stoneDepthCheck(0, false, CaveSurface.FLOOR); public static final ConditionSource UNDER_FLOOR = SurfaceRules.stoneDepthCheck(0, true, CaveSurface.FLOOR); public static final ConditionSource DEEP_UNDER_FLOOR = SurfaceRules.stoneDepthCheck(0, true, 6, CaveSurface.FLOOR); public static final ConditionSource VERY_DEEP_UNDER_FLOOR = SurfaceRules.stoneDepthCheck(0, true, 30, CaveSurface.FLOOR); public static final ConditionSource ON_CEILING = SurfaceRules.stoneDepthCheck(0, false, CaveSurface.CEILING); public static final ConditionSource UNDER_CEILING = SurfaceRules.stoneDepthCheck(0, true, CaveSurface.CEILING); public static ConditionSource stoneDepthCheck(int offset, boolean addSurfaceDepth1, CaveSurface surfaceType) { return new StoneDepthCheck(offset, addSurfaceDepth1, 0, surfaceType); } public static ConditionSource stoneDepthCheck(int offset, boolean addSurfaceDepth1, int secondaryDepthRange, CaveSurface surfaceType) { return new StoneDepthCheck(offset, addSurfaceDepth1, secondaryDepthRange, surfaceType); } public static ConditionSource not(ConditionSource target) { return new NotConditionSource(target); } public static ConditionSource yBlockCheck(VerticalAnchor anchor, int surfaceDepthMultiplier) { return new YConditionSource(anchor, surfaceDepthMultiplier, false); } public static ConditionSource yStartCheck(VerticalAnchor anchor, int surfaceDepthMultiplier) { return new YConditionSource(anchor, surfaceDepthMultiplier, true); } public static ConditionSource waterBlockCheck(int offset, int surfaceDepthMultiplier) { return new WaterConditionSource(offset, surfaceDepthMultiplier, false); } public static ConditionSource waterStartCheck(int offset, int surfaceDepthMultiplier) { return new WaterConditionSource(offset, surfaceDepthMultiplier, true); } @SafeVarargs public static ConditionSource isBiome(ResourceKey ... target) { return SurfaceRules.isBiome(List.of(target)); } private static BiomeConditionSource isBiome(List> target) { return new BiomeConditionSource(target); } public static ConditionSource noiseCondition(ResourceKey noise, double minRange) { return SurfaceRules.noiseCondition(noise, minRange, Double.MAX_VALUE); } public static ConditionSource noiseCondition(ResourceKey noise, double minRange, double maxRange) { return new NoiseThresholdConditionSource(noise, minRange, maxRange); } public static ConditionSource verticalGradient(String randomName, VerticalAnchor trueAtAndBelow, VerticalAnchor falseAtAndAbove) { return new VerticalGradientConditionSource(Identifier.parse(randomName), trueAtAndBelow, falseAtAndAbove); } public static ConditionSource steep() { return Steep.INSTANCE; } public static ConditionSource hole() { return Hole.INSTANCE; } public static ConditionSource abovePreliminarySurface() { return AbovePreliminarySurface.INSTANCE; } public static ConditionSource temperature() { return Temperature.INSTANCE; } public static RuleSource ifTrue(ConditionSource condition, RuleSource next) { return new TestRuleSource(condition, next); } public static RuleSource sequence(RuleSource ... rules) { if (rules.length == 0) { throw new IllegalArgumentException("Need at least 1 rule for a sequence"); } return new SequenceRuleSource(Arrays.asList(rules)); } public static RuleSource state(BlockState state) { return new BlockRuleSource(state); } public static RuleSource bandlands() { return Bandlands.INSTANCE; } private static MapCodec register(Registry> registry, String name, KeyDispatchDataCodec codec) { return Registry.register(registry, name, codec.codec()); } private record StoneDepthCheck(int offset, boolean addSurfaceDepth, int secondaryDepthRange, CaveSurface surfaceType) implements ConditionSource { private static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec(i -> i.group((App)Codec.INT.fieldOf("offset").forGetter(StoneDepthCheck::offset), (App)Codec.BOOL.fieldOf("add_surface_depth").forGetter(StoneDepthCheck::addSurfaceDepth), (App)Codec.INT.fieldOf("secondary_depth_range").forGetter(StoneDepthCheck::secondaryDepthRange), (App)CaveSurface.CODEC.fieldOf("surface_type").forGetter(StoneDepthCheck::surfaceType)).apply((Applicative)i, StoneDepthCheck::new))); @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(final Context ruleContext) { final boolean ceiling = this.surfaceType == CaveSurface.CEILING; class StoneDepthCondition extends LazyYCondition { private StoneDepthCondition() { super(context); } @Override protected boolean compute() { int stoneDepth = ceiling ? this.context.stoneDepthBelow : this.context.stoneDepthAbove; int surfaceDepth = StoneDepthCheck.this.addSurfaceDepth ? this.context.surfaceDepth : 0; int secondarySurfaceDepth = StoneDepthCheck.this.secondaryDepthRange == 0 ? 0 : (int)Mth.map(this.context.getSurfaceSecondary(), -1.0, 1.0, 0.0, (double)StoneDepthCheck.this.secondaryDepthRange); return stoneDepth <= 1 + StoneDepthCheck.this.offset + surfaceDepth + secondarySurfaceDepth; } } return new StoneDepthCondition(); } } private record NotConditionSource(ConditionSource target) implements ConditionSource { private static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(ConditionSource.CODEC.xmap(NotConditionSource::new, NotConditionSource::target).fieldOf("invert")); @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(Context context) { return new NotCondition((Condition)this.target.apply(context)); } } public static interface ConditionSource extends Function { public static final Codec CODEC = BuiltInRegistries.MATERIAL_CONDITION.byNameCodec().dispatch(source -> source.codec().codec(), Function.identity()); public static MapCodec bootstrap(Registry> registry) { SurfaceRules.register(registry, "biome", BiomeConditionSource.CODEC); SurfaceRules.register(registry, "noise_threshold", NoiseThresholdConditionSource.CODEC); SurfaceRules.register(registry, "vertical_gradient", VerticalGradientConditionSource.CODEC); SurfaceRules.register(registry, "y_above", YConditionSource.CODEC); SurfaceRules.register(registry, "water", WaterConditionSource.CODEC); SurfaceRules.register(registry, "temperature", Temperature.CODEC); SurfaceRules.register(registry, "steep", Steep.CODEC); SurfaceRules.register(registry, "not", NotConditionSource.CODEC); SurfaceRules.register(registry, "hole", Hole.CODEC); SurfaceRules.register(registry, "above_preliminary_surface", AbovePreliminarySurface.CODEC); return SurfaceRules.register(registry, "stone_depth", StoneDepthCheck.CODEC); } public KeyDispatchDataCodec codec(); } private record YConditionSource(VerticalAnchor anchor, int surfaceDepthMultiplier, boolean addStoneDepth) implements ConditionSource { private static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec(i -> i.group((App)VerticalAnchor.CODEC.fieldOf("anchor").forGetter(YConditionSource::anchor), (App)Codec.intRange((int)-20, (int)20).fieldOf("surface_depth_multiplier").forGetter(YConditionSource::surfaceDepthMultiplier), (App)Codec.BOOL.fieldOf("add_stone_depth").forGetter(YConditionSource::addStoneDepth)).apply((Applicative)i, YConditionSource::new))); @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(final Context ruleContext) { class YCondition extends LazyYCondition { private YCondition() { super(context); } @Override protected boolean compute() { return this.context.blockY + (YConditionSource.this.addStoneDepth ? this.context.stoneDepthAbove : 0) >= YConditionSource.this.anchor.resolveY(this.context.context) + this.context.surfaceDepth * YConditionSource.this.surfaceDepthMultiplier; } } return new YCondition(); } } private record WaterConditionSource(int offset, int surfaceDepthMultiplier, boolean addStoneDepth) implements ConditionSource { private static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec(i -> i.group((App)Codec.INT.fieldOf("offset").forGetter(WaterConditionSource::offset), (App)Codec.intRange((int)-20, (int)20).fieldOf("surface_depth_multiplier").forGetter(WaterConditionSource::surfaceDepthMultiplier), (App)Codec.BOOL.fieldOf("add_stone_depth").forGetter(WaterConditionSource::addStoneDepth)).apply((Applicative)i, WaterConditionSource::new))); @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(final Context ruleContext) { class WaterCondition extends LazyYCondition { private WaterCondition() { super(context); } @Override protected boolean compute() { return this.context.waterHeight == Integer.MIN_VALUE || this.context.blockY + (WaterConditionSource.this.addStoneDepth ? this.context.stoneDepthAbove : 0) >= this.context.waterHeight + WaterConditionSource.this.offset + this.context.surfaceDepth * WaterConditionSource.this.surfaceDepthMultiplier; } } return new WaterCondition(); } } private static final class BiomeConditionSource implements ConditionSource { private static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(ResourceKey.codec(Registries.BIOME).listOf().fieldOf("biome_is").xmap(SurfaceRules::isBiome, e -> e.biomes)); private final List> biomes; private final Predicate> biomeNameTest; private BiomeConditionSource(List> biomes) { this.biomes = biomes; this.biomeNameTest = Set.copyOf(biomes)::contains; } @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(final Context ruleContext) { class BiomeCondition extends LazyYCondition { private BiomeCondition() { super(context); } @Override protected boolean compute() { return this.context.biome.get().is(BiomeConditionSource.this.biomeNameTest); } } return new BiomeCondition(); } public boolean equals(Object o) { if (this == o) { return true; } if (o instanceof BiomeConditionSource) { BiomeConditionSource that = (BiomeConditionSource)o; return this.biomes.equals(that.biomes); } return false; } public int hashCode() { return this.biomes.hashCode(); } public String toString() { return "BiomeConditionSource[biomes=" + String.valueOf(this.biomes) + "]"; } } private record NoiseThresholdConditionSource(ResourceKey noise, double minThreshold, double maxThreshold) implements ConditionSource { private static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec(i -> i.group((App)ResourceKey.codec(Registries.NOISE).fieldOf("noise").forGetter(NoiseThresholdConditionSource::noise), (App)Codec.DOUBLE.fieldOf("min_threshold").forGetter(NoiseThresholdConditionSource::minThreshold), (App)Codec.DOUBLE.fieldOf("max_threshold").forGetter(NoiseThresholdConditionSource::maxThreshold)).apply((Applicative)i, NoiseThresholdConditionSource::new))); @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(final Context ruleContext) { final NormalNoise noise = ruleContext.randomState.getOrCreateNoise(this.noise); class NoiseThresholdCondition extends LazyXZCondition { private NoiseThresholdCondition() { super(context); } @Override protected boolean compute() { double value = noise.getValue(this.context.blockX, 0.0, this.context.blockZ); return value >= NoiseThresholdConditionSource.this.minThreshold && value <= NoiseThresholdConditionSource.this.maxThreshold; } } return new NoiseThresholdCondition(); } } private record VerticalGradientConditionSource(Identifier randomName, VerticalAnchor trueAtAndBelow, VerticalAnchor falseAtAndAbove) implements ConditionSource { private static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec(i -> i.group((App)Identifier.CODEC.fieldOf("random_name").forGetter(VerticalGradientConditionSource::randomName), (App)VerticalAnchor.CODEC.fieldOf("true_at_and_below").forGetter(VerticalGradientConditionSource::trueAtAndBelow), (App)VerticalAnchor.CODEC.fieldOf("false_at_and_above").forGetter(VerticalGradientConditionSource::falseAtAndAbove)).apply((Applicative)i, VerticalGradientConditionSource::new))); @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(final Context ruleContext) { final int trueAtAndBelow = this.trueAtAndBelow().resolveY(ruleContext.context); final int falseAtAndAbove = this.falseAtAndAbove().resolveY(ruleContext.context); final PositionalRandomFactory randomFactory = ruleContext.randomState.getOrCreateRandomFactory(this.randomName()); class VerticalGradientCondition extends LazyYCondition { private VerticalGradientCondition() { super(context); } @Override protected boolean compute() { int blockY = this.context.blockY; if (blockY <= trueAtAndBelow) { return true; } if (blockY >= falseAtAndAbove) { return false; } double probability = Mth.map((double)blockY, (double)trueAtAndBelow, (double)falseAtAndAbove, 1.0, 0.0); RandomSource random = randomFactory.at(this.context.blockX, blockY, this.context.blockZ); return (double)random.nextFloat() < probability; } } return new VerticalGradientCondition(); } } private static enum Steep implements ConditionSource { INSTANCE; private static final KeyDispatchDataCodec CODEC; @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(Context context) { return context.steep; } static { CODEC = KeyDispatchDataCodec.of(MapCodec.unit((Object)INSTANCE)); } } private static enum Hole implements ConditionSource { INSTANCE; private static final KeyDispatchDataCodec CODEC; @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(Context context) { return context.hole; } static { CODEC = KeyDispatchDataCodec.of(MapCodec.unit((Object)INSTANCE)); } } private static enum AbovePreliminarySurface implements ConditionSource { INSTANCE; private static final KeyDispatchDataCodec CODEC; @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(Context context) { return context.abovePreliminarySurface; } static { CODEC = KeyDispatchDataCodec.of(MapCodec.unit((Object)INSTANCE)); } } private static enum Temperature implements ConditionSource { INSTANCE; private static final KeyDispatchDataCodec CODEC; @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public Condition apply(Context context) { return context.temperature; } static { CODEC = KeyDispatchDataCodec.of(MapCodec.unit((Object)INSTANCE)); } } private record TestRuleSource(ConditionSource ifTrue, RuleSource thenRun) implements RuleSource { private static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec(i -> i.group((App)ConditionSource.CODEC.fieldOf("if_true").forGetter(TestRuleSource::ifTrue), (App)RuleSource.CODEC.fieldOf("then_run").forGetter(TestRuleSource::thenRun)).apply((Applicative)i, TestRuleSource::new))); @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public SurfaceRule apply(Context context) { return new TestRule((Condition)this.ifTrue.apply(context), (SurfaceRule)this.thenRun.apply(context)); } } public static interface RuleSource extends Function { public static final Codec CODEC = BuiltInRegistries.MATERIAL_RULE.byNameCodec().dispatch(source -> source.codec().codec(), Function.identity()); public static MapCodec bootstrap(Registry> registry) { SurfaceRules.register(registry, "bandlands", Bandlands.CODEC); SurfaceRules.register(registry, "block", BlockRuleSource.CODEC); SurfaceRules.register(registry, "sequence", SequenceRuleSource.CODEC); return SurfaceRules.register(registry, "condition", TestRuleSource.CODEC); } public KeyDispatchDataCodec codec(); } private record SequenceRuleSource(List sequence) implements RuleSource { private static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(RuleSource.CODEC.listOf().xmap(SequenceRuleSource::new, SequenceRuleSource::sequence).fieldOf("sequence")); @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public SurfaceRule apply(Context context) { if (this.sequence.size() == 1) { return (SurfaceRule)this.sequence.get(0).apply(context); } ImmutableList.Builder builder = ImmutableList.builder(); for (RuleSource rule : this.sequence) { builder.add((Object)((SurfaceRule)rule.apply(context))); } return new SequenceRule((List)builder.build()); } } private record BlockRuleSource(BlockState resultState, StateRule rule) implements RuleSource { private static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(BlockState.CODEC.xmap(BlockRuleSource::new, BlockRuleSource::resultState).fieldOf("result_state")); private BlockRuleSource(BlockState state) { this(state, new StateRule(state)); } @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public SurfaceRule apply(Context context) { return this.rule; } } private static enum Bandlands implements RuleSource { INSTANCE; private static final KeyDispatchDataCodec CODEC; @Override public KeyDispatchDataCodec codec() { return CODEC; } @Override public SurfaceRule apply(Context context) { return context.system::getBand; } static { CODEC = KeyDispatchDataCodec.of(MapCodec.unit((Object)INSTANCE)); } } private record SequenceRule(List rules) implements SurfaceRule { @Override public @Nullable BlockState tryApply(int blockX, int blockY, int blockZ) { for (SurfaceRule rule : this.rules) { BlockState state = rule.tryApply(blockX, blockY, blockZ); if (state == null) continue; return state; } return null; } } private record TestRule(Condition condition, SurfaceRule followup) implements SurfaceRule { @Override public @Nullable BlockState tryApply(int blockX, int blockY, int blockZ) { if (!this.condition.test()) { return null; } return this.followup.tryApply(blockX, blockY, blockZ); } } private record StateRule(BlockState state) implements SurfaceRule { @Override public BlockState tryApply(int blockX, int blockY, int blockZ) { return this.state; } } protected static interface SurfaceRule { public @Nullable BlockState tryApply(int var1, int var2, int var3); } private record NotCondition(Condition target) implements Condition { @Override public boolean test() { return !this.target.test(); } } private static abstract class LazyYCondition extends LazyCondition { protected LazyYCondition(Context context) { super(context); } @Override protected long getContextLastUpdate() { return this.context.lastUpdateY; } } private static abstract class LazyXZCondition extends LazyCondition { protected LazyXZCondition(Context context) { super(context); } @Override protected long getContextLastUpdate() { return this.context.lastUpdateXZ; } } private static abstract class LazyCondition implements Condition { protected final Context context; private long lastUpdate; @Nullable Boolean result; protected LazyCondition(Context context) { this.context = context; this.lastUpdate = this.getContextLastUpdate() - 1L; } @Override public boolean test() { long lastContextUpdate = this.getContextLastUpdate(); if (lastContextUpdate == this.lastUpdate) { if (this.result == null) { throw new IllegalStateException("Update triggered but the result is null"); } return this.result; } this.lastUpdate = lastContextUpdate; this.result = this.compute(); return this.result; } protected abstract long getContextLastUpdate(); protected abstract boolean compute(); } private static interface Condition { public boolean test(); } protected static final class Context { private static final int HOW_FAR_BELOW_PRELIMINARY_SURFACE_LEVEL_TO_BUILD_SURFACE = 8; private static final int SURFACE_CELL_BITS = 4; private static final int SURFACE_CELL_SIZE = 16; private static final int SURFACE_CELL_MASK = 15; private final SurfaceSystem system; private final Condition temperature = new TemperatureHelperCondition(this); private final Condition steep = new SteepMaterialCondition(this); private final Condition hole = new HoleCondition(this); private final Condition abovePreliminarySurface = new AbovePreliminarySurfaceCondition(); private final RandomState randomState; private final ChunkAccess chunk; private final NoiseChunk noiseChunk; private final Function> biomeGetter; private final WorldGenerationContext context; private long lastPreliminarySurfaceCellOrigin = Long.MAX_VALUE; private final int[] preliminarySurfaceCache = new int[4]; private long lastUpdateXZ = -9223372036854775807L; private int blockX; private int blockZ; private int surfaceDepth; private long lastSurfaceDepth2Update = this.lastUpdateXZ - 1L; private double surfaceSecondary; private long lastMinSurfaceLevelUpdate = this.lastUpdateXZ - 1L; private int minSurfaceLevel; private long lastUpdateY = -9223372036854775807L; private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); private Supplier> biome; private int blockY; private int waterHeight; private int stoneDepthBelow; private int stoneDepthAbove; protected Context(SurfaceSystem system, RandomState randomState, ChunkAccess chunk, NoiseChunk noiseChunk, Function> biomeGetter, Registry biomes, WorldGenerationContext context) { this.system = system; this.randomState = randomState; this.chunk = chunk; this.noiseChunk = noiseChunk; this.biomeGetter = biomeGetter; this.context = context; } protected void updateXZ(int blockX, int blockZ) { ++this.lastUpdateXZ; ++this.lastUpdateY; this.blockX = blockX; this.blockZ = blockZ; this.surfaceDepth = this.system.getSurfaceDepth(blockX, blockZ); } protected void updateY(int stoneDepthAbove, int stoneDepthBelow, int waterHeight, int blockX, int blockY, int blockZ) { ++this.lastUpdateY; this.biome = Suppliers.memoize(() -> this.biomeGetter.apply(this.pos.set(blockX, blockY, blockZ))); this.blockY = blockY; this.waterHeight = waterHeight; this.stoneDepthBelow = stoneDepthBelow; this.stoneDepthAbove = stoneDepthAbove; } protected double getSurfaceSecondary() { if (this.lastSurfaceDepth2Update != this.lastUpdateXZ) { this.lastSurfaceDepth2Update = this.lastUpdateXZ; this.surfaceSecondary = this.system.getSurfaceSecondary(this.blockX, this.blockZ); } return this.surfaceSecondary; } public int getSeaLevel() { return this.system.getSeaLevel(); } private static int blockCoordToSurfaceCell(int blockCoord) { return blockCoord >> 4; } private static int surfaceCellToBlockCoord(int cellCoord) { return cellCoord << 4; } protected int getMinSurfaceLevel() { if (this.lastMinSurfaceLevelUpdate != this.lastUpdateXZ) { int cornerCellZ; this.lastMinSurfaceLevelUpdate = this.lastUpdateXZ; int cornerCellX = Context.blockCoordToSurfaceCell(this.blockX); long preliminarySurfaceCellOrigin = ChunkPos.asLong(cornerCellX, cornerCellZ = Context.blockCoordToSurfaceCell(this.blockZ)); if (this.lastPreliminarySurfaceCellOrigin != preliminarySurfaceCellOrigin) { this.lastPreliminarySurfaceCellOrigin = preliminarySurfaceCellOrigin; this.preliminarySurfaceCache[0] = this.noiseChunk.preliminarySurfaceLevel(Context.surfaceCellToBlockCoord(cornerCellX), Context.surfaceCellToBlockCoord(cornerCellZ)); this.preliminarySurfaceCache[1] = this.noiseChunk.preliminarySurfaceLevel(Context.surfaceCellToBlockCoord(cornerCellX + 1), Context.surfaceCellToBlockCoord(cornerCellZ)); this.preliminarySurfaceCache[2] = this.noiseChunk.preliminarySurfaceLevel(Context.surfaceCellToBlockCoord(cornerCellX), Context.surfaceCellToBlockCoord(cornerCellZ + 1)); this.preliminarySurfaceCache[3] = this.noiseChunk.preliminarySurfaceLevel(Context.surfaceCellToBlockCoord(cornerCellX + 1), Context.surfaceCellToBlockCoord(cornerCellZ + 1)); } int preliminarySurfaceLevel = Mth.floor(Mth.lerp2((float)(this.blockX & 0xF) / 16.0f, (float)(this.blockZ & 0xF) / 16.0f, this.preliminarySurfaceCache[0], this.preliminarySurfaceCache[1], this.preliminarySurfaceCache[2], this.preliminarySurfaceCache[3])); this.minSurfaceLevel = preliminarySurfaceLevel + this.surfaceDepth - 8; } return this.minSurfaceLevel; } private static class TemperatureHelperCondition extends LazyYCondition { private TemperatureHelperCondition(Context context) { super(context); } @Override protected boolean compute() { return this.context.biome.get().value().coldEnoughToSnow(this.context.pos.set(this.context.blockX, this.context.blockY, this.context.blockZ), this.context.getSeaLevel()); } } private static class SteepMaterialCondition extends LazyXZCondition { private SteepMaterialCondition(Context context) { super(context); } @Override protected boolean compute() { int heightEast; int chunkBlockX = this.context.blockX & 0xF; int chunkBlockZ = this.context.blockZ & 0xF; int zNorth = Math.max(chunkBlockZ - 1, 0); int zSouth = Math.min(chunkBlockZ + 1, 15); ChunkAccess chunk = this.context.chunk; int heightNorth = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, chunkBlockX, zNorth); int heightSouth = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, chunkBlockX, zSouth); if (heightSouth >= heightNorth + 4) { return true; } int xWest = Math.max(chunkBlockX - 1, 0); int xEast = Math.min(chunkBlockX + 1, 15); int heightWest = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, xWest, chunkBlockZ); return heightWest >= (heightEast = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, xEast, chunkBlockZ)) + 4; } } private static final class HoleCondition extends LazyXZCondition { private HoleCondition(Context context) { super(context); } @Override protected boolean compute() { return this.context.surfaceDepth <= 0; } } private final class AbovePreliminarySurfaceCondition implements Condition { private AbovePreliminarySurfaceCondition() { } @Override public boolean test() { return Context.this.blockY >= Context.this.getMinSurfaceLevel(); } } } }