Complete phase 4 hydrology expansion
This commit is contained in:
@@ -291,7 +291,7 @@ public class LivingWorldMod {
|
||||
RegionCoordinate spawnCoord = RegionCoordinate.fromBlock(
|
||||
dimId, (int) event.getX(), (int) event.getZ(),
|
||||
LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS);
|
||||
if (bootstrap.isDeadZone(spawnCoord)
|
||||
if (bootstrap.isAquaticSpawnSuppressed(spawnCoord)
|
||||
&& (event.getEntity() instanceof AbstractFish
|
||||
|| event.getEntity() instanceof Squid
|
||||
|| event.getEntity() instanceof Dolphin)) {
|
||||
@@ -563,7 +563,7 @@ public class LivingWorldMod {
|
||||
// -- Pre-compute tidal current for this tick --
|
||||
ServerLevel overworld = minecraftServer.overworld();
|
||||
long dayTime = overworld.getDayTime();
|
||||
int seaLevel = overworld.getSeaLevel();
|
||||
int seaLevel = bootstrap.getDynamicSeaLevel();
|
||||
int moonPhase = overworld.getMoonPhase();
|
||||
double springNeap = 1.0 + 0.40 * Math.cos(moonPhase * Math.PI / 4.0);
|
||||
// Tidal current magnitude = derivative of tide height (max at mid-tide, zero at high/low)
|
||||
@@ -696,7 +696,7 @@ public class LivingWorldMod {
|
||||
if (minecraftServer == null) return;
|
||||
ServerLevel overworld = minecraftServer.overworld();
|
||||
long dayTime = overworld.getDayTime();
|
||||
int seaLevel = overworld.getSeaLevel();
|
||||
int seaLevel = bootstrap.getDynamicSeaLevel();
|
||||
|
||||
// Moon phase (0=full, 4=new); full moon = maximum spring tides
|
||||
int moonPhase = overworld.getMoonPhase();
|
||||
|
||||
@@ -140,6 +140,11 @@ public final class LivingWorldBootstrap {
|
||||
private record GeyserSite(int cycleTick, boolean active) {}
|
||||
private final Map<RegionCoordinate, GeyserSite> geyserSites = new HashMap<>();
|
||||
private int geologicalActivityTick = 0;
|
||||
private final Map<RegionCoordinate, Integer> dryCycles = new HashMap<>();
|
||||
private final Map<RegionCoordinate, Double> previousEffectiveTemperature = new HashMap<>();
|
||||
private final Map<RegionCoordinate, Integer> blizzardHistory = new HashMap<>();
|
||||
private int hydrologyCycleTick = 0;
|
||||
private int dynamicSeaLevel = 62;
|
||||
|
||||
private PlatformAdapter platformAdapter;
|
||||
private Path worldSaveDirectory;
|
||||
@@ -273,7 +278,10 @@ public final class LivingWorldBootstrap {
|
||||
*/
|
||||
/** Sets the ecosystem tuning loaded from the server config. Must be called before onServerStarting(). */
|
||||
public void setEcosystemTuning(EcosystemTuning tuning) {
|
||||
if (tuning != null) ecosystemTuning = tuning;
|
||||
if (tuning != null) {
|
||||
ecosystemTuning = tuning;
|
||||
dynamicSeaLevel = tuning.getSeaLevel();
|
||||
}
|
||||
}
|
||||
|
||||
public void onServerStopping() {
|
||||
@@ -309,6 +317,11 @@ public final class LivingWorldBootstrap {
|
||||
volcanicActivityTick = 0;
|
||||
geyserSites.clear();
|
||||
geologicalActivityTick = 0;
|
||||
dryCycles.clear();
|
||||
previousEffectiveTemperature.clear();
|
||||
blizzardHistory.clear();
|
||||
hydrologyCycleTick = 0;
|
||||
dynamicSeaLevel = ecosystemTuning.getSeaLevel();
|
||||
simSpeedMultiplier = 1;
|
||||
serverReady = false;
|
||||
LivingWorldLogger.info(
|
||||
@@ -474,6 +487,147 @@ public final class LivingWorldBootstrap {
|
||||
if (++geologicalActivityTick % 4 == 0) {
|
||||
applyGeologicalActivity();
|
||||
}
|
||||
applyHydrologyExpansion();
|
||||
}
|
||||
|
||||
private void applyHydrologyExpansion() {
|
||||
Collection<Region> active = regionManager.getActiveRegions();
|
||||
if (active.isEmpty() || worldEffectsModule == null) return;
|
||||
hydrologyCycleTick++;
|
||||
double warming = climateTracker.getWarmingLevel();
|
||||
int[][] offsets = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
Map<RegionCoordinate, Region> byCoord = new HashMap<>();
|
||||
for (Region region : active) byCoord.put(region.getCoordinate(), region);
|
||||
|
||||
double totalRain = 0.0;
|
||||
int rainSamples = 0;
|
||||
for (Region region : active) {
|
||||
RegionCoordinate coord = region.getCoordinate();
|
||||
AtmosphereRegionData atmosphere = region.getModuleData()
|
||||
.get(AtmosphereModule.MODULE_ID, AtmosphereRegionData.class).orElse(null);
|
||||
WaterRegionData water = region.getModuleData()
|
||||
.get(WaterModule.MODULE_ID, WaterRegionData.class).orElse(null);
|
||||
RecoveryRegionData recovery = region.getModuleData()
|
||||
.get(RecoveryModule.MODULE_ID, RecoveryRegionData.class).orElse(null);
|
||||
if (atmosphere == null || water == null || recovery == null) continue;
|
||||
totalRain += atmosphere.getRainLevel() * 100.0;
|
||||
rainSamples++;
|
||||
|
||||
double elevation = regionElevations.getOrDefault(coord, 64.0);
|
||||
double effectiveTemperature = regionTemperatures.getOrDefault(coord, 0.8f) + warming * 0.2;
|
||||
double previousTemperature = previousEffectiveTemperature.getOrDefault(
|
||||
coord, effectiveTemperature);
|
||||
previousEffectiveTemperature.put(coord, effectiveTemperature);
|
||||
|
||||
if (previousTemperature < 0.15 && effectiveTemperature >= 0.15) {
|
||||
worldEffectsModule.queueEffect(new WorldEffectRequest(
|
||||
WorldEffectType.ICE_MELTS, coord, 0.8));
|
||||
worldEffectsModule.queueEffect(new WorldEffectRequest(
|
||||
WorldEffectType.SNOW_MELTS, coord, 0.8));
|
||||
groundwaterLevel.merge(coord, 12.0, Double::sum);
|
||||
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 && neighbourElevation < elevation - 3.0) {
|
||||
worldEffectsModule.queueEffect(new WorldEffectRequest(
|
||||
WorldEffectType.GROUND_SPRING_EMERGES, neighbour, 0.7));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int dry = atmosphere.getRainLevel() < 0.15
|
||||
? dryCycles.merge(coord, 1, Integer::sum) : 0;
|
||||
if (dry == 0) dryCycles.remove(coord);
|
||||
if (dry >= 20) {
|
||||
worldEffectsModule.queueEffect(new WorldEffectRequest(
|
||||
WorldEffectType.RIVERBED_DRIES, coord,
|
||||
Math.min(1.0, (dry - 15) / 30.0)));
|
||||
groundwaterLevel.compute(coord,
|
||||
(ignored, level) -> Math.max(0, (level == null ? 0.0 : level) - 2.0));
|
||||
}
|
||||
|
||||
boolean lowSuccession = recovery.getSuccessionStage() == SuccessionStage.BARREN
|
||||
|| recovery.getSuccessionStage() == SuccessionStage.SPARSE_GRASS;
|
||||
double maxSlope = 0.0;
|
||||
RegionCoordinate lowestNeighbour = null;
|
||||
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 > maxSlope) {
|
||||
maxSlope = elevation - neighbourElevation;
|
||||
lowestNeighbour = neighbour;
|
||||
}
|
||||
}
|
||||
if (atmosphere.getRainLevel() > 0.80 && lowSuccession && maxSlope > 10.0
|
||||
&& windRandom.nextDouble() < 0.02
|
||||
&& !isClimateEventActive(coord, ClimateEventType.FLASH_FLOOD)) {
|
||||
ClimateEvent flood = new ClimateEvent(
|
||||
ClimateEventType.FLASH_FLOOD, coord,
|
||||
simulationManager.getSimulationTickCounter(), 0.8);
|
||||
activeClimateEvents.add(flood);
|
||||
pendingEventMessages.add("[LW] Flash flood at region ("
|
||||
+ coord.x() + "," + coord.z() + ").");
|
||||
}
|
||||
|
||||
double flow = riverFlowIntensity.getOrDefault(coord, 0.0);
|
||||
if (flow > 20.0 && lowestNeighbour != null) {
|
||||
if (oceanicRegions.contains(lowestNeighbour)) {
|
||||
worldEffectsModule.queueEffect(new WorldEffectRequest(
|
||||
WorldEffectType.SEDIMENT_DEPOSIT, lowestNeighbour,
|
||||
Math.min(1.0, flow / 100.0)));
|
||||
pendingElevationResample.add(lowestNeighbour);
|
||||
}
|
||||
if (maxSlope > 10.0) {
|
||||
worldEffectsModule.queueEffect(new WorldEffectRequest(
|
||||
WorldEffectType.PLUNGE_POOL_DEEPENS, lowestNeighbour,
|
||||
Math.min(1.0, flow / 100.0)));
|
||||
}
|
||||
}
|
||||
|
||||
if (isClimateEventActive(coord, ClimateEventType.BLIZZARD)) {
|
||||
blizzardHistory.merge(coord, 1, Integer::sum);
|
||||
}
|
||||
int snowHistory = blizzardHistory.getOrDefault(coord, 0);
|
||||
if (effectiveTemperature < 0.0 && elevation > 90.0 && snowHistory >= 10
|
||||
&& hydrologyCycleTick % 50 == 0) {
|
||||
worldEffectsModule.queueEffect(new WorldEffectRequest(
|
||||
WorldEffectType.GLACIER_ADVANCE, coord, 0.7));
|
||||
worldEffectsModule.queueEffect(new WorldEffectRequest(
|
||||
WorldEffectType.GLACIER_POLISH, coord, 0.7));
|
||||
}
|
||||
|
||||
if (isClimateEventActive(coord, ClimateEventType.ACID_RAIN) && flow > 10.0
|
||||
&& lowestNeighbour != null && oceanicRegions.contains(lowestNeighbour)) {
|
||||
Region downstream = byCoord.get(lowestNeighbour);
|
||||
if (downstream != null) {
|
||||
PollutionRegionData downstreamPollution = downstream.getModuleData()
|
||||
.get(PollutionModule.MODULE_ID, PollutionRegionData.class).orElse(null);
|
||||
if (downstreamPollution != null) {
|
||||
double spike = geothermalRegions.contains(coord) ? 0.2 : 1.0;
|
||||
downstreamPollution.addPollution(0, 0, spike);
|
||||
downstream.getModuleData().put(PollutionModule.MODULE_ID, downstreamPollution);
|
||||
regionManager.markDirty(downstream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hydrologyCycleTick % 500 == 0 && rainSamples > 0) {
|
||||
double averageRain = totalRain / rainSamples;
|
||||
int baseline = ecosystemTuning.getSeaLevel();
|
||||
int oldSeaLevel = dynamicSeaLevel;
|
||||
if (averageRain > 65.0 && dynamicSeaLevel < baseline + 3) dynamicSeaLevel++;
|
||||
if (averageRain < 15.0 && dynamicSeaLevel > baseline - 3) dynamicSeaLevel--;
|
||||
if (oldSeaLevel != dynamicSeaLevel) {
|
||||
pendingEventMessages.add("[LW] Sea level changed to Y=" + dynamicSeaLevel + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getDynamicSeaLevel() {
|
||||
return dynamicSeaLevel;
|
||||
}
|
||||
|
||||
private void applyGeologicalActivity() {
|
||||
@@ -983,6 +1137,15 @@ public final class LivingWorldBootstrap {
|
||||
return coord != null && deadZoneRegions.contains(coord);
|
||||
}
|
||||
|
||||
public boolean isAquaticSpawnSuppressed(RegionCoordinate coord) {
|
||||
if (coord == null || deadZoneRegions.contains(coord)) return coord != null;
|
||||
return regionManager != null && regionManager.resolve(coord)
|
||||
.flatMap(region -> region.getModuleData()
|
||||
.get(PollutionModule.MODULE_ID, PollutionRegionData.class))
|
||||
.map(pollution -> pollution.getWaterPollution() > 50.0)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
/** Returns whether a region is known to be oceanic. */
|
||||
public boolean isOceanicRegion(RegionCoordinate coord) {
|
||||
return coord != null && oceanicRegions.contains(coord);
|
||||
@@ -1795,6 +1958,13 @@ public final class LivingWorldBootstrap {
|
||||
}
|
||||
if (ev.getTicksActive() >= 2) shouldResolve = true;
|
||||
}
|
||||
case FLASH_FLOOD -> {
|
||||
if (worldEffectsModule != null) {
|
||||
worldEffectsModule.queueEffect(new WorldEffectRequest(
|
||||
WorldEffectType.FLASH_FLOOD, coord, ev.getSeverity()));
|
||||
}
|
||||
if (ev.getTicksActive() >= 5) shouldResolve = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ public enum ClimateEventType {
|
||||
SANDSTORM("Sandstorm", "Dry high winds stripping plants and depositing sand"),
|
||||
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");
|
||||
SINKHOLE("Sinkhole", "Groundwater-driven collapse into a shallow cave"),
|
||||
FLASH_FLOOD("Flash Flood", "Rapid runoff surging across barren steep terrain");
|
||||
|
||||
private final String displayName;
|
||||
private final String description;
|
||||
|
||||
@@ -43,6 +43,7 @@ public final class EcosystemTuning {
|
||||
private double corridorBoostMultiplier = 3.5;
|
||||
/** Seeds blocked per unit of pollution score in the target region. */
|
||||
private double seedPollutionBlock = 0.015;
|
||||
private int seaLevel = 62;
|
||||
|
||||
public EcosystemTuning() {}
|
||||
|
||||
@@ -91,4 +92,7 @@ public final class EcosystemTuning {
|
||||
public void setSeedEmissionRate(double v) { seedEmissionRate = v; }
|
||||
public void setCorridorBoostMultiplier(double v) { corridorBoostMultiplier = v; }
|
||||
public void setSeedPollutionBlock(double v) { seedPollutionBlock = v; }
|
||||
|
||||
public int getSeaLevel() { return seaLevel; }
|
||||
public void setSeaLevel(int seaLevel) { this.seaLevel = Math.max(1, Math.min(320, seaLevel)); }
|
||||
}
|
||||
|
||||
@@ -166,4 +166,25 @@ public enum WorldEffectType {
|
||||
|
||||
/** A cooling volcanic roof collapses to reveal a lava-adjacent cavity. */
|
||||
LAVA_TUBE_COLLAPSE,
|
||||
|
||||
/** A temporary surge places water along a low terrain path. */
|
||||
FLASH_FLOOD,
|
||||
|
||||
/** Seasonal thaw converts exposed ice back into water. */
|
||||
ICE_MELTS,
|
||||
|
||||
/** Sustained drought drains exposed river sources and hardens the bed. */
|
||||
RIVERBED_DRIES,
|
||||
|
||||
/** River sediment accumulates below sea level at an ocean mouth. */
|
||||
SEDIMENT_DEPOSIT,
|
||||
|
||||
/** A glacier toe pushes loose rock downhill. */
|
||||
GLACIER_ADVANCE,
|
||||
|
||||
/** Moving ice leaves polished andesite behind. */
|
||||
GLACIER_POLISH,
|
||||
|
||||
/** Falling river water deepens its impact basin. */
|
||||
PLUNGE_POOL_DEEPENS,
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ public final class NeoForgeModConfig {
|
||||
private static final ModConfigSpec.DoubleValue SEED_EMISSION_RATE;
|
||||
private static final ModConfigSpec.DoubleValue CORRIDOR_BOOST_MULTIPLIER;
|
||||
private static final ModConfigSpec.DoubleValue SEED_POLLUTION_BLOCK;
|
||||
private static final ModConfigSpec.IntValue SEA_LEVEL;
|
||||
|
||||
static {
|
||||
ModConfigSpec.Builder b = new ModConfigSpec.Builder();
|
||||
@@ -65,6 +66,12 @@ public final class NeoForgeModConfig {
|
||||
.defineInRange("wind_boost", 0.5, 0.0, 2.0);
|
||||
b.pop();
|
||||
|
||||
b.comment("Long-timescale hydrology").push("hydrology");
|
||||
SEA_LEVEL = b
|
||||
.comment("Baseline simulated sea level. Long climate cycles may drift this by +/-3 blocks.")
|
||||
.defineInRange("sea_level", 62, 1, 320);
|
||||
b.pop();
|
||||
|
||||
b.comment("Vegetation growth rates (per soil-quality unit above threshold per tick)").push("vegetation");
|
||||
GRASS_GROWTH_RATE = b.comment("Grass growth rate. Default: 0.06")
|
||||
.defineInRange("grass_growth", 0.06, 0.001, 2.0);
|
||||
@@ -134,6 +141,7 @@ public final class NeoForgeModConfig {
|
||||
t.setSeedEmissionRate(SEED_EMISSION_RATE.get());
|
||||
t.setCorridorBoostMultiplier(CORRIDOR_BOOST_MULTIPLIER.get());
|
||||
t.setSeedPollutionBlock(SEED_POLLUTION_BLOCK.get());
|
||||
t.setSeaLevel(SEA_LEVEL.get());
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
private final Random random = new Random();
|
||||
private record TimedWater(BlockPos pos, long expiresAt) {}
|
||||
private final Map<ResourceKey<Level>, List<TimedWater>> geyserWater = new HashMap<>();
|
||||
private final Map<ResourceKey<Level>, List<TimedWater>> floodWater = new HashMap<>();
|
||||
|
||||
public NeoForgeWorldEffectExecutor(Supplier<MinecraftServer> serverSupplier) {
|
||||
this.serverSupplier = serverSupplier;
|
||||
@@ -65,6 +66,7 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
int baseX = request.region().x() * REGION_BLOCKS;
|
||||
int baseZ = request.region().z() * REGION_BLOCKS;
|
||||
cleanExpiredGeyserWater(level, dimensionKey);
|
||||
cleanExpiredFloodWater(level, dimensionKey);
|
||||
|
||||
switch (request.type()) {
|
||||
case GRASS_DEGRADES_TO_DIRT ->
|
||||
@@ -134,6 +136,20 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
landslide(level, baseX, baseZ, request.intensity());
|
||||
case LAVA_TUBE_COLLAPSE ->
|
||||
collapseLavaTube(level, baseX, baseZ, request.intensity());
|
||||
case FLASH_FLOOD ->
|
||||
flashFlood(level, dimensionKey, baseX, baseZ, request.intensity());
|
||||
case ICE_MELTS ->
|
||||
meltIce(level, baseX, baseZ, request.intensity());
|
||||
case RIVERBED_DRIES ->
|
||||
dryRiverbed(level, baseX, baseZ, request.intensity());
|
||||
case SEDIMENT_DEPOSIT ->
|
||||
depositSediment(level, baseX, baseZ, request.intensity());
|
||||
case GLACIER_ADVANCE ->
|
||||
advanceGlacier(level, baseX, baseZ, request.intensity());
|
||||
case GLACIER_POLISH ->
|
||||
polishGlacierRock(level, baseX, baseZ, request.intensity());
|
||||
case PLUNGE_POOL_DEEPENS ->
|
||||
deepenPlungePool(level, baseX, baseZ, request.intensity());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,6 +1007,146 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
|| state.is(Blocks.BASALT);
|
||||
}
|
||||
|
||||
private void cleanExpiredFloodWater(ServerLevel level, ResourceKey<Level> dimensionKey) {
|
||||
List<TimedWater> entries = floodWater.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()) floodWater.remove(dimensionKey);
|
||||
}
|
||||
|
||||
private void flashFlood(ServerLevel level, ResourceKey<Level> dimensionKey,
|
||||
int baseX, int baseZ, double intensity) {
|
||||
if (random.nextDouble() > 0.35) return;
|
||||
List<TimedWater> entries = floodWater.computeIfAbsent(dimensionKey, ignored -> new ArrayList<>());
|
||||
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 && (lowest == null || sample.getY() < lowest.getY())) lowest = sample;
|
||||
}
|
||||
if (lowest == null) return;
|
||||
int writes = 0;
|
||||
for (Direction direction : Direction.Plane.HORIZONTAL) {
|
||||
BlockPos pos = lowest.relative(direction).above();
|
||||
if (writes >= 5) break;
|
||||
if (level.isLoaded(pos) && level.getBlockState(pos).isAir()) {
|
||||
level.setBlock(pos, Blocks.WATER.defaultBlockState(), Block.UPDATE_ALL);
|
||||
entries.add(new TimedWater(pos, level.getGameTime() + 6000));
|
||||
writes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void meltIce(ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||
for (int i = 0; i < Math.max(2, (int) (intensity * 8)); i++) {
|
||||
int x = baseX + random.nextInt(REGION_BLOCKS);
|
||||
int z = baseZ + random.nextInt(REGION_BLOCKS);
|
||||
int y = level.getHeight(Heightmap.Types.WORLD_SURFACE, x, z) - 1;
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
if (level.isLoaded(pos) && level.getBlockState(pos).is(Blocks.ICE)) {
|
||||
level.setBlock(pos, Blocks.WATER.defaultBlockState(), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dryRiverbed(ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||
if (random.nextDouble() > 0.25) return;
|
||||
for (int i = 0; i < Math.max(2, (int) (intensity * 8)); i++) {
|
||||
int x = baseX + random.nextInt(REGION_BLOCKS);
|
||||
int z = baseZ + random.nextInt(REGION_BLOCKS);
|
||||
int y = level.getHeight(Heightmap.Types.WORLD_SURFACE, x, z) - 1;
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
if (!level.isLoaded(pos) || !level.getBlockState(pos).is(Blocks.WATER)
|
||||
|| !level.getFluidState(pos).isSource()) continue;
|
||||
BlockPos below = pos.below();
|
||||
var belowState = level.getBlockState(below);
|
||||
if (belowState.is(Blocks.SAND) || belowState.is(Blocks.DIRT)
|
||||
|| belowState.is(Blocks.GRAVEL) || belowState.is(Blocks.STONE)) {
|
||||
level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
|
||||
level.setBlock(below, random.nextBoolean() ? Blocks.COARSE_DIRT.defaultBlockState()
|
||||
: Blocks.GRAVEL.defaultBlockState(), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void depositSediment(ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||
if (random.nextDouble() > 0.25) return;
|
||||
for (int i = 0; i < Math.max(1, (int) (intensity * 5)); i++) {
|
||||
int x = baseX + random.nextInt(REGION_BLOCKS);
|
||||
int z = baseZ + random.nextInt(REGION_BLOCKS);
|
||||
int y = level.getHeight(Heightmap.Types.OCEAN_FLOOR, x, z);
|
||||
if (y >= level.getSeaLevel()) continue;
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
if (level.isLoaded(pos) && level.getFluidState(pos).is(FluidTags.WATER)) {
|
||||
level.setBlock(pos, random.nextBoolean() ? Blocks.SAND.defaultBlockState()
|
||||
: Blocks.GRAVEL.defaultBlockState(), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void advanceGlacier(ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||
BlockPos snow = null;
|
||||
BlockPos low = 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 ((level.getBlockState(sample).is(Blocks.SNOW_BLOCK)
|
||||
|| level.getBlockState(sample).is(Blocks.ICE))
|
||||
&& (snow == null || sample.getY() < snow.getY())) snow = sample;
|
||||
if (low == null || sample.getY() < low.getY()) low = sample;
|
||||
}
|
||||
if (snow == null || low == null) return;
|
||||
int dx = Integer.compare(low.getX(), snow.getX());
|
||||
int dz = Integer.compare(low.getZ(), snow.getZ());
|
||||
for (Direction direction : Direction.Plane.HORIZONTAL) {
|
||||
BlockPos source = snow.relative(direction);
|
||||
var sourceState = level.getBlockState(source);
|
||||
if (!sourceState.is(Blocks.GRAVEL) && !sourceState.is(Blocks.STONE)) continue;
|
||||
BlockPos target = source.offset(dx, 0, dz);
|
||||
if (level.isLoaded(target) && level.getBlockState(target).isAir()) {
|
||||
level.setBlock(source, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
|
||||
level.setBlock(target, sourceState, Block.UPDATE_ALL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void polishGlacierRock(ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
BlockPos pos = surfaceAt(level, baseX + random.nextInt(REGION_BLOCKS),
|
||||
baseZ + random.nextInt(REGION_BLOCKS));
|
||||
if (pos != null && (level.getBlockState(pos).is(Blocks.STONE)
|
||||
|| level.getBlockState(pos).is(Blocks.ANDESITE))) {
|
||||
level.setBlock(pos, Blocks.POLISHED_ANDESITE.defaultBlockState(), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deepenPlungePool(ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||
if (random.nextDouble() > 0.25) return;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int x = baseX + random.nextInt(REGION_BLOCKS);
|
||||
int z = baseZ + random.nextInt(REGION_BLOCKS);
|
||||
int surfaceY = level.getHeight(Heightmap.Types.WORLD_SURFACE, x, z) - 1;
|
||||
BlockPos water = new BlockPos(x, surfaceY, z);
|
||||
if (!level.isLoaded(water) || !level.getFluidState(water).is(FluidTags.WATER)) continue;
|
||||
BlockPos base = water.below();
|
||||
var state = level.getBlockState(base);
|
||||
if (state.is(Blocks.STONE) || state.is(Blocks.GRAVEL) || state.is(Blocks.SAND)) {
|
||||
level.setBlock(base, Blocks.WATER.defaultBlockState(), Block.UPDATE_ALL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BlockPos surfaceAt(ServerLevel level, int x, int z) {
|
||||
int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
|
||||
if (y < level.getMinBuildHeight()) {
|
||||
|
||||
Reference in New Issue
Block a user