Complete phase 6 seasonal cycles
This commit is contained in:
@@ -154,6 +154,11 @@ public final class LivingWorldBootstrap {
|
||||
private final Map<RegionCoordinate, Integer> rewildingBoostCycles = new HashMap<>();
|
||||
private final Map<RegionCoordinate, Integer> bogWetCycles = new HashMap<>();
|
||||
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 Path worldSaveDirectory;
|
||||
@@ -340,6 +345,11 @@ public final class LivingWorldBootstrap {
|
||||
rewildingBoostCycles.clear();
|
||||
bogWetCycles.clear();
|
||||
waterloggedRegions.clear();
|
||||
previousSeasonalTemperature.clear();
|
||||
leafColourCycles.clear();
|
||||
bareDryCycles.clear();
|
||||
permafrostWarmCycles.clear();
|
||||
thawedPermafrostRegions.clear();
|
||||
simSpeedMultiplier = 1;
|
||||
serverReady = false;
|
||||
LivingWorldLogger.info(
|
||||
@@ -508,6 +518,92 @@ public final class LivingWorldBootstrap {
|
||||
}
|
||||
applyHydrologyExpansion();
|
||||
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) {
|
||||
|
||||
@@ -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
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@@ -190,4 +190,19 @@ public enum WorldEffectType {
|
||||
|
||||
/** Waterlogged wetland soil slowly develops a persistent peat profile. */
|
||||
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());
|
||||
case PEAT_FORMS ->
|
||||
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) {
|
||||
int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
|
||||
if (y < level.getMinBuildHeight()) {
|
||||
|
||||
Reference in New Issue
Block a user