503 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			503 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.world.level.levelgen;
 | |
| 
 | |
| import java.util.Arrays;
 | |
| import net.minecraft.core.BlockPos;
 | |
| import net.minecraft.core.SectionPos;
 | |
| import net.minecraft.util.Mth;
 | |
| import net.minecraft.util.RandomSource;
 | |
| import net.minecraft.world.level.ChunkPos;
 | |
| import net.minecraft.world.level.biome.OverworldBiomeBuilder;
 | |
| import net.minecraft.world.level.block.Blocks;
 | |
| import net.minecraft.world.level.block.state.BlockState;
 | |
| import net.minecraft.world.level.dimension.DimensionType;
 | |
| import org.apache.commons.lang3.mutable.MutableDouble;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| 
 | |
| /**
 | |
|  * Aquifers are responsible for non-sea level fluids found in terrain generation, but also managing that different aquifers don't intersect with each other in ways that would create undesirable fluid placement.
 | |
|  * The aquifer interface itself is a modifier on a per-block basis. It computes a block state to be placed for each position in the world.
 | |
|  * <p>
 | |
|  * Aquifers work by first partitioning a single chunk into a low resolution grid. They then generate, via various noise layers, an {@link NoiseBasedAquifer.AquiferStatus} at each grid point.
 | |
|  * At each point, the grid cell containing that point is calculated, and then of the eight grid corners, the three closest aquifers are found, by square euclidean distance.
 | |
|  * Borders between aquifers are created by comparing nearby aquifers to see if the given point is near-equidistant from them, indicating a border if so, or fluid/air depending on the aquifer height if not.
 | |
|  */
 | |
