Add /lw set debug commands for ecosystem testing
Adds RegionSetCommand under /lw set with sub-commands to directly mutate simulation data in the player's current region: /lw set soil fertility <0-100> — soil fertility /lw set soil moisture <0-100> — soil moisture /lw set water availability <0-100> — water availability /lw set water drought <0-100> — drought risk /lw set pollution <0-100> — all pollution layers (proportional) /lw set stage <STAGE> — force succession stage (resets progress) /lw set cap <STAGE> — force succession ceiling /lw set rain <0.0-1.0> — override regional rain level Stage names have tab-completion. Forcing /lw set stage to a higher stage than the current cap auto-raises the cap. All changes are immediately persisted via markDirty. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import java.util.stream.Collectors;
|
|||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.arguments.DoubleArgumentType;
|
||||||
|
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.Commands;
|
import net.minecraft.commands.Commands;
|
||||||
@@ -120,7 +121,8 @@ public final class LivingWorldCommandRoot {
|
|||||||
String info = climateStatus.apply(context.getSource());
|
String info = climateStatus.apply(context.getSource());
|
||||||
context.getSource().sendSuccess(() -> Component.literal(info), false);
|
context.getSource().sendSuccess(() -> Component.literal(info), false);
|
||||||
return 1;
|
return 1;
|
||||||
})));
|
}))
|
||||||
|
.then(RegionSetCommand.build(regionManager)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int toggleHud(CommandSourceStack source, Function<UUID, Boolean> hudToggle) {
|
private static int toggleHud(CommandSourceStack source, Function<UUID, Boolean> hudToggle) {
|
||||||
|
|||||||
@@ -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 <property> <value>}.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <h3>Sub-commands</h3>
|
||||||
|
* <pre>
|
||||||
|
* /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)
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public final class RegionSetCommand {
|
||||||
|
|
||||||
|
private static final SuggestionProvider<CommandSourceStack> STAGE_SUGGESTIONS =
|
||||||
|
(ctx, builder) -> SharedSuggestionProvider.suggest(
|
||||||
|
Arrays.stream(SuccessionStage.values()).map(Enum::name), builder);
|
||||||
|
|
||||||
|
private RegionSetCommand() {}
|
||||||
|
|
||||||
|
public static LiteralArgumentBuilder<CommandSourceStack> build(Supplier<RegionManager> 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> T getOrDefault(Region region, String moduleId, Class<T> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user