Add VEGETATION_DIES world effect — trees and plants visibly die under bad conditions
WorldEffectType: new VEGETATION_DIES variant. WorldEffectsModule: emits VEGETATION_DIES whenever soilQuality < 20 OR pollutionScore > 30, mirroring VegetationModule's bad-conditions gate so block changes stay in sync with the data-layer die-off. Intensity is the max of the soil-shortage and pollution-excess fractions. NeoForgeWorldEffectExecutor.killVegetation(): - Detects tree canopy by comparing MOTION_BLOCKING (includes leaves/logs) height vs MOTION_BLOCKING_NO_LEAVES (ground) height. - When canopy is present: strips all leaf blocks downward from the canopy top; at intensity > 0.3 also removes upper trunk log sections, leaving bottom two blocks as a visible stump. - When no canopy: clears surface plants (short grass, fern, tall grass, large fern, saplings, dandelion, poppy). Previously the simulation correctly tracked vegetation pressure dropping to zero but the Minecraft world remained a full forest, so there was no visible feedback for degradation. /lw demo degrade now visibly kills the forest canopy while the succession stage regresses. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,13 @@ public enum WorldEffectType {
|
|||||||
*/
|
*/
|
||||||
WILDFIRE,
|
WILDFIRE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sustained bad conditions (low soil quality or high pollution) cause living
|
||||||
|
* vegetation to die visibly: leaves are removed, upper log sections stripped to
|
||||||
|
* stumps, and surface plants replaced with dead material.
|
||||||
|
*/
|
||||||
|
VEGETATION_DIES,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Heavy regional rain on low-succession (barren / sparse-grass) terrain causes
|
* Heavy regional rain on low-succession (barren / sparse-grass) terrain causes
|
||||||
* water to pool in low-lying spots. The platform adapter places water source blocks
|
* water to pool in low-lying spots. The platform adapter places water source blocks
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ public final class WorldEffectsModule implements SimulationModule {
|
|||||||
// Soil defaults to 60 so the gate is set at 75 to avoid blocking early effects.
|
// Soil defaults to 60 so the gate is set at 75 to avoid blocking early effects.
|
||||||
private static final double GRASS_DEGRADE_POLLUTION_MIN = 15.0;
|
private static final double GRASS_DEGRADE_POLLUTION_MIN = 15.0;
|
||||||
private static final double GRASS_DEGRADE_SOIL_MAX = 75.0;
|
private static final double GRASS_DEGRADE_SOIL_MAX = 75.0;
|
||||||
|
// Vegetation die-off: mirrors VegetationModule's bad-conditions thresholds.
|
||||||
|
private static final double DIEOFF_SOIL_MAX = 20.0;
|
||||||
|
private static final double DIEOFF_POLLUTION_MIN = 30.0;
|
||||||
private static final double VEG_SPREAD_VEG_MIN = 60.0;
|
private static final double VEG_SPREAD_VEG_MIN = 60.0;
|
||||||
private static final double VEG_SPREAD_SOIL_MIN = 50.0;
|
private static final double VEG_SPREAD_SOIL_MIN = 50.0;
|
||||||
private static final double SAPLING_SLOW_LOGGING_MIN = 50.0;
|
private static final double SAPLING_SLOW_LOGGING_MIN = 50.0;
|
||||||
@@ -206,7 +209,26 @@ public final class WorldEffectsModule implements SimulationModule {
|
|||||||
emitted = true;
|
emitted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Effect 7: rain pools in arid low-succession terrain ---
|
// --- Effect 7: vegetation dies under bad soil or heavy pollution ---
|
||||||
|
// Mirrors the bad-conditions gate in VegetationModule so block changes
|
||||||
|
// stay in sync with the data-layer die-off.
|
||||||
|
boolean badSoil = m.getSoilQuality() < DIEOFF_SOIL_MAX;
|
||||||
|
boolean badPollution = m.getPollutionScore() > DIEOFF_POLLUTION_MIN;
|
||||||
|
if (badSoil || badPollution) {
|
||||||
|
double intensitySoil = badSoil
|
||||||
|
? computeIntensity(DIEOFF_SOIL_MAX - m.getSoilQuality(), DIEOFF_SOIL_MAX)
|
||||||
|
: 0.0;
|
||||||
|
double intensityPoll = badPollution
|
||||||
|
? computeIntensity(m.getPollutionScore() - DIEOFF_POLLUTION_MIN, 70.0)
|
||||||
|
: 0.0;
|
||||||
|
double dieoffIntensity = Math.max(intensitySoil, intensityPoll);
|
||||||
|
emit(new WorldEffectRequest(
|
||||||
|
WorldEffectType.VEGETATION_DIES, region.getCoordinate(),
|
||||||
|
Math.max(0.1, dieoffIntensity)));
|
||||||
|
emitted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Effect 8: rain pools in arid low-succession terrain ---
|
||||||
// Heavy regional rain on barren/sparse-grass land cannot drain into vegetation;
|
// Heavy regional rain on barren/sparse-grass land cannot drain into vegetation;
|
||||||
// water collects in depressions, forming puddles visible as actual water blocks.
|
// water collects in depressions, forming puddles visible as actual water blocks.
|
||||||
if (recovery != null && atm != null
|
if (recovery != null && atm != null
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
|||||||
case SAPLING_GROWTH_SLOWED -> {} // requires mixin; deferred
|
case SAPLING_GROWTH_SLOWED -> {} // requires mixin; deferred
|
||||||
case WILDFIRE ->
|
case WILDFIRE ->
|
||||||
igniteVegetation(level, baseX, baseZ, request.intensity());
|
igniteVegetation(level, baseX, baseZ, request.intensity());
|
||||||
|
case VEGETATION_DIES ->
|
||||||
|
killVegetation(level, baseX, baseZ, request.intensity());
|
||||||
case WATER_POOL_FORMS ->
|
case WATER_POOL_FORMS ->
|
||||||
formWaterPool(level, baseX, baseZ, request.intensity());
|
formWaterPool(level, baseX, baseZ, request.intensity());
|
||||||
}
|
}
|
||||||
@@ -188,6 +190,55 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips leaves and upper trunk sections from trees, and removes surface plants.
|
||||||
|
* Uses MOTION_BLOCKING (includes canopy) vs MOTION_BLOCKING_NO_LEAVES (ground) to
|
||||||
|
* detect trees — when the two heights differ, a canopy is present.
|
||||||
|
* At low intensity: leaves only. At high intensity: leaves + upper log sections.
|
||||||
|
*/
|
||||||
|
private void killVegetation(ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||||
|
int attempts = Math.max(2, (int)(intensity * BLOCK_ATTEMPTS * 2));
|
||||||
|
for (int i = 0; i < attempts; i++) {
|
||||||
|
int x = baseX + random.nextInt(REGION_BLOCKS);
|
||||||
|
int z = baseZ + random.nextInt(REGION_BLOCKS);
|
||||||
|
|
||||||
|
int canopyY = level.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z) - 1;
|
||||||
|
int groundY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
|
||||||
|
|
||||||
|
if (canopyY > groundY) {
|
||||||
|
// A tree canopy is present — strip downward from canopy.
|
||||||
|
for (int y = canopyY; y > groundY; y--) {
|
||||||
|
BlockPos pos = new BlockPos(x, y, z);
|
||||||
|
if (!level.isLoaded(pos)) break;
|
||||||
|
var state = level.getBlockState(pos);
|
||||||
|
if (state.is(BlockTags.LEAVES)) {
|
||||||
|
level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
|
||||||
|
} else if (state.is(BlockTags.LOGS) && y > groundY + 1 && intensity > 0.3) {
|
||||||
|
// Remove upper trunk, leave bottom two blocks as a stump.
|
||||||
|
level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No canopy — clear surface plants (grass, flowers, saplings).
|
||||||
|
if (groundY < level.getMinBuildHeight()) continue;
|
||||||
|
BlockPos surfacePos = new BlockPos(x, groundY, z);
|
||||||
|
if (!level.isLoaded(surfacePos)) continue;
|
||||||
|
BlockPos abovePos = surfacePos.above();
|
||||||
|
if (!level.isLoaded(abovePos)) continue;
|
||||||
|
var above = level.getBlockState(abovePos);
|
||||||
|
if (above.is(Blocks.SHORT_GRASS) || above.is(Blocks.FERN)
|
||||||
|
|| above.is(Blocks.TALL_GRASS) || above.is(Blocks.LARGE_FERN)
|
||||||
|
|| above.is(BlockTags.SAPLINGS)
|
||||||
|
|| above.is(Blocks.DANDELION) || above.is(Blocks.POPPY)) {
|
||||||
|
level.setBlock(abovePos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LivingWorldLogger.info(DiagnosticCategory.SIMULATION,
|
||||||
|
"WorldEffect VEGETATION_DIES intensity=" + String.format("%.2f", intensity)
|
||||||
|
+ " at (" + baseX + "," + baseZ + ")");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Places water source blocks in the lowest depression found among random surface samples.
|
* Places water source blocks in the lowest depression found among random surface samples.
|
||||||
* Fires ~5% of times it is called so pools build gradually rather than flooding the region.
|
* Fires ~5% of times it is called so pools build gradually rather than flooding the region.
|
||||||
|
|||||||
Reference in New Issue
Block a user