package net.minecraft.gametest.framework; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.logging.LogUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.ToIntFunction; import java.util.stream.Stream; import net.minecraft.ChatFormatting; import net.minecraft.FileUtil; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Vec3i; import net.minecraft.data.CachedOutput; import net.minecraft.data.structures.NbtToSnbt; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.NbtUtils; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.ComponentUtils; import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.Style; import net.minecraft.network.protocol.game.DebugPackets; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.entity.StructureBlockEntity; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.phys.BlockHitResult; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.commons.lang3.mutable.MutableInt; import org.slf4j.Logger; public class TestCommand { public static final int STRUCTURE_BLOCK_NEARBY_SEARCH_RADIUS = 15; public static final int STRUCTURE_BLOCK_FULL_SEARCH_RADIUS = 200; private static final Logger LOGGER = LogUtils.getLogger(); private static final int DEFAULT_CLEAR_RADIUS = 200; private static final int MAX_CLEAR_RADIUS = 1024; private static final int TEST_POS_Z_OFFSET_FROM_PLAYER = 3; private static final int SHOW_POS_DURATION_MS = 10000; private static final int DEFAULT_X_SIZE = 5; private static final int DEFAULT_Y_SIZE = 5; private static final int DEFAULT_Z_SIZE = 5; private static final String STRUCTURE_BLOCK_ENTITY_COULD_NOT_BE_FOUND = "Structure block entity could not be found"; private static final TestFinder.Builder testFinder = new TestFinder.Builder<>(TestCommand.Runner::new); private static ArgumentBuilder runWithRetryOptions( ArgumentBuilder argumentBuilder, Function, TestCommand.Runner> runnerGetter, Function, ArgumentBuilder> modifier ) { return argumentBuilder.executes(commandContext -> ((TestCommand.Runner)runnerGetter.apply(commandContext)).run()) .then( Commands.argument("numberOfTimes", IntegerArgumentType.integer(0)) .executes( commandContext -> ((TestCommand.Runner)runnerGetter.apply(commandContext)) .run(new RetryOptions(IntegerArgumentType.getInteger(commandContext, "numberOfTimes"), false)) ) .then( (ArgumentBuilder)modifier.apply( Commands.argument("untilFailed", BoolArgumentType.bool()) .executes( commandContext -> ((TestCommand.Runner)runnerGetter.apply(commandContext)) .run(new RetryOptions(IntegerArgumentType.getInteger(commandContext, "numberOfTimes"), BoolArgumentType.getBool(commandContext, "untilFailed"))) ) ) ) ); } private static ArgumentBuilder runWithRetryOptions( ArgumentBuilder argumentBuilder, Function, TestCommand.Runner> runnerGetter ) { return runWithRetryOptions(argumentBuilder, runnerGetter, argumentBuilderx -> argumentBuilderx); } private static ArgumentBuilder runWithRetryOptionsAndBuildInfo( ArgumentBuilder argumentBuilder, Function, TestCommand.Runner> runnerGetter ) { return runWithRetryOptions( argumentBuilder, runnerGetter, argumentBuilderx -> argumentBuilderx.then( Commands.argument("rotationSteps", IntegerArgumentType.integer()) .executes( commandContext -> ((TestCommand.Runner)runnerGetter.apply(commandContext)) .run( new RetryOptions(IntegerArgumentType.getInteger(commandContext, "numberOfTimes"), BoolArgumentType.getBool(commandContext, "untilFailed")), IntegerArgumentType.getInteger(commandContext, "rotationSteps") ) ) .then( Commands.argument("testsPerRow", IntegerArgumentType.integer()) .executes( commandContext -> ((TestCommand.Runner)runnerGetter.apply(commandContext)) .run( new RetryOptions(IntegerArgumentType.getInteger(commandContext, "numberOfTimes"), BoolArgumentType.getBool(commandContext, "untilFailed")), IntegerArgumentType.getInteger(commandContext, "rotationSteps"), IntegerArgumentType.getInteger(commandContext, "testsPerRow") ) ) ) ) ); } public static void register(CommandDispatcher dispatcher) { ArgumentBuilder argumentBuilder = runWithRetryOptionsAndBuildInfo( Commands.argument("onlyRequiredTests", BoolArgumentType.bool()), commandContext -> testFinder.failedTests(commandContext, BoolArgumentType.getBool(commandContext, "onlyRequiredTests")) ); ArgumentBuilder argumentBuilder2 = runWithRetryOptionsAndBuildInfo( Commands.argument("testClassName", TestClassNameArgument.testClassName()), commandContext -> testFinder.allTestsInClass(commandContext, TestClassNameArgument.getTestClassName(commandContext, "testClassName")) ); dispatcher.register( Commands.literal("test") .then( Commands.literal("run") .then( runWithRetryOptionsAndBuildInfo( Commands.argument("testName", TestFunctionArgument.testFunctionArgument()), commandContext -> testFinder.byArgument(commandContext, "testName") ) ) ) .then( Commands.literal("runmultiple") .then( Commands.argument("testName", TestFunctionArgument.testFunctionArgument()) .executes(commandContext -> testFinder.byArgument(commandContext, "testName").run()) .then( Commands.argument("amount", IntegerArgumentType.integer()) .executes( commandContext -> testFinder.createMultipleCopies(IntegerArgumentType.getInteger(commandContext, "amount")) .byArgument(commandContext, "testName") .run() ) ) ) ) .then(runWithRetryOptionsAndBuildInfo(Commands.literal("runall").then(argumentBuilder2), testFinder::allTests)) .then(runWithRetryOptions(Commands.literal("runthese"), testFinder::allNearby)) .then(runWithRetryOptions(Commands.literal("runclosest"), testFinder::nearest)) .then(runWithRetryOptions(Commands.literal("runthat"), testFinder::lookedAt)) .then(runWithRetryOptionsAndBuildInfo(Commands.literal("runfailed").then(argumentBuilder), testFinder::failedTests)) .then( Commands.literal("verify") .then( Commands.argument("testName", TestFunctionArgument.testFunctionArgument()) .executes(commandContext -> testFinder.byArgument(commandContext, "testName").verify()) ) ) .then( Commands.literal("verifyclass") .then( Commands.argument("testClassName", TestClassNameArgument.testClassName()) .executes( commandContext -> testFinder.allTestsInClass(commandContext, TestClassNameArgument.getTestClassName(commandContext, "testClassName")).verify() ) ) ) .then( Commands.literal("locate") .then( Commands.argument("testName", TestFunctionArgument.testFunctionArgument()) .executes( commandContext -> testFinder.locateByName( commandContext, "minecraft:" + TestFunctionArgument.getTestFunction(commandContext, "testName").structureName() ) .locate() ) ) ) .then(Commands.literal("resetclosest").executes(commandContext -> testFinder.nearest(commandContext).reset())) .then(Commands.literal("resetthese").executes(commandContext -> testFinder.allNearby(commandContext).reset())) .then(Commands.literal("resetthat").executes(commandContext -> testFinder.lookedAt(commandContext).reset())) .then( Commands.literal("export") .then( Commands.argument("testName", StringArgumentType.word()) .executes(commandContext -> exportTestStructure(commandContext.getSource(), "minecraft:" + StringArgumentType.getString(commandContext, "testName"))) ) ) .then(Commands.literal("exportclosest").executes(commandContext -> testFinder.nearest(commandContext).export())) .then(Commands.literal("exportthese").executes(commandContext -> testFinder.allNearby(commandContext).export())) .then(Commands.literal("exportthat").executes(commandContext -> testFinder.lookedAt(commandContext).export())) .then(Commands.literal("clearthat").executes(commandContext -> testFinder.lookedAt(commandContext).clear())) .then(Commands.literal("clearthese").executes(commandContext -> testFinder.allNearby(commandContext).clear())) .then( Commands.literal("clearall") .executes(commandContext -> testFinder.radius(commandContext, 200).clear()) .then( Commands.argument("radius", IntegerArgumentType.integer()) .executes(commandContext -> testFinder.radius(commandContext, Mth.clamp(IntegerArgumentType.getInteger(commandContext, "radius"), 0, 1024)).clear()) ) ) .then( Commands.literal("import") .then( Commands.argument("testName", StringArgumentType.word()) .executes(commandContext -> importTestStructure(commandContext.getSource(), StringArgumentType.getString(commandContext, "testName"))) ) ) .then(Commands.literal("stop").executes(commandContext -> stopTests())) .then( Commands.literal("pos") .executes(commandContext -> showPos(commandContext.getSource(), "pos")) .then( Commands.argument("var", StringArgumentType.word()) .executes(commandContext -> showPos(commandContext.getSource(), StringArgumentType.getString(commandContext, "var"))) ) ) .then( Commands.literal("create") .then( Commands.argument("testName", StringArgumentType.word()) .suggests(TestFunctionArgument::suggestTestFunction) .executes(commandContext -> createNewStructure(commandContext.getSource(), StringArgumentType.getString(commandContext, "testName"), 5, 5, 5)) .then( Commands.argument("width", IntegerArgumentType.integer()) .executes( commandContext -> createNewStructure( commandContext.getSource(), StringArgumentType.getString(commandContext, "testName"), IntegerArgumentType.getInteger(commandContext, "width"), IntegerArgumentType.getInteger(commandContext, "width"), IntegerArgumentType.getInteger(commandContext, "width") ) ) .then( Commands.argument("height", IntegerArgumentType.integer()) .then( Commands.argument("depth", IntegerArgumentType.integer()) .executes( commandContext -> createNewStructure( commandContext.getSource(), StringArgumentType.getString(commandContext, "testName"), IntegerArgumentType.getInteger(commandContext, "width"), IntegerArgumentType.getInteger(commandContext, "height"), IntegerArgumentType.getInteger(commandContext, "depth") ) ) ) ) ) ) ) ); } private static int resetGameTestInfo(GameTestInfo gameTestInfo) { gameTestInfo.getLevel().getEntities(null, gameTestInfo.getStructureBounds()).stream().forEach(entity -> entity.remove(Entity.RemovalReason.DISCARDED)); gameTestInfo.getStructureBlockEntity().placeStructure(gameTestInfo.getLevel()); StructureUtils.removeBarriers(gameTestInfo.getStructureBounds(), gameTestInfo.getLevel()); say(gameTestInfo.getLevel(), "Reset succeded for: " + gameTestInfo.getTestName(), ChatFormatting.GREEN); return 1; } static Stream toGameTestInfos(CommandSourceStack source, RetryOptions retryOptions, StructureBlockPosFinder structureBlockPosFinder) { return structureBlockPosFinder.findStructureBlockPos() .map(blockPos -> createGameTestInfo(blockPos, source.getLevel(), retryOptions)) .flatMap(Optional::stream); } static Stream toGameTestInfo(CommandSourceStack source, RetryOptions retryOptions, TestFunctionFinder testFunctionFinder, int rotationSteps) { return testFunctionFinder.findTestFunctions() .filter(testFunction -> verifyStructureExists(source.getLevel(), testFunction.structureName())) .map(testFunction -> new GameTestInfo(testFunction, StructureUtils.getRotationForRotationSteps(rotationSteps), source.getLevel(), retryOptions)); } private static Optional createGameTestInfo(BlockPos pos, ServerLevel level, RetryOptions retryOptions) { StructureBlockEntity structureBlockEntity = (StructureBlockEntity)level.getBlockEntity(pos); if (structureBlockEntity == null) { say(level, "Structure block entity could not be found", ChatFormatting.RED); return Optional.empty(); } else { String string = structureBlockEntity.getMetaData(); Optional optional = GameTestRegistry.findTestFunction(string); if (optional.isEmpty()) { say(level, "Test function for test " + string + " could not be found", ChatFormatting.RED); return Optional.empty(); } else { TestFunction testFunction = (TestFunction)optional.get(); GameTestInfo gameTestInfo = new GameTestInfo(testFunction, structureBlockEntity.getRotation(), level, retryOptions); gameTestInfo.setStructureBlockPos(pos); return !verifyStructureExists(level, gameTestInfo.getStructureName()) ? Optional.empty() : Optional.of(gameTestInfo); } } } private static int createNewStructure(CommandSourceStack source, String structureName, int x, int y, int z) { if (x <= 48 && y <= 48 && z <= 48) { ServerLevel serverLevel = source.getLevel(); BlockPos blockPos = createTestPositionAround(source).below(); StructureUtils.createNewEmptyStructureBlock(structureName.toLowerCase(), blockPos, new Vec3i(x, y, z), Rotation.NONE, serverLevel); BlockPos blockPos2 = blockPos.above(); BlockPos blockPos3 = blockPos2.offset(x - 1, 0, z - 1); BlockPos.betweenClosedStream(blockPos2, blockPos3).forEach(blockPosx -> serverLevel.setBlockAndUpdate(blockPosx, Blocks.BEDROCK.defaultBlockState())); StructureUtils.addCommandBlockAndButtonToStartTest(blockPos, new BlockPos(1, 0, -1), Rotation.NONE, serverLevel); return 0; } else { throw new IllegalArgumentException("The structure must be less than 48 blocks big in each axis"); } } private static int showPos(CommandSourceStack source, String variableName) throws CommandSyntaxException { BlockHitResult blockHitResult = (BlockHitResult)source.getPlayerOrException().pick(10.0, 1.0F, false); BlockPos blockPos = blockHitResult.getBlockPos(); ServerLevel serverLevel = source.getLevel(); Optional optional = StructureUtils.findStructureBlockContainingPos(blockPos, 15, serverLevel); if (optional.isEmpty()) { optional = StructureUtils.findStructureBlockContainingPos(blockPos, 200, serverLevel); } if (optional.isEmpty()) { source.sendFailure(Component.literal("Can't find a structure block that contains the targeted pos " + blockPos)); return 0; } else { StructureBlockEntity structureBlockEntity = (StructureBlockEntity)serverLevel.getBlockEntity((BlockPos)optional.get()); if (structureBlockEntity == null) { say(serverLevel, "Structure block entity could not be found", ChatFormatting.RED); return 0; } else { BlockPos blockPos2 = blockPos.subtract((Vec3i)optional.get()); String string = blockPos2.getX() + ", " + blockPos2.getY() + ", " + blockPos2.getZ(); String string2 = structureBlockEntity.getMetaData(); Component component = Component.literal(string) .setStyle( Style.EMPTY .withBold(true) .withColor(ChatFormatting.GREEN) .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("Click to copy to clipboard"))) .withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "final BlockPos " + variableName + " = new BlockPos(" + string + ");")) ); source.sendSuccess(() -> Component.literal("Position relative to " + string2 + ": ").append(component), false); DebugPackets.sendGameTestAddMarker(serverLevel, new BlockPos(blockPos), string, -2147418368, 10000); return 1; } } } static int stopTests() { GameTestTicker.SINGLETON.clear(); return 1; } static int trackAndStartRunner(CommandSourceStack source, ServerLevel level, GameTestRunner runner) { runner.addListener(new TestCommand.TestBatchSummaryDisplayer(source)); MultipleTestTracker multipleTestTracker = new MultipleTestTracker(runner.getTestInfos()); multipleTestTracker.addListener(new TestCommand.TestSummaryDisplayer(level, multipleTestTracker)); multipleTestTracker.addFailureListener(gameTestInfo -> GameTestRegistry.rememberFailedTest(gameTestInfo.getTestFunction())); runner.start(); return 1; } static int saveAndExportTestStructure(CommandSourceStack source, StructureBlockEntity structureBlockEntity) { String string = structureBlockEntity.getStructureName(); if (!structureBlockEntity.saveStructure(true)) { say(source, "Failed to save structure " + string); } return exportTestStructure(source, string); } private static int exportTestStructure(CommandSourceStack source, String structurePath) { Path path = Paths.get(StructureUtils.testStructuresDir); ResourceLocation resourceLocation = ResourceLocation.parse(structurePath); Path path2 = source.getLevel().getStructureManager().createAndValidatePathToGeneratedStructure(resourceLocation, ".nbt"); Path path3 = NbtToSnbt.convertStructure(CachedOutput.NO_CACHE, path2, resourceLocation.getPath(), path); if (path3 == null) { say(source, "Failed to export " + path2); return 1; } else { try { FileUtil.createDirectoriesSafe(path3.getParent()); } catch (IOException var7) { say(source, "Could not create folder " + path3.getParent()); LOGGER.error("Could not create export folder", (Throwable)var7); return 1; } say(source, "Exported " + structurePath + " to " + path3.toAbsolutePath()); return 0; } } private static boolean verifyStructureExists(ServerLevel level, String structure) { if (level.getStructureManager().get(ResourceLocation.parse(structure)).isEmpty()) { say(level, "Test structure " + structure + " could not be found", ChatFormatting.RED); return false; } else { return true; } } static BlockPos createTestPositionAround(CommandSourceStack source) { BlockPos blockPos = BlockPos.containing(source.getPosition()); int i = source.getLevel().getHeightmapPos(Heightmap.Types.WORLD_SURFACE, blockPos).getY(); return new BlockPos(blockPos.getX(), i + 1, blockPos.getZ() + 3); } static void say(CommandSourceStack source, String message) { source.sendSuccess(() -> Component.literal(message), false); } private static int importTestStructure(CommandSourceStack source, String structurePath) { Path path = Paths.get(StructureUtils.testStructuresDir, structurePath + ".snbt"); ResourceLocation resourceLocation = ResourceLocation.withDefaultNamespace(structurePath); Path path2 = source.getLevel().getStructureManager().createAndValidatePathToGeneratedStructure(resourceLocation, ".nbt"); try { BufferedReader bufferedReader = Files.newBufferedReader(path); String string = IOUtils.toString(bufferedReader); Files.createDirectories(path2.getParent()); OutputStream outputStream = Files.newOutputStream(path2); try { NbtIo.writeCompressed(NbtUtils.snbtToStructure(string), outputStream); } catch (Throwable var11) { if (outputStream != null) { try { outputStream.close(); } catch (Throwable var10) { var11.addSuppressed(var10); } } throw var11; } if (outputStream != null) { outputStream.close(); } source.getLevel().getStructureManager().remove(resourceLocation); say(source, "Imported to " + path2.toAbsolutePath()); return 0; } catch (CommandSyntaxException | IOException var12) { LOGGER.error("Failed to load structure {}", structurePath, var12); return 1; } } static void say(ServerLevel serverLevel, String message, ChatFormatting formatting) { serverLevel.getPlayers(serverPlayer -> true).forEach(serverPlayer -> serverPlayer.sendSystemMessage(Component.literal(message).withStyle(formatting))); } public static class Runner { private final TestFinder finder; public Runner(TestFinder finder) { this.finder = finder; } public int reset() { TestCommand.stopTests(); return TestCommand.toGameTestInfos(this.finder.source(), RetryOptions.noRetries(), this.finder).map(TestCommand::resetGameTestInfo).toList().isEmpty() ? 0 : 1; } private void logAndRun(Stream structureBlockPos, ToIntFunction testCounter, Runnable onFail, Consumer onSuccess) { int i = structureBlockPos.mapToInt(testCounter).sum(); if (i == 0) { onFail.run(); } else { onSuccess.accept(i); } } public int clear() { TestCommand.stopTests(); CommandSourceStack commandSourceStack = this.finder.source(); ServerLevel serverLevel = commandSourceStack.getLevel(); GameTestRunner.clearMarkers(serverLevel); this.logAndRun( this.finder.findStructureBlockPos(), blockPos -> { StructureBlockEntity structureBlockEntity = (StructureBlockEntity)serverLevel.getBlockEntity(blockPos); if (structureBlockEntity == null) { return 0; } else { BoundingBox boundingBox = StructureUtils.getStructureBoundingBox(structureBlockEntity); StructureUtils.clearSpaceForStructure(boundingBox, serverLevel); return 1; } }, () -> TestCommand.say(serverLevel, "Could not find any structures to clear", ChatFormatting.RED), integer -> TestCommand.say(commandSourceStack, "Cleared " + integer + " structures") ); return 1; } public int export() { MutableBoolean mutableBoolean = new MutableBoolean(true); CommandSourceStack commandSourceStack = this.finder.source(); ServerLevel serverLevel = commandSourceStack.getLevel(); this.logAndRun( this.finder.findStructureBlockPos(), blockPos -> { StructureBlockEntity structureBlockEntity = (StructureBlockEntity)serverLevel.getBlockEntity(blockPos); if (structureBlockEntity == null) { TestCommand.say(serverLevel, "Structure block entity could not be found", ChatFormatting.RED); mutableBoolean.setFalse(); return 0; } else { if (TestCommand.saveAndExportTestStructure(commandSourceStack, structureBlockEntity) != 0) { mutableBoolean.setFalse(); } return 1; } }, () -> TestCommand.say(serverLevel, "Could not find any structures to export", ChatFormatting.RED), integer -> TestCommand.say(commandSourceStack, "Exported " + integer + " structures") ); return mutableBoolean.getValue() ? 0 : 1; } int verify() { TestCommand.stopTests(); CommandSourceStack commandSourceStack = this.finder.source(); ServerLevel serverLevel = commandSourceStack.getLevel(); BlockPos blockPos = TestCommand.createTestPositionAround(commandSourceStack); Collection collection = Stream.concat( TestCommand.toGameTestInfos(commandSourceStack, RetryOptions.noRetries(), this.finder), TestCommand.toGameTestInfo(commandSourceStack, RetryOptions.noRetries(), this.finder, 0) ) .toList(); int i = 10; GameTestRunner.clearMarkers(serverLevel); GameTestRegistry.forgetFailedTests(); Collection collection2 = new ArrayList(); for (GameTestInfo gameTestInfo : collection) { for (Rotation rotation : Rotation.values()) { Collection collection3 = new ArrayList(); for (int j = 0; j < 100; j++) { GameTestInfo gameTestInfo2 = new GameTestInfo(gameTestInfo.getTestFunction(), rotation, serverLevel, new RetryOptions(1, true)); collection3.add(gameTestInfo2); } GameTestBatch gameTestBatch = GameTestBatchFactory.toGameTestBatch(collection3, gameTestInfo.getTestFunction().batchName(), rotation.ordinal()); collection2.add(gameTestBatch); } } StructureGridSpawner structureGridSpawner = new StructureGridSpawner(blockPos, 10, true); GameTestRunner gameTestRunner = GameTestRunner.Builder.fromBatches(collection2, serverLevel) .batcher(GameTestBatchFactory.fromGameTestInfo(100)) .newStructureSpawner(structureGridSpawner) .existingStructureSpawner(structureGridSpawner) .haltOnError(true) .build(); return TestCommand.trackAndStartRunner(commandSourceStack, serverLevel, gameTestRunner); } public int run(RetryOptions retryOptions, int rotationSteps, int testsPerRow) { TestCommand.stopTests(); CommandSourceStack commandSourceStack = this.finder.source(); ServerLevel serverLevel = commandSourceStack.getLevel(); BlockPos blockPos = TestCommand.createTestPositionAround(commandSourceStack); Collection collection = Stream.concat( TestCommand.toGameTestInfos(commandSourceStack, retryOptions, this.finder), TestCommand.toGameTestInfo(commandSourceStack, retryOptions, this.finder, rotationSteps) ) .toList(); if (collection.isEmpty()) { TestCommand.say(commandSourceStack, "No tests found"); return 0; } else { GameTestRunner.clearMarkers(serverLevel); GameTestRegistry.forgetFailedTests(); TestCommand.say(commandSourceStack, "Running " + collection.size() + " tests..."); GameTestRunner gameTestRunner = GameTestRunner.Builder.fromInfo(collection, serverLevel) .newStructureSpawner(new StructureGridSpawner(blockPos, testsPerRow, false)) .build(); return TestCommand.trackAndStartRunner(commandSourceStack, serverLevel, gameTestRunner); } } public int run(int rotationSteps, int testsPerRow) { return this.run(RetryOptions.noRetries(), rotationSteps, testsPerRow); } public int run(int rotationSteps) { return this.run(RetryOptions.noRetries(), rotationSteps, 8); } public int run(RetryOptions retryOptions, int rotationSteps) { return this.run(retryOptions, rotationSteps, 8); } public int run(RetryOptions retryOptions) { return this.run(retryOptions, 0, 8); } public int run() { return this.run(RetryOptions.noRetries()); } public int locate() { TestCommand.say(this.finder.source(), "Started locating test structures, this might take a while.."); MutableInt mutableInt = new MutableInt(0); BlockPos blockPos = BlockPos.containing(this.finder.source().getPosition()); this.finder .findStructureBlockPos() .forEach( blockPos2 -> { StructureBlockEntity structureBlockEntity = (StructureBlockEntity)this.finder.source().getLevel().getBlockEntity(blockPos2); if (structureBlockEntity != null) { Direction direction = structureBlockEntity.getRotation().rotate(Direction.NORTH); BlockPos blockPos3 = structureBlockEntity.getBlockPos().relative(direction, 2); int ix = (int)direction.getOpposite().toYRot(); String string = String.format("/tp @s %d %d %d %d 0", blockPos3.getX(), blockPos3.getY(), blockPos3.getZ(), ix); int j = blockPos.getX() - blockPos2.getX(); int k = blockPos.getZ() - blockPos2.getZ(); int l = Mth.floor(Mth.sqrt(j * j + k * k)); Component component = ComponentUtils.wrapInSquareBrackets( Component.translatable("chat.coordinates", blockPos2.getX(), blockPos2.getY(), blockPos2.getZ()) ) .withStyle( style -> style.withColor(ChatFormatting.GREEN) .withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, string)) .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.coordinates.tooltip"))) ); Component component2 = Component.literal("Found structure at: ").append(component).append(" (distance: " + l + ")"); this.finder.source().sendSuccess(() -> component2, false); mutableInt.increment(); } } ); int i = mutableInt.intValue(); if (i == 0) { TestCommand.say(this.finder.source().getLevel(), "No such test structure found", ChatFormatting.RED); return 0; } else { TestCommand.say(this.finder.source().getLevel(), "Finished locating, found " + i + " structure(s)", ChatFormatting.GREEN); return 1; } } } record TestBatchSummaryDisplayer(CommandSourceStack source) implements GameTestBatchListener { @Override public void testBatchStarting(GameTestBatch batch) { TestCommand.say(this.source, "Starting batch: " + batch.name()); } @Override public void testBatchFinished(GameTestBatch batch) { } } public record TestSummaryDisplayer(ServerLevel level, MultipleTestTracker tracker) implements GameTestListener { @Override public void testStructureLoaded(GameTestInfo testInfo) { } @Override public void testPassed(GameTestInfo test, GameTestRunner runner) { showTestSummaryIfAllDone(this.level, this.tracker); } @Override public void testFailed(GameTestInfo test, GameTestRunner runner) { showTestSummaryIfAllDone(this.level, this.tracker); } @Override public void testAddedForRerun(GameTestInfo oldTest, GameTestInfo newTest, GameTestRunner runner) { this.tracker.addTestToTrack(newTest); } private static void showTestSummaryIfAllDone(ServerLevel level, MultipleTestTracker tracker) { if (tracker.isDone()) { TestCommand.say(level, "GameTest done! " + tracker.getTotalCount() + " tests were run", ChatFormatting.WHITE); if (tracker.hasFailedRequired()) { TestCommand.say(level, tracker.getFailedRequiredCount() + " required tests failed :(", ChatFormatting.RED); } else { TestCommand.say(level, "All required tests passed :)", ChatFormatting.GREEN); } if (tracker.hasFailedOptional()) { TestCommand.say(level, tracker.getFailedOptionalCount() + " optional tests failed", ChatFormatting.GRAY); } } } } }