diff --git a/build.gradle b/build.gradle index ef432394d4..acb329f308 100644 --- a/build.gradle +++ b/build.gradle @@ -329,11 +329,19 @@ sourceSets { testmod } +// These modules are not included in the fat jar, maven will resolve them via the pom. +def devOnlyModules = [ + "fabric-gametest-api-v1" +] + dependencies { afterEvaluate { subprojects.each { api project(path: ":${it.name}", configuration: "dev") - include project("${it.name}:") + + if (!(it.name in devOnlyModules)) { + include project("${it.name}:") + } testmodImplementation project("${it.name}:").sourceSets.testmod.output } diff --git a/fabric-gametest-api-v1/build.gradle b/fabric-gametest-api-v1/build.gradle new file mode 100644 index 0000000000..23ecc624ea --- /dev/null +++ b/fabric-gametest-api-v1/build.gradle @@ -0,0 +1,24 @@ +archivesBaseName = "fabric-gametest-api-v1" +version = getSubprojectVersion(project, "1.0.0") + +moduleDependencies(project, [ + 'fabric-api-base', + 'fabric-resource-loader-v0' +]) + +loom { + runs { + gametest { + server() + name "Game Test" + vmArg "-Dfabric-api.gametest" + vmArg "-Dfabric-api.gametest.report-file=${project.buildDir}/junit.xml" + runDir "build/gametest" + + // Specific to fabric api + source sourceSets.testmod + ideConfigGenerated true + } + } +} +test.dependsOn runGametest diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/api/gametest/v1/FabricGameTest.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/api/gametest/v1/FabricGameTest.java new file mode 100644 index 0000000000..8345dbcfd7 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/api/gametest/v1/FabricGameTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.gametest.v1; + +import java.lang.reflect.Method; + +import net.minecraft.test.TestContext; + +import net.fabricmc.fabric.impl.gametest.FabricGameTestHelper; + +/** + * This interface can be optionally implemented on your test class. + */ +public interface FabricGameTest { + /** + * Use in {@link net.minecraft.test.GameTest} structureName to use an empty 8x8 structure for the test. + */ + String EMPTY_STRUCTURE = "fabric-gametest-api-v1:empty"; + + /** + * Override this method to implement custom logic to invoke the test method. + * This can be used to run code before or after each test. + * You can also pass in custom objects into the test method if desired. + * The structure will have been placed in the world before this method is invoked. + * + * @param context The vanilla test context + * @param method The test method to invoke + */ + default void invokeTestMethod(TestContext context, Method method) { + FabricGameTestHelper.invokeTestMethod(context, method, this); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestHelper.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestHelper.java new file mode 100644 index 0000000000..2fa207314f --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestHelper.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.gametest; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.function.Consumer; + +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ServerResourceManager; +import net.minecraft.server.MinecraftServer; +import net.minecraft.test.GameTestBatch; +import net.minecraft.test.TestContext; +import net.minecraft.test.TestFailureLogger; +import net.minecraft.test.TestFunction; +import net.minecraft.test.TestFunctions; +import net.minecraft.test.TestServer; +import net.minecraft.test.TestUtil; +import net.minecraft.test.XmlReportingTestCompletionListener; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.DynamicRegistryManager; +import net.minecraft.world.level.storage.LevelStorage; + +import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; + +public final class FabricGameTestHelper { + public static final boolean ENABLED = System.getProperty("fabric-api.gametest") != null; + + private static final Logger LOGGER = LogManager.getLogger(); + + private FabricGameTestHelper() { + } + + public static void runHeadlessServer(LevelStorage.Session session, ResourcePackManager resourcePackManager, ServerResourceManager serverResourceManager, DynamicRegistryManager.Impl registryManager) { + String reportPath = System.getProperty("fabric-api.gametest.report-file"); + + if (reportPath != null) { + try { + TestFailureLogger.setCompletionListener(new XmlReportingTestCompletionListener(new File(reportPath))); + } catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } + } + + LOGGER.info("Starting test server"); + MinecraftServer server = TestServer.startServer(thread -> { + TestServer testServer = new TestServer(thread, session, resourcePackManager, serverResourceManager, getBatches(), BlockPos.ORIGIN, registryManager); + return testServer; + }); + } + + public static Consumer getTestMethodInvoker(Method method) { + return testContext -> { + Class testClass = method.getDeclaringClass(); + + Constructor constructor; + + try { + constructor = testClass.getConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Test class (%s) provided by (%s) must have a public default or no args constructor".formatted(testClass.getSimpleName(), FabricGameTestModInitializer.getModIdForTestClass(testClass))); + } + + Object testObject; + + try { + testObject = constructor.newInstance(); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Failed to create instance of test class (%s)".formatted(testClass.getCanonicalName()), e); + } + + if (testObject instanceof FabricGameTest fabricGameTest) { + fabricGameTest.invokeTestMethod(testContext, method); + } else { + invokeTestMethod(testContext, method, testObject); + } + }; + } + + public static void invokeTestMethod(TestContext testContext, Method method, Object testObject) { + try { + method.invoke(testObject, testContext); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Failed to invoke test method (%s) in (%s)".formatted(method.getName(), method.getDeclaringClass().getCanonicalName()), e); + } + } + + private static Collection getBatches() { + return TestUtil.createBatches(getTestFunctions()); + } + + private static Collection getTestFunctions() { + return TestFunctions.getTestFunctions(); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestModInitializer.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestModInitializer.java new file mode 100644 index 0000000000..9ad570913c --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestModInitializer.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.gametest; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.test.TestFunctions; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.entrypoint.EntrypointContainer; + +@ApiStatus.Internal +public final class FabricGameTestModInitializer implements ModInitializer { + private static final String ENTRYPOINT_KEY = "fabric-gametest"; + private static final Map, String> GAME_TEST_IDS = new HashMap<>(); + private static final Logger LOGGER = LogManager.getLogger(); + + @Override + public void onInitialize() { + List> entrypointContainers = FabricLoader.getInstance() + .getEntrypointContainers(ENTRYPOINT_KEY, Object.class); + + for (EntrypointContainer container : entrypointContainers) { + Class testClass = container.getEntrypoint().getClass(); + String modid = container.getProvider().getMetadata().getId(); + + if (GAME_TEST_IDS.containsKey(testClass)) { + throw new UnsupportedOperationException("Test class (%s) has already been registered with mod (%s)".formatted(testClass.getCanonicalName(), modid)); + } + + GAME_TEST_IDS.put(testClass, modid); + TestFunctions.register(testClass); + + LOGGER.debug("Registered test class {} for mod {}", testClass.getCanonicalName(), modid); + } + } + + public static String getModIdForTestClass(Class testClass) { + if (!GAME_TEST_IDS.containsKey(testClass)) { + throw new UnsupportedOperationException("The test class (%s) was not registered using the '%s' entrypoint".formatted(testClass.getCanonicalName(), ENTRYPOINT_KEY)); + } + + return GAME_TEST_IDS.get(testClass); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/CommandManagerMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/CommandManagerMixin.java new file mode 100644 index 0000000000..f6c802e466 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/CommandManagerMixin.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.gametest; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import com.mojang.brigadier.CommandDispatcher; + +import net.minecraft.SharedConstants; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.command.TestCommand; + +@Mixin(CommandManager.class) +public abstract class CommandManagerMixin { + @Shadow + @Final + private CommandDispatcher dispatcher; + + @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/command/WorldBorderCommand;register(Lcom/mojang/brigadier/CommandDispatcher;)V", shift = At.Shift.AFTER)) + private void construct(CommandManager.RegistrationEnvironment environment, CallbackInfo info) { + // Registered by vanilla when isDevelopment is enabled. + if (!SharedConstants.isDevelopment) { + TestCommand.register(this.dispatcher); + } + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/MinecraftServerMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/MinecraftServerMixin.java new file mode 100644 index 0000000000..c10a15cd0f --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/MinecraftServerMixin.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.gametest; + +import java.util.function.BooleanSupplier; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.SharedConstants; +import net.minecraft.server.MinecraftServer; +import net.minecraft.test.TestManager; + +@Mixin(MinecraftServer.class) +public abstract class MinecraftServerMixin { + @Inject(method = "tickWorlds", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;updatePlayerLatency()V", shift = At.Shift.AFTER)) + private void tickWorlds(BooleanSupplier shouldKeepTicking, CallbackInfo callbackInfo) { + // Called by vanilla when isDevelopment is enabled. + if (!SharedConstants.isDevelopment) { + TestManager.INSTANCE.tick(); + } + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTestUtilMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTestUtilMixin.java new file mode 100644 index 0000000000..fbfc72c800 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTestUtilMixin.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.gametest; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtHelper; +import net.minecraft.resource.Resource; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.structure.Structure; +import net.minecraft.test.StructureTestUtil; +import net.minecraft.util.Identifier; + +@Mixin(StructureTestUtil.class) +public abstract class StructureTestUtilMixin { + private static final String GAMETEST_STRUCTURE_PATH = "gametest/structures/"; + + // Replace the default test structure loading with something that works a bit better for mods. + @Inject(at = @At("HEAD"), method = "createStructure(Ljava/lang/String;Lnet/minecraft/server/world/ServerWorld;)Lnet/minecraft/structure/Structure;", cancellable = true) + private static void createStructure(String id, ServerWorld world, CallbackInfoReturnable cir) { + Identifier baseId = new Identifier(id); + Identifier structureId = new Identifier(baseId.getNamespace(), GAMETEST_STRUCTURE_PATH + baseId.getPath() + ".snbt"); + + try { + Resource resource = world.getServer().getResourceManager().getResource(structureId); + String snbt; + + try (InputStream inputStream = resource.getInputStream()) { + snbt = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + + NbtCompound nbtCompound = NbtHelper.method_32260(snbt); + Structure structure = world.getStructureManager().createStructure(nbtCompound); + + cir.setReturnValue(structure); + } catch (IOException | CommandSyntaxException e) { + throw new RuntimeException("Error while trying to load structure: " + structureId, e); + } + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestFunctionsMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestFunctionsMixin.java new file mode 100644 index 0000000000..84d8acf5df --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestFunctionsMixin.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.gametest; + +import java.lang.reflect.Method; +import java.util.Locale; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.test.GameTest; +import net.minecraft.test.StructureTestUtil; +import net.minecraft.test.TestFunction; +import net.minecraft.test.TestFunctions; + +import net.fabricmc.fabric.impl.gametest.FabricGameTestHelper; +import net.fabricmc.fabric.impl.gametest.FabricGameTestModInitializer; + +@Mixin(TestFunctions.class) +public abstract class TestFunctionsMixin { + @Inject(at = @At("HEAD"), method = "getTestFunction(Ljava/lang/reflect/Method;)Lnet/minecraft/test/TestFunction;", cancellable = true) + private static void getTestFunction(Method method, CallbackInfoReturnable cir) { + GameTest gameTest = method.getAnnotation(GameTest.class); + String testSuiteName = method.getDeclaringClass().getSimpleName().toLowerCase(Locale.ROOT); + String testCaseName = testSuiteName + "." + method.getName().toLowerCase(Locale.ROOT); + + String modId = FabricGameTestModInitializer.getModIdForTestClass(method.getDeclaringClass()); + String structureName = "%s:%s".formatted(modId, testCaseName); + + if (!gameTest.structureName().isEmpty()) { + structureName = gameTest.structureName(); + } + + TestFunction testFunction = new TestFunction(gameTest.batchId(), + testCaseName, + structureName, + StructureTestUtil.getRotation(gameTest.rotation()), + gameTest.tickLimit(), + gameTest.duration(), + gameTest.required(), + gameTest.requiredSuccesses(), + gameTest.maxAttempts(), + FabricGameTestHelper.getTestMethodInvoker(method) + ); + + cir.setReturnValue(testFunction); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestServerMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestServerMixin.java new file mode 100644 index 0000000000..400261b006 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestServerMixin.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.gametest; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.test.TestServer; + +@Mixin(TestServer.class) +public abstract class TestServerMixin { + @Inject(method = "isDedicated", at = @At("HEAD"), cancellable = true) + public void isDedicated(CallbackInfoReturnable cir) { + // Allow dedicated server commands to be registered. + // Should aid with mods that use this to detect if they are running on a dedicated server as well. + cir.setReturnValue(true); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java new file mode 100644 index 0000000000..545b5fe21f --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.gametest.server; + +import java.io.File; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import net.minecraft.resource.DataPackSettings; +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ServerResourceManager; +import net.minecraft.server.Main; +import net.minecraft.server.dedicated.EulaReader; +import net.minecraft.server.dedicated.ServerPropertiesLoader; +import net.minecraft.util.UserCache; +import net.minecraft.util.dynamic.RegistryOps; +import net.minecraft.util.registry.DynamicRegistryManager; +import net.minecraft.world.level.storage.LevelStorage; +import net.minecraft.world.level.storage.LevelSummary; + +import net.fabricmc.fabric.impl.gametest.FabricGameTestHelper; + +@Mixin(Main.class) +public class MainMixin { + @Redirect(method = "main", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/dedicated/EulaReader;isEulaAgreedTo()Z")) + private static boolean isEulaAgreedTo(EulaReader reader) { + return FabricGameTestHelper.ENABLED || reader.isEulaAgreedTo(); + } + + @Inject(method = "main", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/LevelStorage$Session;readLevelProperties(Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/resource/DataPackSettings;)Lnet/minecraft/world/SaveProperties;")) + private static void main(String[] args, CallbackInfo info, OptionParser optionParser, OptionSpec o1, OptionSpec o2, OptionSpec o3, OptionSpec o4, OptionSpec o5, OptionSpec o6, OptionSpec o7, OptionSpec o8, OptionSpec o9, OptionSpec o10, OptionSpec o11, OptionSpec o12, OptionSpec o13, OptionSpec o14, OptionSet optionSet, DynamicRegistryManager.Impl impl, Path path, ServerPropertiesLoader serverPropertiesLoader, Path path2, EulaReader eulaReader, File file, YggdrasilAuthenticationService yggdrasilAuthenticationService, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, String string, LevelStorage levelStorage, LevelStorage.Session session, LevelSummary levelSummary, DataPackSettings dataPackSettings, boolean bl, ResourcePackManager resourcePackManager, DataPackSettings dataPackSettings2, CompletableFuture completableFuture, ServerResourceManager serverResourceManager, RegistryOps registryOps) { + if (FabricGameTestHelper.ENABLED) { + FabricGameTestHelper.runHeadlessServer(session, resourcePackManager, serverResourceManager, impl); + info.cancel(); // Do not progress in starting the normal dedicated server + } + } + + // Exit with a non-zero exit code when the server fails to start. + // Otherwise gradlew test will succeed without errors, although no tests have been run. + @Inject(method = "main", at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;fatal(Ljava/lang/String;Ljava/lang/Throwable;)V", shift = At.Shift.AFTER)) + private static void exitOnError(CallbackInfo info) { + if (FabricGameTestHelper.ENABLED) { + System.exit(-1); + } + } +} diff --git a/fabric-gametest-api-v1/src/main/resources/assets/fabric-gametest-api-v1/icon.png b/fabric-gametest-api-v1/src/main/resources/assets/fabric-gametest-api-v1/icon.png new file mode 100644 index 0000000000..2931efbf61 Binary files /dev/null and b/fabric-gametest-api-v1/src/main/resources/assets/fabric-gametest-api-v1/icon.png differ diff --git a/fabric-gametest-api-v1/src/main/resources/data/fabric-gametest-api-v1/gametest/structures/empty.snbt b/fabric-gametest-api-v1/src/main/resources/data/fabric-gametest-api-v1/gametest/structures/empty.snbt new file mode 100644 index 0000000000..a5c22bb576 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/resources/data/fabric-gametest-api-v1/gametest/structures/empty.snbt @@ -0,0 +1,522 @@ +{ + DataVersion: 2730, + size: [8, 8, 8], + data: [ + {pos: [0, 0, 0], state: "minecraft:air"}, + {pos: [0, 0, 1], state: "minecraft:air"}, + {pos: [0, 0, 2], state: "minecraft:air"}, + {pos: [0, 0, 3], state: "minecraft:air"}, + {pos: [0, 0, 4], state: "minecraft:air"}, + {pos: [0, 0, 5], state: "minecraft:air"}, + {pos: [0, 0, 6], state: "minecraft:air"}, + {pos: [0, 0, 7], state: "minecraft:air"}, + {pos: [1, 0, 0], state: "minecraft:air"}, + {pos: [1, 0, 1], state: "minecraft:air"}, + {pos: [1, 0, 2], state: "minecraft:air"}, + {pos: [1, 0, 3], state: "minecraft:air"}, + {pos: [1, 0, 4], state: "minecraft:air"}, + {pos: [1, 0, 5], state: "minecraft:air"}, + {pos: [1, 0, 6], state: "minecraft:air"}, + {pos: [1, 0, 7], state: "minecraft:air"}, + {pos: [2, 0, 0], state: "minecraft:air"}, + {pos: [2, 0, 1], state: "minecraft:air"}, + {pos: [2, 0, 2], state: "minecraft:air"}, + {pos: [2, 0, 3], state: "minecraft:air"}, + {pos: [2, 0, 4], state: "minecraft:air"}, + {pos: [2, 0, 5], state: "minecraft:air"}, + {pos: [2, 0, 6], state: "minecraft:air"}, + {pos: [2, 0, 7], state: "minecraft:air"}, + {pos: [3, 0, 0], state: "minecraft:air"}, + {pos: [3, 0, 1], state: "minecraft:air"}, + {pos: [3, 0, 2], state: "minecraft:air"}, + {pos: [3, 0, 3], state: "minecraft:air"}, + {pos: [3, 0, 4], state: "minecraft:air"}, + {pos: [3, 0, 5], state: "minecraft:air"}, + {pos: [3, 0, 6], state: "minecraft:air"}, + {pos: [3, 0, 7], state: "minecraft:air"}, + {pos: [4, 0, 0], state: "minecraft:air"}, + {pos: [4, 0, 1], state: "minecraft:air"}, + {pos: [4, 0, 2], state: "minecraft:air"}, + {pos: [4, 0, 3], state: "minecraft:air"}, + {pos: [4, 0, 4], state: "minecraft:air"}, + {pos: [4, 0, 5], state: "minecraft:air"}, + {pos: [4, 0, 6], state: "minecraft:air"}, + {pos: [4, 0, 7], state: "minecraft:air"}, + {pos: [5, 0, 0], state: "minecraft:air"}, + {pos: [5, 0, 1], state: "minecraft:air"}, + {pos: [5, 0, 2], state: "minecraft:air"}, + {pos: [5, 0, 3], state: "minecraft:air"}, + {pos: [5, 0, 4], state: "minecraft:air"}, + {pos: [5, 0, 5], state: "minecraft:air"}, + {pos: [5, 0, 6], state: "minecraft:air"}, + {pos: [5, 0, 7], state: "minecraft:air"}, + {pos: [6, 0, 0], state: "minecraft:air"}, + {pos: [6, 0, 1], state: "minecraft:air"}, + {pos: [6, 0, 2], state: "minecraft:air"}, + {pos: [6, 0, 3], state: "minecraft:air"}, + {pos: [6, 0, 4], state: "minecraft:air"}, + {pos: [6, 0, 5], state: "minecraft:air"}, + {pos: [6, 0, 6], state: "minecraft:air"}, + {pos: [6, 0, 7], state: "minecraft:air"}, + {pos: [7, 0, 0], state: "minecraft:air"}, + {pos: [7, 0, 1], state: "minecraft:air"}, + {pos: [7, 0, 2], state: "minecraft:air"}, + {pos: [7, 0, 3], state: "minecraft:air"}, + {pos: [7, 0, 4], state: "minecraft:air"}, + {pos: [7, 0, 5], state: "minecraft:air"}, + {pos: [7, 0, 6], state: "minecraft:air"}, + {pos: [7, 0, 7], state: "minecraft:air"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [0, 1, 3], state: "minecraft:air"}, + {pos: [0, 1, 4], state: "minecraft:air"}, + {pos: [0, 1, 5], state: "minecraft:air"}, + {pos: [0, 1, 6], state: "minecraft:air"}, + {pos: [0, 1, 7], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [1, 1, 5], state: "minecraft:air"}, + {pos: [1, 1, 6], state: "minecraft:air"}, + {pos: [1, 1, 7], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 5], state: "minecraft:air"}, + {pos: [2, 1, 6], state: "minecraft:air"}, + {pos: [2, 1, 7], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "minecraft:air"}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 5], state: "minecraft:air"}, + {pos: [3, 1, 6], state: "minecraft:air"}, + {pos: [3, 1, 7], state: "minecraft:air"}, + {pos: [4, 1, 0], state: "minecraft:air"}, + {pos: [4, 1, 1], state: "minecraft:air"}, + {pos: [4, 1, 2], state: "minecraft:air"}, + {pos: [4, 1, 3], state: "minecraft:air"}, + {pos: [4, 1, 4], state: "minecraft:air"}, + {pos: [4, 1, 5], state: "minecraft:air"}, + {pos: [4, 1, 6], state: "minecraft:air"}, + {pos: [4, 1, 7], state: "minecraft:air"}, + {pos: [5, 1, 0], state: "minecraft:air"}, + {pos: [5, 1, 1], state: "minecraft:air"}, + {pos: [5, 1, 2], state: "minecraft:air"}, + {pos: [5, 1, 3], state: "minecraft:air"}, + {pos: [5, 1, 4], state: "minecraft:air"}, + {pos: [5, 1, 5], state: "minecraft:air"}, + {pos: [5, 1, 6], state: "minecraft:air"}, + {pos: [5, 1, 7], state: "minecraft:air"}, + {pos: [6, 1, 0], state: "minecraft:air"}, + {pos: [6, 1, 1], state: "minecraft:air"}, + {pos: [6, 1, 2], state: "minecraft:air"}, + {pos: [6, 1, 3], state: "minecraft:air"}, + {pos: [6, 1, 4], state: "minecraft:air"}, + {pos: [6, 1, 5], state: "minecraft:air"}, + {pos: [6, 1, 6], state: "minecraft:air"}, + {pos: [6, 1, 7], state: "minecraft:air"}, + {pos: [7, 1, 0], state: "minecraft:air"}, + {pos: [7, 1, 1], state: "minecraft:air"}, + {pos: [7, 1, 2], state: "minecraft:air"}, + {pos: [7, 1, 3], state: "minecraft:air"}, + {pos: [7, 1, 4], state: "minecraft:air"}, + {pos: [7, 1, 5], state: "minecraft:air"}, + {pos: [7, 1, 6], state: "minecraft:air"}, + {pos: [7, 1, 7], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [0, 2, 3], state: "minecraft:air"}, + {pos: [0, 2, 4], state: "minecraft:air"}, + {pos: [0, 2, 5], state: "minecraft:air"}, + {pos: [0, 2, 6], state: "minecraft:air"}, + {pos: [0, 2, 7], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 3], state: "minecraft:air"}, + {pos: [1, 2, 4], state: "minecraft:air"}, + {pos: [1, 2, 5], state: "minecraft:air"}, + {pos: [1, 2, 6], state: "minecraft:air"}, + {pos: [1, 2, 7], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], state: "minecraft:air"}, + {pos: [2, 2, 5], state: "minecraft:air"}, + {pos: [2, 2, 6], state: "minecraft:air"}, + {pos: [2, 2, 7], state: "minecraft:air"}, + {pos: [3, 2, 0], state: "minecraft:air"}, + {pos: [3, 2, 1], state: "minecraft:air"}, + {pos: [3, 2, 2], state: "minecraft:air"}, + {pos: [3, 2, 3], state: "minecraft:air"}, + {pos: [3, 2, 4], state: "minecraft:air"}, + {pos: [3, 2, 5], state: "minecraft:air"}, + {pos: [3, 2, 6], state: "minecraft:air"}, + {pos: [3, 2, 7], state: "minecraft:air"}, + {pos: [4, 2, 0], state: "minecraft:air"}, + {pos: [4, 2, 1], state: "minecraft:air"}, + {pos: [4, 2, 2], state: "minecraft:air"}, + {pos: [4, 2, 3], state: "minecraft:air"}, + {pos: [4, 2, 4], state: "minecraft:air"}, + {pos: [4, 2, 5], state: "minecraft:air"}, + {pos: [4, 2, 6], state: "minecraft:air"}, + {pos: [4, 2, 7], state: "minecraft:air"}, + {pos: [5, 2, 0], state: "minecraft:air"}, + {pos: [5, 2, 1], state: "minecraft:air"}, + {pos: [5, 2, 2], state: "minecraft:air"}, + {pos: [5, 2, 3], state: "minecraft:air"}, + {pos: [5, 2, 4], state: "minecraft:air"}, + {pos: [5, 2, 5], state: "minecraft:air"}, + {pos: [5, 2, 6], state: "minecraft:air"}, + {pos: [5, 2, 7], state: "minecraft:air"}, + {pos: [6, 2, 0], state: "minecraft:air"}, + {pos: [6, 2, 1], state: "minecraft:air"}, + {pos: [6, 2, 2], state: "minecraft:air"}, + {pos: [6, 2, 3], state: "minecraft:air"}, + {pos: [6, 2, 4], state: "minecraft:air"}, + {pos: [6, 2, 5], state: "minecraft:air"}, + {pos: [6, 2, 6], state: "minecraft:air"}, + {pos: [6, 2, 7], state: "minecraft:air"}, + {pos: [7, 2, 0], state: "minecraft:air"}, + {pos: [7, 2, 1], state: "minecraft:air"}, + {pos: [7, 2, 2], state: "minecraft:air"}, + {pos: [7, 2, 3], state: "minecraft:air"}, + {pos: [7, 2, 4], state: "minecraft:air"}, + {pos: [7, 2, 5], state: "minecraft:air"}, + {pos: [7, 2, 6], state: "minecraft:air"}, + {pos: [7, 2, 7], state: "minecraft:air"}, + {pos: [0, 3, 0], state: "minecraft:air"}, + {pos: [0, 3, 1], state: "minecraft:air"}, + {pos: [0, 3, 2], state: "minecraft:air"}, + {pos: [0, 3, 3], state: "minecraft:air"}, + {pos: [0, 3, 4], state: "minecraft:air"}, + {pos: [0, 3, 5], state: "minecraft:air"}, + {pos: [0, 3, 6], state: "minecraft:air"}, + {pos: [0, 3, 7], state: "minecraft:air"}, + {pos: [1, 3, 0], state: "minecraft:air"}, + {pos: [1, 3, 1], state: "minecraft:air"}, + {pos: [1, 3, 2], state: "minecraft:air"}, + {pos: [1, 3, 3], state: "minecraft:air"}, + {pos: [1, 3, 4], state: "minecraft:air"}, + {pos: [1, 3, 5], state: "minecraft:air"}, + {pos: [1, 3, 6], state: "minecraft:air"}, + {pos: [1, 3, 7], state: "minecraft:air"}, + {pos: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "minecraft:air"}, + {pos: [2, 3, 2], state: "minecraft:air"}, + {pos: [2, 3, 3], state: "minecraft:air"}, + {pos: [2, 3, 4], state: "minecraft:air"}, + {pos: [2, 3, 5], state: "minecraft:air"}, + {pos: [2, 3, 6], state: "minecraft:air"}, + {pos: [2, 3, 7], state: "minecraft:air"}, + {pos: [3, 3, 0], state: "minecraft:air"}, + {pos: [3, 3, 1], state: "minecraft:air"}, + {pos: [3, 3, 2], state: "minecraft:air"}, + {pos: [3, 3, 3], state: "minecraft:air"}, + {pos: [3, 3, 4], state: "minecraft:air"}, + {pos: [3, 3, 5], state: "minecraft:air"}, + {pos: [3, 3, 6], state: "minecraft:air"}, + {pos: [3, 3, 7], state: "minecraft:air"}, + {pos: [4, 3, 0], state: "minecraft:air"}, + {pos: [4, 3, 1], state: "minecraft:air"}, + {pos: [4, 3, 2], state: "minecraft:air"}, + {pos: [4, 3, 3], state: "minecraft:air"}, + {pos: [4, 3, 4], state: "minecraft:air"}, + {pos: [4, 3, 5], state: "minecraft:air"}, + {pos: [4, 3, 6], state: "minecraft:air"}, + {pos: [4, 3, 7], state: "minecraft:air"}, + {pos: [5, 3, 0], state: "minecraft:air"}, + {pos: [5, 3, 1], state: "minecraft:air"}, + {pos: [5, 3, 2], state: "minecraft:air"}, + {pos: [5, 3, 3], state: "minecraft:air"}, + {pos: [5, 3, 4], state: "minecraft:air"}, + {pos: [5, 3, 5], state: "minecraft:air"}, + {pos: [5, 3, 6], state: "minecraft:air"}, + {pos: [5, 3, 7], state: "minecraft:air"}, + {pos: [6, 3, 0], state: "minecraft:air"}, + {pos: [6, 3, 1], state: "minecraft:air"}, + {pos: [6, 3, 2], state: "minecraft:air"}, + {pos: [6, 3, 3], state: "minecraft:air"}, + {pos: [6, 3, 4], state: "minecraft:air"}, + {pos: [6, 3, 5], state: "minecraft:air"}, + {pos: [6, 3, 6], state: "minecraft:air"}, + {pos: [6, 3, 7], state: "minecraft:air"}, + {pos: [7, 3, 0], state: "minecraft:air"}, + {pos: [7, 3, 1], state: "minecraft:air"}, + {pos: [7, 3, 2], state: "minecraft:air"}, + {pos: [7, 3, 3], state: "minecraft:air"}, + {pos: [7, 3, 4], state: "minecraft:air"}, + {pos: [7, 3, 5], state: "minecraft:air"}, + {pos: [7, 3, 6], state: "minecraft:air"}, + {pos: [7, 3, 7], state: "minecraft:air"}, + {pos: [0, 4, 0], state: "minecraft:air"}, + {pos: [0, 4, 1], state: "minecraft:air"}, + {pos: [0, 4, 2], state: "minecraft:air"}, + {pos: [0, 4, 3], state: "minecraft:air"}, + {pos: [0, 4, 4], state: "minecraft:air"}, + {pos: [0, 4, 5], state: "minecraft:air"}, + {pos: [0, 4, 6], state: "minecraft:air"}, + {pos: [0, 4, 7], state: "minecraft:air"}, + {pos: [1, 4, 0], state: "minecraft:air"}, + {pos: [1, 4, 1], state: "minecraft:air"}, + {pos: [1, 4, 2], state: "minecraft:air"}, + {pos: [1, 4, 3], state: "minecraft:air"}, + {pos: [1, 4, 4], state: "minecraft:air"}, + {pos: [1, 4, 5], state: "minecraft:air"}, + {pos: [1, 4, 6], state: "minecraft:air"}, + {pos: [1, 4, 7], state: "minecraft:air"}, + {pos: [2, 4, 0], state: "minecraft:air"}, + {pos: [2, 4, 1], state: "minecraft:air"}, + {pos: [2, 4, 2], state: "minecraft:air"}, + {pos: [2, 4, 3], state: "minecraft:air"}, + {pos: [2, 4, 4], state: "minecraft:air"}, + {pos: [2, 4, 5], state: "minecraft:air"}, + {pos: [2, 4, 6], state: "minecraft:air"}, + {pos: [2, 4, 7], state: "minecraft:air"}, + {pos: [3, 4, 0], state: "minecraft:air"}, + {pos: [3, 4, 1], state: "minecraft:air"}, + {pos: [3, 4, 2], state: "minecraft:air"}, + {pos: [3, 4, 3], state: "minecraft:air"}, + {pos: [3, 4, 4], state: "minecraft:air"}, + {pos: [3, 4, 5], state: "minecraft:air"}, + {pos: [3, 4, 6], state: "minecraft:air"}, + {pos: [3, 4, 7], state: "minecraft:air"}, + {pos: [4, 4, 0], state: "minecraft:air"}, + {pos: [4, 4, 1], state: "minecraft:air"}, + {pos: [4, 4, 2], state: "minecraft:air"}, + {pos: [4, 4, 3], state: "minecraft:air"}, + {pos: [4, 4, 4], state: "minecraft:air"}, + {pos: [4, 4, 5], state: "minecraft:air"}, + {pos: [4, 4, 6], state: "minecraft:air"}, + {pos: [4, 4, 7], state: "minecraft:air"}, + {pos: [5, 4, 0], state: "minecraft:air"}, + {pos: [5, 4, 1], state: "minecraft:air"}, + {pos: [5, 4, 2], state: "minecraft:air"}, + {pos: [5, 4, 3], state: "minecraft:air"}, + {pos: [5, 4, 4], state: "minecraft:air"}, + {pos: [5, 4, 5], state: "minecraft:air"}, + {pos: [5, 4, 6], state: "minecraft:air"}, + {pos: [5, 4, 7], state: "minecraft:air"}, + {pos: [6, 4, 0], state: "minecraft:air"}, + {pos: [6, 4, 1], state: "minecraft:air"}, + {pos: [6, 4, 2], state: "minecraft:air"}, + {pos: [6, 4, 3], state: "minecraft:air"}, + {pos: [6, 4, 4], state: "minecraft:air"}, + {pos: [6, 4, 5], state: "minecraft:air"}, + {pos: [6, 4, 6], state: "minecraft:air"}, + {pos: [6, 4, 7], state: "minecraft:air"}, + {pos: [7, 4, 0], state: "minecraft:air"}, + {pos: [7, 4, 1], state: "minecraft:air"}, + {pos: [7, 4, 2], state: "minecraft:air"}, + {pos: [7, 4, 3], state: "minecraft:air"}, + {pos: [7, 4, 4], state: "minecraft:air"}, + {pos: [7, 4, 5], state: "minecraft:air"}, + {pos: [7, 4, 6], state: "minecraft:air"}, + {pos: [7, 4, 7], state: "minecraft:air"}, + {pos: [0, 5, 0], state: "minecraft:air"}, + {pos: [0, 5, 1], state: "minecraft:air"}, + {pos: [0, 5, 2], state: "minecraft:air"}, + {pos: [0, 5, 3], state: "minecraft:air"}, + {pos: [0, 5, 4], state: "minecraft:air"}, + {pos: [0, 5, 5], state: "minecraft:air"}, + {pos: [0, 5, 6], state: "minecraft:air"}, + {pos: [0, 5, 7], state: "minecraft:air"}, + {pos: [1, 5, 0], state: "minecraft:air"}, + {pos: [1, 5, 1], state: "minecraft:air"}, + {pos: [1, 5, 2], state: "minecraft:air"}, + {pos: [1, 5, 3], state: "minecraft:air"}, + {pos: [1, 5, 4], state: "minecraft:air"}, + {pos: [1, 5, 5], state: "minecraft:air"}, + {pos: [1, 5, 6], state: "minecraft:air"}, + {pos: [1, 5, 7], state: "minecraft:air"}, + {pos: [2, 5, 0], state: "minecraft:air"}, + {pos: [2, 5, 1], state: "minecraft:air"}, + {pos: [2, 5, 2], state: "minecraft:air"}, + {pos: [2, 5, 3], state: "minecraft:air"}, + {pos: [2, 5, 4], state: "minecraft:air"}, + {pos: [2, 5, 5], state: "minecraft:air"}, + {pos: [2, 5, 6], state: "minecraft:air"}, + {pos: [2, 5, 7], state: "minecraft:air"}, + {pos: [3, 5, 0], state: "minecraft:air"}, + {pos: [3, 5, 1], state: "minecraft:air"}, + {pos: [3, 5, 2], state: "minecraft:air"}, + {pos: [3, 5, 3], state: "minecraft:air"}, + {pos: [3, 5, 4], state: "minecraft:air"}, + {pos: [3, 5, 5], state: "minecraft:air"}, + {pos: [3, 5, 6], state: "minecraft:air"}, + {pos: [3, 5, 7], state: "minecraft:air"}, + {pos: [4, 5, 0], state: "minecraft:air"}, + {pos: [4, 5, 1], state: "minecraft:air"}, + {pos: [4, 5, 2], state: "minecraft:air"}, + {pos: [4, 5, 3], state: "minecraft:air"}, + {pos: [4, 5, 4], state: "minecraft:air"}, + {pos: [4, 5, 5], state: "minecraft:air"}, + {pos: [4, 5, 6], state: "minecraft:air"}, + {pos: [4, 5, 7], state: "minecraft:air"}, + {pos: [5, 5, 0], state: "minecraft:air"}, + {pos: [5, 5, 1], state: "minecraft:air"}, + {pos: [5, 5, 2], state: "minecraft:air"}, + {pos: [5, 5, 3], state: "minecraft:air"}, + {pos: [5, 5, 4], state: "minecraft:air"}, + {pos: [5, 5, 5], state: "minecraft:air"}, + {pos: [5, 5, 6], state: "minecraft:air"}, + {pos: [5, 5, 7], state: "minecraft:air"}, + {pos: [6, 5, 0], state: "minecraft:air"}, + {pos: [6, 5, 1], state: "minecraft:air"}, + {pos: [6, 5, 2], state: "minecraft:air"}, + {pos: [6, 5, 3], state: "minecraft:air"}, + {pos: [6, 5, 4], state: "minecraft:air"}, + {pos: [6, 5, 5], state: "minecraft:air"}, + {pos: [6, 5, 6], state: "minecraft:air"}, + {pos: [6, 5, 7], state: "minecraft:air"}, + {pos: [7, 5, 0], state: "minecraft:air"}, + {pos: [7, 5, 1], state: "minecraft:air"}, + {pos: [7, 5, 2], state: "minecraft:air"}, + {pos: [7, 5, 3], state: "minecraft:air"}, + {pos: [7, 5, 4], state: "minecraft:air"}, + {pos: [7, 5, 5], state: "minecraft:air"}, + {pos: [7, 5, 6], state: "minecraft:air"}, + {pos: [7, 5, 7], state: "minecraft:air"}, + {pos: [0, 6, 0], state: "minecraft:air"}, + {pos: [0, 6, 1], state: "minecraft:air"}, + {pos: [0, 6, 2], state: "minecraft:air"}, + {pos: [0, 6, 3], state: "minecraft:air"}, + {pos: [0, 6, 4], state: "minecraft:air"}, + {pos: [0, 6, 5], state: "minecraft:air"}, + {pos: [0, 6, 6], state: "minecraft:air"}, + {pos: [0, 6, 7], state: "minecraft:air"}, + {pos: [1, 6, 0], state: "minecraft:air"}, + {pos: [1, 6, 1], state: "minecraft:air"}, + {pos: [1, 6, 2], state: "minecraft:air"}, + {pos: [1, 6, 3], state: "minecraft:air"}, + {pos: [1, 6, 4], state: "minecraft:air"}, + {pos: [1, 6, 5], state: "minecraft:air"}, + {pos: [1, 6, 6], state: "minecraft:air"}, + {pos: [1, 6, 7], state: "minecraft:air"}, + {pos: [2, 6, 0], state: "minecraft:air"}, + {pos: [2, 6, 1], state: "minecraft:air"}, + {pos: [2, 6, 2], state: "minecraft:air"}, + {pos: [2, 6, 3], state: "minecraft:air"}, + {pos: [2, 6, 4], state: "minecraft:air"}, + {pos: [2, 6, 5], state: "minecraft:air"}, + {pos: [2, 6, 6], state: "minecraft:air"}, + {pos: [2, 6, 7], state: "minecraft:air"}, + {pos: [3, 6, 0], state: "minecraft:air"}, + {pos: [3, 6, 1], state: "minecraft:air"}, + {pos: [3, 6, 2], state: "minecraft:air"}, + {pos: [3, 6, 3], state: "minecraft:air"}, + {pos: [3, 6, 4], state: "minecraft:air"}, + {pos: [3, 6, 5], state: "minecraft:air"}, + {pos: [3, 6, 6], state: "minecraft:air"}, + {pos: [3, 6, 7], state: "minecraft:air"}, + {pos: [4, 6, 0], state: "minecraft:air"}, + {pos: [4, 6, 1], state: "minecraft:air"}, + {pos: [4, 6, 2], state: "minecraft:air"}, + {pos: [4, 6, 3], state: "minecraft:air"}, + {pos: [4, 6, 4], state: "minecraft:air"}, + {pos: [4, 6, 5], state: "minecraft:air"}, + {pos: [4, 6, 6], state: "minecraft:air"}, + {pos: [4, 6, 7], state: "minecraft:air"}, + {pos: [5, 6, 0], state: "minecraft:air"}, + {pos: [5, 6, 1], state: "minecraft:air"}, + {pos: [5, 6, 2], state: "minecraft:air"}, + {pos: [5, 6, 3], state: "minecraft:air"}, + {pos: [5, 6, 4], state: "minecraft:air"}, + {pos: [5, 6, 5], state: "minecraft:air"}, + {pos: [5, 6, 6], state: "minecraft:air"}, + {pos: [5, 6, 7], state: "minecraft:air"}, + {pos: [6, 6, 0], state: "minecraft:air"}, + {pos: [6, 6, 1], state: "minecraft:air"}, + {pos: [6, 6, 2], state: "minecraft:air"}, + {pos: [6, 6, 3], state: "minecraft:air"}, + {pos: [6, 6, 4], state: "minecraft:air"}, + {pos: [6, 6, 5], state: "minecraft:air"}, + {pos: [6, 6, 6], state: "minecraft:air"}, + {pos: [6, 6, 7], state: "minecraft:air"}, + {pos: [7, 6, 0], state: "minecraft:air"}, + {pos: [7, 6, 1], state: "minecraft:air"}, + {pos: [7, 6, 2], state: "minecraft:air"}, + {pos: [7, 6, 3], state: "minecraft:air"}, + {pos: [7, 6, 4], state: "minecraft:air"}, + {pos: [7, 6, 5], state: "minecraft:air"}, + {pos: [7, 6, 6], state: "minecraft:air"}, + {pos: [7, 6, 7], state: "minecraft:air"}, + {pos: [0, 7, 0], state: "minecraft:air"}, + {pos: [0, 7, 1], state: "minecraft:air"}, + {pos: [0, 7, 2], state: "minecraft:air"}, + {pos: [0, 7, 3], state: "minecraft:air"}, + {pos: [0, 7, 4], state: "minecraft:air"}, + {pos: [0, 7, 5], state: "minecraft:air"}, + {pos: [0, 7, 6], state: "minecraft:air"}, + {pos: [0, 7, 7], state: "minecraft:air"}, + {pos: [1, 7, 0], state: "minecraft:air"}, + {pos: [1, 7, 1], state: "minecraft:air"}, + {pos: [1, 7, 2], state: "minecraft:air"}, + {pos: [1, 7, 3], state: "minecraft:air"}, + {pos: [1, 7, 4], state: "minecraft:air"}, + {pos: [1, 7, 5], state: "minecraft:air"}, + {pos: [1, 7, 6], state: "minecraft:air"}, + {pos: [1, 7, 7], state: "minecraft:air"}, + {pos: [2, 7, 0], state: "minecraft:air"}, + {pos: [2, 7, 1], state: "minecraft:air"}, + {pos: [2, 7, 2], state: "minecraft:air"}, + {pos: [2, 7, 3], state: "minecraft:air"}, + {pos: [2, 7, 4], state: "minecraft:air"}, + {pos: [2, 7, 5], state: "minecraft:air"}, + {pos: [2, 7, 6], state: "minecraft:air"}, + {pos: [2, 7, 7], state: "minecraft:air"}, + {pos: [3, 7, 0], state: "minecraft:air"}, + {pos: [3, 7, 1], state: "minecraft:air"}, + {pos: [3, 7, 2], state: "minecraft:air"}, + {pos: [3, 7, 3], state: "minecraft:air"}, + {pos: [3, 7, 4], state: "minecraft:air"}, + {pos: [3, 7, 5], state: "minecraft:air"}, + {pos: [3, 7, 6], state: "minecraft:air"}, + {pos: [3, 7, 7], state: "minecraft:air"}, + {pos: [4, 7, 0], state: "minecraft:air"}, + {pos: [4, 7, 1], state: "minecraft:air"}, + {pos: [4, 7, 2], state: "minecraft:air"}, + {pos: [4, 7, 3], state: "minecraft:air"}, + {pos: [4, 7, 4], state: "minecraft:air"}, + {pos: [4, 7, 5], state: "minecraft:air"}, + {pos: [4, 7, 6], state: "minecraft:air"}, + {pos: [4, 7, 7], state: "minecraft:air"}, + {pos: [5, 7, 0], state: "minecraft:air"}, + {pos: [5, 7, 1], state: "minecraft:air"}, + {pos: [5, 7, 2], state: "minecraft:air"}, + {pos: [5, 7, 3], state: "minecraft:air"}, + {pos: [5, 7, 4], state: "minecraft:air"}, + {pos: [5, 7, 5], state: "minecraft:air"}, + {pos: [5, 7, 6], state: "minecraft:air"}, + {pos: [5, 7, 7], state: "minecraft:air"}, + {pos: [6, 7, 0], state: "minecraft:air"}, + {pos: [6, 7, 1], state: "minecraft:air"}, + {pos: [6, 7, 2], state: "minecraft:air"}, + {pos: [6, 7, 3], state: "minecraft:air"}, + {pos: [6, 7, 4], state: "minecraft:air"}, + {pos: [6, 7, 5], state: "minecraft:air"}, + {pos: [6, 7, 6], state: "minecraft:air"}, + {pos: [6, 7, 7], state: "minecraft:air"}, + {pos: [7, 7, 0], state: "minecraft:air"}, + {pos: [7, 7, 1], state: "minecraft:air"}, + {pos: [7, 7, 2], state: "minecraft:air"}, + {pos: [7, 7, 3], state: "minecraft:air"}, + {pos: [7, 7, 4], state: "minecraft:air"}, + {pos: [7, 7, 5], state: "minecraft:air"}, + {pos: [7, 7, 6], state: "minecraft:air"}, + {pos: [7, 7, 7], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:air" + ] +} diff --git a/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json b/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json new file mode 100644 index 0000000000..1ae56b7a0b --- /dev/null +++ b/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.gametest", + "compatibilityLevel": "JAVA_16", + "mixins": [ + "CommandManagerMixin", + "MinecraftServerMixin", + "StructureTestUtilMixin", + "TestFunctionsMixin", + "TestServerMixin" + ], + "server": [ + "server.MainMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-gametest-api-v1/src/main/resources/fabric.mod.json b/fabric-gametest-api-v1/src/main/resources/fabric.mod.json new file mode 100644 index 0000000000..1a52716340 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/resources/fabric.mod.json @@ -0,0 +1,33 @@ +{ + "schemaVersion": 1, + "id": "fabric-gametest-api-v1", + "name": "Fabric Game Test API (v1)", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/fabric-gametest-api-v1/icon.png", + "contact": { + "homepage": "https://fabricmc.net", + "irc": "irc://irc.esper.net:6667/fabric", + "issues": "https://github.com/FabricMC/fabric/issues", + "sources": "https://github.com/FabricMC/fabric" + }, + "authors": [ + "FabricMC" + ], + "entrypoints": { + "main" : [ + "net.fabricmc.fabric.impl.gametest.FabricGameTestModInitializer" + ] + }, + "depends": { + "fabric-resource-loader-v0": "*" + }, + "description": "Allows registration of custom game tests.", + "mixins": [ + "fabric-gametest-api-v1.mixins.json" + ], + "custom": { + "fabric-api:module-lifecycle": "stable" + } +} diff --git a/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleFabricTestSuite.java b/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleFabricTestSuite.java new file mode 100644 index 0000000000..95ce2c844b --- /dev/null +++ b/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleFabricTestSuite.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.gametest; + +import java.lang.reflect.Method; + +import net.minecraft.block.Blocks; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestContext; +import net.minecraft.util.math.BlockPos; + +import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; + +// optional to impl FabricGameTest +public class ExampleFabricTestSuite implements FabricGameTest { + /** + * By overriding invokeTestMethod you can wrap the method call. + * This can be used as shown to run code before and after each test. + */ + @Override + public void invokeTestMethod(TestContext context, Method method) { + beforeEach(context); + + FabricGameTest.super.invokeTestMethod(context, method); + + afterEach(context); + } + + private void beforeEach(TestContext context) { + System.out.println("Hello beforeEach"); + context.setBlockState(0, 5, 0, Blocks.GOLD_BLOCK); + } + + private void afterEach(TestContext context) { + context.addInstantFinalTask(() -> + context.checkBlock(new BlockPos(0, 2, 0), (block) -> block == Blocks.DIAMOND_BLOCK, "Expect block to be gold") + ); + } + + @GameTest(structureName = "fabric-gametest-api-v1-testmod:exampletestsuite.diamond") + public void diamond(TestContext context) { + // Nothing to do as the structure placed the block. + } + + @GameTest(structureName = EMPTY_STRUCTURE) + public void noStructure(TestContext context) { + context.setBlockState(0, 2, 0, Blocks.DIAMOND_BLOCK); + } +} diff --git a/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleTestSuite.java b/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleTestSuite.java new file mode 100644 index 0000000000..795c3227e6 --- /dev/null +++ b/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleTestSuite.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.gametest; + +import net.minecraft.block.Blocks; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestContext; +import net.minecraft.util.math.BlockPos; + +import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; + +public class ExampleTestSuite { + @GameTest + public void diamond(TestContext context) { + context.addInstantFinalTask(() -> + context.checkBlock(new BlockPos(0, 2, 0), (block) -> block == Blocks.DIAMOND_BLOCK, "Expect block to be diamond") + ); + } + + @GameTest(structureName = FabricGameTest.EMPTY_STRUCTURE) + public void noStructure(TestContext context) { + context.setBlockState(0, 2, 0, Blocks.DIAMOND_BLOCK); + + context.addInstantFinalTask(() -> + context.checkBlock(new BlockPos(0, 2, 0), (block) -> block == Blocks.DIAMOND_BLOCK, "Expect block to be diamond") + ); + } +} diff --git a/fabric-gametest-api-v1/src/testmod/resources/data/fabric-gametest-api-v1-testmod/gametest/structures/exampletestsuite.diamond.snbt b/fabric-gametest-api-v1/src/testmod/resources/data/fabric-gametest-api-v1-testmod/gametest/structures/exampletestsuite.diamond.snbt new file mode 100644 index 0000000000..7ec6d74332 --- /dev/null +++ b/fabric-gametest-api-v1/src/testmod/resources/data/fabric-gametest-api-v1-testmod/gametest/structures/exampletestsuite.diamond.snbt @@ -0,0 +1,524 @@ +{ + DataVersion: 2730, + size: [8, 8, 8], + data: [ + {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [0, 1, 0], state: "minecraft:diamond_block"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [0, 1, 3], state: "minecraft:air"}, + {pos: [0, 1, 4], state: "minecraft:air"}, + {pos: [0, 1, 5], state: "minecraft:air"}, + {pos: [0, 1, 6], state: "minecraft:air"}, + {pos: [0, 1, 7], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [1, 1, 5], state: "minecraft:air"}, + {pos: [1, 1, 6], state: "minecraft:air"}, + {pos: [1, 1, 7], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 5], state: "minecraft:air"}, + {pos: [2, 1, 6], state: "minecraft:air"}, + {pos: [2, 1, 7], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "minecraft:air"}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 5], state: "minecraft:air"}, + {pos: [3, 1, 6], state: "minecraft:air"}, + {pos: [3, 1, 7], state: "minecraft:air"}, + {pos: [4, 1, 0], state: "minecraft:air"}, + {pos: [4, 1, 1], state: "minecraft:air"}, + {pos: [4, 1, 2], state: "minecraft:air"}, + {pos: [4, 1, 3], state: "minecraft:air"}, + {pos: [4, 1, 4], state: "minecraft:air"}, + {pos: [4, 1, 5], state: "minecraft:air"}, + {pos: [4, 1, 6], state: "minecraft:air"}, + {pos: [4, 1, 7], state: "minecraft:air"}, + {pos: [5, 1, 0], state: "minecraft:air"}, + {pos: [5, 1, 1], state: "minecraft:air"}, + {pos: [5, 1, 2], state: "minecraft:air"}, + {pos: [5, 1, 3], state: "minecraft:air"}, + {pos: [5, 1, 4], state: "minecraft:air"}, + {pos: [5, 1, 5], state: "minecraft:air"}, + {pos: [5, 1, 6], state: "minecraft:air"}, + {pos: [5, 1, 7], state: "minecraft:air"}, + {pos: [6, 1, 0], state: "minecraft:air"}, + {pos: [6, 1, 1], state: "minecraft:air"}, + {pos: [6, 1, 2], state: "minecraft:air"}, + {pos: [6, 1, 3], state: "minecraft:air"}, + {pos: [6, 1, 4], state: "minecraft:air"}, + {pos: [6, 1, 5], state: "minecraft:air"}, + {pos: [6, 1, 6], state: "minecraft:air"}, + {pos: [6, 1, 7], state: "minecraft:air"}, + {pos: [7, 1, 0], state: "minecraft:air"}, + {pos: [7, 1, 1], state: "minecraft:air"}, + {pos: [7, 1, 2], state: "minecraft:air"}, + {pos: [7, 1, 3], state: "minecraft:air"}, + {pos: [7, 1, 4], state: "minecraft:air"}, + {pos: [7, 1, 5], state: "minecraft:air"}, + {pos: [7, 1, 6], state: "minecraft:air"}, + {pos: [7, 1, 7], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [0, 2, 3], state: "minecraft:air"}, + {pos: [0, 2, 4], state: "minecraft:air"}, + {pos: [0, 2, 5], state: "minecraft:air"}, + {pos: [0, 2, 6], state: "minecraft:air"}, + {pos: [0, 2, 7], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 3], state: "minecraft:air"}, + {pos: [1, 2, 4], state: "minecraft:air"}, + {pos: [1, 2, 5], state: "minecraft:air"}, + {pos: [1, 2, 6], state: "minecraft:air"}, + {pos: [1, 2, 7], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], state: "minecraft:air"}, + {pos: [2, 2, 5], state: "minecraft:air"}, + {pos: [2, 2, 6], state: "minecraft:air"}, + {pos: [2, 2, 7], state: "minecraft:air"}, + {pos: [3, 2, 0], state: "minecraft:air"}, + {pos: [3, 2, 1], state: "minecraft:air"}, + {pos: [3, 2, 2], state: "minecraft:air"}, + {pos: [3, 2, 3], state: "minecraft:air"}, + {pos: [3, 2, 4], state: "minecraft:air"}, + {pos: [3, 2, 5], state: "minecraft:air"}, + {pos: [3, 2, 6], state: "minecraft:air"}, + {pos: [3, 2, 7], state: "minecraft:air"}, + {pos: [4, 2, 0], state: "minecraft:air"}, + {pos: [4, 2, 1], state: "minecraft:air"}, + {pos: [4, 2, 2], state: "minecraft:air"}, + {pos: [4, 2, 3], state: "minecraft:air"}, + {pos: [4, 2, 4], state: "minecraft:air"}, + {pos: [4, 2, 5], state: "minecraft:air"}, + {pos: [4, 2, 6], state: "minecraft:air"}, + {pos: [4, 2, 7], state: "minecraft:air"}, + {pos: [5, 2, 0], state: "minecraft:air"}, + {pos: [5, 2, 1], state: "minecraft:air"}, + {pos: [5, 2, 2], state: "minecraft:air"}, + {pos: [5, 2, 3], state: "minecraft:air"}, + {pos: [5, 2, 4], state: "minecraft:air"}, + {pos: [5, 2, 5], state: "minecraft:air"}, + {pos: [5, 2, 6], state: "minecraft:air"}, + {pos: [5, 2, 7], state: "minecraft:air"}, + {pos: [6, 2, 0], state: "minecraft:air"}, + {pos: [6, 2, 1], state: "minecraft:air"}, + {pos: [6, 2, 2], state: "minecraft:air"}, + {pos: [6, 2, 3], state: "minecraft:air"}, + {pos: [6, 2, 4], state: "minecraft:air"}, + {pos: [6, 2, 5], state: "minecraft:air"}, + {pos: [6, 2, 6], state: "minecraft:air"}, + {pos: [6, 2, 7], state: "minecraft:air"}, + {pos: [7, 2, 0], state: "minecraft:air"}, + {pos: [7, 2, 1], state: "minecraft:air"}, + {pos: [7, 2, 2], state: "minecraft:air"}, + {pos: [7, 2, 3], state: "minecraft:air"}, + {pos: [7, 2, 4], state: "minecraft:air"}, + {pos: [7, 2, 5], state: "minecraft:air"}, + {pos: [7, 2, 6], state: "minecraft:air"}, + {pos: [7, 2, 7], state: "minecraft:air"}, + {pos: [0, 3, 0], state: "minecraft:air"}, + {pos: [0, 3, 1], state: "minecraft:air"}, + {pos: [0, 3, 2], state: "minecraft:air"}, + {pos: [0, 3, 3], state: "minecraft:air"}, + {pos: [0, 3, 4], state: "minecraft:air"}, + {pos: [0, 3, 5], state: "minecraft:air"}, + {pos: [0, 3, 6], state: "minecraft:air"}, + {pos: [0, 3, 7], state: "minecraft:air"}, + {pos: [1, 3, 0], state: "minecraft:air"}, + {pos: [1, 3, 1], state: "minecraft:air"}, + {pos: [1, 3, 2], state: "minecraft:air"}, + {pos: [1, 3, 3], state: "minecraft:air"}, + {pos: [1, 3, 4], state: "minecraft:air"}, + {pos: [1, 3, 5], state: "minecraft:air"}, + {pos: [1, 3, 6], state: "minecraft:air"}, + {pos: [1, 3, 7], state: "minecraft:air"}, + {pos: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "minecraft:air"}, + {pos: [2, 3, 2], state: "minecraft:air"}, + {pos: [2, 3, 3], state: "minecraft:air"}, + {pos: [2, 3, 4], state: "minecraft:air"}, + {pos: [2, 3, 5], state: "minecraft:air"}, + {pos: [2, 3, 6], state: "minecraft:air"}, + {pos: [2, 3, 7], state: "minecraft:air"}, + {pos: [3, 3, 0], state: "minecraft:air"}, + {pos: [3, 3, 1], state: "minecraft:air"}, + {pos: [3, 3, 2], state: "minecraft:air"}, + {pos: [3, 3, 3], state: "minecraft:air"}, + {pos: [3, 3, 4], state: "minecraft:air"}, + {pos: [3, 3, 5], state: "minecraft:air"}, + {pos: [3, 3, 6], state: "minecraft:air"}, + {pos: [3, 3, 7], state: "minecraft:air"}, + {pos: [4, 3, 0], state: "minecraft:air"}, + {pos: [4, 3, 1], state: "minecraft:air"}, + {pos: [4, 3, 2], state: "minecraft:air"}, + {pos: [4, 3, 3], state: "minecraft:air"}, + {pos: [4, 3, 4], state: "minecraft:air"}, + {pos: [4, 3, 5], state: "minecraft:air"}, + {pos: [4, 3, 6], state: "minecraft:air"}, + {pos: [4, 3, 7], state: "minecraft:air"}, + {pos: [5, 3, 0], state: "minecraft:air"}, + {pos: [5, 3, 1], state: "minecraft:air"}, + {pos: [5, 3, 2], state: "minecraft:air"}, + {pos: [5, 3, 3], state: "minecraft:air"}, + {pos: [5, 3, 4], state: "minecraft:air"}, + {pos: [5, 3, 5], state: "minecraft:air"}, + {pos: [5, 3, 6], state: "minecraft:air"}, + {pos: [5, 3, 7], state: "minecraft:air"}, + {pos: [6, 3, 0], state: "minecraft:air"}, + {pos: [6, 3, 1], state: "minecraft:air"}, + {pos: [6, 3, 2], state: "minecraft:air"}, + {pos: [6, 3, 3], state: "minecraft:air"}, + {pos: [6, 3, 4], state: "minecraft:air"}, + {pos: [6, 3, 5], state: "minecraft:air"}, + {pos: [6, 3, 6], state: "minecraft:air"}, + {pos: [6, 3, 7], state: "minecraft:air"}, + {pos: [7, 3, 0], state: "minecraft:air"}, + {pos: [7, 3, 1], state: "minecraft:air"}, + {pos: [7, 3, 2], state: "minecraft:air"}, + {pos: [7, 3, 3], state: "minecraft:air"}, + {pos: [7, 3, 4], state: "minecraft:air"}, + {pos: [7, 3, 5], state: "minecraft:air"}, + {pos: [7, 3, 6], state: "minecraft:air"}, + {pos: [7, 3, 7], state: "minecraft:air"}, + {pos: [0, 4, 0], state: "minecraft:air"}, + {pos: [0, 4, 1], state: "minecraft:air"}, + {pos: [0, 4, 2], state: "minecraft:air"}, + {pos: [0, 4, 3], state: "minecraft:air"}, + {pos: [0, 4, 4], state: "minecraft:air"}, + {pos: [0, 4, 5], state: "minecraft:air"}, + {pos: [0, 4, 6], state: "minecraft:air"}, + {pos: [0, 4, 7], state: "minecraft:air"}, + {pos: [1, 4, 0], state: "minecraft:air"}, + {pos: [1, 4, 1], state: "minecraft:air"}, + {pos: [1, 4, 2], state: "minecraft:air"}, + {pos: [1, 4, 3], state: "minecraft:air"}, + {pos: [1, 4, 4], state: "minecraft:air"}, + {pos: [1, 4, 5], state: "minecraft:air"}, + {pos: [1, 4, 6], state: "minecraft:air"}, + {pos: [1, 4, 7], state: "minecraft:air"}, + {pos: [2, 4, 0], state: "minecraft:air"}, + {pos: [2, 4, 1], state: "minecraft:air"}, + {pos: [2, 4, 2], state: "minecraft:air"}, + {pos: [2, 4, 3], state: "minecraft:air"}, + {pos: [2, 4, 4], state: "minecraft:air"}, + {pos: [2, 4, 5], state: "minecraft:air"}, + {pos: [2, 4, 6], state: "minecraft:air"}, + {pos: [2, 4, 7], state: "minecraft:air"}, + {pos: [3, 4, 0], state: "minecraft:air"}, + {pos: [3, 4, 1], state: "minecraft:air"}, + {pos: [3, 4, 2], state: "minecraft:air"}, + {pos: [3, 4, 3], state: "minecraft:air"}, + {pos: [3, 4, 4], state: "minecraft:air"}, + {pos: [3, 4, 5], state: "minecraft:air"}, + {pos: [3, 4, 6], state: "minecraft:air"}, + {pos: [3, 4, 7], state: "minecraft:air"}, + {pos: [4, 4, 0], state: "minecraft:air"}, + {pos: [4, 4, 1], state: "minecraft:air"}, + {pos: [4, 4, 2], state: "minecraft:air"}, + {pos: [4, 4, 3], state: "minecraft:air"}, + {pos: [4, 4, 4], state: "minecraft:air"}, + {pos: [4, 4, 5], state: "minecraft:air"}, + {pos: [4, 4, 6], state: "minecraft:air"}, + {pos: [4, 4, 7], state: "minecraft:air"}, + {pos: [5, 4, 0], state: "minecraft:air"}, + {pos: [5, 4, 1], state: "minecraft:air"}, + {pos: [5, 4, 2], state: "minecraft:air"}, + {pos: [5, 4, 3], state: "minecraft:air"}, + {pos: [5, 4, 4], state: "minecraft:air"}, + {pos: [5, 4, 5], state: "minecraft:air"}, + {pos: [5, 4, 6], state: "minecraft:air"}, + {pos: [5, 4, 7], state: "minecraft:air"}, + {pos: [6, 4, 0], state: "minecraft:air"}, + {pos: [6, 4, 1], state: "minecraft:air"}, + {pos: [6, 4, 2], state: "minecraft:air"}, + {pos: [6, 4, 3], state: "minecraft:air"}, + {pos: [6, 4, 4], state: "minecraft:air"}, + {pos: [6, 4, 5], state: "minecraft:air"}, + {pos: [6, 4, 6], state: "minecraft:air"}, + {pos: [6, 4, 7], state: "minecraft:air"}, + {pos: [7, 4, 0], state: "minecraft:air"}, + {pos: [7, 4, 1], state: "minecraft:air"}, + {pos: [7, 4, 2], state: "minecraft:air"}, + {pos: [7, 4, 3], state: "minecraft:air"}, + {pos: [7, 4, 4], state: "minecraft:air"}, + {pos: [7, 4, 5], state: "minecraft:air"}, + {pos: [7, 4, 6], state: "minecraft:air"}, + {pos: [7, 4, 7], state: "minecraft:air"}, + {pos: [0, 5, 0], state: "minecraft:air"}, + {pos: [0, 5, 1], state: "minecraft:air"}, + {pos: [0, 5, 2], state: "minecraft:air"}, + {pos: [0, 5, 3], state: "minecraft:air"}, + {pos: [0, 5, 4], state: "minecraft:air"}, + {pos: [0, 5, 5], state: "minecraft:air"}, + {pos: [0, 5, 6], state: "minecraft:air"}, + {pos: [0, 5, 7], state: "minecraft:air"}, + {pos: [1, 5, 0], state: "minecraft:air"}, + {pos: [1, 5, 1], state: "minecraft:air"}, + {pos: [1, 5, 2], state: "minecraft:air"}, + {pos: [1, 5, 3], state: "minecraft:air"}, + {pos: [1, 5, 4], state: "minecraft:air"}, + {pos: [1, 5, 5], state: "minecraft:air"}, + {pos: [1, 5, 6], state: "minecraft:air"}, + {pos: [1, 5, 7], state: "minecraft:air"}, + {pos: [2, 5, 0], state: "minecraft:air"}, + {pos: [2, 5, 1], state: "minecraft:air"}, + {pos: [2, 5, 2], state: "minecraft:air"}, + {pos: [2, 5, 3], state: "minecraft:air"}, + {pos: [2, 5, 4], state: "minecraft:air"}, + {pos: [2, 5, 5], state: "minecraft:air"}, + {pos: [2, 5, 6], state: "minecraft:air"}, + {pos: [2, 5, 7], state: "minecraft:air"}, + {pos: [3, 5, 0], state: "minecraft:air"}, + {pos: [3, 5, 1], state: "minecraft:air"}, + {pos: [3, 5, 2], state: "minecraft:air"}, + {pos: [3, 5, 3], state: "minecraft:air"}, + {pos: [3, 5, 4], state: "minecraft:air"}, + {pos: [3, 5, 5], state: "minecraft:air"}, + {pos: [3, 5, 6], state: "minecraft:air"}, + {pos: [3, 5, 7], state: "minecraft:air"}, + {pos: [4, 5, 0], state: "minecraft:air"}, + {pos: [4, 5, 1], state: "minecraft:air"}, + {pos: [4, 5, 2], state: "minecraft:air"}, + {pos: [4, 5, 3], state: "minecraft:air"}, + {pos: [4, 5, 4], state: "minecraft:air"}, + {pos: [4, 5, 5], state: "minecraft:air"}, + {pos: [4, 5, 6], state: "minecraft:air"}, + {pos: [4, 5, 7], state: "minecraft:air"}, + {pos: [5, 5, 0], state: "minecraft:air"}, + {pos: [5, 5, 1], state: "minecraft:air"}, + {pos: [5, 5, 2], state: "minecraft:air"}, + {pos: [5, 5, 3], state: "minecraft:air"}, + {pos: [5, 5, 4], state: "minecraft:air"}, + {pos: [5, 5, 5], state: "minecraft:air"}, + {pos: [5, 5, 6], state: "minecraft:air"}, + {pos: [5, 5, 7], state: "minecraft:air"}, + {pos: [6, 5, 0], state: "minecraft:air"}, + {pos: [6, 5, 1], state: "minecraft:air"}, + {pos: [6, 5, 2], state: "minecraft:air"}, + {pos: [6, 5, 3], state: "minecraft:air"}, + {pos: [6, 5, 4], state: "minecraft:air"}, + {pos: [6, 5, 5], state: "minecraft:air"}, + {pos: [6, 5, 6], state: "minecraft:air"}, + {pos: [6, 5, 7], state: "minecraft:air"}, + {pos: [7, 5, 0], state: "minecraft:air"}, + {pos: [7, 5, 1], state: "minecraft:air"}, + {pos: [7, 5, 2], state: "minecraft:air"}, + {pos: [7, 5, 3], state: "minecraft:air"}, + {pos: [7, 5, 4], state: "minecraft:air"}, + {pos: [7, 5, 5], state: "minecraft:air"}, + {pos: [7, 5, 6], state: "minecraft:air"}, + {pos: [7, 5, 7], state: "minecraft:air"}, + {pos: [0, 6, 0], state: "minecraft:air"}, + {pos: [0, 6, 1], state: "minecraft:air"}, + {pos: [0, 6, 2], state: "minecraft:air"}, + {pos: [0, 6, 3], state: "minecraft:air"}, + {pos: [0, 6, 4], state: "minecraft:air"}, + {pos: [0, 6, 5], state: "minecraft:air"}, + {pos: [0, 6, 6], state: "minecraft:air"}, + {pos: [0, 6, 7], state: "minecraft:air"}, + {pos: [1, 6, 0], state: "minecraft:air"}, + {pos: [1, 6, 1], state: "minecraft:air"}, + {pos: [1, 6, 2], state: "minecraft:air"}, + {pos: [1, 6, 3], state: "minecraft:air"}, + {pos: [1, 6, 4], state: "minecraft:air"}, + {pos: [1, 6, 5], state: "minecraft:air"}, + {pos: [1, 6, 6], state: "minecraft:air"}, + {pos: [1, 6, 7], state: "minecraft:air"}, + {pos: [2, 6, 0], state: "minecraft:air"}, + {pos: [2, 6, 1], state: "minecraft:air"}, + {pos: [2, 6, 2], state: "minecraft:air"}, + {pos: [2, 6, 3], state: "minecraft:air"}, + {pos: [2, 6, 4], state: "minecraft:air"}, + {pos: [2, 6, 5], state: "minecraft:air"}, + {pos: [2, 6, 6], state: "minecraft:air"}, + {pos: [2, 6, 7], state: "minecraft:air"}, + {pos: [3, 6, 0], state: "minecraft:air"}, + {pos: [3, 6, 1], state: "minecraft:air"}, + {pos: [3, 6, 2], state: "minecraft:air"}, + {pos: [3, 6, 3], state: "minecraft:air"}, + {pos: [3, 6, 4], state: "minecraft:air"}, + {pos: [3, 6, 5], state: "minecraft:air"}, + {pos: [3, 6, 6], state: "minecraft:air"}, + {pos: [3, 6, 7], state: "minecraft:air"}, + {pos: [4, 6, 0], state: "minecraft:air"}, + {pos: [4, 6, 1], state: "minecraft:air"}, + {pos: [4, 6, 2], state: "minecraft:air"}, + {pos: [4, 6, 3], state: "minecraft:air"}, + {pos: [4, 6, 4], state: "minecraft:air"}, + {pos: [4, 6, 5], state: "minecraft:air"}, + {pos: [4, 6, 6], state: "minecraft:air"}, + {pos: [4, 6, 7], state: "minecraft:air"}, + {pos: [5, 6, 0], state: "minecraft:air"}, + {pos: [5, 6, 1], state: "minecraft:air"}, + {pos: [5, 6, 2], state: "minecraft:air"}, + {pos: [5, 6, 3], state: "minecraft:air"}, + {pos: [5, 6, 4], state: "minecraft:air"}, + {pos: [5, 6, 5], state: "minecraft:air"}, + {pos: [5, 6, 6], state: "minecraft:air"}, + {pos: [5, 6, 7], state: "minecraft:air"}, + {pos: [6, 6, 0], state: "minecraft:air"}, + {pos: [6, 6, 1], state: "minecraft:air"}, + {pos: [6, 6, 2], state: "minecraft:air"}, + {pos: [6, 6, 3], state: "minecraft:air"}, + {pos: [6, 6, 4], state: "minecraft:air"}, + {pos: [6, 6, 5], state: "minecraft:air"}, + {pos: [6, 6, 6], state: "minecraft:air"}, + {pos: [6, 6, 7], state: "minecraft:air"}, + {pos: [7, 6, 0], state: "minecraft:air"}, + {pos: [7, 6, 1], state: "minecraft:air"}, + {pos: [7, 6, 2], state: "minecraft:air"}, + {pos: [7, 6, 3], state: "minecraft:air"}, + {pos: [7, 6, 4], state: "minecraft:air"}, + {pos: [7, 6, 5], state: "minecraft:air"}, + {pos: [7, 6, 6], state: "minecraft:air"}, + {pos: [7, 6, 7], state: "minecraft:air"}, + {pos: [0, 7, 0], state: "minecraft:air"}, + {pos: [0, 7, 1], state: "minecraft:air"}, + {pos: [0, 7, 2], state: "minecraft:air"}, + {pos: [0, 7, 3], state: "minecraft:air"}, + {pos: [0, 7, 4], state: "minecraft:air"}, + {pos: [0, 7, 5], state: "minecraft:air"}, + {pos: [0, 7, 6], state: "minecraft:air"}, + {pos: [0, 7, 7], state: "minecraft:air"}, + {pos: [1, 7, 0], state: "minecraft:air"}, + {pos: [1, 7, 1], state: "minecraft:air"}, + {pos: [1, 7, 2], state: "minecraft:air"}, + {pos: [1, 7, 3], state: "minecraft:air"}, + {pos: [1, 7, 4], state: "minecraft:air"}, + {pos: [1, 7, 5], state: "minecraft:air"}, + {pos: [1, 7, 6], state: "minecraft:air"}, + {pos: [1, 7, 7], state: "minecraft:air"}, + {pos: [2, 7, 0], state: "minecraft:air"}, + {pos: [2, 7, 1], state: "minecraft:air"}, + {pos: [2, 7, 2], state: "minecraft:air"}, + {pos: [2, 7, 3], state: "minecraft:air"}, + {pos: [2, 7, 4], state: "minecraft:air"}, + {pos: [2, 7, 5], state: "minecraft:air"}, + {pos: [2, 7, 6], state: "minecraft:air"}, + {pos: [2, 7, 7], state: "minecraft:air"}, + {pos: [3, 7, 0], state: "minecraft:air"}, + {pos: [3, 7, 1], state: "minecraft:air"}, + {pos: [3, 7, 2], state: "minecraft:air"}, + {pos: [3, 7, 3], state: "minecraft:air"}, + {pos: [3, 7, 4], state: "minecraft:air"}, + {pos: [3, 7, 5], state: "minecraft:air"}, + {pos: [3, 7, 6], state: "minecraft:air"}, + {pos: [3, 7, 7], state: "minecraft:air"}, + {pos: [4, 7, 0], state: "minecraft:air"}, + {pos: [4, 7, 1], state: "minecraft:air"}, + {pos: [4, 7, 2], state: "minecraft:air"}, + {pos: [4, 7, 3], state: "minecraft:air"}, + {pos: [4, 7, 4], state: "minecraft:air"}, + {pos: [4, 7, 5], state: "minecraft:air"}, + {pos: [4, 7, 6], state: "minecraft:air"}, + {pos: [4, 7, 7], state: "minecraft:air"}, + {pos: [5, 7, 0], state: "minecraft:air"}, + {pos: [5, 7, 1], state: "minecraft:air"}, + {pos: [5, 7, 2], state: "minecraft:air"}, + {pos: [5, 7, 3], state: "minecraft:air"}, + {pos: [5, 7, 4], state: "minecraft:air"}, + {pos: [5, 7, 5], state: "minecraft:air"}, + {pos: [5, 7, 6], state: "minecraft:air"}, + {pos: [5, 7, 7], state: "minecraft:air"}, + {pos: [6, 7, 0], state: "minecraft:air"}, + {pos: [6, 7, 1], state: "minecraft:air"}, + {pos: [6, 7, 2], state: "minecraft:air"}, + {pos: [6, 7, 3], state: "minecraft:air"}, + {pos: [6, 7, 4], state: "minecraft:air"}, + {pos: [6, 7, 5], state: "minecraft:air"}, + {pos: [6, 7, 6], state: "minecraft:air"}, + {pos: [6, 7, 7], state: "minecraft:air"}, + {pos: [7, 7, 0], state: "minecraft:air"}, + {pos: [7, 7, 1], state: "minecraft:air"}, + {pos: [7, 7, 2], state: "minecraft:air"}, + {pos: [7, 7, 3], state: "minecraft:air"}, + {pos: [7, 7, 4], state: "minecraft:air"}, + {pos: [7, 7, 5], state: "minecraft:air"}, + {pos: [7, 7, 6], state: "minecraft:air"}, + {pos: [7, 7, 7], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:diamond_block", + "minecraft:air" + ] +} diff --git a/fabric-gametest-api-v1/src/testmod/resources/fabric.mod.json b/fabric-gametest-api-v1/src/testmod/resources/fabric.mod.json new file mode 100644 index 0000000000..078fb5752c --- /dev/null +++ b/fabric-gametest-api-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,14 @@ +{ + "schemaVersion": 1, + "id": "fabric-gametest-api-v1-testmod", + "name": "Fabric Game Test API (v1) Test Mod", + "version": "1.0.0", + "environment": "*", + "license": "Apache-2.0", + "entrypoints": { + "fabric-gametest" : [ + "net.fabricmc.fabric.test.gametest.ExampleFabricTestSuite", + "net.fabricmc.fabric.test.gametest.ExampleTestSuite" + ] + } +} diff --git a/settings.gradle b/settings.gradle index b459a3352b..2c47ad615b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,6 +26,7 @@ include 'fabric-entity-events-v1' include 'fabric-events-interaction-v0' include 'fabric-events-lifecycle-v0' include 'fabric-game-rule-api-v1' +include 'fabric-gametest-api-v1' include 'fabric-item-api-v1' include 'fabric-item-groups-v0' include 'fabric-keybindings-v0'