minecraft-src/net/minecraft/world/level/levelgen/carver/WorldCarver.java
2025-07-04 03:45:38 +03:00

249 lines
9.4 KiB
Java

package net.minecraft.world.level.levelgen.carver;
import com.google.common.collect.ImmutableSet;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import java.util.Set;
import java.util.function.Function;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
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.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.chunk.CarvingMask;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.levelgen.Aquifer;
import net.minecraft.world.level.levelgen.DensityFunction;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.jetbrains.annotations.Nullable;
public abstract class WorldCarver<C extends CarverConfiguration> {
public static final WorldCarver<CaveCarverConfiguration> CAVE = register("cave", new CaveWorldCarver(CaveCarverConfiguration.CODEC));
public static final WorldCarver<CaveCarverConfiguration> NETHER_CAVE = register("nether_cave", new NetherWorldCarver(CaveCarverConfiguration.CODEC));
public static final WorldCarver<CanyonCarverConfiguration> CANYON = register("canyon", new CanyonWorldCarver(CanyonCarverConfiguration.CODEC));
protected static final BlockState AIR = Blocks.AIR.defaultBlockState();
protected static final BlockState CAVE_AIR = Blocks.CAVE_AIR.defaultBlockState();
protected static final FluidState WATER = Fluids.WATER.defaultFluidState();
protected static final FluidState LAVA = Fluids.LAVA.defaultFluidState();
protected Set<Fluid> liquids = ImmutableSet.of(Fluids.WATER);
private final MapCodec<ConfiguredWorldCarver<C>> configuredCodec;
private static <C extends CarverConfiguration, F extends WorldCarver<C>> F register(String key, F carver) {
return Registry.register(BuiltInRegistries.CARVER, key, carver);
}
public WorldCarver(Codec<C> codec) {
this.configuredCodec = codec.fieldOf("config").xmap(this::configured, ConfiguredWorldCarver::config);
}
public ConfiguredWorldCarver<C> configured(C config) {
return new ConfiguredWorldCarver<>(this, config);
}
public MapCodec<ConfiguredWorldCarver<C>> configuredCodec() {
return this.configuredCodec;
}
public int getRange() {
return 4;
}
/**
* Carves blocks in an ellipsoid (more accurately a spheroid), defined by a center (x, y, z) position, with a horizontal and vertical radius (the semi-axes)
*
* @param skipChecker Used to skip certain blocks within the carved region.
*/
protected boolean carveEllipsoid(
CarvingContext context,
C config,
ChunkAccess chunk,
Function<BlockPos, Holder<Biome>> biomeAccessor,
Aquifer aquifer,
double x,
double y,
double z,
double horizontalRadius,
double verticalRadius,
CarvingMask carvingMask,
WorldCarver.CarveSkipChecker skipChecker
) {
ChunkPos chunkPos = chunk.getPos();
double d = chunkPos.getMiddleBlockX();
double e = chunkPos.getMiddleBlockZ();
double f = 16.0 + horizontalRadius * 2.0;
if (!(Math.abs(x - d) > f) && !(Math.abs(z - e) > f)) {
int i = chunkPos.getMinBlockX();
int j = chunkPos.getMinBlockZ();
int k = Math.max(Mth.floor(x - horizontalRadius) - i - 1, 0);
int l = Math.min(Mth.floor(x + horizontalRadius) - i, 15);
int m = Math.max(Mth.floor(y - verticalRadius) - 1, context.getMinGenY() + 1);
int n = chunk.isUpgrading() ? 0 : 7;
int o = Math.min(Mth.floor(y + verticalRadius) + 1, context.getMinGenY() + context.getGenDepth() - 1 - n);
int p = Math.max(Mth.floor(z - horizontalRadius) - j - 1, 0);
int q = Math.min(Mth.floor(z + horizontalRadius) - j, 15);
boolean bl = false;
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
BlockPos.MutableBlockPos mutableBlockPos2 = new BlockPos.MutableBlockPos();
for (int r = k; r <= l; r++) {
int s = chunkPos.getBlockX(r);
double g = (s + 0.5 - x) / horizontalRadius;
for (int t = p; t <= q; t++) {
int u = chunkPos.getBlockZ(t);
double h = (u + 0.5 - z) / horizontalRadius;
if (!(g * g + h * h >= 1.0)) {
MutableBoolean mutableBoolean = new MutableBoolean(false);
for (int v = o; v > m; v--) {
double w = (v - 0.5 - y) / verticalRadius;
if (!skipChecker.shouldSkip(context, g, w, h, v) && (!carvingMask.get(r, v, t) || isDebugEnabled(config))) {
carvingMask.set(r, v, t);
mutableBlockPos.set(s, v, u);
bl |= this.carveBlock(context, config, chunk, biomeAccessor, carvingMask, mutableBlockPos, mutableBlockPos2, aquifer, mutableBoolean);
}
}
}
}
}
return bl;
} else {
return false;
}
}
/**
* Carves a single block, replacing it with the appropriate state if possible, and handles replacing exposed dirt with grass.
*
* @param pos The position to carve at. The method does not mutate this position.
* @param checkPos An additional mutable block position object to be used and modified by the method
* @param reachedSurface Set to true if the block carved was the surface, which is checked as being either grass or mycelium
*/
protected boolean carveBlock(
CarvingContext context,
C config,
ChunkAccess chunk,
Function<BlockPos, Holder<Biome>> biomeGetter,
CarvingMask carvingMask,
BlockPos.MutableBlockPos pos,
BlockPos.MutableBlockPos checkPos,
Aquifer aquifer,
MutableBoolean reachedSurface
) {
BlockState blockState = chunk.getBlockState(pos);
if (blockState.is(Blocks.GRASS_BLOCK) || blockState.is(Blocks.MYCELIUM)) {
reachedSurface.setTrue();
}
if (!this.canReplaceBlock(config, blockState) && !isDebugEnabled(config)) {
return false;
} else {
BlockState blockState2 = this.getCarveState(context, config, pos, aquifer);
if (blockState2 == null) {
return false;
} else {
chunk.setBlockState(pos, blockState2);
if (aquifer.shouldScheduleFluidUpdate() && !blockState2.getFluidState().isEmpty()) {
chunk.markPosForPostprocessing(pos);
}
if (reachedSurface.isTrue()) {
checkPos.setWithOffset(pos, Direction.DOWN);
if (chunk.getBlockState(checkPos).is(Blocks.DIRT)) {
context.topMaterial(biomeGetter, chunk, checkPos, !blockState2.getFluidState().isEmpty()).ifPresent(blockStatex -> {
chunk.setBlockState(checkPos, blockStatex);
if (!blockStatex.getFluidState().isEmpty()) {
chunk.markPosForPostprocessing(checkPos);
}
});
}
}
return true;
}
}
}
@Nullable
private BlockState getCarveState(CarvingContext context, C config, BlockPos pos, Aquifer aquifer) {
if (pos.getY() <= config.lavaLevel.resolveY(context)) {
return LAVA.createLegacyBlock();
} else {
BlockState blockState = aquifer.computeSubstance(new DensityFunction.SinglePointContext(pos.getX(), pos.getY(), pos.getZ()), 0.0);
if (blockState == null) {
return isDebugEnabled(config) ? config.debugSettings.getBarrierState() : null;
} else {
return isDebugEnabled(config) ? getDebugState(config, blockState) : blockState;
}
}
}
private static BlockState getDebugState(CarverConfiguration config, BlockState state) {
if (state.is(Blocks.AIR)) {
return config.debugSettings.getAirState();
} else if (state.is(Blocks.WATER)) {
BlockState blockState = config.debugSettings.getWaterState();
return blockState.hasProperty(BlockStateProperties.WATERLOGGED) ? blockState.setValue(BlockStateProperties.WATERLOGGED, true) : blockState;
} else {
return state.is(Blocks.LAVA) ? config.debugSettings.getLavaState() : state;
}
}
/**
* Carves the given chunk with caves that originate from the given {@code chunkPos}.
* This method is invoked 289 times in order to generate each chunk (once for every position in an 8 chunk radius, or 17x17 chunk area, centered around the target chunk).
*
* @see net.minecraft.world.level.chunk.ChunkGenerator#applyCarvers
*
* @param chunk The chunk to be carved
* @param chunkPos The chunk position this carver is being called from
*/
public abstract boolean carve(
CarvingContext context,
C config,
ChunkAccess chunk,
Function<BlockPos, Holder<Biome>> biomeAccessor,
RandomSource random,
Aquifer aquifer,
ChunkPos chunkPos,
CarvingMask carvingMask
);
public abstract boolean isStartChunk(C config, RandomSource random);
protected boolean canReplaceBlock(C config, BlockState state) {
return state.is(config.replaceable);
}
protected static boolean canReach(ChunkPos chunkPos, double x, double z, int branchIndex, int branchCount, float width) {
double d = chunkPos.getMiddleBlockX();
double e = chunkPos.getMiddleBlockZ();
double f = x - d;
double g = z - e;
double h = branchCount - branchIndex;
double i = width + 2.0F + 16.0F;
return f * f + g * g - h * h <= i * i;
}
private static boolean isDebugEnabled(CarverConfiguration config) {
return config.debugSettings.isDebugMode();
}
/**
* Used to define certain positions to skip or ignore when carving.
*/
public interface CarveSkipChecker {
boolean shouldSkip(CarvingContext carvingContext, double d, double e, double f, int i);
}
}