minecraft-src/net/minecraft/gametest/framework/TestCommand.java
2025-07-04 02:00:41 +03:00

716 lines
31 KiB
Java

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.gametest.framework.TestFinder.Builder;
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;
public static final int VERIFY_TEST_GRID_AXIS_SIZE = 10;
public static final int VERIFY_TEST_BATCH_SIZE = 100;
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 Builder<TestCommand.Runner> testFinder = new Builder<>(TestCommand.Runner::new);
private static ArgumentBuilder<CommandSourceStack, ?> runWithRetryOptions(
ArgumentBuilder<CommandSourceStack, ?> argumentBuilder,
Function<CommandContext<CommandSourceStack>, TestCommand.Runner> runnerGetter,
Function<ArgumentBuilder<CommandSourceStack, ?>, ArgumentBuilder<CommandSourceStack, ?>> 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<CommandSourceStack, ?>)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<CommandSourceStack, ?> runWithRetryOptions(
ArgumentBuilder<CommandSourceStack, ?> argumentBuilder, Function<CommandContext<CommandSourceStack>, TestCommand.Runner> runnerGetter
) {
return runWithRetryOptions(argumentBuilder, runnerGetter, argumentBuilderx -> argumentBuilderx);
}
private static ArgumentBuilder<CommandSourceStack, ?> runWithRetryOptionsAndBuildInfo(
ArgumentBuilder<CommandSourceStack, ?> argumentBuilder, Function<CommandContext<CommandSourceStack>, 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<CommandSourceStack> dispatcher) {
ArgumentBuilder<CommandSourceStack, ?> argumentBuilder = runWithRetryOptionsAndBuildInfo(
Commands.argument("onlyRequiredTests", BoolArgumentType.bool()),
commandContext -> testFinder.failedTests(commandContext, BoolArgumentType.getBool(commandContext, "onlyRequiredTests"))
);
ArgumentBuilder<CommandSourceStack, ?> 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<GameTestInfo> toGameTestInfos(CommandSourceStack source, RetryOptions retryOptions, StructureBlockPosFinder structureBlockPosFinder) {
return structureBlockPosFinder.findStructureBlockPos()
.map(blockPos -> createGameTestInfo(blockPos, source.getLevel(), retryOptions))
.flatMap(Optional::stream);
}
static Stream<GameTestInfo> 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<GameTestInfo> 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<TestFunction> 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<BlockPos> 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<TestCommand.Runner> finder;
public Runner(TestFinder<TestCommand.Runner> 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 <T> void logAndRun(Stream<T> structureBlockPos, ToIntFunction<T> testCounter, Runnable onFail, Consumer<Integer> 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<GameTestInfo> collection = Stream.concat(
TestCommand.toGameTestInfos(commandSourceStack, RetryOptions.noRetries(), this.finder),
TestCommand.toGameTestInfo(commandSourceStack, RetryOptions.noRetries(), this.finder, 0)
)
.toList();
GameTestRunner.clearMarkers(serverLevel);
GameTestRegistry.forgetFailedTests();
Collection<GameTestBatch> collection2 = new ArrayList();
for (GameTestInfo gameTestInfo : collection) {
for (Rotation rotation : Rotation.values()) {
Collection<GameTestInfo> collection3 = new ArrayList();
for (int i = 0; i < 100; i++) {
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 = net.minecraft.gametest.framework.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<GameTestInfo> 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 = net.minecraft.gametest.framework.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);
}
}
}
}
}