| public interface Aquifer {
 | |
| 	/**
 | |
| 	 * Creates a standard noise based aquifer. This aquifer will place liquid (both water and lava), air, and stone as described above.
 | |
| 	 */
 | |
| 	static Aquifer create(
 | |
| 		NoiseChunk chunk,
 | |
| 		ChunkPos chunkPos,
 | |
| 		NoiseRouter noiseRouter,
 | |
| 		PositionalRandomFactory positionalRandomFactory,
 | |
| 		int minY,
 | |
| 		int height,
 | |
| 		Aquifer.FluidPicker globalFluidPicker
 | |
| 	) {
 | |
| 		return new Aquifer.NoiseBasedAquifer(chunk, chunkPos, noiseRouter, positionalRandomFactory, minY, height, globalFluidPicker);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Creates a disabled, or no-op aquifer. This will fill any open areas below sea level with the default fluid.
 | |
| 	 */
 | |
| 	static Aquifer createDisabled(Aquifer.FluidPicker defaultFluid) {
 | |
| 		return new Aquifer() {
 | |
| 			@Nullable
 | |
| 			@Override
 | |
| 			public BlockState computeSubstance(DensityFunction.FunctionContext context, double substance) {
 | |
| 				return substance > 0.0 ? null : defaultFluid.computeFluid(context.blockX(), context.blockY(), context.blockZ()).at(context.blockY());
 | |
| 			}
 | |
| 
 | |
| 			@Override
 | |
| 			public boolean shouldScheduleFluidUpdate() {
 | |
| 				return false;
 | |
| 			}
 | |
| 		};
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	BlockState computeSubstance(DensityFunction.FunctionContext context, double substance);
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns {@code true} if there should be a fluid update scheduled - due to a fluid block being placed in a possibly unsteady position - at the last position passed into {@link #computeState}.
 | |
| 	 * This <strong>must</strong> be invoked only after {@link #computeState}, and will be using the same parameters as that method.
 | |
| 	 */
 | |
| 	boolean shouldScheduleFluidUpdate();
 | |
| 
 | |
| 	public interface FluidPicker {
 | |
| 		Aquifer.FluidStatus computeFluid(int i, int j, int k);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param fluidLevel The y height of the aquifer.
 | |
| 	 * @param fluidType The fluid state the aquifer is filled with.
 | |
| 	 */
 | |
| 	public record FluidStatus(int fluidLevel, BlockState fluidType) {
 | |
| 
 | |
| 		public BlockState at(int y) {
 | |
| 			return y < this.fluidLevel ? this.fluidType : Blocks.AIR.defaultBlockState();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public static class NoiseBasedAquifer implements Aquifer {
 | |
| 		private static final int X_RANGE = 10;
 | |
| 		private static final int Y_RANGE = 9;
 | |
| 		private static final int Z_RANGE = 10;
 | |
| 		private static final int X_SEPARATION = 6;
 | |
| 		private static final int Y_SEPARATION = 3;
 | |
| 		private static final int Z_SEPARATION = 6;
 | |
| 		private static final int X_SPACING = 16;
 | |
| 		private static final int Y_SPACING = 12;
 | |
| 		private static final int Z_SPACING = 16;
 | |
| 		private static final int MAX_REASONABLE_DISTANCE_TO_AQUIFER_CENTER = 11;
 | |
| 		private static final double FLOWING_UPDATE_SIMULARITY = similarity(Mth.square(10), Mth.square(12));
 | |
| 		private final NoiseChunk noiseChunk;
 | |
| 		private final DensityFunction barrierNoise;
 | |
| 		private final DensityFunction fluidLevelFloodednessNoise;
 | |
| 		private final DensityFunction fluidLevelSpreadNoise;
 | |
| 		private final DensityFunction lavaNoise;
 | |
| 		private final PositionalRandomFactory positionalRandomFactory;
 | |
| 		private final Aquifer.FluidStatus[] aquiferCache;
 | |
| 		private final long[] aquiferLocationCache;
 | |
| 		private final Aquifer.FluidPicker globalFluidPicker;
 | |
| 		private final DensityFunction erosion;
 | |
| 		private final DensityFunction depth;
 | |
| 		private boolean shouldScheduleFluidUpdate;
 | |
| 		private final int minGridX;
 | |
| 		private final int minGridY;
 | |
| 		private final int minGridZ;
 | |
| 		private final int gridSizeX;
 | |
| 		private final int gridSizeZ;
 | |
| 		private static final int[][] SURFACE_SAMPLING_OFFSETS_IN_CHUNKS = new int[][]{
 | |
| 			{0, 0}, {-2, -1}, {-1, -1}, {0, -1}, {1, -1}, {-3, 0}, {-2, 0}, {-1, 0}, {1, 0}, {-2, 1}, {-1, 1}, {0, 1}, {1, 1}
 | |
| 		};
 | |
| 
 | |
| 		NoiseBasedAquifer(
 | |
| 			NoiseChunk noiseChunk,
 | |
| 			ChunkPos chunkPos,
 | |
| 			NoiseRouter noiseRouter,
 | |
| 			PositionalRandomFactory positionalRandomFactory,
 | |
| 			int minY,
 | |
| 			int height,
 | |
| 			Aquifer.FluidPicker globalFluidPicker
 | |
| 		) {
 | |
| 			this.noiseChunk = noiseChunk;
 | |
| 			this.barrierNoise = noiseRouter.barrierNoise();
 | |
| 			this.fluidLevelFloodednessNoise = noiseRouter.fluidLevelFloodednessNoise();
 | |
| 			this.fluidLevelSpreadNoise = noiseRouter.fluidLevelSpreadNoise();
 | |
| 			this.lavaNoise = noiseRouter.lavaNoise();
 | |
| 			this.erosion = noiseRouter.erosion();
 | |
| 			this.depth = noiseRouter.depth();
 | |
| 			this.positionalRandomFactory = positionalRandomFactory;
 | |
| 			this.minGridX = this.gridX(chunkPos.getMinBlockX()) - 1;
 | |
| 			this.globalFluidPicker = globalFluidPicker;
 | |
| 			int i = this.gridX(chunkPos.getMaxBlockX()) + 1;
 | |
| 			this.gridSizeX = i - this.minGridX + 1;
 | |
| 			this.minGridY = this.gridY(minY) - 1;
 | |
| 			int j = this.gridY(minY + height) + 1;
 | |
| 			int k = j - this.minGridY + 1;
 | |
| 			this.minGridZ = this.gridZ(chunkPos.getMinBlockZ()) - 1;
 | |
| 			int l = this.gridZ(chunkPos.getMaxBlockZ()) + 1;
 | |
| 			this.gridSizeZ = l - this.minGridZ + 1;
 | |
| 			int m = this.gridSizeX * k * this.gridSizeZ;
 | |
| 			this.aquiferCache = new Aquifer.FluidStatus[m];
 | |
| 			this.aquiferLocationCache = new long[m];
 | |
| 			Arrays.fill(this.aquiferLocationCache, Long.MAX_VALUE);
 | |
| 		}
 | |
| 
 | |
| 		/**
 | |
| 		 * @return A cache index based on grid positions.
 | |
| 		 */
 | |
| 		private int getIndex(int gridX, int gridY, int gridZ) {
 | |
| 			int i = gridX - this.minGridX;
 | |
| 			int j = gridY - this.minGridY;
 | |
| 			int k = gridZ - this.minGridZ;
 | |
| 			return (j * this.gridSizeZ + k) * this.gridSizeX + i;
 | |
| 		}
 | |
| 
 | |
| 		@Nullable
 | |
| 		@Override
 | |
| 		public BlockState computeSubstance(DensityFunction.FunctionContext context, double substance) {
 | |
| 			int i = context.blockX();
 | |
| 			int j = context.blockY();
 | |
| 			int k = context.blockZ();
 | |
| 			if (substance > 0.0) {
 | |
| 				this.shouldScheduleFluidUpdate = false;
 | |
| 				return null;
 | |
| 			} else {
 | |
| 				Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(i, j, k);
 | |
| 				if (fluidStatus.at(j).is(Blocks.LAVA)) {
 | |
| 					this.shouldScheduleFluidUpdate = false;
 | |
| 					return Blocks.LAVA.defaultBlockState();
 | |
| 				} else {
 | |
| 					int l = Math.floorDiv(i - 5, 16);
 | |
| 					int m = Math.floorDiv(j + 1, 12);
 | |
| 					int n = Math.floorDiv(k - 5, 16);
 | |
| 					int o = Integer.MAX_VALUE;
 | |
| 					int p = Integer.MAX_VALUE;
 | |
| 					int q = Integer.MAX_VALUE;
 | |
| 					int r = Integer.MAX_VALUE;
 | |
| 					long s = 0L;
 | |
| 					long t = 0L;
 | |
| 					long u = 0L;
 | |
| 					long v = 0L;
 | |
| 
 | |
| 					for (int w = 0; w <= 1; w++) {
 | |
| 						for (int x = -1; x <= 1; x++) {
 | |
| 							for (int y = 0; y <= 1; y++) {
 | |
| 								int z = l + w;
 | |
| 								int aa = m + x;
 | |
| 								int ab = n + y;
 | |
| 								int ac = this.getIndex(z, aa, ab);
 | |
| 								long ad = this.aquiferLocationCache[ac];
 | |
| 								long ae;
 | |
| 								if (ad != Long.MAX_VALUE) {
 | |
| 									ae = ad;
 | |
| 								} else {
 | |
| 									RandomSource randomSource = this.positionalRandomFactory.at(z, aa, ab);
 | |
| 									ae = BlockPos.asLong(z * 16 + randomSource.nextInt(10), aa * 12 + randomSource.nextInt(9), ab * 16 + randomSource.nextInt(10));
 | |
| 									this.aquiferLocationCache[ac] = ae;
 | |
| 								}
 | |
| 
 | |
| 								int af = BlockPos.getX(ae) - i;
 | |
| 								int ag = BlockPos.getY(ae) - j;
 | |
| 								int ah = BlockPos.getZ(ae) - k;
 | |
| 								int ai = af * af + ag * ag + ah * ah;
 | |
| 								if (o >= ai) {
 | |
| 									v = u;
 | |
| 									u = t;
 | |
| 									t = s;
 | |
| 									s = ae;
 | |
| 									r = q;
 | |
| 									q = p;
 | |
| 									p = o;
 | |
| 									o = ai;
 | |
| 								} else if (p >= ai) {
 | |
| 									v = u;
 | |
| 									u = t;
 | |
| 									t = ae;
 | |
| 									r = q;
 | |
| 									q = p;
 | |
| 									p = ai;
 | |
| 								} else if (q >= ai) {
 | |
| 									v = u;
 | |
| 									u = ae;
 | |
| 									r = q;
 | |
| 									q = ai;
 | |
| 								} else if (r >= ai) {
 | |
| 									v = ae;
 | |
| 									r = ai;
 | |
| 								}
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					Aquifer.FluidStatus fluidStatus2 = this.getAquiferStatus(s);
 | |
| 					double d = similarity(o, p);
 | |
| 					BlockState blockState = fluidStatus2.at(j);
 | |
| 					if (d <= 0.0) {
 | |
| 						if (d >= FLOWING_UPDATE_SIMULARITY) {
 | |
| 							Aquifer.FluidStatus fluidStatus3 = this.getAquiferStatus(t);
 | |
| 							this.shouldScheduleFluidUpdate = !fluidStatus2.equals(fluidStatus3);
 | |
| 						} else {
 | |
| 							this.shouldScheduleFluidUpdate = false;
 | |
| 						}
 | |
| 
 | |
| 						return blockState;
 | |
| 					} else if (blockState.is(Blocks.WATER) && this.globalFluidPicker.computeFluid(i, j - 1, k).at(j - 1).is(Blocks.LAVA)) {
 | |
| 						this.shouldScheduleFluidUpdate = true;
 | |
| 						return blockState;
 | |
| 					} else {
 | |
| 						MutableDouble mutableDouble = new MutableDouble(Double.NaN);
 | |
| 						Aquifer.FluidStatus fluidStatus4 = this.getAquiferStatus(t);
 | |
| 						double e = d * this.calculatePressure(context, mutableDouble, fluidStatus2, fluidStatus4);
 | |
| 						if (substance + e > 0.0) {
 | |
| 							this.shouldScheduleFluidUpdate = false;
 | |
| 							return null;
 | |
| 						} else {
 | |
| 							Aquifer.FluidStatus fluidStatus5 = this.getAquiferStatus(u);
 | |
| 							double f = similarity(o, q);
 | |
| 							if (f > 0.0) {
 | |
| 								double g = d * f * this.calculatePressure(context, mutableDouble, fluidStatus2, fluidStatus5);
 | |
| 								if (substance + g > 0.0) {
 | |
| 									this.shouldScheduleFluidUpdate = false;
 | |
| 									return null;
 | |
| 								}
 | |
| 							}
 | |
| 
 | |
| 							double g = similarity(p, q);
 | |
| 							if (g > 0.0) {
 | |
| 								double h = d * g * this.calculatePressure(context, mutableDouble, fluidStatus4, fluidStatus5);
 | |
| 								if (substance + h > 0.0) {
 | |
| 									this.shouldScheduleFluidUpdate = false;
 | |
| 									return null;
 | |
| 								}
 | |
| 							}
 | |
| 
 | |
| 							boolean bl = !fluidStatus2.equals(fluidStatus4);
 | |
| 							boolean bl2 = g >= FLOWING_UPDATE_SIMULARITY && !fluidStatus4.equals(fluidStatus5);
 | |
| 							boolean bl3 = f >= FLOWING_UPDATE_SIMULARITY && !fluidStatus2.equals(fluidStatus5);
 | |
| 							if (!bl && !bl2 && !bl3) {
 | |
| 								this.shouldScheduleFluidUpdate = f >= FLOWING_UPDATE_SIMULARITY
 | |
| 									&& similarity(o, r) >= FLOWING_UPDATE_SIMULARITY
 | |
| 									&& !fluidStatus2.equals(this.getAquiferStatus(v));
 | |
| 							} else {
 | |
| 								this.shouldScheduleFluidUpdate = true;
 | |
| 							}
 | |
| 
 | |
| 							return blockState;
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public boolean shouldScheduleFluidUpdate() {
 | |
| 			return this.shouldScheduleFluidUpdate;
 | |
| 		}
 | |
| 
 | |
| 		/**
 | |
| 		 * Compares two distances (between aquifers).
 | |
| 		 * @return {@code 1.0} if the distances are equal, and returns smaller values the more different in absolute value the two distances are.
 | |
| 		 */
 | |
| 		private static double similarity(int firstDistance, int secondDistance) {
 | |
| 			double d = 25.0;
 | |
| 			return 1.0 - Math.abs(secondDistance - firstDistance) / 25.0;
 | |
| 		}
 | |
| 
 | |
| 		private double calculatePressure(
 | |
| 			DensityFunction.FunctionContext context, MutableDouble substance, Aquifer.FluidStatus firstFluid, Aquifer.FluidStatus secondFluid
 | |
| 		) {
 | |
| 			int i = context.blockY();
 | |
| 			BlockState blockState = firstFluid.at(i);
 | |
| 			BlockState blockState2 = secondFluid.at(i);
 | |
| 			if ((!blockState.is(Blocks.LAVA) || !blockState2.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState2.is(Blocks.LAVA))) {
 | |
| 				int j = Math.abs(firstFluid.fluidLevel - secondFluid.fluidLevel);
 | |
| 				if (j == 0) {
 | |
| 					return 0.0;
 | |
| 				} else {
 | |
| 					double d = 0.5 * (firstFluid.fluidLevel + secondFluid.fluidLevel);
 | |
| 					double e = i + 0.5 - d;
 | |
| 					double f = j / 2.0;
 | |
| 					double g = 0.0;
 | |
| 					double h = 2.5;
 | |
| 					double k = 1.5;
 | |
| 					double l = 3.0;
 | |
| 					double m = 10.0;
 | |
| 					double n = 3.0;
 | |
| 					double o = f - Math.abs(e);
 | |
| 					double q;
 | |
| 					if (e > 0.0) {
 | |
| 						double p = 0.0 + o;
 | |
| 						if (p > 0.0) {
 | |
| 							q = p / 1.5;
 | |
| 						} else {
 | |
| 							q = p / 2.5;
 | |
| 						}
 | |
| 					} else {
 | |
| 						double p = 3.0 + o;
 | |
| 						if (p > 0.0) {
 | |
| 							q = p / 3.0;
 | |
| 						} else {
 | |
| 							q = p / 10.0;
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					double px = 2.0;
 | |
| 					double r;
 | |
| 					if (!(q < -2.0) && !(q > 2.0)) {
 | |
| 						double s = substance.getValue();
 | |
| 						if (Double.isNaN(s)) {
 | |
| 							double t = this.barrierNoise.compute(context);
 | |
| 							substance.setValue(t);
 | |
| 							r = t;
 | |
| 						} else {
 | |
| 							r = s;
 | |
| 						}
 | |
| 					} else {
 | |
| 						r = 0.0;
 | |
| 					}
 | |
| 
 | |
| 					return 2.0 * (r + q);
 | |
| 				}
 | |
| 			} else {
 | |
| 				return 2.0;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		private int gridX(int x) {
 | |
| 			return Math.floorDiv(x, 16);
 | |
| 		}
 | |
| 
 | |
| 		private int gridY(int y) {
 | |
| 			return Math.floorDiv(y, 12);
 | |
| 		}
 | |
| 
 | |
| 		private int gridZ(int z) {
 | |
| 			return Math.floorDiv(z, 16);
 | |
| 		}
 | |
| 
 | |
| 		/**
 | |
| 		 * Calculates the aquifer at a given location. Internally references a cache using the grid positions as an index. If the cache is not populated, computes a new aquifer at that grid location using {@link #computeFluid}.
 | |
| 		 * 
 | |
| 		 * @param packedPos The aquifer block position, packed into a {@code long}.
 | |
| 		 */
 | |
| 		private Aquifer.FluidStatus getAquiferStatus(long packedPos) {
 | |
| 			int i = BlockPos.getX(packedPos);
 | |
| 			int j = BlockPos.getY(packedPos);
 | |
| 			int k = BlockPos.getZ(packedPos);
 | |
| 			int l = this.gridX(i);
 | |
| 			int m = this.gridY(j);
 | |
| 			int n = this.gridZ(k);
 | |
| 			int o = this.getIndex(l, m, n);
 | |
| 			Aquifer.FluidStatus fluidStatus = this.aquiferCache[o];
 | |
| 			if (fluidStatus != null) {
 | |
| 				return fluidStatus;
 | |
| 			} else {
 | |
| 				Aquifer.FluidStatus fluidStatus2 = this.computeFluid(i, j, k);
 | |
| 				this.aquiferCache[o] = fluidStatus2;
 | |
| 				return fluidStatus2;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		private Aquifer.FluidStatus computeFluid(int x, int y, int z) {
 | |
| 			Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(x, y, z);
 | |
| 			int i = Integer.MAX_VALUE;
 | |
| 			int j = y + 12;
 | |
| 			int k = y - 12;
 | |
| 			boolean bl = false;
 | |
| 
 | |
| 			for (int[] is : SURFACE_SAMPLING_OFFSETS_IN_CHUNKS) {
 | |
| 				int l = x + SectionPos.sectionToBlockCoord(is[0]);
 | |
| 				int m = z + SectionPos.sectionToBlockCoord(is[1]);
 | |
| 				int n = this.noiseChunk.preliminarySurfaceLevel(l, m);
 | |
| 				int o = n + 8;
 | |
| 				boolean bl2 = is[0] == 0 && is[1] == 0;
 | |
| 				if (bl2 && k > o) {
 | |
| 					return fluidStatus;
 | |
| 				}
 | |
| 
 | |
| 				boolean bl3 = j > o;
 | |
| 				if (bl3 || bl2) {
 | |
| 					Aquifer.FluidStatus fluidStatus2 = this.globalFluidPicker.computeFluid(l, o, m);
 | |
| 					if (!fluidStatus2.at(o).isAir()) {
 | |
| 						if (bl2) {
 | |
| 							bl = true;
 | |
| 						}
 | |
| 
 | |
| 						if (bl3) {
 | |
| 							return fluidStatus2;
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				i = Math.min(i, n);
 | |
| 			}
 | |
| 
 | |
| 			int p = this.computeSurfaceLevel(x, y, z, fluidStatus, i, bl);
 | |
| 			return new Aquifer.FluidStatus(p, this.computeFluidType(x, y, z, fluidStatus, p));
 | |
| 		}
 | |
| 
 | |
| 		private int computeSurfaceLevel(int x, int y, int z, Aquifer.FluidStatus fluidStatus, int maxSurfaceLevel, boolean fluidPresent) {
 | |
| 			DensityFunction.SinglePointContext singlePointContext = new DensityFunction.SinglePointContext(x, y, z);
 | |
| 			double d;
 | |
| 			double e;
 | |
| 			if (OverworldBiomeBuilder.isDeepDarkRegion(this.erosion, this.depth, singlePointContext)) {
 | |
| 				d = -1.0;
 | |
| 				e = -1.0;
 | |
| 			} else {
 | |
| 				int i = maxSurfaceLevel + 8 - y;
 | |
| 				int j = 64;
 | |
| 				double f = fluidPresent ? Mth.clampedMap((double)i, 0.0, 64.0, 1.0, 0.0) : 0.0;
 | |
| 				double g = Mth.clamp(this.fluidLevelFloodednessNoise.compute(singlePointContext), -1.0, 1.0);
 | |
| 				double h = Mth.map(f, 1.0, 0.0, -0.3, 0.8);
 | |
| 				double k = Mth.map(f, 1.0, 0.0, -0.8, 0.4);
 | |
| 				d = g - k;
 | |
| 				e = g - h;
 | |
| 			}
 | |
| 
 | |
| 			int i;
 | |
| 			if (e > 0.0) {
 | |
| 				i = fluidStatus.fluidLevel;
 | |
| 			} else if (d > 0.0) {
 | |
| 				i = this.computeRandomizedFluidSurfaceLevel(x, y, z, maxSurfaceLevel);
 | |
| 			} else {
 | |
| 				i = DimensionType.WAY_BELOW_MIN_Y;
 | |
| 			}
 | |
| 
 | |
| 			return i;
 | |
| 		}
 | |
| 
 | |
| 		private int computeRandomizedFluidSurfaceLevel(int x, int y, int z, int maxSurfaceLevel) {
 | |
| 			int i = 16;
 | |
| 			int j = 40;
 | |
| 			int k = Math.floorDiv(x, 16);
 | |
| 			int l = Math.floorDiv(y, 40);
 | |
| 			int m = Math.floorDiv(z, 16);
 | |
| 			int n = l * 40 + 20;
 | |
| 			int o = 10;
 | |
| 			double d = this.fluidLevelSpreadNoise.compute(new DensityFunction.SinglePointContext(k, l, m)) * 10.0;
 | |
| 			int p = Mth.quantize(d, 3);
 | |
| 			int q = n + p;
 | |
| 			return Math.min(maxSurfaceLevel, q);
 | |
| 		}
 | |
| 
 | |
| 		private BlockState computeFluidType(int x, int y, int z, Aquifer.FluidStatus fluidStatus, int surfaceLevel) {
 | |
| 			BlockState blockState = fluidStatus.fluidType;
 | |
| 			if (surfaceLevel <= -10 && surfaceLevel != DimensionType.WAY_BELOW_MIN_Y && fluidStatus.fluidType != Blocks.LAVA.defaultBlockState()) {
 | |
| 				int i = 64;
 | |
| 				int j = 40;
 | |
| 				int k = Math.floorDiv(x, 64);
 | |
| 				int l = Math.floorDiv(y, 40);
 | |
| 				int m = Math.floorDiv(z, 64);
 | |
| 				double d = this.lavaNoise.compute(new DensityFunction.SinglePointContext(k, l, m));
 | |
| 				if (Math.abs(d) > 0.3) {
 | |
| 					blockState = Blocks.LAVA.defaultBlockState();
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return blockState;
 | |
| 		}
 | |
| 	}
 | |
| }
 |