diff --git a/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java b/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java index 4faf132..6fc0bd2 100644 --- a/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java +++ b/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java @@ -48,8 +48,8 @@ public final class LivingWorldCommandRoot { context.getSource(), regionManager, moduleRegistry, simulationManager))) .then(Commands.literal("region") .then(Commands.literal("info") - .executes(context -> placeholder( - context.getSource(), "Region inspection is not connected yet.")))) + .executes(context -> RegionInfoCommand.execute( + context.getSource(), regionManager)))) .then(Commands.literal("modules") .then(Commands.literal("list") .executes(context -> listModules( diff --git a/src/main/java/com/livingworld/commands/RegionInfoCommand.java b/src/main/java/com/livingworld/commands/RegionInfoCommand.java new file mode 100644 index 0000000..0d9b1af --- /dev/null +++ b/src/main/java/com/livingworld/commands/RegionInfoCommand.java @@ -0,0 +1,44 @@ +package com.livingworld.commands; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.world.phys.Vec3; + +import com.livingworld.regions.Region; +import com.livingworld.regions.RegionManager; + +/** + * Prints the Living World region state at the command source position. + */ +public final class RegionInfoCommand { + + private RegionInfoCommand() { + } + + public static int execute( + CommandSourceStack source, + RegionManager regionManager) { + if (source == null) { + throw new IllegalArgumentException("source must not be null"); + } + if (regionManager == null) { + throw new IllegalArgumentException("regionManager must not be null"); + } + + Vec3 position = source.getPosition(); + String dimensionId = source.getLevel().dimension().location().toString(); + Region region = regionManager.getOrCreateRegionAtBlock( + dimensionId, + floorToBlock(position.x), + floorToBlock(position.z)); + + for (String line : RegionInfoFormatter.format(region)) { + source.sendSuccess(() -> Component.literal(line), false); + } + return 1; + } + + private static int floorToBlock(double coordinate) { + return (int) Math.floor(coordinate); + } +} diff --git a/src/main/java/com/livingworld/commands/RegionInfoFormatter.java b/src/main/java/com/livingworld/commands/RegionInfoFormatter.java new file mode 100644 index 0000000..3a20844 --- /dev/null +++ b/src/main/java/com/livingworld/commands/RegionInfoFormatter.java @@ -0,0 +1,46 @@ +package com.livingworld.commands; + +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import com.livingworld.regions.Region; +import com.livingworld.regions.RegionFlags; +import com.livingworld.regions.RegionMetrics; + +/** + * Formats region diagnostics without depending on Minecraft classes. + */ +public final class RegionInfoFormatter { + + private RegionInfoFormatter() { + } + + public static List format(Region region) { + if (region == null) { + throw new IllegalArgumentException("region must not be null"); + } + + RegionMetrics metrics = region.getMetrics(); + RegionFlags flags = region.getFlags(); + Set moduleIds = new TreeSet<>(region.getModuleData().moduleIds()); + + return List.of( + "Region: " + region.getCoordinate().stableId(), + "Lifecycle: " + region.getLifecycleState() + ", dirty=" + region.isDirty(), + "Metrics: ecosystemHealth=" + metrics.getEcosystemHealth() + + ", pollution=" + metrics.getPollutionScore() + + ", soilQuality=" + metrics.getSoilQuality() + + ", waterQuality=" + metrics.getWaterQuality() + + ", vegetationPressure=" + metrics.getVegetationPressure() + + ", resourceDepletion=" + metrics.getResourceDepletion() + + ", recoveryPressure=" + metrics.getRecoveryPressure(), + "Flags: playerActivity=" + flags.isHasPlayerActivity() + + ", highPollution=" + flags.isHasHighPollution() + + ", lowSoilQuality=" + flags.isHasLowSoilQuality() + + ", activeEcosystemEvent=" + flags.isHasActiveEcosystemEvent() + + ", forceLoaded=" + flags.isForceLoadedBySimulation() + + ", corrupted=" + flags.isCorrupted(), + "Module data: " + (moduleIds.isEmpty() ? "none" : String.join(", ", moduleIds))); + } +} diff --git a/src/test/java/com/livingworld/commands/RegionInfoFormatterTest.java b/src/test/java/com/livingworld/commands/RegionInfoFormatterTest.java new file mode 100644 index 0000000..6b99721 --- /dev/null +++ b/src/test/java/com/livingworld/commands/RegionInfoFormatterTest.java @@ -0,0 +1,40 @@ +package com.livingworld.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.livingworld.regions.Region; +import com.livingworld.regions.RegionCoordinate; +import com.livingworld.regions.RegionFactory; + +class RegionInfoFormatterTest { + + @Test + void includesIdentityLifecycleMetricsFlagsAndSortedModuleIds() { + Region region = new RegionFactory().createNewRegion( + new RegionCoordinate("minecraft:overworld", -1, 2), 0); + region.getMetrics().setPollutionScore(75); + region.getFlags().setHasHighPollution(true); + region.getModuleData().put("water", "data"); + region.getModuleData().put("soil", "data"); + + List lines = RegionInfoFormatter.format(region); + + assertEquals(5, lines.size()); + assertTrue(lines.get(0).contains("minecraft:overworld:-1:2")); + assertTrue(lines.get(1).contains("ACTIVE")); + assertTrue(lines.get(2).contains("pollution=75.0")); + assertTrue(lines.get(3).contains("highPollution=true")); + assertEquals("Module data: soil, water", lines.get(4)); + } + + @Test + void validatesRegion() { + assertThrows(IllegalArgumentException.class, () -> RegionInfoFormatter.format(null)); + } +}