Complete phase 3 geological activity

This commit is contained in:
George
2026-06-11 18:08:56 +01:00
parent f8995392eb
commit 5331d5b207
5 changed files with 341 additions and 1 deletions
@@ -33,6 +33,7 @@ import net.minecraft.world.entity.animal.Squid;
import net.minecraft.world.entity.monster.Monster; import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.CampfireBlockEntity; import net.minecraft.world.level.block.entity.CampfireBlockEntity;
@@ -223,6 +224,10 @@ public class LivingWorldMod {
|| biome.is(BiomeTags.IS_DEEP_OCEAN) || downfall > 0.8f) { || biome.is(BiomeTags.IS_DEEP_OCEAN) || downfall > 0.8f) {
bootstrap.markCoastalOrWetlandRegion(coord); bootstrap.markCoastalOrWetlandRegion(coord);
} }
int caveY = Math.max(level.getMinBuildHeight() + 1, by - 32);
if (level.getBiome(new BlockPos(bx, caveY, bz)).is(Biomes.DRIPSTONE_CAVES)) {
bootstrap.markGeothermalRegion(coord);
}
} }
}); });
NeoForge.EVENT_BUS.addListener(ServerStoppingEvent.class, event -> { NeoForge.EVENT_BUS.addListener(ServerStoppingEvent.class, event -> {
@@ -525,6 +530,14 @@ public class LivingWorldMod {
player.addEffect(new MobEffectInstance( player.addEffect(new MobEffectInstance(
MobEffects.MOVEMENT_SLOWDOWN, 60, 0, true, false)); MobEffects.MOVEMENT_SLOWDOWN, 60, 0, true, false));
} }
if (bootstrap.isClimateEventActive(
coord, com.livingworld.climate.ClimateEventType.EARTHQUAKE)) {
var movement = player.getDeltaMovement();
player.setDeltaMovement(
movement.x + (random.nextDouble() - 0.5) * 0.35,
Math.max(movement.y, 0.12),
movement.z + (random.nextDouble() - 0.5) * 0.35);
}
} }
} }
} }
@@ -784,6 +797,10 @@ public class LivingWorldMod {
|| biome.is(BiomeTags.IS_DEEP_OCEAN) || downfall > 0.8f) { || biome.is(BiomeTags.IS_DEEP_OCEAN) || downfall > 0.8f) {
bootstrap.markCoastalOrWetlandRegion(coord); bootstrap.markCoastalOrWetlandRegion(coord);
} }
int caveY = Math.max(level.getMinBuildHeight() + 1, cy - 32);
if (level.getBiome(new BlockPos(cx, caveY, cz)).is(Biomes.DRIPSTONE_CAVES)) {
bootstrap.markGeothermalRegion(coord);
}
} }
private static final int REGION_BLOCKS = private static final int REGION_BLOCKS =
@@ -130,12 +130,16 @@ public final class LivingWorldBootstrap {
private final Map<RegionCoordinate, Float> regionDownfall = new HashMap<>(); private final Map<RegionCoordinate, Float> regionDownfall = new HashMap<>();
private final Set<RegionCoordinate> desertRegions = new HashSet<>(); private final Set<RegionCoordinate> desertRegions = new HashSet<>();
private final Set<RegionCoordinate> coastalWetlandRegions = new HashSet<>(); private final Set<RegionCoordinate> coastalWetlandRegions = new HashSet<>();
private final Set<RegionCoordinate> geothermalRegions = new HashSet<>();
private final Map<RegionCoordinate, Integer> oceanPollutionCycles = new HashMap<>(); private final Map<RegionCoordinate, Integer> oceanPollutionCycles = new HashMap<>();
private final Set<RegionCoordinate> deadZoneRegions = new HashSet<>(); private final Set<RegionCoordinate> deadZoneRegions = new HashSet<>();
private final Set<RegionCoordinate> ventedRegions = new HashSet<>(); private final Set<RegionCoordinate> ventedRegions = new HashSet<>();
/** Regions whose terrain has changed significantly and need an elevation re-sample. */ /** Regions whose terrain has changed significantly and need an elevation re-sample. */
private final Set<RegionCoordinate> pendingElevationResample = new HashSet<>(); private final Set<RegionCoordinate> pendingElevationResample = new HashSet<>();
private int volcanicActivityTick = 0; private int volcanicActivityTick = 0;
private record GeyserSite(int cycleTick, boolean active) {}
private final Map<RegionCoordinate, GeyserSite> geyserSites = new HashMap<>();
private int geologicalActivityTick = 0;
private PlatformAdapter platformAdapter; private PlatformAdapter platformAdapter;
private Path worldSaveDirectory; private Path worldSaveDirectory;
@@ -297,11 +301,14 @@ public final class LivingWorldBootstrap {
regionDownfall.clear(); regionDownfall.clear();
desertRegions.clear(); desertRegions.clear();
coastalWetlandRegions.clear(); coastalWetlandRegions.clear();
geothermalRegions.clear();
oceanPollutionCycles.clear(); oceanPollutionCycles.clear();
deadZoneRegions.clear(); deadZoneRegions.clear();
ventedRegions.clear(); ventedRegions.clear();
pendingElevationResample.clear(); pendingElevationResample.clear();
volcanicActivityTick = 0; volcanicActivityTick = 0;
geyserSites.clear();
geologicalActivityTick = 0;
simSpeedMultiplier = 1; simSpeedMultiplier = 1;
serverReady = false; serverReady = false;
LivingWorldLogger.info( LivingWorldLogger.info(
@@ -464,6 +471,95 @@ public final class LivingWorldBootstrap {
if (++volcanicActivityTick % VOLCANO_TICK_INTERVAL == 0) { if (++volcanicActivityTick % VOLCANO_TICK_INTERVAL == 0) {
applyVolcanicActivity(); applyVolcanicActivity();
} }
if (++geologicalActivityTick % 4 == 0) {
applyGeologicalActivity();
}
}
private void applyGeologicalActivity() {
Collection<Region> active = regionManager.getActiveRegions();
if (active.isEmpty() || worldEffectsModule == null) return;
long simTick = simulationManager.getSimulationTickCounter();
Map<RegionCoordinate, Region> byCoord = new HashMap<>();
for (Region region : active) byCoord.put(region.getCoordinate(), region);
int[][] offsets = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (Region region : active) {
RegionCoordinate coord = region.getCoordinate();
double elevation = regionElevations.getOrDefault(coord, 64.0);
VolcanoPhase phase = volcanoPhase.get(coord);
boolean volcanicAdjacent = volcanicRegions.contains(coord);
for (int[] offset : offsets) {
RegionCoordinate neighbour = new RegionCoordinate(
coord.dimensionId(), coord.x() + offset[0], coord.z() + offset[1]);
volcanicAdjacent |= volcanicRegions.contains(neighbour);
}
if (!geyserSites.containsKey(coord)
&& (volcanicAdjacent || elevation > 100.0 || geothermalRegions.contains(coord))
&& windRandom.nextDouble() < 0.02) {
geyserSites.put(coord, new GeyserSite(0, false));
}
GeyserSite site = geyserSites.get(coord);
if (site != null) {
int cycle = (site.cycleTick() + 1) % 165;
boolean activeNow = cycle >= 150;
geyserSites.put(coord, new GeyserSite(cycle, activeNow));
if (activeNow) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.GEYSER_ERUPT, coord, 0.8));
if (cycle == 150) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.TRAVERTINE_DEPOSIT, coord, 0.7));
}
}
}
double quakeChance = phase == VolcanoPhase.BUILDING || phase == VolcanoPhase.ERUPTING
? 0.003 : elevation > 90.0 ? 0.001 : 0.0;
if (quakeChance > 0 && windRandom.nextDouble() < quakeChance
&& !isClimateEventActive(coord, ClimateEventType.EARTHQUAKE)) {
double magnitude = 1.0 + windRandom.nextDouble() * 4.0;
ClimateEvent quake = new ClimateEvent(
ClimateEventType.EARTHQUAKE, coord, simTick, magnitude / 5.0);
activeClimateEvents.add(quake);
pendingEventMessages.add("[LW] Earthquake magnitude "
+ String.format("%.1f", magnitude) + " at region ("
+ coord.x() + "," + coord.z() + ").");
}
double groundwater = groundwaterLevel.getOrDefault(coord, 0.0);
if (groundwater > 80.0 && windRandom.nextDouble() < 0.0005
&& !isClimateEventActive(coord, ClimateEventType.SINKHOLE)) {
ClimateEvent sinkhole = new ClimateEvent(
ClimateEventType.SINKHOLE, coord, simTick, 0.8);
activeClimateEvents.add(sinkhole);
pendingEventMessages.add("[LW] Sinkhole collapse at region ("
+ coord.x() + "," + coord.z() + ").");
}
AtmosphereRegionData atmosphere = region.getModuleData()
.get(AtmosphereModule.MODULE_ID, AtmosphereRegionData.class).orElse(null);
if (atmosphere != null && atmosphere.getRainLevel() > 0.55) {
for (int[] offset : offsets) {
RegionCoordinate neighbour = new RegionCoordinate(
coord.dimensionId(), coord.x() + offset[0], coord.z() + offset[1]);
Double neighbourElevation = regionElevations.get(neighbour);
if (neighbourElevation != null && elevation - neighbourElevation > 15.0
&& windRandom.nextDouble() < 0.01) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.LANDSLIDE, coord, 0.7));
break;
}
}
}
if (phase == VolcanoPhase.COOLING && windRandom.nextDouble() < 0.001) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.LAVA_TUBE_COLLAPSE, coord, 0.8));
}
}
} }
/** Sets the simulation speed multiplier (1 = real-time, max 100). */ /** Sets the simulation speed multiplier (1 = real-time, max 100). */
@@ -900,6 +996,10 @@ public final class LivingWorldBootstrap {
if (coord != null) coastalWetlandRegions.add(coord); if (coord != null) coastalWetlandRegions.add(coord);
} }
public void markGeothermalRegion(RegionCoordinate coord) {
if (coord != null) geothermalRegions.add(coord);
}
public boolean isDesertRegion(RegionCoordinate coord) { public boolean isDesertRegion(RegionCoordinate coord) {
return coord != null && desertRegions.contains(coord); return coord != null && desertRegions.contains(coord);
} }
@@ -1675,6 +1775,26 @@ public final class LivingWorldBootstrap {
WorldEffectType.WILDFIRE, coord, 0.25)); WorldEffectType.WILDFIRE, coord, 0.25));
} }
} }
case EARTHQUAKE -> {
if (ev.getTicksActive() == 1 && worldEffectsModule != null) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
ev.getSeverity() >= 0.8
? WorldEffectType.FISSURE_OPENS : WorldEffectType.GROUND_CRACK,
coord, ev.getSeverity()));
if (ev.getSeverity() >= 0.8) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.LANDSLIDE, coord, ev.getSeverity()));
}
}
if (ev.getTicksActive() >= 3) shouldResolve = true;
}
case SINKHOLE -> {
if (ev.getTicksActive() == 1 && worldEffectsModule != null) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.SINKHOLE_COLLAPSES, coord, ev.getSeverity()));
}
if (ev.getTicksActive() >= 2) shouldResolve = true;
}
} }
} }
@@ -10,7 +10,9 @@ public enum ClimateEventType {
ACID_RAIN("Acid Rain", "Polluted rainfall damaging soil, stone and vegetation"), ACID_RAIN("Acid Rain", "Polluted rainfall damaging soil, stone and vegetation"),
BLIZZARD("Blizzard", "Wind-driven snow accumulation and freezing water"), BLIZZARD("Blizzard", "Wind-driven snow accumulation and freezing water"),
SANDSTORM("Sandstorm", "Dry high winds stripping plants and depositing sand"), SANDSTORM("Sandstorm", "Dry high winds stripping plants and depositing sand"),
LIGHTNING_STORM("Lightning Storm", "Dry thunderstorm producing isolated ignition strikes"); LIGHTNING_STORM("Lightning Storm", "Dry thunderstorm producing isolated ignition strikes"),
EARTHQUAKE("Earthquake", "Seismic rupture opening cracks and unstable slopes"),
SINKHOLE("Sinkhole", "Groundwater-driven collapse into a shallow cave");
private final String displayName; private final String displayName;
private final String description; private final String description;
@@ -145,4 +145,25 @@ public enum WorldEffectType {
/** Dry high winds deposit sand and strip fragile surface plants. */ /** Dry high winds deposit sand and strip fragile surface plants. */
SAND_DEPOSIT, SAND_DEPOSIT,
/** A registered geothermal site ejects a temporary column of water. */
GEYSER_ERUPT,
/** Repeated geyser eruptions build a calcite and tuff travertine ring. */
TRAVERTINE_DEPOSIT,
/** Low-magnitude seismic activity removes a short line of surface blocks. */
GROUND_CRACK,
/** High-magnitude seismic activity opens a narrow, deep fissure. */
FISSURE_OPENS,
/** Saturated ground collapses into a detected shallow cave. */
SINKHOLE_COLLAPSES,
/** Unstable steep terrain transfers loose surface material downhill. */
LANDSLIDE,
/** A cooling volcanic roof collapses to reveal a lava-adjacent cavity. */
LAVA_TUBE_COLLAPSE,
} }
@@ -6,6 +6,10 @@ import com.livingworld.debug.LivingWorldLogger;
import com.livingworld.modules.worldeffects.WorldEffectConsumer; import com.livingworld.modules.worldeffects.WorldEffectConsumer;
import com.livingworld.modules.worldeffects.WorldEffectRequest; import com.livingworld.modules.worldeffects.WorldEffectRequest;
import java.util.Random; import java.util.Random;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
@@ -38,6 +42,8 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
private final Supplier<MinecraftServer> serverSupplier; private final Supplier<MinecraftServer> serverSupplier;
private final Random random = new Random(); private final Random random = new Random();
private record TimedWater(BlockPos pos, long expiresAt) {}
private final Map<ResourceKey<Level>, List<TimedWater>> geyserWater = new HashMap<>();
public NeoForgeWorldEffectExecutor(Supplier<MinecraftServer> serverSupplier) { public NeoForgeWorldEffectExecutor(Supplier<MinecraftServer> serverSupplier) {
this.serverSupplier = serverSupplier; this.serverSupplier = serverSupplier;
@@ -58,6 +64,7 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
} }
int baseX = request.region().x() * REGION_BLOCKS; int baseX = request.region().x() * REGION_BLOCKS;
int baseZ = request.region().z() * REGION_BLOCKS; int baseZ = request.region().z() * REGION_BLOCKS;
cleanExpiredGeyserWater(level, dimensionKey);
switch (request.type()) { switch (request.type()) {
case GRASS_DEGRADES_TO_DIRT -> case GRASS_DEGRADES_TO_DIRT ->
@@ -113,6 +120,20 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
meltSnow(level, baseX, baseZ, request.intensity()); meltSnow(level, baseX, baseZ, request.intensity());
case SAND_DEPOSIT -> case SAND_DEPOSIT ->
depositSand(level, baseX, baseZ, request.intensity()); depositSand(level, baseX, baseZ, request.intensity());
case GEYSER_ERUPT ->
eruptGeyser(level, dimensionKey, baseX, baseZ, request.intensity());
case TRAVERTINE_DEPOSIT ->
depositTravertine(level, baseX, baseZ, request.intensity());
case GROUND_CRACK ->
openGroundCrack(level, baseX, baseZ, request.intensity(), false);
case FISSURE_OPENS ->
openGroundCrack(level, baseX, baseZ, request.intensity(), true);
case SINKHOLE_COLLAPSES ->
collapseSinkhole(level, baseX, baseZ, request.intensity());
case LANDSLIDE ->
landslide(level, baseX, baseZ, request.intensity());
case LAVA_TUBE_COLLAPSE ->
collapseLavaTube(level, baseX, baseZ, request.intensity());
} }
} }
@@ -811,6 +832,165 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
} }
} }
private void cleanExpiredGeyserWater(ServerLevel level, ResourceKey<Level> dimensionKey) {
List<TimedWater> entries = geyserWater.get(dimensionKey);
if (entries == null) return;
long now = level.getGameTime();
entries.removeIf(entry -> {
if (entry.expiresAt() > now) return false;
if (level.isLoaded(entry.pos()) && level.getBlockState(entry.pos()).is(Blocks.WATER)) {
level.setBlock(entry.pos(), Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
}
return true;
});
if (entries.isEmpty()) geyserWater.remove(dimensionKey);
}
private void eruptGeyser(ServerLevel level, ResourceKey<Level> dimensionKey,
int baseX, int baseZ, double intensity) {
int x = baseX + REGION_BLOCKS / 2;
int z = baseZ + REGION_BLOCKS / 2;
int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z);
List<TimedWater> entries = geyserWater.computeIfAbsent(dimensionKey, ignored -> new ArrayList<>());
int height = 3 + random.nextInt(3);
for (int i = 0; i < height; i++) {
BlockPos pos = new BlockPos(x, y + i, z);
if (!level.isLoaded(pos) || (!level.getBlockState(pos).isAir()
&& !level.getBlockState(pos).is(Blocks.WATER))) break;
level.setBlock(pos, Blocks.WATER.defaultBlockState(), Block.UPDATE_ALL);
entries.add(new TimedWater(pos, level.getGameTime() + 40));
level.sendParticles(ParticleTypes.BUBBLE_COLUMN_UP,
x + 0.5, y + i + 0.5, z + 0.5, 3, 0.2, 0.3, 0.2, 0.05);
}
}
private void depositTravertine(ServerLevel level, int baseX, int baseZ, double intensity) {
int x = baseX + REGION_BLOCKS / 2;
int z = baseZ + REGION_BLOCKS / 2;
int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
int radius = 2 + random.nextInt(3);
int writes = 0;
for (int dx = -radius; dx <= radius && writes < 10; dx++) {
for (int dz = -radius; dz <= radius && writes < 10; dz++) {
if (Math.abs(Math.sqrt(dx * dx + dz * dz) - radius) > 0.75) continue;
BlockPos pos = new BlockPos(x + dx, y, z + dz);
if (!level.isLoaded(pos)) continue;
var state = level.getBlockState(pos);
if (state.is(Blocks.DIRT) || state.is(Blocks.GRASS_BLOCK)
|| state.is(Blocks.STONE) || state.is(Blocks.GRAVEL)) {
level.setBlock(pos, random.nextBoolean() ? Blocks.CALCITE.defaultBlockState()
: Blocks.TUFF.defaultBlockState(), Block.UPDATE_ALL);
writes++;
}
}
}
}
private void openGroundCrack(ServerLevel level, int baseX, int baseZ,
double intensity, boolean fissure) {
int x = baseX + random.nextInt(REGION_BLOCKS);
int z = baseZ + random.nextInt(REGION_BLOCKS);
int dx = random.nextBoolean() ? 1 : 0;
int dz = dx == 0 ? 1 : 0;
int writes = 0;
int length = fissure ? 5 : 3;
int depth = fissure ? 8 + random.nextInt(8) : 1;
for (int step = 0; step < length && writes < 10; step++) {
int surfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
x + dx * step, z + dz * step) - 1;
for (int d = 0; d < depth && writes < 10; d++) {
BlockPos pos = new BlockPos(x + dx * step, surfaceY - d, z + dz * step);
if (!level.isLoaded(pos) || !isNaturalTerrain(level.getBlockState(pos))) continue;
level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
writes++;
}
}
}
private void collapseSinkhole(ServerLevel level, int baseX, int baseZ, double intensity) {
int centerX = baseX + random.nextInt(REGION_BLOCKS);
int centerZ = baseZ + random.nextInt(REGION_BLOCKS);
int surfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, centerX, centerZ) - 1;
int caveY = Integer.MIN_VALUE;
for (int y = surfaceY - 3; y >= Math.max(level.getMinBuildHeight(), surfaceY - 15); y--) {
BlockPos probe = new BlockPos(centerX, y, centerZ);
if (level.isLoaded(probe) && level.getBlockState(probe).isAir()) {
caveY = y;
break;
}
}
if (caveY == Integer.MIN_VALUE) return;
int radius = 1 + random.nextInt(2);
int writes = 0;
for (int dx = -radius; dx <= radius && writes < 10; dx++) {
for (int dz = -radius; dz <= radius && writes < 10; dz++) {
for (int y = surfaceY; y > caveY && writes < 10; y--) {
BlockPos pos = new BlockPos(centerX + dx, y, centerZ + dz);
if (!level.isLoaded(pos) || !isNaturalTerrain(level.getBlockState(pos))) continue;
level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
writes++;
}
}
}
}
private void landslide(ServerLevel level, int baseX, int baseZ, double intensity) {
BlockPos highest = null;
BlockPos lowest = null;
for (int i = 0; i < 10; i++) {
BlockPos sample = surfaceAt(level, baseX + random.nextInt(REGION_BLOCKS),
baseZ + random.nextInt(REGION_BLOCKS));
if (sample == null) continue;
if (highest == null || sample.getY() > highest.getY()) highest = sample;
if (lowest == null || sample.getY() < lowest.getY()) lowest = sample;
}
if (highest == null || lowest == null || highest.getY() - lowest.getY() < 8) return;
for (int i = 0; i < 2; i++) {
BlockPos source = highest.below(i);
var state = level.getBlockState(source);
if (!state.is(Blocks.DIRT) && !state.is(Blocks.GRAVEL) && !state.is(Blocks.SAND)) continue;
BlockPos target = lowest.above(i + 1);
if (!level.isLoaded(target) || !level.getBlockState(target).isAir()) continue;
level.setBlock(source, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
level.setBlock(target, state, Block.UPDATE_ALL);
}
}
private void collapseLavaTube(ServerLevel level, int baseX, int baseZ, double intensity) {
for (int attempt = 0; attempt < 10; attempt++) {
int x = baseX + random.nextInt(REGION_BLOCKS);
int z = baseZ + random.nextInt(REGION_BLOCKS);
int surfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
for (int y = surfaceY - 2; y >= Math.max(level.getMinBuildHeight(), surfaceY - 20); y--) {
BlockPos air = new BlockPos(x, y, z);
if (!level.isLoaded(air) || !level.getBlockState(air).isAir()) continue;
boolean lavaAdjacent = false;
for (Direction direction : Direction.values()) {
if (level.getBlockState(air.relative(direction)).is(Blocks.LAVA)) {
lavaAdjacent = true;
break;
}
}
if (lavaAdjacent) {
BlockPos ceiling = air.above();
if (isNaturalTerrain(level.getBlockState(ceiling))) {
level.setBlock(ceiling, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
}
return;
}
}
}
}
private boolean isNaturalTerrain(net.minecraft.world.level.block.state.BlockState state) {
return state.is(Blocks.STONE) || state.is(Blocks.DEEPSLATE)
|| state.is(Blocks.DIRT) || state.is(Blocks.GRASS_BLOCK)
|| state.is(Blocks.GRAVEL) || state.is(Blocks.SAND)
|| state.is(Blocks.TUFF) || state.is(Blocks.ANDESITE)
|| state.is(Blocks.DIORITE) || state.is(Blocks.GRANITE)
|| state.is(Blocks.BASALT);
}
private BlockPos surfaceAt(ServerLevel level, int x, int z) { private BlockPos surfaceAt(ServerLevel level, int x, int z) {
int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1; int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
if (y < level.getMinBuildHeight()) { if (y < level.getMinBuildHeight()) {