From 725ac6b33f38d5b863cee96948d98153e95e3ab7 Mon Sep 17 00:00:00 2001 From: George Date: Sun, 7 Jun 2026 22:27:50 +0100 Subject: [PATCH] Add /lw demo degrade/recover for one-command ecosystem transformation demo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../livingworld/commands/EcoDemoCommand.java | 167 ++++++++++++++++++ .../commands/LivingWorldCommandRoot.java | 3 +- 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/livingworld/commands/EcoDemoCommand.java diff --git a/src/main/java/com/livingworld/commands/EcoDemoCommand.java b/src/main/java/com/livingworld/commands/EcoDemoCommand.java new file mode 100644 index 0000000..7cb948b --- /dev/null +++ b/src/main/java/com/livingworld/commands/EcoDemoCommand.java @@ -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. + * + *
+ *   /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
+ * 
+ */ +public final class EcoDemoCommand { + + private EcoDemoCommand() {} + + public static LiteralArgumentBuilder build(Supplier 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); } +} diff --git a/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java b/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java index d039888..7467854 100644 --- a/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java +++ b/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java @@ -143,7 +143,8 @@ public final class LivingWorldCommandRoot { false); return 1; }))) - .then(RegionSetCommand.build(regionManager))); + .then(RegionSetCommand.build(regionManager)) + .then(EcoDemoCommand.build(regionManager))); } private static int toggleHud(CommandSourceStack source, Function hudToggle) {