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> 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()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user