Complete phase 6 seasonal cycles

This commit is contained in:
George
2026-06-11 18:18:52 +01:00
parent 2304266544
commit 98f97bfbc4
4 changed files with 215 additions and 0 deletions
@@ -154,6 +154,11 @@ public final class LivingWorldBootstrap {
private final Map<RegionCoordinate, Integer> rewildingBoostCycles = new HashMap<>(); private final Map<RegionCoordinate, Integer> rewildingBoostCycles = new HashMap<>();
private final Map<RegionCoordinate, Integer> bogWetCycles = new HashMap<>(); private final Map<RegionCoordinate, Integer> bogWetCycles = new HashMap<>();
private final Set<RegionCoordinate> waterloggedRegions = new HashSet<>(); private final Set<RegionCoordinate> waterloggedRegions = new HashSet<>();
private final Map<RegionCoordinate, Double> previousSeasonalTemperature = new HashMap<>();
private final Map<RegionCoordinate, Integer> leafColourCycles = new HashMap<>();
private final Map<RegionCoordinate, Integer> bareDryCycles = new HashMap<>();
private final Map<RegionCoordinate, Integer> permafrostWarmCycles = new HashMap<>();
private final Set<RegionCoordinate> thawedPermafrostRegions = new HashSet<>();
private PlatformAdapter platformAdapter; private PlatformAdapter platformAdapter;
private Path worldSaveDirectory; private Path worldSaveDirectory;
@@ -340,6 +345,11 @@ public final class LivingWorldBootstrap {
rewildingBoostCycles.clear(); rewildingBoostCycles.clear();
bogWetCycles.clear(); bogWetCycles.clear();
waterloggedRegions.clear(); waterloggedRegions.clear();
previousSeasonalTemperature.clear();
leafColourCycles.clear();
bareDryCycles.clear();
permafrostWarmCycles.clear();
thawedPermafrostRegions.clear();
simSpeedMultiplier = 1; simSpeedMultiplier = 1;
serverReady = false; serverReady = false;
LivingWorldLogger.info( LivingWorldLogger.info(
@@ -508,6 +518,92 @@ public final class LivingWorldBootstrap {
} }
applyHydrologyExpansion(); applyHydrologyExpansion();
applyEcologyExpansion(); applyEcologyExpansion();
applyLongCycleEffects();
}
private void applyLongCycleEffects() {
if (worldEffectsModule == null) return;
Season season = getCurrentSeason();
double seasonalOffset = switch (season) {
case SPRING -> 0.05;
case SUMMER -> 0.15;
case AUTUMN -> -0.05;
case WINTER -> -0.20;
};
double warming = climateTracker.getWarmingLevel() * 0.25;
for (Region region : regionManager.getActiveRegions()) {
RegionCoordinate coord = region.getCoordinate();
double baseTemperature = regionTemperatures.getOrDefault(coord, 0.8f);
double seasonalTemperature = baseTemperature + seasonalOffset + warming;
double previous = previousSeasonalTemperature.getOrDefault(
coord, seasonalTemperature);
previousSeasonalTemperature.put(coord, seasonalTemperature);
VegetationRegionData vegetation = region.getModuleData()
.get(VegetationModule.MODULE_ID, VegetationRegionData.class).orElse(null);
SoilRegionData soil = region.getModuleData()
.get(SoilModule.MODULE_ID, SoilRegionData.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);
AtmosphereRegionData atmosphere = region.getModuleData()
.get(AtmosphereModule.MODULE_ID, AtmosphereRegionData.class).orElse(null);
if (vegetation == null || soil == null || water == null
|| recovery == null || atmosphere == null) continue;
if (previous >= 0.2 && seasonalTemperature < 0.2
&& vegetation.getTreePressure() > 10.0) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.LEAVES_CHANGE_COLOUR, coord, 0.8));
leafColourCycles.put(coord, 5 + windRandom.nextInt(6));
}
Integer colourCycles = leafColourCycles.get(coord);
if (colourCycles != null) {
if (colourCycles <= 1) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.LEAVES_FALL, coord, 0.8));
leafColourCycles.remove(coord);
} else {
leafColourCycles.put(coord, colourCycles - 1);
}
}
boolean bareAndDry = dryCycles.getOrDefault(coord, 0) >= 20
&& vegetation.getGrassPressure() < 10.0
&& soil.getMoisture() < 20.0;
if (bareAndDry) {
int exposed = bareDryCycles.merge(coord, 1, Integer::sum);
if (exposed >= 15) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.SOIL_CRUSTS, coord,
Math.min(1.0, exposed / 40.0)));
water.setWaterAvailability(Math.max(0, water.getWaterAvailability() - 0.1));
}
} else if (atmosphere.getRainLevel() > 0.65) {
bareDryCycles.remove(coord);
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.SOIL_CRUST_BREAKS, coord, atmosphere.getRainLevel()));
}
if (baseTemperature < 0.0 && warming > 0.03
&& !thawedPermafrostRegions.contains(coord)) {
int warmCycles = permafrostWarmCycles.merge(coord, 1, Integer::sum);
if (warmCycles >= 20) {
worldEffectsModule.queueEffect(new WorldEffectRequest(
WorldEffectType.PERMAFROST_THAWS, coord, 0.8));
groundwaterLevel.merge(coord, 20.0, Double::sum);
recovery.raiseCapOneStage();
thawedPermafrostRegions.add(coord);
pendingElevationResample.add(coord);
}
}
region.getModuleData().put(WaterModule.MODULE_ID, water);
region.getModuleData().put(RecoveryModule.MODULE_ID, recovery);
regionManager.markDirty(region);
}
} }
public void recordPassiveMobSpawn(RegionCoordinate coord) { public void recordPassiveMobSpawn(RegionCoordinate coord) {
@@ -82,6 +82,13 @@ public final class RecoveryRegionData {
} }
} }
/** Permanently raises the ecological ceiling by one stage. */
public void raiseCapOneStage() {
if (maxSuccessionStage.hasNext()) {
maxSuccessionStage = maxSuccessionStage.next();
}
}
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// Mutation // Mutation
// ------------------------------------------------------------------ // ------------------------------------------------------------------
@@ -190,4 +190,19 @@ public enum WorldEffectType {
/** Waterlogged wetland soil slowly develops a persistent peat profile. */ /** Waterlogged wetland soil slowly develops a persistent peat profile. */
PEAT_FORMS, PEAT_FORMS,
/** Deciduous leaves enter a temporary autumn colour stage. */
LEAVES_CHANGE_COLOUR,
/** Coloured deciduous leaves fall after the seasonal display. */
LEAVES_FALL,
/** Exposed drought-baked dirt develops an impermeable clay crust. */
SOIL_CRUSTS,
/** Heavy rain breaks clay crust back through gravel toward soil. */
SOIL_CRUST_BREAKS,
/** Warming frozen subsoil collapses into wet mud and meltwater. */
PERMAFROST_THAWS,
} }
@@ -162,6 +162,16 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
deepenPlungePool(level, baseX, baseZ, request.intensity()); deepenPlungePool(level, baseX, baseZ, request.intensity());
case PEAT_FORMS -> case PEAT_FORMS ->
formPeat(level, baseX, baseZ, request.intensity()); formPeat(level, baseX, baseZ, request.intensity());
case LEAVES_CHANGE_COLOUR ->
changeLeafColour(level, baseX, baseZ, request.intensity());
case LEAVES_FALL ->
dropLeaves(level, baseX, baseZ, request.intensity());
case SOIL_CRUSTS ->
crustSoil(level, baseX, baseZ, request.intensity());
case SOIL_CRUST_BREAKS ->
breakSoilCrust(level, baseX, baseZ, request.intensity());
case PERMAFROST_THAWS ->
thawPermafrost(level, baseX, baseZ, request.intensity());
} }
} }
@@ -1177,6 +1187,93 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
} }
} }
private void changeLeafColour(ServerLevel level, int baseX, int baseZ, double intensity) {
for (int i = 0; i < Math.max(3, (int) (intensity * 10)); i++) {
int x = baseX + random.nextInt(REGION_BLOCKS);
int z = baseZ + random.nextInt(REGION_BLOCKS);
int topY = level.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z) - 1;
for (int y = topY; y >= topY - 6; y--) {
BlockPos pos = new BlockPos(x, y, z);
if (!level.isLoaded(pos)) break;
var state = level.getBlockState(pos);
if (state.is(Blocks.OAK_LEAVES) || state.is(Blocks.BIRCH_LEAVES)) {
level.setBlock(pos, random.nextBoolean()
? Blocks.ORANGE_TERRACOTTA.defaultBlockState()
: Blocks.BROWN_TERRACOTTA.defaultBlockState(), Block.UPDATE_ALL);
break;
}
}
}
}
private void dropLeaves(ServerLevel level, int baseX, int baseZ, double intensity) {
for (int i = 0; i < Math.max(3, (int) (intensity * 10)); i++) {
int x = baseX + random.nextInt(REGION_BLOCKS);
int z = baseZ + random.nextInt(REGION_BLOCKS);
int topY = level.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z) - 1;
for (int y = topY; y >= topY - 6; y--) {
BlockPos pos = new BlockPos(x, y, z);
if (!level.isLoaded(pos)) break;
if (level.getBlockState(pos).is(Blocks.ORANGE_TERRACOTTA)
|| level.getBlockState(pos).is(Blocks.BROWN_TERRACOTTA)) {
level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
level.sendParticles(ParticleTypes.FALLING_SPORE_BLOSSOM,
x + 0.5, y + 0.5, z + 0.5, 2, 0.4, 0.3, 0.4, 0.01);
break;
}
}
}
}
private void crustSoil(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++) {
BlockPos pos = surfaceAt(level, baseX + random.nextInt(REGION_BLOCKS),
baseZ + random.nextInt(REGION_BLOCKS));
if (pos != null && level.canSeeSky(pos.above())
&& (level.getBlockState(pos).is(Blocks.DIRT)
|| level.getBlockState(pos).is(Blocks.COARSE_DIRT))) {
level.setBlock(pos, Blocks.TERRACOTTA.defaultBlockState(), Block.UPDATE_ALL);
}
}
}
private void breakSoilCrust(ServerLevel level, int baseX, int baseZ, double intensity) {
for (int i = 0; i < Math.max(2, (int) (intensity * 8)); i++) {
BlockPos pos = surfaceAt(level, baseX + random.nextInt(REGION_BLOCKS),
baseZ + random.nextInt(REGION_BLOCKS));
if (pos == null) continue;
if (level.getBlockState(pos).is(Blocks.TERRACOTTA)) {
level.setBlock(pos, Blocks.GRAVEL.defaultBlockState(), Block.UPDATE_ALL);
} else if (level.getBlockState(pos).is(Blocks.GRAVEL)) {
level.setBlock(pos, Blocks.DIRT.defaultBlockState(), Block.UPDATE_ALL);
}
}
}
private void thawPermafrost(ServerLevel level, int baseX, int baseZ, double intensity) {
int writes = 0;
for (int i = 0; i < 10 && writes < 10; i++) {
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 depth = 1; depth <= 3 && writes < 10; depth++) {
BlockPos pos = new BlockPos(x, surfaceY - depth, z);
if (!level.isLoaded(pos)) continue;
var state = level.getBlockState(pos);
if (state.is(Blocks.STONE) || state.is(Blocks.GRAVEL)) {
level.setBlock(pos, Blocks.MUD.defaultBlockState(), Block.UPDATE_ALL);
writes++;
}
}
BlockPos surface = new BlockPos(x, surfaceY, z);
if (writes < 10 && isNaturalTerrain(level.getBlockState(surface))) {
level.setBlock(surface, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
writes++;
}
}
}
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()) {