Add /lw demo degrade/recover for one-command ecosystem transformation demo

EcoDemoCommand provides two complete condition presets applied in one command:

  /lw demo degrade  — forces stage=MATURE_FOREST with full lush vegetation,
                      then applies dead soil (fertility=5, contamination=8,
                      erosion=8), severe drought (availability=5, risk=90),
                      and clean air. soilQuality collapses to ~0, triggering
                      VegetationModule badConditions and RecoveryModule
                      conditionsMissedForRegression every cycle. Region
                      visibly regresses toward BARREN.

  /lw demo recover  — caps succession at MATURE_FOREST, resets soil to
                      fertility=85/contamination=0/erosion=0, water to
                      availability=85/drought=5, seeds minimal grass.
                      soilQuality immediately hits ~85, unlocking full
                      vegetation succession chain and rapid stage advancement.

Full demo sequence:
  /lw speed 20
  /lw demo degrade        (watch ~30s: MATURE_FOREST → BARREN)
  /lw demo recover
  /lw speed 50            (watch ~30s: BARREN → MATURE_FOREST)
  /lw speed reset

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
George
2026-06-07 22:27:50 +01:00
parent 1727c3a406
commit 725ac6b33f
2 changed files with 169 additions and 1 deletions
@@ -0,0 +1,167 @@
package com.livingworld.commands;
import java.util.function.Supplier;
import com.livingworld.core.LivingWorldConstants;
import com.livingworld.modules.atmosphere.AtmosphereModule;
import com.livingworld.modules.atmosphere.AtmosphereRegionData;
import com.livingworld.modules.pollution.PollutionModule;
import com.livingworld.modules.pollution.PollutionRegionData;
import com.livingworld.modules.recovery.RecoveryModule;
import com.livingworld.modules.recovery.RecoveryRegionData;
import com.livingworld.modules.recovery.SuccessionStage;
import com.livingworld.modules.soil.SoilModule;
import com.livingworld.modules.soil.SoilRegionData;
import com.livingworld.modules.vegetation.VegetationModule;
import com.livingworld.modules.vegetation.VegetationRegionData;
import com.livingworld.modules.water.WaterModule;
import com.livingworld.modules.water.WaterRegionData;
import com.livingworld.regions.Region;
import com.livingworld.regions.RegionCoordinate;
import com.livingworld.regions.RegionManager;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
/**
* One-command demo presets for watching ecosystem transformation.
*
* <pre>
* /lw demo degrade — forces MATURE_FOREST then applies severe drought + dead soil
* so the region visibly regresses toward BARREN.
* Recommended speed: /lw speed 20
*
* /lw demo recover — resets soil and water to lush conditions so the region
* advances from wherever it is back toward MATURE_FOREST.
* Recommended speed: /lw speed 50
* </pre>
*/
public final class EcoDemoCommand {
private EcoDemoCommand() {}
public static LiteralArgumentBuilder<CommandSourceStack> build(Supplier<RegionManager> rm) {
return Commands.literal("demo")
.then(Commands.literal("degrade")
.executes(ctx -> runDegrade(ctx.getSource(), rm.get())))
.then(Commands.literal("recover")
.executes(ctx -> runRecover(ctx.getSource(), rm.get())));
}
// ------------------------------------------------------------------
// Presets
// ------------------------------------------------------------------
private static int runDegrade(CommandSourceStack src, RegionManager rm) {
return applyPreset(src, rm, "DEGRADE", region -> {
// Force starting point: peak forest.
region.getModuleData().put(RecoveryModule.MODULE_ID,
new RecoveryRegionData(SuccessionStage.MATURE_FOREST, 0.0, 0.0,
SuccessionStage.MATURE_FOREST));
// Lush vegetation so it visibly dies rather than starting at zero.
region.getModuleData().put(VegetationModule.MODULE_ID,
new VegetationRegionData(100.0, 80.0, 80.0, 70.0, 0.0));
// Dead soil: near-zero fertility, high erosion and contamination.
// soilQuality ≈ 5 8×0.4 8×0.3 = 5 3.2 2.4 = 0.6 → 0
// That floor triggers badConditions in VegetationModule and
// conditionsMissedForRegression in RecoveryModule every cycle.
SoilRegionData soil = SoilRegionData.defaults();
soil.setFertility(5.0);
soil.setMoisture(5.0);
soil.setContamination(8.0);
soil.setErosion(8.0);
region.getModuleData().put(SoilModule.MODULE_ID, soil);
// Severe drought — water availability near zero.
WaterRegionData water = WaterRegionData.defaults();
water.setWaterAvailability(5.0);
water.setDroughtRisk(90.0);
water.setPurificationCapacity(5.0);
region.getModuleData().put(WaterModule.MODULE_ID, water);
// Clear pollution so it doesn't confuse the read-out (soil drives the decay here).
region.getModuleData().put(PollutionModule.MODULE_ID, PollutionRegionData.defaults());
// Dry sky: low rain so per-player weather reflects the drought.
AtmosphereRegionData atm = new AtmosphereRegionData(0.05, 0.0);
region.getModuleData().put(AtmosphereModule.MODULE_ID, atm);
}, "Stage forced to MATURE_FOREST | Soil destroyed | Drought=90 | Use /lw speed 20 to watch");
}
private static int runRecover(CommandSourceStack src, RegionManager rm) {
return applyPreset(src, rm, "RECOVER", region -> {
// Raise cap — region must be allowed to reach MATURE_FOREST again.
RecoveryRegionData rec = region.getModuleData()
.get(RecoveryModule.MODULE_ID, RecoveryRegionData.class)
.orElseGet(RecoveryRegionData::defaults);
rec.setMaxSuccessionStage(SuccessionStage.MATURE_FOREST);
region.getModuleData().put(RecoveryModule.MODULE_ID, rec);
// Rich, clean soil — contamination and erosion both zeroed.
SoilRegionData soil = SoilRegionData.defaults();
soil.setFertility(85.0);
soil.setMoisture(75.0);
soil.setContamination(0.0);
soil.setErosion(0.0);
region.getModuleData().put(SoilModule.MODULE_ID, soil);
// Plentiful water, no drought.
WaterRegionData water = WaterRegionData.defaults();
water.setWaterAvailability(85.0);
water.setDroughtRisk(5.0);
water.setPurificationCapacity(80.0);
region.getModuleData().put(WaterModule.MODULE_ID, water);
// Clean air.
region.getModuleData().put(PollutionModule.MODULE_ID, PollutionRegionData.defaults());
// Good rain signal in the atmosphere.
AtmosphereRegionData atm = new AtmosphereRegionData(0.75, 0.0);
region.getModuleData().put(AtmosphereModule.MODULE_ID, atm);
// Seed minimal grass so vegetation succession has something to start from.
// (Shrubs and trees grow naturally from here.)
VegetationRegionData veg = region.getModuleData()
.get(VegetationModule.MODULE_ID, VegetationRegionData.class)
.orElseGet(VegetationRegionData::defaults);
veg.setGrassPressure(Math.max(veg.getGrassPressure(), 5.0));
veg.setDeadVegetation(0.0);
region.getModuleData().put(VegetationModule.MODULE_ID, veg);
}, "Cap raised to MATURE_FOREST | Soil+water restored | Use /lw speed 50 to watch");
}
// ------------------------------------------------------------------
private static int applyPreset(CommandSourceStack src, RegionManager rm,
String label, RegionMutation mutation, String hint) {
if (rm == null) { src.sendFailure(Component.literal("Region manager not ready.")); return 0; }
ServerPlayer player;
try { player = src.getPlayerOrException(); }
catch (CommandSyntaxException e) {
src.sendFailure(Component.literal("/lw demo must be run by a player."));
return 0;
}
String dimId = player.level().dimension().location().toString();
int rx = (int) Math.floor(player.getX() / (LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS * 16.0));
int rz = (int) Math.floor(player.getZ() / (LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS * 16.0));
RegionCoordinate coord = new RegionCoordinate(dimId, rx, rz);
Region region = rm.getOrCreateRegion(coord);
mutation.apply(region);
rm.markDirty(region);
src.sendSuccess(() -> Component.literal(
"[LW] Demo:" + label + " applied to (" + rx + "," + rz + ") — " + hint), false);
return 1;
}
@FunctionalInterface
private interface RegionMutation { void apply(Region region); }
}
@@ -143,7 +143,8 @@ public final class LivingWorldCommandRoot {
false); false);
return 1; return 1;
}))) })))
.then(RegionSetCommand.build(regionManager))); .then(RegionSetCommand.build(regionManager))
.then(EcoDemoCommand.build(regionManager)));
} }
private static int toggleHud(CommandSourceStack source, Function<UUID, Boolean> hudToggle) { private static int toggleHud(CommandSourceStack source, Function<UUID, Boolean> hudToggle) {