diff --git a/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java b/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java index 8d0788b..387d875 100644 --- a/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java +++ b/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java @@ -8,6 +8,7 @@ import java.util.stream.Collectors; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.arguments.DoubleArgumentType; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; @@ -120,7 +121,8 @@ public final class LivingWorldCommandRoot { String info = climateStatus.apply(context.getSource()); context.getSource().sendSuccess(() -> Component.literal(info), false); return 1; - }))); + })) + .then(RegionSetCommand.build(regionManager))); } private static int toggleHud(CommandSourceStack source, Function hudToggle) { diff --git a/src/main/java/com/livingworld/commands/RegionSetCommand.java b/src/main/java/com/livingworld/commands/RegionSetCommand.java new file mode 100644 index 0000000..1fd4d8e --- /dev/null +++ b/src/main/java/com/livingworld/commands/RegionSetCommand.java @@ -0,0 +1,245 @@ +package com.livingworld.commands; + +import java.util.Arrays; +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.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.arguments.DoubleArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; + +/** + * Debug command branch: {@code /lw set }. + * + *

Directly mutates simulation data in the player's current region so that + * ecosystem transformation systems (succession, water runoff, pool formation) + * can be tested without waiting for natural accumulation. + * + *

Sub-commands

+ *
+ *   /lw set soil fertility <0-100>
+ *   /lw set soil moisture  <0-100>
+ *   /lw set water availability <0-100>
+ *   /lw set water drought      <0-100>
+ *   /lw set pollution <0-100>   (sets all three pollution layers proportionally)
+ *   /lw set stage <STAGE_NAME>  (forces succession stage; resets progress)
+ *   /lw set cap   <STAGE_NAME>  (forces succession ceiling)
+ *   /lw set rain  <0.0-1.0>    (overrides regional rain level for one sim cycle)
+ * 
+ */ +public final class RegionSetCommand { + + private static final SuggestionProvider STAGE_SUGGESTIONS = + (ctx, builder) -> SharedSuggestionProvider.suggest( + Arrays.stream(SuccessionStage.values()).map(Enum::name), builder); + + private RegionSetCommand() {} + + public static LiteralArgumentBuilder build(Supplier rm) { + return Commands.literal("set") + // --- soil --- + .then(Commands.literal("soil") + .then(Commands.literal("fertility") + .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 100)) + .executes(ctx -> setSoilFertility(ctx.getSource(), rm.get(), + DoubleArgumentType.getDouble(ctx, "value"))))) + .then(Commands.literal("moisture") + .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 100)) + .executes(ctx -> setSoilMoisture(ctx.getSource(), rm.get(), + DoubleArgumentType.getDouble(ctx, "value")))))) + // --- water --- + .then(Commands.literal("water") + .then(Commands.literal("availability") + .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 100)) + .executes(ctx -> setWaterAvailability(ctx.getSource(), rm.get(), + DoubleArgumentType.getDouble(ctx, "value"))))) + .then(Commands.literal("drought") + .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 100)) + .executes(ctx -> setWaterDrought(ctx.getSource(), rm.get(), + DoubleArgumentType.getDouble(ctx, "value")))))) + // --- pollution --- + .then(Commands.literal("pollution") + .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 100)) + .executes(ctx -> setPollution(ctx.getSource(), rm.get(), + DoubleArgumentType.getDouble(ctx, "value"))))) + // --- succession stage --- + .then(Commands.literal("stage") + .then(Commands.argument("name", StringArgumentType.word()) + .suggests(STAGE_SUGGESTIONS) + .executes(ctx -> setStage(ctx.getSource(), rm.get(), + StringArgumentType.getString(ctx, "name"))))) + // --- succession cap --- + .then(Commands.literal("cap") + .then(Commands.argument("name", StringArgumentType.word()) + .suggests(STAGE_SUGGESTIONS) + .executes(ctx -> setCap(ctx.getSource(), rm.get(), + StringArgumentType.getString(ctx, "name"))))) + // --- regional rain level --- + .then(Commands.literal("rain") + .then(Commands.argument("value", DoubleArgumentType.doubleArg(0, 1)) + .executes(ctx -> setRain(ctx.getSource(), rm.get(), + DoubleArgumentType.getDouble(ctx, "value"))))); + } + + // ------------------------------------------------------------------ + // Executors + // ------------------------------------------------------------------ + + private static int setSoilFertility(CommandSourceStack src, RegionManager rm, double value) { + return modifyRegion(src, rm, "soil.fertility=" + value, region -> { + SoilRegionData soil = getOrDefault(region, SoilModule.MODULE_ID, SoilRegionData.class, + SoilRegionData.defaults()); + soil.setFertility(value); + region.getModuleData().put(SoilModule.MODULE_ID, soil); + }); + } + + private static int setSoilMoisture(CommandSourceStack src, RegionManager rm, double value) { + return modifyRegion(src, rm, "soil.moisture=" + value, region -> { + SoilRegionData soil = getOrDefault(region, SoilModule.MODULE_ID, SoilRegionData.class, + SoilRegionData.defaults()); + soil.setMoisture(value); + region.getModuleData().put(SoilModule.MODULE_ID, soil); + }); + } + + private static int setWaterAvailability(CommandSourceStack src, RegionManager rm, double value) { + return modifyRegion(src, rm, "water.availability=" + value, region -> { + WaterRegionData water = getOrDefault(region, WaterModule.MODULE_ID, WaterRegionData.class, + WaterRegionData.defaults()); + water.setWaterAvailability(value); + region.getModuleData().put(WaterModule.MODULE_ID, water); + }); + } + + private static int setWaterDrought(CommandSourceStack src, RegionManager rm, double value) { + return modifyRegion(src, rm, "water.drought=" + value, region -> { + WaterRegionData water = getOrDefault(region, WaterModule.MODULE_ID, WaterRegionData.class, + WaterRegionData.defaults()); + water.setDroughtRisk(value); + region.getModuleData().put(WaterModule.MODULE_ID, water); + }); + } + + private static int setPollution(CommandSourceStack src, RegionManager rm, double value) { + return modifyRegion(src, rm, "pollution=" + value, region -> { + PollutionRegionData existing = getOrDefault(region, PollutionModule.MODULE_ID, + PollutionRegionData.class, PollutionRegionData.defaults()); + region.getModuleData().put(PollutionModule.MODULE_ID, + new PollutionRegionData(value, value * 0.5, value * 0.3, + existing.getDecayResistance())); + }); + } + + private static int setStage(CommandSourceStack src, RegionManager rm, String stageName) { + SuccessionStage stage = parseStage(src, stageName); + if (stage == null) return 0; + return modifyRegion(src, rm, "stage=" + stage.name(), region -> { + RecoveryRegionData rec = getOrDefault(region, RecoveryModule.MODULE_ID, + RecoveryRegionData.class, RecoveryRegionData.defaults()); + // Construct a fresh data object at the desired stage, preserving cap. + SuccessionStage cap = rec.getMaxSuccessionStage(); + if (stage.ordinal() > cap.ordinal()) { + // Auto-raise cap if forcing to a higher stage. + cap = stage; + } + region.getModuleData().put(RecoveryModule.MODULE_ID, + new RecoveryRegionData(stage, 0.0, 0.0, cap)); + }); + } + + private static int setCap(CommandSourceStack src, RegionManager rm, String stageName) { + SuccessionStage stage = parseStage(src, stageName); + if (stage == null) return 0; + return modifyRegion(src, rm, "cap=" + stage.name(), region -> { + RecoveryRegionData rec = getOrDefault(region, RecoveryModule.MODULE_ID, + RecoveryRegionData.class, RecoveryRegionData.defaults()); + rec.setMaxSuccessionStage(stage); + region.getModuleData().put(RecoveryModule.MODULE_ID, rec); + }); + } + + private static int setRain(CommandSourceStack src, RegionManager rm, double value) { + return modifyRegion(src, rm, "rain=" + value, region -> { + AtmosphereRegionData atm = getOrDefault(region, AtmosphereModule.MODULE_ID, + AtmosphereRegionData.class, AtmosphereRegionData.defaults()); + atm.setRainLevel(value); + region.getModuleData().put(AtmosphereModule.MODULE_ID, atm); + }); + } + + // ------------------------------------------------------------------ + // Shared helpers + // ------------------------------------------------------------------ + + /** Resolves the player's region, applies {@code mutation}, marks dirty, replies. */ + private static int modifyRegion(CommandSourceStack src, RegionManager rm, + String change, RegionMutation mutation) { + 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 set 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( + String.format("[LW] Set %s in region (%d,%d)", change, rx, rz)), false); + return 1; + } + + private static T getOrDefault(Region region, String moduleId, Class type, T fallback) { + return region.getModuleData().get(moduleId, type).orElse(fallback); + } + + private static SuccessionStage parseStage(CommandSourceStack src, String name) { + try { + return SuccessionStage.valueOf(name.toUpperCase()); + } catch (IllegalArgumentException e) { + src.sendFailure(Component.literal( + "Unknown stage '" + name + "'. Valid: BARREN, SPARSE_GRASS, GRASSLAND," + + " SCRUBLAND, YOUNG_WOODLAND, MATURE_FOREST")); + return null; + } + } + + @FunctionalInterface + private interface RegionMutation { + void apply(Region region); + } +}