diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index f3c7b7f7e..62d0cd328 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -10,4 +10,6 @@ on: jobs: build-and-test: - uses: IntelligenceModding/actions/.github/workflows/build-and-test.yaml@master \ No newline at end of file + uses: IntelligenceModding/actions/.github/workflows/build-and-test.yaml@master + run-gametests: + uses: IntelligenceModding/actions/.github/workflows/run-gametests.yaml@master \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8fa3a9245..6bf01776a 100644 --- a/.gitignore +++ b/.gitignore @@ -21,12 +21,12 @@ build eclipse run server +/.jqwik-database # Files from Forge MDK forge*changelog.txt #github - 1.17 .env diff --git a/build.gradle b/build.gradle index b176a7695..e96611609 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,4 @@ +import cc.tweaked.gradle.ClientJavaExec import net.darkhax.curseforgegradle.TaskPublishCurseForge import java.text.SimpleDateFormat @@ -7,8 +8,8 @@ plugins { id 'net.darkhax.curseforgegradle' version '1.1.16' id 'org.jetbrains.changelog' version '1.2.1' id "com.modrinth.minotaur" version "2.+" - id "org.jetbrains.kotlin.jvm" version "1.6.10" - id 'net.minecraftforge.gradle' version '[6.0.18,6.2)' + id "org.jetbrains.kotlin.jvm" version "${kotlin_version}" + id 'net.minecraftforge.gradle' id 'org.parchmentmc.librarian.forgegradle' version '1.+' id 'org.spongepowered.mixin' version '0.7.+' id "com.github.breadmoirai.github-release" version "2.5.2" @@ -61,7 +62,23 @@ sourceSets { main.resources { srcDir 'src/generated/resources' } - testMod {} + testMod { + java.srcDir 'src/testMod/java' + kotlin.srcDir 'src/testMod/kotlin' + resources.srcDir 'src/testMod/resources' + compileClasspath += main.compileClasspath + test.compileClasspath + runtimeClasspath += main.runtimeClasspath + test.runtimeClasspath + } + testFixtures { + java.srcDir 'src/testFixtures/java' + kotlin.srcDir 'src/testFixtures/kotlin' + resources.srcDir 'src/testFixtures/resources' + } +} + +java.registerFeature("testFixtures") { + usingSourceSet(sourceSets.testFixtures) + disablePublication() } minecraft { @@ -71,7 +88,13 @@ minecraft { accessTransformer file('src/main/resources/META-INF/accesstransformer.cfg') accessTransformer file('src/testMod/resources/META-INF/accesstransformer.cfg') + runs { + all { + lazyToken('minecraft_classpath') { + configurations.library.copyRecursive().resolve().collect { it.absolutePath }.join(File.pathSeparator) + } + } client { workingDirectory project.file('run') @@ -135,34 +158,68 @@ minecraft { workingDirectory project.file('test-files/client') parent runs.client + def oldClasspath = lazyTokens["minecraft_classpath"] + lazyToken("minecraft_classpath") { + // Add all files in testMinecraftLibrary to the classpath. + def allFiles = new HashSet(); + + def minecraftClasspath = oldClasspath?.get() + if (minecraftClasspath != null && !minecraftClasspath.isEmpty()) allFiles.addAll(minecraftClasspath.split(File.pathSeparatorChar)) + + for (file in configurations["testMinecraftLibrary"]) allFiles.add(file.absolutePath) + + println("New classpath $allFiles") + allFiles.join(File.pathSeparator) + } + + property 'forge.logging.markers', 'REGISTRIES' + property 'forge.logging.console.level', 'debug' + property 'forge.enabledGameTestNamespaces', 'advancedperipherals,advancedperipheralstest' + property 'advancedperipheralstest.sources', file("src/testMod/resources/data/advancedperipheralstest").absolutePath + property 'advancedperipheralstest.tags', 'common,client' + + args "--mixin.config=ccgametest.mixins.json" + args "--mixin.config=advancedperipheralstest.mixins.json" + mods { - aptest { + advancedperipheralstest { source sourceSets.testMod + source sourceSets.testFixtures } } + } - lazyToken('minecraft_classpath') { - (configurations.implementationExtra.copyRecursive().resolve()) - .collect { it.absolutePath } - .join(File.pathSeparator) + gameTestServer { + def oldClasspath = lazyTokens["minecraft_classpath"] + lazyToken("minecraft_classpath") { + // Add all files in testMinecraftLibrary to the classpath. + def allFiles = new HashSet(); + + def minecraftClasspath = oldClasspath?.get() + if (minecraftClasspath != null && !minecraftClasspath.isEmpty()) allFiles.addAll(minecraftClasspath.split(File.pathSeparatorChar)) + + for (file in configurations["testMinecraftLibrary"]) allFiles.add(file.absolutePath) + + println("New classpath $allFiles") + allFiles.join(File.pathSeparator) } - } - testServer { workingDirectory project.file('test-files/server') + property 'forge.logging.markers', 'REGISTRIES' + property 'forge.logging.console.level', 'debug' + property 'forge.enabledGameTestNamespaces', 'advancedperipherals,advancedperipheralstest' + property 'advancedperipheralstest.sources', file("src/testMod/resources/data/advancedperipheralstest").absolutePath + + args "--mixin.config=ccgametest.mixins.json" + args "--mixin.config=advancedperipheralstest.mixins.json" parent runs.server mods { - aptest { + advancedperipheralstest { source sourceSets.testMod + source sourceSets.testFixtures } } - - lazyToken('minecraft_classpath') { - (configurations.implementationExtra.copyRecursive().resolve()) - .collect { it.absolutePath } - .join(File.pathSeparator) - } } } } @@ -252,9 +309,27 @@ repositories { } configurations { + minecraftLibrary { extendsFrom(minecraftEmbed) } + + testMinecraftLibrary { + canBeResolved = true + canBeConsumed = false + // Prevent ending up with multiple versions of libraries on the classpath. + shouldResolveConsistentlyWith(minecraftLibrary) + } + + library + implementation.extendsFrom library implementationExtra + testImplementation.extendsFrom(implementation) testModImplementation.extendsFrom(implementation) testModImplementation.extendsFrom(testImplementation) + testFixturesImplementation.extendsFrom(implementation) + testFixturesImplementation.extendsFrom(testImplementation) +} + +processTestModResources { + duplicatesStrategy = 'exclude' } dependencies { @@ -265,6 +340,7 @@ dependencies { compileOnly "org.jetbrains:annotations:${jb_annotations}" minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" implementation fg.deobf("org.squiddev:cc-tweaked-${minecraft_version}:${cc_version}") + implementation fg.deobf("org.squiddev:cc-tweaked-${minecraft_version}:${cc_version}:api") // Minimal requirements end // Extended requirements @@ -300,7 +376,7 @@ dependencies { compileOnly fg.deobf("curse.maven:ae-additions-493962:${ae2additions_version}") //runtimeOnly fg.deobf("curse.maven:ae-additions-493962:${ae2additions_version}") - implementation fg.deobf("thedarkcolour:kotlinforforge:${kotlinforforge_version}") + runtimeOnly fg.deobf("thedarkcolour:kotlinforforge:${kotlinforforge_version}") // Botania compileOnly fg.deobf("vazkii.botania:Botania:${botania_version}") @@ -345,12 +421,17 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_version}" testImplementation "org.junit.jupiter:junit-jupiter-params:${junit_version}" + testImplementation "org.junit.jupiter:junit-jupiter-engine:${junit_version}" + testImplementation "net.jqwik:jqwik:${jqwikVersion}" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlin_coroutines_version}" testImplementation "org.hamcrest:hamcrest:${hamcrest_version}" - testImplementation "org.jetbrains.kotlin:kotlin-reflect:${kotlin_version}" - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlinx_coroutines_version}" testModImplementation sourceSets.main.output + testModImplementation sourceSets.testFixtures.output + + testMinecraftLibrary("org.junit.jupiter:junit-jupiter-api:${junit_version}") + testMinecraftLibrary("org.junit.jupiter:junit-jupiter-params:${junit_version}") + testMinecraftLibrary("org.hamcrest:hamcrest:${hamcrest_version}") - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_version}" // Testing stuff // JEI implementation fg.deobf("mezz.jei:jei-${jei_version}") @@ -381,6 +462,11 @@ changelog { compileTestModJava { dependsOn(compileJava) + dependsOn(compileKotlin) + dependsOn(compileTestJava) + dependsOn(compileTestKotlin) + dependsOn(compileTestFixturesJava) + dependsOn(compileTestFixturesKotlin) } task setupServer(type: Copy) { @@ -394,28 +480,12 @@ task setupServer(type: Copy) { into "test-files/server" } -["Client", "Server"].forEach { name -> - tasks.register("test$name", JavaExec.class).configure { - it.group('In-game tests') - it.description("Runs tests on a temporary Minecraft instance.") - it.dependsOn(setupServer, "prepareRunTest$name", "cleanTest$name", 'compileTestModJava') - - JavaExec exec = tasks.getByName("runTest$name") - exec.copyTo(it) - it.setClasspath(exec.getClasspath()) - it.mainClass = exec.mainClass - it.setArgs(exec.getArgs()) - - it.systemProperty('forge.logging.console.level', 'debug') - it.systemProperty('ttoolkit.run', 'true') - } -} - test { useJUnitPlatform() testLogging { events "skipped", "failed" } + finalizedBy('runGameTestServer') } afterEvaluate { @@ -612,3 +682,11 @@ publishing { } } } + +tasks.register('runGameTestClient', ClientJavaExec, { task -> + task.outputs.upToDateWhen { false } + + description "Runs client-side gametests with no mods" + setRunConfig(minecraft.runs.testClient) + tags("client") +}) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000..d2d420771 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + `java-gradle-plugin` + `kotlin-dsl` + kotlin("jvm") version "1.9.23" +} + +repositories { + mavenCentral() + + maven("https://maven.minecraftforge.net") { + name = "Forge" + content { + includeGroup("net.minecraftforge") + includeGroup("net.minecraftforge.gradle") + } + } +} + +dependencies { + implementation("net.minecraftforge.gradle:ForgeGradle:[6.0.18,6.2)") +} diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/Extensions.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/Extensions.kt new file mode 100644 index 000000000..8afc4f0f3 --- /dev/null +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/Extensions.kt @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package cc.tweaked.gradle + +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.JavaExec +import org.gradle.process.BaseExecSpec +import org.gradle.process.JavaExecSpec +import org.gradle.process.ProcessForkOptions + +/** + * Add an annotation processor to all source sets. + */ +fun DependencyHandler.annotationProcessorEverywhere(dep: Any) { + add("compileOnly", dep) + add("annotationProcessor", dep) + + add("clientCompileOnly", dep) + add("clientAnnotationProcessor", dep) + + add("testCompileOnly", dep) + add("testAnnotationProcessor", dep) +} + +/** + * A version of [JavaExecSpec.copyTo] which copies *all* properties. + */ +fun JavaExec.copyToFull(spec: JavaExec) { + copyTo(spec) + + // Additional Java options + spec.jvmArgs = jvmArgs // Fabric overrides getJvmArgs so copyTo doesn't do the right thing. + spec.args = args + spec.argumentProviders.addAll(argumentProviders) + spec.mainClass.set(mainClass) + spec.classpath = classpath + spec.javaLauncher.set(javaLauncher) + if (executable != null) spec.setExecutable(executable!!) + + // Additional ExecSpec options + copyToExec(spec) +} + +/** + * Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo]. + */ +fun BaseExecSpec.copyToExec(spec: BaseExecSpec) { + spec.isIgnoreExitValue = isIgnoreExitValue + if (standardInput != null) spec.standardInput = standardInput + if (standardOutput != null) spec.standardOutput = standardOutput + if (errorOutput != null) spec.errorOutput = errorOutput +} + +/** + * An alternative to [Nothing] with a more descriptive name. Use to enforce calling a function with named arguments: + * + * ```kotlin + * fun f(vararg unused: UseNamedArgs, arg1: Int, arg2: Int) { + * // ... + * } + * ``` + */ +class UseNamedArgs private constructor() + +/** + * An [AutoCloseable] implementation which can be used to combine other [AutoCloseable] instances. + * + * Values which implement [AutoCloseable] can be dynamically registered with [CloseScope.add]. When the scope is closed, + * each value is closed in the opposite order. + * + * This is largely intended for cases where it's not appropriate to nest [AutoCloseable.use], for instance when nested + * would be too deep. + */ +class CloseScope : AutoCloseable { + private val toClose = ArrayDeque() + + /** + * Add a value to be closed when this scope is closed. + */ + public fun add(value: AutoCloseable) { + toClose.addLast(value) + } + + override fun close() { + close(null) + } + + @PublishedApi + internal fun close(baseException: Throwable?) { + var exception = baseException + + while (true) { + var toClose = toClose.removeLastOrNull() ?: break + try { + toClose.close() + } catch (e: Throwable) { + if (exception == null) { + exception = e + } else { + exception.addSuppressed(e) + } + } + } + + if (exception != null) throw exception + } + + inline fun use(block: (CloseScope) -> R): R { + var exception: Throwable? = null + try { + return block(this) + } catch (e: Throwable) { + exception = e + throw e + } finally { + close(exception) + } + } +} + +/** Proxy method to avoid overload ambiguity. */ +fun Property.setProvider(provider: Provider) = set(provider) + +/** Short-cut method to get the absolute path of a [FileSystemLocation] provider. */ +fun Provider.getAbsolutePath(): String = get().asFile.absolutePath + +/** + * Get the version immediately after the provided version. + * + * For example, given "1.2.3", this will return "1.2.4". + */ +fun getNextVersion(version: String): String { + // Split a version like x.y.z-SNAPSHOT into x.y.z and -SNAPSHOT + val dashIndex = version.indexOf('-') + val mainVersion = if (dashIndex < 0) version else version.substring(0, dashIndex) + + // Find the last component in x.y.z and increment it. + val lastIndex = mainVersion.lastIndexOf('.') + if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"") + val lastVersion = try { + version.substring(lastIndex + 1).toInt() + } catch (e: NumberFormatException) { + throw IllegalArgumentException("Cannot parse version format \"$version\"", e) + } + + // Then append all components together. + val out = StringBuilder() + out.append(version, 0, lastIndex + 1) + out.append(lastVersion + 1) + if (dashIndex >= 0) out.append(version, dashIndex, version.length) + return out.toString() +} diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/ForgeExtensions.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/ForgeExtensions.kt new file mode 100644 index 000000000..bbf6e86c5 --- /dev/null +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/ForgeExtensions.kt @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package cc.tweaked.gradle + +import net.minecraftforge.gradle.common.util.RunConfig +import net.minecraftforge.gradle.common.util.runs.setRunConfigInternal +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.JavaExec +import org.gradle.jvm.toolchain.JavaToolchainService +import java.nio.file.Files + +/** + * Set [JavaExec] task to run a given [RunConfig]. + */ +fun JavaExec.setRunConfig(config: RunConfig) { + dependsOn("prepareRuns") + setRunConfigInternal(project, this, config) + doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) } + + javaLauncher.set( + project.extensions.getByType(JavaToolchainService::class.java) + .launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain), + ) +} diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftExec.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftExec.kt new file mode 100644 index 000000000..4755f0274 --- /dev/null +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftExec.kt @@ -0,0 +1,220 @@ +// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package cc.tweaked.gradle + +import net.minecraftforge.gradle.common.util.RunConfig +import org.gradle.api.GradleException +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.invocation.Gradle +import org.gradle.api.provider.Provider +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters +import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.getByName +import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.gradle.process.CommandLineArgumentProvider +import java.io.File +import java.nio.file.Files +import java.util.concurrent.TimeUnit +import java.util.function.Supplier +import javax.inject.Inject +import kotlin.random.Random + +/** + * A [JavaExec] task for client-tests. This sets some common setup, and uses [MinecraftRunnerService] to ensure only one + * test runs at once. + */ +abstract class ClientJavaExec : JavaExec() { + private val clientRunner: Provider = MinecraftRunnerService.get(project.gradle) + + init { + group = LifecycleBasePlugin.VERIFICATION_GROUP + usesService(clientRunner) + } + + @get:Input + val renderdoc get() = project.hasProperty("renderdoc") + + /** + * When [false], tests will not be run automatically, allowing the user to debug rendering. + */ + @get:Input + val clientDebug get() = renderdoc || project.hasProperty("clientDebug") + + /** + * When [false], tests will not run under a framebuffer. + */ + @get:Input + val useFramebuffer get() = !clientDebug && !project.hasProperty("clientNoFramebuffer") + + /** + * The path test results are written to. + */ + @get:OutputFile + val testResults = project.layout.buildDirectory.file("test-results/$name.xml") + + private fun setTestProperties() { + if (!clientDebug) systemProperty("advancedperipheralstest.client", "") + if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so") + systemProperty("advancedperipheralstest.gametest-report", testResults.get().asFile.absoluteFile) + workingDir(project.layout.buildDirectory.dir("gametest/$name")) + } + + init { + setTestProperties() + } + + /** + * Set this task to run a given [RunConfig]. + */ + fun setRunConfig(config: RunConfig) { + (this as JavaExec).setRunConfig(config) + setTestProperties() // setRunConfig may clobber some properties, ensure everything is set. + } + + /** + * Copy configuration from a task with the given name. + */ + fun copyFrom(path: String) = copyFrom(project.tasks.getByName(path, JavaExec::class)) + + /** + * Copy configuration from an existing [JavaExec] task. + */ + fun copyFrom(task: JavaExec) { + for (dep in task.dependsOn) dependsOn(dep) + task.copyToFull(this) + setTestProperties() // copyToFull may clobber some properties, ensure everything is set. + } + + /** + * Only run tests with the given tags. + */ + fun tags(vararg tags: String) { + jvmArgumentProviders.add( + CommandLineArgumentProvider { + listOf("-Dadvancedperipheralstest.tags=${tags.joinToString(",")}") + } + ) + } + + /** + * Write a file with the given contents before starting Minecraft. This may be useful for writing config files. + */ + fun withFileContents(path: Any, contents: Supplier) { + val file = project.file(path).toPath() + doFirst { + Files.createDirectories(file.parent) + Files.writeString(file, contents.get()) + } + } + + /** + * Copy a file to the provided path before starting Minecraft. This copy only occurs if the file does not already + * exist. + */ + fun withFileFrom(path: Any, source: Supplier) { + val file = project.file(path).toPath() + doFirst { + Files.createDirectories(file.parent) + if (!Files.exists(file)) Files.copy(source.get().toPath(), file) + } + } + + @TaskAction + override fun exec() { + Files.createDirectories(workingDir.toPath()) + fsOperations.delete { delete(workingDir.resolve("screenshots")) } + + if (useFramebuffer) { + clientRunner.get().wrapClient(this) { super.exec() } + } else { + super.exec() + } + } + + @get:Inject + protected abstract val fsOperations: FileSystemOperations +} + +/** + * A service for [JavaExec] tasks which start Minecraft. + * + * Tasks may run `usesService(MinecraftRunnerService.get(gradle))` to ensure that only one Minecraft-related task runs + * at once. + */ +abstract class MinecraftRunnerService : BuildService { + private val hasXvfb = lazy { + System.getProperty("os.name", "").equals("linux", ignoreCase = true) && ProcessHelpers.onPath("xvfb-run") + } + + internal fun wrapClient(exec: JavaExec, run: () -> Unit) = when { + hasXvfb.value -> runXvfb(exec, run) + else -> run() + } + + /** + * Run a program under Xvfb, preventing it spawning a window. + */ + private fun runXvfb(exec: JavaExec, run: () -> Unit) { + fun ProcessBuilder.startVerbose(): Process { + exec.logger.info("Running ${this.command()}") + return start() + } + + CloseScope().use { scope -> + val dir = Files.createTempDirectory("cctweaked").toAbsolutePath() + scope.add { fsOperations.delete { delete(dir) } } + + val authFile = Files.createTempFile(dir, "Xauthority", "").toAbsolutePath() + + val cookie = StringBuilder().also { + for (i in 0..31) it.append("0123456789abcdef"[Random.nextInt(16)]) + }.toString() + + val xvfb = + ProcessBuilder("Xvfb", "-displayfd", "1", "-screen", "0", "640x480x24", "-nolisten", "tcp").also { + it.inheritIO() + it.environment()["XAUTHORITY"] = authFile.toString() + it.redirectOutput(ProcessBuilder.Redirect.PIPE) + }.startVerbose() + scope.add { xvfb.destroyForcibly().waitFor() } + + val server = xvfb.inputReader().use { it.readLine().trim() } + exec.logger.info("Running at :$server (XAUTHORITY=$authFile.toA") + + ProcessBuilder("xauth", "add", ":$server", ".", cookie).also { + it.inheritIO() + it.environment()["XAUTHORITY"] = authFile.toString() + }.startVerbose().waitForOrThrow("Failed to setup XAuthority file") + + scope.add { + ProcessBuilder("xauth", "remove", ":$server").also { + it.inheritIO() + it.environment()["XAUTHORITY"] = authFile.toString() + }.startVerbose().waitFor() + } + + // Wait a few seconds for Xvfb to start. Ugly, but identical to xvfb-run. + if (xvfb.waitFor(3, TimeUnit.SECONDS)) { + throw GradleException("Xvfb unexpectedly exited (with status code ${xvfb.exitValue()})") + } + + exec.environment("XAUTHORITY", authFile.toString()) + exec.environment("DISPLAY", ":$server") + + run() + } + } + + @get:Inject + protected abstract val fsOperations: FileSystemOperations + + companion object { + fun get(gradle: Gradle): Provider = + gradle.sharedServices.registerIfAbsent("cc.tweaked.gradle.ClientJavaExec", MinecraftRunnerService::class.java) { + maxParallelUsages.set(1) + } + } +} diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/ProcessHelpers.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/ProcessHelpers.kt new file mode 100644 index 000000000..2fdb2a650 --- /dev/null +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/ProcessHelpers.kt @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package cc.tweaked.gradle + +import org.codehaus.groovy.runtime.ProcessGroovyMethods +import org.gradle.api.GradleException +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.nio.charset.StandardCharsets + +internal object ProcessHelpers { + fun startProcess(vararg command: String): Process { + // Something randomly passes in "GIT_DIR=" as an environment variable which clobbers everything else. Don't + // inherit the environment array! + return ProcessBuilder() + .command(*command) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .also { it.environment().clear() } + .start() + } + + fun captureOut(vararg command: String): String { + val process = startProcess(*command) + process.outputStream.close() + + val result = ProcessGroovyMethods.getText(process) + process.waitForOrThrow("Failed to run command") + return result + } + + fun captureLines(vararg command: String): List { + val process = startProcess(*command) + process.outputStream.close() + + val out = BufferedReader(InputStreamReader(process.inputStream, StandardCharsets.UTF_8)).use { reader -> + reader.lines().filter { it.isNotEmpty() }.toList() + } + ProcessGroovyMethods.closeStreams(process) + process.waitForOrThrow("Failed to run command") + return out + } + + fun onPath(name: String): Boolean { + val path = System.getenv("PATH") ?: return false + return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() } + } + + /** + * Search for an executable on the `PATH` if required. + * + * [Process]/[ProcessBuilder] does not handle all executable file extensions on Windows (such as `.com). When on + * Windows, this function searches `PATH` and `PATHEXT` for an executable matching [name]. + */ + fun getExecutable(name: String): String { + if (!System.getProperty("os.name").lowercase().contains("windows")) return name + + val path = (System.getenv("PATH") ?: return name).split(File.pathSeparator) + val pathExt = (System.getenv("PATHEXT") ?: return name).split(File.pathSeparator) + + for (pathEntry in path) { + for (ext in pathExt) { + val resolved = File(pathEntry, name + ext) + if (resolved.exists()) return resolved.getAbsolutePath() + } + } + + return name + } +} + +internal fun Process.waitForOrThrow(message: String) { + val ret = waitFor() + if (ret != 0) throw GradleException("$message (exited with $ret)") +} diff --git a/buildSrc/src/main/kotlin/net/minecraftforge/gradle/common/util/runs/RunConfigSetup.kt b/buildSrc/src/main/kotlin/net/minecraftforge/gradle/common/util/runs/RunConfigSetup.kt new file mode 100644 index 000000000..3494c5358 --- /dev/null +++ b/buildSrc/src/main/kotlin/net/minecraftforge/gradle/common/util/runs/RunConfigSetup.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package net.minecraftforge.gradle.common.util.runs + +import net.minecraftforge.gradle.common.util.RunConfig +import org.gradle.api.Project +import org.gradle.process.CommandLineArgumentProvider +import org.gradle.process.JavaExecSpec +import java.io.File + +/** + * Set up a [JavaExecSpec] to execute a [RunConfig]. + * + * [MinecraftRunTask] sets up all its properties when the task is executed, rather than when configured. As such, it's + * not possible to use [cc.tweaked.gradle.copyToFull] like we do for Fabric. Instead, we set up the task manually. + * + * Unfortunately most of the functionality we need is package-private, and so we have to put our code into the package. + */ +internal fun setRunConfigInternal(project: Project, spec: JavaExecSpec, config: RunConfig) { + spec.workingDir = File(config.workingDirectory) + + spec.mainClass.set(config.main) + for (source in config.allSources) spec.classpath(source.runtimeClasspath) + + val originalTask = project.tasks.named(config.taskName, MinecraftRunTask::class.java) + + // Add argument and JVM argument via providers, to be as lazy as possible with fetching artifacts. + val lazyTokens = RunConfigGenerator.configureTokensLazy( + project, config, RunConfigGenerator.mapModClassesToGradle(project, config), + originalTask.get().minecraftArtifacts, + originalTask.get().runtimeClasspathArtifacts, + ) + spec.argumentProviders.add( + CommandLineArgumentProvider { + RunConfigGenerator.getArgsStream(config, lazyTokens, false).toList() + }, + ) + spec.jvmArgumentProviders.add( + CommandLineArgumentProvider { + (if (config.isClient) config.jvmArgs + originalTask.get().additionalClientArgs.get() else config.jvmArgs).map { config.replace(lazyTokens, it) } + + config.properties.map { (k, v) -> "-D${k}=${config.replace(lazyTokens, v)}" } + }, + ) + + for ((key, value) in config.environment) spec.environment(key, config.replace(lazyTokens, value)) +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index a379e1d45..7f3effb6d 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -22,7 +22,7 @@ - + @@ -92,12 +92,12 @@ - - + + diff --git a/docs/CREATING_TESTS.MD b/docs/CREATING_TESTS.MD new file mode 100644 index 000000000..15a348581 --- /dev/null +++ b/docs/CREATING_TESTS.MD @@ -0,0 +1,133 @@ +## Creating Game Tests + +Since [#562](https://github.com/IntelligenceModding/AdvancedPeripherals/pull/562), Advanced Peripherals has a testing framework that allows us to write tests for our peripherals. +The testing framework is copied from [ComputerCraft](https://github.com/cc-tweaked/CC-Tweaked) and adapted to Advanced Peripherals. + +### First things first. + +To write a test, you need to have a test world. But this needs to be done in the testing environment. +To run the testing environment, run the gradle task `runTestClient`. This will start a new instance of Minecraft with the testing environment. +That includes the ability to run tests, import and export tests using the `/cctest` and the `/test` command. + +After the tests are created, you can use the gradle task `runGameTestServer` to start the game test server which then runs all the test. +To test a single test, you can also use `/test run ` in the testing environment. + +### Building your test structure + +Tests should have a base plate made out of polished andesite. This is not a standardized design, it is just the way it is done everywhere else. +Every test needs one computer and the peripheral you want to test. For example the environment detector. + +![assets/img.png](assets/img.png) + +Now you need to set a label to the computer, so it can run the lua test script. +`label set .` +For example, peripheraltest as the test type. The test type is the name of the `@GameTestHolder` class. PeripheralTest.kt for this type. +`environment` would be our test name. So the label would be `peripheraltest.environment`. + +The computer can either have the id 0 or 1. It depends what you want to do later. +It is possible to either run a test script on the computer which is explained in "Writing the test" and you can run +code on the computer using the kotlin game tests. +Use id 0 if you want to run a lua script on the computer and id 1 if you only want to run code using kotlin game tests. + +Now to save your test, get a structure block, place it in the lower right corner of the test structure and set the mode to save. +Use the command `/give @p minecraft:structure_block` to get a structure block. + +Set the size for your test and the structure name. The structure name is your test name with the minecraft namespace. `minecraft:peripheraltest.environment` in our case. + +![assets/img_1.png](assets/img_1.png) + +Click on done to save the content of the structure block and then on SAVE to save the structure. +We now have our test structure saved as a .nbt file in the world folder. To export it as a snbt file, use the command `/cctest export` +to export the file to the `src/testMod/resources/data/advancedperipherals/structures` folder, so it can be used by the game test server later. + +### Writing the test + +To write a test, you need to create a new class in the `src/testMod/kotlin/advancedperipherals/test` package or use one of the existing classes. +The class should be annotated with `@GameTestHolder`. +The class should have a function annotated with `@GameTest` that will be executed by the game test server later. +The function needs to have the parameter `GameTestContext` that will be used to interact with the game world. + +There are two ways to test things on a computer. You can either run a lua script or run code on the computer using kotlin. + +#### Lua Tests + +A simple game test would look like this. You can see that the context can be used to interact with the world. +You can then use one of the helper functions to interact with the computer. For example, `thenComputerOk()` to check if the script on the computer was executed without any fails. +```kt + @GameTest + fun environment(context: GameTestHelper) = context.sequence { + context.level.setWeatherParameters(6000, 0, false, false); + thenComputerOk(); + } +``` + +Of course, you need a lua script to interact with the peripheral. +The script should be placed in the `src/testMod/resources/data/advancedperipheralstest/computer/tests` folder and should be named after the test name. +`peripheraltest.environment.lua` in our case. + +```lua +detector = peripheral.find("environmentDetector") +test.assert(detectorl, true, "Peripheral not found") + +isRaining = detector.isRaining() +test.eq(false, isRaining, "It should not rain") +``` + +Last but not least, import the script to the computer. You need to do that when you're currently in the test world, and you want to write the script in your IDE. +Luckily, the test framework provides a command for that. +`/cctest import` imports the scripts from the resources folder to the world folder. +You can then find the script in the computer's folder using `ls tests/`. + + +#### Kotlin Tests + +To run tests on a computer, the computer needs to have the id 1 + +```kt +thenOnComputer { callPeripheral("left", "playNote") } +``` + +This would run `callPeripheral("left", "playNote")` on the computer which just calls a function on the peripheral on the left + +Here is an example with our note block test + +```kt + @GameTest(timeoutTicks = 300) + fun minecraftNoteBlock_Triggering_Allay(context: GameTestHelper) = context.sequence { + // test if playNote triggers an allay + // related issue: https://github.com/IntelligenceModding/AdvancedPeripherals/issues/603 + + val item = Items.DIAMOND + var allay: Allay? = null + thenExecute { + allay = context.spawn(EntityType.ALLAY, 2, 3, 2) + allay?.setItemInHand(InteractionHand.MAIN_HAND, ItemStack(item)) + + context.spawnItem(item, 2f, 3f, 2f) + } + + thenWaitUntil { context.assertEntityNotPresent(EntityType.ITEM) } + thenWaitUntil { + if (allay?.inventory?.getItem(0)?.count != 1) + context.fail("Expected Allay to pick up item") + } + thenOnComputer { callPeripheral("left", "playNote") } + thenWaitUntil { context.assertEntityPresent(EntityType.ITEM) } + thenWaitUntil { + if (allay?.inventory?.getItem(0)?.count != 0) + context.fail("Expected Allay to drop item") + } + } +``` + +### Client Tests + +Similar to the common game tests described above, you can also write tests that will be executed on the client. +To define a client test, use the annotation `@ClientGameTest` instead of `@GameTest`. The rest of the process is the same. +In the test function, you can then use `thenOnClient { ... }` to run code on the client. + +To run the client tests automatically, use the gradle task `runGameTestClient`. + +--- + +For more examples, you can also check how the tests from CC work [here](https://github.com/cc-tweaked/CC-Tweaked/tree/mc-1.19.2/src/testMod/kotlin/dan200/computercraft/gametest). \ No newline at end of file diff --git a/docs/assets/img.png b/docs/assets/img.png new file mode 100644 index 000000000..1887d9961 Binary files /dev/null and b/docs/assets/img.png differ diff --git a/docs/assets/img_1.png b/docs/assets/img_1.png new file mode 100644 index 000000000..1e943f018 Binary files /dev/null and b/docs/assets/img_1.png differ diff --git a/gradle.properties b/gradle.properties index 740b947f1..d905797a5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.logging.level=info # Minecraft related mod_id=advancedperipherals minecraft_version=1.19.2 -forge_version=43.4.0 +forge_version=43.4.2 loader_version=43 mod_version=0.8r release_type=release @@ -14,11 +14,14 @@ mappings_version=2022.11.20-1.19.2 jb_annotations=21.0.1 # Test dependencies -junit_version=5.7.2 +junit_version=5.10.2 hamcrest_version=2.2 kotlin_version=1.8.0 kotlinx_coroutines_version=1.7.3 ttoolkit_version=0.1.3 +jqwikVersion=1.8.4 +kotlin_version=1.9.23 +kotlin_coroutines_version=1.8.0 # Mod dependencies cc_version=1.101.3 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 309b4e18d..bb6c19194 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 6b39aa618..e95fe7b62 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,5 +16,5 @@ pluginManagement { } plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' } \ No newline at end of file diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/integrations/NoteBlockIntegration.java b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/integrations/NoteBlockIntegration.java index ad5a653d3..c158a5672 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/integrations/NoteBlockIntegration.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/integrations/NoteBlockIntegration.java @@ -19,7 +19,7 @@ public NoteBlockIntegration(Level world, BlockPos pos) { @NotNull @Override public String getType() { - return "noteBlock"; + return "note_block"; } @LuaFunction(mainThread = true) diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/FluidDetectorPeripheral.java b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/FluidDetectorPeripheral.java index c54dde612..8bace2906 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/FluidDetectorPeripheral.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/FluidDetectorPeripheral.java @@ -9,10 +9,10 @@ public class FluidDetectorPeripheral extends BasePeripheral> { - public static final String TYPE = "fluid_detector"; + public static final String PERIPHERAL_TYPE = "fluid_detector"; public FluidDetectorPeripheral(FluidDetectorEntity tileEntity) { - super(TYPE, new BlockEntityPeripheralOwner<>(tileEntity)); + super(PERIPHERAL_TYPE, new BlockEntityPeripheralOwner<>(tileEntity)); } @Override diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/GasDetectorPeripheral.java b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/GasDetectorPeripheral.java index fd94811e3..b066c72ee 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/GasDetectorPeripheral.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/GasDetectorPeripheral.java @@ -8,10 +8,10 @@ public class GasDetectorPeripheral extends BasePeripheral> { - public static final String TYPE = "gas_detector"; + public static final String PERIPHERAL_TYPE = "gas_detector"; public GasDetectorPeripheral(GasDetectorEntity tileEntity) { - super(TYPE, new BlockEntityPeripheralOwner<>(tileEntity)); + super(PERIPHERAL_TYPE, new BlockEntityPeripheralOwner<>(tileEntity)); } @Override diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/MeBridgePeripheral.java b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/MeBridgePeripheral.java index 86776f7b0..d577b5d66 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/MeBridgePeripheral.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/MeBridgePeripheral.java @@ -37,7 +37,7 @@ public class MeBridgePeripheral extends BasePeripheral> implements IStorageSystemPeripheral { - public static final String PERIPHERAL_TYPE = "meBridge"; + public static final String PERIPHERAL_TYPE = "me_bridge"; private final MeBridgeEntity tile; private IGridNode node; @@ -262,7 +262,7 @@ public final MethodResult importItem(IComputerAccess computer, IArguments argume String side = arguments.getString(1); IItemHandler inventory; - if (Direction.byName(side.toUpperCase(Locale.ROOT)) == null && ComputerSide.valueOfInsensitive(side.toUpperCase(Locale.ROOT)) == null) { + if (Direction.byName(side) != null || ComputerSide.valueOfInsensitive(side.toUpperCase(Locale.ROOT)) != null) { inventory = InventoryUtil.getHandlerFromDirection(arguments.getString(1), owner); } else { inventory = InventoryUtil.getHandlerFromName(computer, arguments.getString(1)); @@ -280,7 +280,7 @@ public final MethodResult exportItem(IComputerAccess computer, @NotNull IArgumen String side = arguments.getString(1); IItemHandler inventory; - if (Direction.byName(side.toUpperCase(Locale.ROOT)) == null && ComputerSide.valueOfInsensitive(side.toUpperCase(Locale.ROOT)) == null) { + if (Direction.byName(side) != null || ComputerSide.valueOfInsensitive(side.toUpperCase(Locale.ROOT)) != null) { inventory = InventoryUtil.getHandlerFromDirection(arguments.getString(1), owner); } else { inventory = InventoryUtil.getHandlerFromName(computer, arguments.getString(1)); diff --git a/src/main/java/de/srendi/advancedperipherals/common/items/MemoryCardItem.java b/src/main/java/de/srendi/advancedperipherals/common/items/MemoryCardItem.java index 0bd126d4b..e9294ae98 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/items/MemoryCardItem.java +++ b/src/main/java/de/srendi/advancedperipherals/common/items/MemoryCardItem.java @@ -11,6 +11,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -27,7 +28,7 @@ public boolean isEnabled() { } @Override - public void appendHoverText(ItemStack stack, @Nullable Level levelIn, List tooltip, TooltipFlag flagIn) { + public void appendHoverText(@NotNull ItemStack stack, @Nullable Level levelIn, @NotNull List tooltip, @NotNull TooltipFlag flagIn) { super.appendHoverText(stack, levelIn, tooltip, flagIn); CompoundTag data = stack.getOrCreateTag(); // TODO <0.8>: remove the owner name field diff --git a/src/main/java/de/srendi/advancedperipherals/common/items/WeakAutomataCore.java b/src/main/java/de/srendi/advancedperipherals/common/items/WeakAutomataCore.java index 0dd8b101f..018ec6465 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/items/WeakAutomataCore.java +++ b/src/main/java/de/srendi/advancedperipherals/common/items/WeakAutomataCore.java @@ -59,7 +59,7 @@ public boolean isEnabled() { } @Override - public void appendHoverText(ItemStack stack, @Nullable Level worldIn, List tooltip, TooltipFlag flagIn) { + public void appendHoverText(@NotNull ItemStack stack, @Nullable Level worldIn, @NotNull List tooltip, @NotNull TooltipFlag flagIn) { super.appendHoverText(stack, worldIn, tooltip, flagIn); CompoundTag tag = stack.getOrCreateTag(); CompoundTag consumedData = tag.getCompound(CONSUMER_ENTITY_COMPOUND); diff --git a/src/main/java/de/srendi/advancedperipherals/common/items/base/BaseBlockItem.java b/src/main/java/de/srendi/advancedperipherals/common/items/base/BaseBlockItem.java index 999618f26..31237b250 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/items/base/BaseBlockItem.java +++ b/src/main/java/de/srendi/advancedperipherals/common/items/base/BaseBlockItem.java @@ -1,10 +1,8 @@ package de.srendi.advancedperipherals.common.items.base; import de.srendi.advancedperipherals.AdvancedPeripherals; -import de.srendi.advancedperipherals.client.KeyBindings; -import de.srendi.advancedperipherals.common.util.EnumColor; -import de.srendi.advancedperipherals.common.util.KeybindUtil; import de.srendi.advancedperipherals.common.util.TranslationUtil; +import de.srendi.advancedperipherals.common.util.inventory.ItemUtil; import net.minecraft.network.chat.Component; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.ItemStack; @@ -28,15 +26,9 @@ public BaseBlockItem(Block blockIn) { } @Override - public void appendHoverText(ItemStack stack, @Nullable Level levelIn, List tooltip, TooltipFlag flagIn) { + public void appendHoverText(@NotNull ItemStack stack, @Nullable Level levelIn, @NotNull List tooltip, @NotNull TooltipFlag flagIn) { super.appendHoverText(stack, levelIn, tooltip, flagIn); - if (!KeybindUtil.isKeyPressed(KeyBindings.DESCRIPTION_KEYBINDING)) { - tooltip.add(EnumColor.buildTextComponent(Component.translatable("item.advancedperipherals.tooltip.show_desc", KeyBindings.DESCRIPTION_KEYBINDING.getTranslatedKeyMessage()))); - } else { - tooltip.add(EnumColor.buildTextComponent(getDescription())); - } - if (!isEnabled()) - tooltip.add(EnumColor.buildTextComponent(Component.translatable("item.advancedperipherals.tooltip.disabled"))); + ItemUtil.buildItemTooltip(getDescription(), isEnabled(), tooltip); } public @NotNull Component getDescription() { diff --git a/src/main/java/de/srendi/advancedperipherals/common/items/base/BaseItem.java b/src/main/java/de/srendi/advancedperipherals/common/items/base/BaseItem.java index bc95f02d1..5c17112ca 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/items/base/BaseItem.java +++ b/src/main/java/de/srendi/advancedperipherals/common/items/base/BaseItem.java @@ -1,10 +1,8 @@ package de.srendi.advancedperipherals.common.items.base; import de.srendi.advancedperipherals.AdvancedPeripherals; -import de.srendi.advancedperipherals.client.KeyBindings; -import de.srendi.advancedperipherals.common.util.EnumColor; -import de.srendi.advancedperipherals.common.util.KeybindUtil; import de.srendi.advancedperipherals.common.util.TranslationUtil; +import de.srendi.advancedperipherals.common.util.inventory.ItemUtil; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; @@ -45,18 +43,11 @@ public InteractionResultHolder use(Level worldIn, Player playerIn, In } @Override - public void appendHoverText(ItemStack stack, @Nullable Level worldIn, List tooltip, TooltipFlag flagIn) { + public void appendHoverText(@NotNull ItemStack stack, @Nullable Level worldIn, @NotNull List tooltip, @NotNull TooltipFlag flagIn) { super.appendHoverText(stack, worldIn, tooltip, flagIn); - if (!KeybindUtil.isKeyPressed(KeyBindings.DESCRIPTION_KEYBINDING)) { - tooltip.add(EnumColor.buildTextComponent(Component.translatable("item.advancedperipherals.tooltip.show_desc", KeyBindings.DESCRIPTION_KEYBINDING.getTranslatedKeyMessage()))); - } else { - tooltip.add(EnumColor.buildTextComponent(getDescription())); - } - if (!isEnabled()) - tooltip.add(EnumColor.buildTextComponent(Component.translatable("item.advancedperipherals.tooltip.disabled"))); + ItemUtil.buildItemTooltip(getDescription(), isEnabled(), tooltip); } - public @NotNull Component getDescription() { if (description == null) description = TranslationUtil.itemTooltip(getDescriptionId()); return description; diff --git a/src/main/java/de/srendi/advancedperipherals/common/util/inventory/ItemUtil.java b/src/main/java/de/srendi/advancedperipherals/common/util/inventory/ItemUtil.java index fbd61f7f2..1985ccfd0 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/util/inventory/ItemUtil.java +++ b/src/main/java/de/srendi/advancedperipherals/common/util/inventory/ItemUtil.java @@ -2,8 +2,12 @@ import dan200.computercraft.shared.Registry; import de.srendi.advancedperipherals.AdvancedPeripherals; +import de.srendi.advancedperipherals.client.KeyBindings; +import de.srendi.advancedperipherals.common.util.EnumColor; import de.srendi.advancedperipherals.common.util.StringUtil; import net.minecraft.ResourceLocationException; +import net.minecraft.core.NonNullList; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @@ -83,6 +87,27 @@ public static List getItemsFromItemHandler(IItemHandler handler) { return items; } + public static void addComputerItemToTab(ResourceLocation turtleID, ResourceLocation pocketID, NonNullList items) { + if (turtleID != null) { + items.add(makeTurtle(TURTLE_ADVANCED, turtleID.toString())); + items.add(makeTurtle(TURTLE_NORMAL, turtleID.toString())); + } + if (pocketID != null) { + items.add(makePocket(POCKET_ADVANCED, pocketID.toString())); + items.add(makePocket(POCKET_NORMAL, pocketID.toString())); + } + } + + public static void buildItemTooltip(Component description, boolean isEnabled, List tooltip) { + if (!KeyBindings.DESCRIPTION_KEYBINDING.isDown()) { + tooltip.add(EnumColor.buildTextComponent(Component.translatable("item.advancedperipherals.tooltip.show_desc", KeyBindings.DESCRIPTION_KEYBINDING.getTranslatedKeyMessage()))); + } else { + tooltip.add(EnumColor.buildTextComponent(description)); + } + if (!isEnabled) + tooltip.add(EnumColor.buildTextComponent(Component.translatable("item.advancedperipherals.tooltip.disabled"))); + } + public static ResourceLocation getRegistryKey(Item item) { return ForgeRegistries.ITEMS.getKey(item); } diff --git a/src/test/java/de/srendi/advancedperipherals/lib/peripherals/owner/OperationAbilityTest.java b/src/test/java/de/srendi/advancedperipherals/lib/peripherals/owner/OperationAbilityTest.java deleted file mode 100644 index a2781aa68..000000000 --- a/src/test/java/de/srendi/advancedperipherals/lib/peripherals/owner/OperationAbilityTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package de.srendi.advancedperipherals.lib.peripherals.owner; - -public class OperationAbilityTest { - - //Commented out until we port ttoolkit to 1.18 - /*@Test - public void testCooldownLong() { - DummyPeripheralOwner owner = new DummyPeripheralOwner(); - owner.attachOperation(DummyOperations.LONG); - OperationAbility operationAbility = owner.getAbility(PeripheralOwnerAbility.OPERATION); - assertNotNull(operationAbility); - int abilityCooldown = operationAbility.getCurrentCooldown(DummyOperations.LONG); - assertTrue(operationAbility.isOnCooldown(DummyOperations.LONG)); - assertTrue(abilityCooldown > 10_000); - assertTrue(abilityCooldown < 20_001); - } - - @Test - public void testCooldownShort() { - DummyPeripheralOwner owner = new DummyPeripheralOwner(); - assertTrue(LibConfig.initialCooldownSensetiveLevel > DummyOperations.SHORT.cooldown); - owner.attachOperation(DummyOperations.LONG); - OperationAbility operationAbility = owner.getAbility(PeripheralOwnerAbility.OPERATION); - assertNotNull(operationAbility); - int abilityCooldown = operationAbility.getCurrentCooldown(DummyOperations.SHORT); - assertFalse(operationAbility.isOnCooldown(DummyOperations.SHORT)); - assertEquals(0, abilityCooldown); - } - - @Test - public void testCooldownWithoutInitialCooldown() { - DummyPeripheralOwner owner = new DummyPeripheralOwner(); - LibConfig.setTestMode(true); - owner.attachOperation(DummyOperations.LONG); - OperationAbility operationAbility = owner.getAbility(PeripheralOwnerAbility.OPERATION); - assertNotNull(operationAbility); - int abilityCooldown = operationAbility.getCurrentCooldown(DummyOperations.LONG); - assertFalse(operationAbility.isOnCooldown(DummyOperations.LONG)); - assertEquals(0, abilityCooldown); - LibConfig.setTestMode(false); - } - - public enum DummyOperations implements IPeripheralOperation { - LONG(20_000), SHORT(3_000); - - private final int cooldown; - - DummyOperations(int cooldown) { - this.cooldown = cooldown; - } - - @Override - public void addToConfig(ForgeConfigSpec.Builder builder) { - } - - @Override - public int getInitialCooldown() { - return cooldown; - } - - @Override - public int getCooldown(Object context) { - return cooldown; - } - - @Override - public int getCost(Object context) { - return 3; - } - - @Override - public Map computerDescription() { - return new HashMap<>(); - } - }*/ - -} diff --git a/src/test/java/de/srendi/advancedperipherals/test/DummyPeripheralOwner.java b/src/test/java/de/srendi/advancedperipherals/test/DummyPeripheralOwner.java deleted file mode 100644 index 66390a63a..000000000 --- a/src/test/java/de/srendi/advancedperipherals/test/DummyPeripheralOwner.java +++ /dev/null @@ -1,79 +0,0 @@ -package de.srendi.advancedperipherals.test; - -public class DummyPeripheralOwner /*extends BasePeripheralOwner*/ { - - /* private final CompoundTag dataStorage = new CompoundTag(); - - @Override - @Nullable - public String getCustomName() { - return null; - } - - @Override - @Nullable - public Level getLevel() { - return null; - } - - @Override - @NotNull - public BlockPos getPos() { - return new BlockPos(0, 0, 0); - } - - @Override - @NotNull - public Direction getFacing() { - return Direction.NORTH; - } - - @Override - @Nullable - public Player getOwner() { - return null; - } - - @Override - @NotNull - public CompoundTag getDataStorage() { - return dataStorage; - } - - @Override - public void markDataStorageDirty() { - - } - - @Override - public T withPlayer(Function function) { - throw new RuntimeException("Not implemented"); - } - - @Override - public ItemStack getToolInMainHand() { - return ItemStack.EMPTY; - } - - @Override - public ItemStack storeItem(ItemStack stored) { - return stored; - } - - @Override - public void destroyUpgrade() { - - } - - @Override - public boolean isMovementPossible(@NotNull Level level, @NotNull BlockPos pos) { - return false; - } - - @Override - public boolean move(@NotNull Level level, @NotNull BlockPos pos) { - return false; - } - */ - -} diff --git a/src/test/java/de/srendi/advancedperipherals/tests/utiltests/TranslationAndStringTests.java b/src/test/java/de/srendi/advancedperipherals/tests/utiltests/TranslationAndStringTests.java new file mode 100644 index 000000000..e45a24652 --- /dev/null +++ b/src/test/java/de/srendi/advancedperipherals/tests/utiltests/TranslationAndStringTests.java @@ -0,0 +1,23 @@ +package de.srendi.advancedperipherals.tests.utiltests; + +import de.srendi.advancedperipherals.AdvancedPeripherals; +import de.srendi.advancedperipherals.common.util.TranslationUtil; +import net.minecraft.Util; +import net.minecraft.resources.ResourceLocation; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TranslationAndStringTests { + + @Test + void translationTests() { + assertEquals("pocket.advancedperipherals.chatty_pocket", TranslationUtil.pocket("chatty_pocket")); + + assertEquals("turtle.advancedperipherals.husbandry_automata", TranslationUtil.turtle("husbandry_automata")); + + String descriptionId = Util.makeDescriptionId("item", new ResourceLocation(AdvancedPeripherals.MOD_ID, "peripheral_casing")); + assertEquals("item.advancedperipherals.tooltip.peripheral_casing", TranslationUtil.itemTooltip(descriptionId).getString()); + } + +} diff --git a/src/testFixtures/java/dan200/computercraft/test/core/ArbitraryByteBuffer.java b/src/testFixtures/java/dan200/computercraft/test/core/ArbitraryByteBuffer.java new file mode 100644 index 000000000..8efdc74c1 --- /dev/null +++ b/src/testFixtures/java/dan200/computercraft/test/core/ArbitraryByteBuffer.java @@ -0,0 +1,174 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core; + +import net.jqwik.api.Arbitraries; +import net.jqwik.api.Arbitrary; +import net.jqwik.api.EdgeCases; +import net.jqwik.api.RandomDistribution; +import net.jqwik.api.RandomGenerator; +import net.jqwik.api.Shrinkable; +import net.jqwik.api.ShrinkingDistance; +import net.jqwik.api.arbitraries.SizableArbitrary; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.function.ToIntFunction; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Generate arbitrary byte buffers with irrelevant (but random) contents. + *

+ * This is more efficient than using {@link Arbitraries#bytes()} and {@link Arbitrary#array(Class)}, as it does not + * try to shrink the contents, only the size. + */ +public final class ArbitraryByteBuffer implements SizableArbitrary { + private static final ArbitraryByteBuffer DEFAULT = new ArbitraryByteBuffer(0, null, null); + private final @Nullable Integer maxSize; + private final @Nullable RandomDistribution distribution; + private int minSize = 0; + + private ArbitraryByteBuffer(int minSize, @Nullable Integer maxSize, @Nullable RandomDistribution distribution) { + this.minSize = minSize; + this.maxSize = maxSize; + this.distribution = distribution; + } + + public static ArbitraryByteBuffer bytes() { + return DEFAULT; + } + + private static ToIntFunction sizeGeneratorWithCutoff(int minSize, int maxSize, int genSize) { + // If we've a large range, we either pick between generating small (<10) or large lists. + int range = maxSize - minSize; + int offset = (int) Math.max(Math.round(Math.sqrt(genSize)), 10); + int cutoff = range <= offset ? maxSize : Math.min(offset + minSize, maxSize); + + if (cutoff >= maxSize) return random -> nextInt(random, minSize, maxSize); + + // Choose size below cutoff with probability of 0.1. + double maxSizeProbability = Math.min(0.02, 1.0 / (genSize / 10.0)); + double cutoffProbability = 0.1; + return random -> { + if (random.nextDouble() <= maxSizeProbability) { + return maxSize; + } else if (random.nextDouble() <= cutoffProbability + maxSizeProbability) { + return nextInt(random, cutoff + 1, maxSize); + } else { + return nextInt(random, minSize, cutoff); + } + }; + } + + private static int nextInt(Random random, int minSize, int maxSize) { + return random.nextInt(maxSize - minSize + 1) + minSize; + } + + private static ByteBuffer allocateRandom(int size, Random random) { + ByteBuffer buffer = ByteBuffer.allocate(size); + + for (int i = 0; i < size; i++) buffer.put(i, (byte) random.nextInt()); + return buffer.asReadOnlyBuffer(); + } + + @Nonnull + @Override + public SizableArbitrary ofMinSize(int minSize) { + return new ArbitraryByteBuffer(minSize, maxSize, distribution); + } + + @Nonnull + @Override + public SizableArbitrary ofMaxSize(int maxSize) { + return new ArbitraryByteBuffer(minSize, maxSize, distribution); + } + + @Nonnull + @Override + public SizableArbitrary withSizeDistribution(@Nonnull RandomDistribution distribution) { + return new ArbitraryByteBuffer(minSize, maxSize, distribution); + } + + @Nonnull + @Override + public RandomGenerator generator(int genSize) { + BigInteger min = BigInteger.valueOf(minSize); + ToIntFunction generator; + if (distribution == null) { + generator = sizeGeneratorWithCutoff(minSize, getMaxSize(), genSize); + } else { + RandomDistribution.RandomNumericGenerator gen = distribution.createGenerator(genSize, min, BigInteger.valueOf(getMaxSize()), min); + generator = r -> gen.next(r).intValueExact(); + } + return r -> { + int size = generator.applyAsInt(r); + return new ShrinkableBuffer(allocateRandom(size, r), minSize); + }; + } + + @Nonnull + @Override + public EdgeCases edgeCases(int maxEdgeCases) { + return EdgeCases.fromSuppliers(Arrays.asList( + () -> new ShrinkableBuffer(allocateRandom(minSize, new Random()), minSize), + () -> new ShrinkableBuffer(allocateRandom(getMaxSize(), new Random()), minSize) + )); + } + + private int getMaxSize() { + return maxSize == null ? Math.max(minSize * 2, 255) : maxSize; + } + + private static final class ShrinkableBuffer implements Shrinkable { + private final ByteBuffer value; + private final int minSize; + + private ShrinkableBuffer(ByteBuffer value, int minSize) { + this.value = value; + this.minSize = minSize; + } + + @Nonnull + @Override + public ByteBuffer value() { + return value; + } + + @Nonnull + @Override + public Stream> shrink() { + return StreamSupport.stream(new Spliterators.AbstractSpliterator>(3, 0) { + int size = value.remaining(); + + @Override + public boolean tryAdvance(Consumer> action) { + if (size <= minSize) return false; + + int half = (size / 2) - (minSize / 2); + size = half == 0 ? minSize : size - half; + + ByteBuffer slice = value.duplicate(); + slice.limit(size); + action.accept(new ShrinkableBuffer(slice.slice(), minSize)); + return true; + } + }, false); + } + + @Nonnull + @Override + public ShrinkingDistance distance() { + return ShrinkingDistance.of(value.remaining() - minSize); + } + } +} diff --git a/src/testFixtures/java/dan200/computercraft/test/core/ByteBufferMatcher.java b/src/testFixtures/java/dan200/computercraft/test/core/ByteBufferMatcher.java new file mode 100644 index 000000000..46e27235b --- /dev/null +++ b/src/testFixtures/java/dan200/computercraft/test/core/ByteBufferMatcher.java @@ -0,0 +1,68 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import java.nio.ByteBuffer; + +public final class ByteBufferMatcher extends TypeSafeMatcher { + private final ByteBuffer expected; + + private ByteBufferMatcher(ByteBuffer expected) { + this.expected = expected; + } + + public static Matcher bufferEqual(ByteBuffer buffer) { + return new ByteBufferMatcher(buffer); + } + + @Override + protected boolean matchesSafely(ByteBuffer actual) { + return expected.equals(actual); + } + + @Override + public void describeTo(Description description) { + description.appendValue(expected); + } + + @Override + protected void describeMismatchSafely(ByteBuffer actual, Description mismatchDescription) { + if (expected.remaining() != actual.remaining()) { + mismatchDescription + .appendValue(actual).appendText(" has ").appendValue(actual.remaining()).appendText(" bytes remaining"); + return; + } + + int remaining = expected.remaining(); + int expectedPos = expected.position(); + int actualPos = actual.position(); + for (int i = 0; i < remaining; i++) { + if (expected.get(expectedPos + i) == actual.get(actualPos + i)) + continue; + + int offset = Math.max(i - 5, 0); + int length = Math.min(i + 5, remaining - 1) - offset + 1; + + byte[] expectedBytes = new byte[length]; + expected.duplicate().position(expectedPos + offset); + expected.get(expectedBytes); + + byte[] actualBytes = new byte[length]; + actual.duplicate().position(actualPos + offset); + actual.get(actualBytes); + + mismatchDescription + .appendText("failed at ").appendValue(i).appendText(System.lineSeparator()) + .appendText("expected ").appendValue(expectedBytes).appendText(System.lineSeparator()) + .appendText("was ").appendValue(actual); + return; + } + } +} diff --git a/src/testFixtures/java/dan200/computercraft/test/core/CallCounter.java b/src/testFixtures/java/dan200/computercraft/test/core/CallCounter.java new file mode 100644 index 000000000..e828eba6d --- /dev/null +++ b/src/testFixtures/java/dan200/computercraft/test/core/CallCounter.java @@ -0,0 +1,29 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CallCounter implements Runnable { + private int timesCalled = 0; + + @Override + public void run() { + timesCalled++; + } + + public void assertCalledTimes(int expectedTimesCalled) { + assertEquals(expectedTimesCalled, timesCalled, "Callback was not called the correct number of times"); + } + + public void assertNotCalled() { + assertEquals(0, timesCalled, "Should never have been called."); + } + + public void reset() { + this.timesCalled = 0; + } +} diff --git a/src/testFixtures/java/dan200/computercraft/test/core/ContramapMatcher.java b/src/testFixtures/java/dan200/computercraft/test/core/ContramapMatcher.java new file mode 100644 index 000000000..50bc7fd4e --- /dev/null +++ b/src/testFixtures/java/dan200/computercraft/test/core/ContramapMatcher.java @@ -0,0 +1,40 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core; + +import org.hamcrest.FeatureMatcher; +import org.hamcrest.Matcher; + +import java.util.function.Function; + +/** + * Given some function from {@code T} to {@code U}, converts a {@code Matcher} to {@code Matcher}. This is useful + * when you want to match on a particular field (or some other projection) as part of a larger matcher. + * + * @param The type of the object to be matched. + * @param The type of the projection/field to be matched. + */ +public final class ContramapMatcher extends FeatureMatcher { + private final Function convert; + + public ContramapMatcher(String desc, Function convert, Matcher matcher) { + super(matcher, desc, desc); + this.convert = convert; + } + + public static Matcher contramap(Matcher matcher, String desc, Function convert) { + return new ContramapMatcher<>(desc, convert, matcher); + } + + public static Matcher contramap(Matcher matcher, Function convert) { + return new ContramapMatcher<>("-f(_)->", convert, matcher); + } + + @Override + protected U featureValueOf(T actual) { + return convert.apply(actual); + } +} diff --git a/src/testFixtures/java/dan200/computercraft/test/core/CustomMatchers.java b/src/testFixtures/java/dan200/computercraft/test/core/CustomMatchers.java new file mode 100644 index 000000000..ced9d73ac --- /dev/null +++ b/src/testFixtures/java/dan200/computercraft/test/core/CustomMatchers.java @@ -0,0 +1,29 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core; + +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.function.Function; + +import static org.hamcrest.Matchers.contains; + +public class CustomMatchers { + /** + * Assert two lists are equal according to some matcher. + *

+ * This method is simple, but helps avoid some issues with generics we'd see otherwise. + * + * @param items The items the matched list should be equal to. + * @param matcher Generate a matcher for a single item in the list. + * @param The type to compare against. + * @return A matcher which compares against a list of items. + */ + public static Matcher> containsWith(List items, Function> matcher) { + return contains(items.stream().map(matcher).toList()); + } +} diff --git a/src/testFixtures/java/dan200/computercraft/test/core/apis/BasicApiEnvironment.java b/src/testFixtures/java/dan200/computercraft/test/core/apis/BasicApiEnvironment.java new file mode 100644 index 000000000..349cb83ad --- /dev/null +++ b/src/testFixtures/java/dan200/computercraft/test/core/apis/BasicApiEnvironment.java @@ -0,0 +1,137 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core.apis; + +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IWorkMonitor; +import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.computer.ComputerEnvironment; +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.core.computer.GlobalEnvironment; +import dan200.computercraft.core.filesystem.FileSystem; +import dan200.computercraft.core.metrics.Metric; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.test.core.computer.BasicEnvironment; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BasicApiEnvironment implements IAPIEnvironment { + private final BasicEnvironment environment; + private @Nullable String label; + + public BasicApiEnvironment(BasicEnvironment environment) { + this.environment = environment; + } + + @Override + public int getComputerID() { + return 0; + } + + @Nonnull + @Override + public ComputerEnvironment getComputerEnvironment() { + return environment; + } + + @Nonnull + @Override + public GlobalEnvironment getGlobalEnvironment() { + return environment; + } + + @Nonnull + @Override + public IWorkMonitor getMainThreadMonitor() { + throw new IllegalStateException("Main thread monitor not available"); + } + + @Nonnull + @Override + public Terminal getTerminal() { + throw new IllegalStateException("Terminal not available"); + } + + @Override + public FileSystem getFileSystem() { + throw new IllegalStateException("Filesystem not available"); + } + + @Override + public void shutdown() { + } + + @Override + public void reboot() { + } + + @Override + public void setOutput(ComputerSide side, int output) { + } + + @Override + public int getOutput(ComputerSide side) { + return 0; + } + + @Override + public int getInput(ComputerSide side) { + return 0; + } + + @Override + public void setBundledOutput(ComputerSide side, int output) { + } + + @Override + public int getBundledOutput(ComputerSide side) { + return 0; + } + + @Override + public int getBundledInput(ComputerSide side) { + return 0; + } + + @Override + public void setPeripheralChangeListener(@Nullable IPeripheralChangeListener listener) { + } + + @Nullable + @Override + public IPeripheral getPeripheral(ComputerSide side) { + return null; + } + + @Nullable + @Override + public String getLabel() { + return label; + } + + @Override + public void setLabel(@Nullable String label) { + this.label = label; + } + + @Override + public int startTimer(long ticks) { + throw new IllegalStateException("Cannot start timers"); + } + + @Override + public void cancelTimer(int id) { + } + + @Override + public void observe(@Nonnull Metric.Event summary, long value) { + } + + @Override + public void observe(@Nonnull Metric.Counter counter) { + } +} diff --git a/src/testFixtures/java/dan200/computercraft/test/core/computer/BasicEnvironment.java b/src/testFixtures/java/dan200/computercraft/test/core/computer/BasicEnvironment.java new file mode 100644 index 000000000..f763238eb --- /dev/null +++ b/src/testFixtures/java/dan200/computercraft/test/core/computer/BasicEnvironment.java @@ -0,0 +1,139 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core.computer; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.core.computer.ComputerEnvironment; +import dan200.computercraft.core.computer.GlobalEnvironment; +import dan200.computercraft.core.filesystem.FileMount; +import dan200.computercraft.core.filesystem.JarMount; +import dan200.computercraft.core.metrics.Metric; +import dan200.computercraft.core.metrics.MetricsObserver; +import dan200.computercraft.test.core.filesystem.MemoryMount; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; + +/** + * A basic implementation of {@link ComputerEnvironment} and {@link GlobalEnvironment}, suitable for a context which + * will only run a single computer. + */ +public class BasicEnvironment implements ComputerEnvironment, GlobalEnvironment, MetricsObserver { + private final IWritableMount mount; + + public BasicEnvironment() { + this(new MemoryMount()); + } + + public BasicEnvironment(IWritableMount mount) { + this.mount = mount; + } + + public static IMount createMount(Class klass, String path, String fallback) { + File file = getContainingFile(klass); + + if (file.isFile()) { + try { + return new JarMount(file, path); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } else { + File wholeFile = new File(file, path); + + // If we don't exist, walk up the tree looking for resource folders + File baseFile = file; + while (baseFile != null && !wholeFile.exists()) { + baseFile = baseFile.getParentFile(); + wholeFile = new File(baseFile, "src/" + fallback + "/resources/" + path); + } + + if (!wholeFile.exists()) throw new IllegalStateException("Cannot find ROM mount at " + file); + + return new FileMount(wholeFile, 0); + } + } + + private static File getContainingFile(Class klass) { + String path = klass.getProtectionDomain().getCodeSource().getLocation().getPath(); + int bangIndex = path.indexOf("!"); + + // Plain old file, so step up from dan200.computercraft. + if (bangIndex < 0) return new File(path); + + path = path.substring(0, bangIndex); + URL url; + try { + url = new URL(path); + } catch (MalformedURLException e) { + throw new IllegalStateException(e); + } + + try { + return new File(url.toURI()); + } catch (URISyntaxException e) { + return new File(url.getPath()); + } + } + + @Override + public IWritableMount createRootMount() { + return mount; + } + + @Override + public int getDay() { + return 0; + } + + @Override + public double getTimeOfDay() { + return 0; + } + + @Override + public MetricsObserver getMetrics() { + return this; + } + + @Nonnull + @Override + public String getHostString() { + return "ComputerCraft 1.0 (Test environment)"; + } + + @Nonnull + @Override + public String getUserAgent() { + return "ComputerCraft/1.0"; + } + + @Override + public IMount createResourceMount(String domain, String subPath) { + return createMount(ComputerCraft.class, "data/" + domain + "/" + subPath, "main"); + } + + @Override + public InputStream createResourceFile(String domain, String subPath) { + return ComputerCraft.class.getClassLoader().getResourceAsStream("data/" + domain + "/" + subPath); + } + + @Override + public void observe(Metric.Counter counter) { + } + + @Override + public void observe(Metric.Event event, long value) { + } +} diff --git a/src/testFixtures/java/dan200/computercraft/test/core/filesystem/MemoryMount.java b/src/testFixtures/java/dan200/computercraft/test/core/filesystem/MemoryMount.java new file mode 100644 index 000000000..29863cf5e --- /dev/null +++ b/src/testFixtures/java/dan200/computercraft/test/core/filesystem/MemoryMount.java @@ -0,0 +1,130 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core.filesystem; + +import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.core.apis.handles.ArrayByteChannel; + +import javax.annotation.Nonnull; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * In-memory file mounts. + */ +public class MemoryMount implements IWritableMount { + private final Map files = new HashMap<>(); + private final Set directories = new HashSet<>(); + + public MemoryMount() { + directories.add(""); + } + + + @Override + public void makeDirectory(@Nonnull String path) { + File file = new File(path); + while (file != null) { + directories.add(file.getPath()); + file = file.getParentFile(); + } + } + + @Override + public void delete(@Nonnull String path) { + if (files.containsKey(path)) { + files.remove(path); + } else { + directories.remove(path); + for (String file : files.keySet().toArray(new String[0])) { + if (file.startsWith(path)) { + files.remove(file); + } + } + + File parent = new File(path).getParentFile(); + if (parent != null) + delete(parent.getPath()); + } + } + + @Nonnull + @Override + public WritableByteChannel openForWrite(@Nonnull final String path) { + return Channels.newChannel(new ByteArrayOutputStream() { + @Override + public void close() throws IOException { + super.close(); + files.put(path, toByteArray()); + } + }); + } + + @Nonnull + @Override + public WritableByteChannel openForAppend(@Nonnull final String path) throws IOException { + ByteArrayOutputStream stream = new ByteArrayOutputStream() { + @Override + public void close() throws IOException { + super.close(); + files.put(path, toByteArray()); + } + }; + + byte[] current = files.get(path); + if (current != null) + stream.write(current); + + return Channels.newChannel(stream); + } + + @Override + public long getRemainingSpace() { + return 1000000L; + } + + @Override + public boolean exists(@Nonnull String path) { + return files.containsKey(path) || directories.contains(path); + } + + @Override + public boolean isDirectory(@Nonnull String path) { + return directories.contains(path); + } + + @Override + public void list(@Nonnull String path, @Nonnull List files) { + for (String file : this.files.keySet()) { + if (file.startsWith(path)) files.add(file.substring(path.length() + 1)); + } + } + + @Override + public long getSize(@Nonnull String path) { + throw new RuntimeException("Not implemented"); + } + + @Nonnull + @Override + public ReadableByteChannel openForRead(@Nonnull String path) { + return new ArrayByteChannel(files.get(path)); + } + + public MemoryMount addFile(String file, String contents) { + files.put(file, contents.getBytes()); + return this; + } +} diff --git a/src/testFixtures/java/dan200/computercraft/test/core/terminal/TerminalMatchers.java b/src/testFixtures/java/dan200/computercraft/test/core/terminal/TerminalMatchers.java new file mode 100644 index 000000000..80520f2f9 --- /dev/null +++ b/src/testFixtures/java/dan200/computercraft/test/core/terminal/TerminalMatchers.java @@ -0,0 +1,47 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core.terminal; + +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.core.terminal.TextBuffer; +import dan200.computercraft.test.core.ContramapMatcher; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +import java.util.Arrays; + +public class TerminalMatchers { + public static Matcher textColourMatches(String[] x) { + return linesMatch("text colour", Terminal::getTextColourLine, x); + } + + public static Matcher backgroundColourMatches(String[] x) { + return linesMatch("background colour", Terminal::getBackgroundColourLine, x); + } + + public static Matcher textMatches(String[] x) { + return linesMatch("text", Terminal::getLine, x); + } + + @SuppressWarnings("unchecked") + public static Matcher linesMatch(String kind, LineProvider getLine, String[] lines) { + return linesMatchWith(kind, getLine, Arrays.stream(lines).map(Matchers::equalTo).toArray(Matcher[]::new)); + } + + public static Matcher linesMatchWith(String kind, LineProvider getLine, Matcher[] lines) { + return ContramapMatcher.contramap(Matchers.array(lines), kind, terminal -> { + String[] termLines = new String[terminal.getHeight()]; + for (int i = 0; i < termLines.length; i++) termLines[i] = getLine.getLine(terminal, i).toString(); + return termLines; + }); + } + + @FunctionalInterface + public interface LineProvider { + TextBuffer getLine(Terminal terminal, int line); + } + +} diff --git a/src/testFixtures/kotlin/dan200/computercraft/test/core/Assertions.kt b/src/testFixtures/kotlin/dan200/computercraft/test/core/Assertions.kt new file mode 100644 index 000000000..e83e80381 --- /dev/null +++ b/src/testFixtures/kotlin/dan200/computercraft/test/core/Assertions.kt @@ -0,0 +1,63 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core + +import org.hamcrest.BaseMatcher +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.collection.IsArray +import org.junit.jupiter.api.Assertions + +/** Postfix version of [Assertions.assertArrayEquals] */ +fun Array?.assertArrayEquals(vararg expected: Any?, message: String? = null) { + assertThat( + message ?: "", + this, + IsArrayVerbose(expected.map { FuzzyEqualTo(it) }.toTypedArray()), + ) +} + +/** + * Extension of [IsArray] which always prints the array, not just when the items are mismatched. + */ +internal class IsArrayVerbose(private val elementMatchers: Array>) : IsArray(elementMatchers) { + override fun describeMismatchSafely(actual: Array, description: Description) { + description.appendText("array was ").appendValue(actual) + if (actual.size != elementMatchers.size) { + description.appendText(" with length ").appendValue(actual.size) + return + } + + for (i in actual.indices) { + if (!elementMatchers[i].matches(actual[i])) { + description.appendText("with element ").appendValue(i).appendText(" ") + elementMatchers[i].describeMismatch(actual[i], description) + return + } + } + } +} + +/** + * An equality matcher which is slightly more relaxed on comparing some values. + */ +internal class FuzzyEqualTo(private val expected: Any?) : BaseMatcher() { + override fun describeTo(description: Description) { + description.appendValue(expected) + } + + override fun matches(actual: Any?): Boolean { + if (actual == null) return false + + if (actual is Number && expected is Number && actual.javaClass != expected.javaClass) { + // Allow equating integers and floats. + return actual.toDouble() == expected.toDouble() + } + + return actual == expected + } +} diff --git a/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/KotlinComputerManager.kt b/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/KotlinComputerManager.kt new file mode 100644 index 000000000..f263eed3c --- /dev/null +++ b/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/KotlinComputerManager.kt @@ -0,0 +1,190 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.test.core.computer + +import dan200.computercraft.api.lua.ILuaAPI +import dan200.computercraft.core.ComputerContext +import dan200.computercraft.core.computer.Computer +import dan200.computercraft.core.computer.ComputerThread +import dan200.computercraft.core.computer.TimeoutState +import dan200.computercraft.core.computer.mainthread.NoWorkMainThreadScheduler +import dan200.computercraft.core.lua.MachineEnvironment +import dan200.computercraft.core.lua.MachineResult +import dan200.computercraft.core.terminal.Terminal +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.Lock +import java.util.concurrent.locks.ReentrantLock + +typealias FakeComputerTask = (state: TimeoutState) -> MachineResult + +/** + * Creates "fake" computers, which just run user-defined tasks rather than Lua code. + */ +class KotlinComputerManager : AutoCloseable { + + private val machines: MutableMap> = HashMap() + private val context = + ComputerContext(BasicEnvironment(), ComputerThread(1), NoWorkMainThreadScheduler()) { DummyLuaMachine(it) } + private val errorLock: Lock = ReentrantLock() + private val hasError = errorLock.newCondition() + + @Volatile + private var error: Throwable? = null + override fun close() { + try { + context.ensureClosed(1, TimeUnit.SECONDS) + } catch (e: InterruptedException) { + throw IllegalStateException("Runtime thread was interrupted", e) + } + } + + fun context(): ComputerContext { + return context + } + + /** + * Create a new computer which pulls from our task queue. + * + * @return The computer. This will not be started yet, you must call [Computer.turnOn] and + * [Computer.tick] to do so. + */ + fun create(): Computer { + val queue: Queue = ConcurrentLinkedQueue() + val computer = Computer(context, BasicEnvironment(), Terminal(51, 19, true), 0) + computer.addApi(QueuePassingAPI(queue)) // Inject an extra API to pass the queue to the machine. + machines[computer] = queue + return computer + } + + /** + * Create and start a new computer which loops forever. + */ + fun createLoopingComputer() { + val computer = create() + enqueueForever(computer) { + Thread.sleep(100) + MachineResult.OK + } + computer.turnOn() + computer.tick() + } + + /** + * Enqueue a task on a computer. + * + * @param computer The computer to enqueue the work on. + * @param task The task to run. + */ + fun enqueue(computer: Computer, task: FakeComputerTask) { + machines[computer]!!.offer(task) + } + + /** + * Enqueue a repeated task on a computer. This is automatically requeued when the task finishes, meaning the task + * queue is never empty. + * + * @param computer The computer to enqueue the work on. + * @param task The task to run. + */ + private fun enqueueForever(computer: Computer, task: FakeComputerTask) { + machines[computer]!!.offer { + val result = task(it) + enqueueForever(computer, task) + computer.queueEvent("some_event", null) + result + } + } + + /** + * Sleep for a given period, immediately propagating any exceptions thrown by a computer. + * + * @param delay The duration to sleep for. + * @param unit The time unit the duration is measured in. + * @throws Exception An exception thrown by a running computer. + */ + @Throws(Exception::class) + fun sleep(delay: Long, unit: TimeUnit?) { + errorLock.lock() + try { + rethrowIfNeeded() + if (hasError.await(delay, unit)) rethrowIfNeeded() + } finally { + errorLock.unlock() + } + } + + /** + * Start a computer and wait for it to finish. + * + * @param computer The computer to wait for. + * @throws Exception An exception thrown by a running computer. + */ + @Throws(Exception::class) + fun startAndWait(computer: Computer) { + computer.turnOn() + computer.tick() + do { + sleep(100, TimeUnit.MILLISECONDS) + } while (context.computerScheduler().hasPendingWork() || computer.isOn) + + rethrowIfNeeded() + } + + @Throws(Exception::class) + private fun rethrowIfNeeded() { + val error = error ?: return + throw error + } + + private class QueuePassingAPI constructor(val tasks: Queue) : ILuaAPI { + override fun getNames(): Array = arrayOf() + } + + private inner class DummyLuaMachine(private val environment: MachineEnvironment) : KotlinLuaMachine(environment) { + private var tasks: Queue? = null + override fun addAPI(api: ILuaAPI) { + super.addAPI(api) + if (api is QueuePassingAPI) tasks = api.tasks + } + + override fun getTask(): (suspend KotlinLuaMachine.() -> Unit)? { + try { + val tasks = this.tasks ?: throw NullPointerException("Not received tasks yet") + val task = tasks.remove() + return { + try { + task(environment.timeout) + } catch (e: Throwable) { + reportError(e) + } + } + } catch (e: Throwable) { + reportError(e) + return null + } + } + + override fun close() {} + + private fun reportError(e: Throwable) { + errorLock.lock() + try { + if (error == null) { + error = e + hasError.signal() + } else { + error!!.addSuppressed(e) + } + } finally { + errorLock.unlock() + } + + if (e is Exception || e is AssertionError) return else throw e + } + } +} diff --git a/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/KotlinLuaMachine.kt b/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/KotlinLuaMachine.kt new file mode 100644 index 000000000..3e94dea31 --- /dev/null +++ b/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/KotlinLuaMachine.kt @@ -0,0 +1,55 @@ +package dan200.computercraft.test.core.computer + +import dan200.computercraft.api.lua.ILuaAPI +import dan200.computercraft.api.lua.ILuaContext +import dan200.computercraft.core.lua.ILuaMachine +import dan200.computercraft.core.lua.MachineEnvironment +import dan200.computercraft.core.lua.MachineResult +import kotlinx.coroutines.* +import java.io.InputStream +import kotlin.coroutines.CoroutineContext + +/** + * An [ILuaMachine] which runs Kotlin functions instead. + */ +abstract class KotlinLuaMachine(environment: MachineEnvironment) : ILuaMachine, AbstractLuaTaskContext() { + override val context: ILuaContext = environment.context + + override fun addAPI(api: ILuaAPI) = addApi(api) + + override fun loadBios(bios: InputStream): MachineResult = MachineResult.OK + + override fun handleEvent(eventName: String?, arguments: Array?): MachineResult { + if (hasEventListeners) { + queueEvent(eventName, arguments) + } else { + val task = getTask() + if (task != null) CoroutineScope(NeverDispatcher() + CoroutineName("Computer")).launch { task() } + } + + return MachineResult.OK + } + + override fun printExecutionState(out: StringBuilder) {} + + /** + * Get the next task to execute on this computer. + */ + protected abstract fun getTask(): (suspend KotlinLuaMachine.() -> Unit)? + + /** + * A [CoroutineDispatcher] which only allows resuming from the computer thread. In practice, this means the only + * way to yield is with [pullEvent]. + */ + private class NeverDispatcher : CoroutineDispatcher() { + private val expectedGroup = Thread.currentThread().threadGroup + + override fun dispatch(context: CoroutineContext, block: Runnable) { + if (Thread.currentThread().threadGroup != expectedGroup) { + throw UnsupportedOperationException("Cannot perform arbitrary yields") + } + + block.run() + } + } +} diff --git a/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/LuaTaskContext.kt b/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/LuaTaskContext.kt new file mode 100644 index 000000000..066045b87 --- /dev/null +++ b/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/LuaTaskContext.kt @@ -0,0 +1,102 @@ +package dan200.computercraft.test.core.computer + +import dan200.computercraft.api.lua.ILuaAPI +import dan200.computercraft.api.lua.ILuaContext +import dan200.computercraft.api.lua.MethodResult +import dan200.computercraft.api.lua.ObjectArguments +import dan200.computercraft.core.apis.OSAPI +import dan200.computercraft.core.apis.PeripheralAPI +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.time.Duration + +/** + * The context for tasks which consume Lua objects. + * + * This provides helpers for converting CC's callback-based code into a more direct style based on Kotlin coroutines. + */ +interface LuaTaskContext { + /** The current Lua context, to be passed to method calls. */ + val context: ILuaContext + + /** Get a registered API. */ + fun getApi(api: Class): T + + /** Pull a Lua event */ + suspend fun pullEvent(event: String? = null): Array + + /** Resolve a [MethodResult] until completion, returning the resulting values. */ + suspend fun MethodResult.await(): Array? { + var result = this + while (true) { + val callback = result.callback + val values = result.result + + if (callback == null) return values + + val filter = if (values == null) null else values[0] as String? + result = callback.resume(pullEvent(filter)) + } + } + + /** Call a peripheral method. */ + suspend fun LuaTaskContext.callPeripheral(name: String, method: String, vararg args: Any?): Array? = + getApi().call(context, ObjectArguments(name, method, *args)).await() + + /** + * Sleep for the given duration. This uses the internal computer clock, so won't be accurate. + */ + suspend fun LuaTaskContext.sleep(duration: Duration) { + val timer = getApi().startTimer(duration.inWholeMilliseconds / 1000.0) + while (true) { + val event = pullEvent("timer") + if (event[0] == "timer" && event[1] is Number && (event[1] as Number).toInt() == timer) { + return + } + } + } +} + +/** Get a registered API. */ +inline fun LuaTaskContext.getApi(): T = getApi(T::class.java) + +abstract class AbstractLuaTaskContext : LuaTaskContext, AutoCloseable { + private val pullEvents = mutableListOf() + private val apis = mutableMapOf, ILuaAPI>() + + protected fun addApi(api: ILuaAPI) { + apis[api.javaClass] = api + } + + protected val hasEventListeners + get() = pullEvents.isNotEmpty() + + protected fun queueEvent(eventName: String?, arguments: Array?) { + val fullEvent: Array = when { + eventName == null && arguments == null -> arrayOf() + eventName != null && arguments == null -> arrayOf(eventName) + eventName == null && arguments != null -> arguments + else -> arrayOf(eventName, *arguments!!) + } + for (i in pullEvents.size - 1 downTo 0) { + val puller = pullEvents[i] + if (puller.name == null || puller.name == eventName || eventName == "terminate") { + pullEvents.removeAt(i) + puller.cont.resumeWith(Result.success(fullEvent)) + } + } + } + + override fun close() { + for (pullEvent in pullEvents) pullEvent.cont.cancel() + pullEvents.clear() + } + + final override fun getApi(api: Class): T = + api.cast(apis[api] ?: throw IllegalStateException("No API of type ${api.name}")) + + final override suspend fun pullEvent(event: String?): Array = + suspendCancellableCoroutine { cont -> pullEvents.add(PullEvent(event, cont)) } + + private class PullEvent(val name: String?, val cont: CancellableContinuation>) +} diff --git a/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/LuaTaskRunner.kt b/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/LuaTaskRunner.kt new file mode 100644 index 000000000..4ac65d1cb --- /dev/null +++ b/src/testFixtures/kotlin/dan200/computercraft/test/core/computer/LuaTaskRunner.kt @@ -0,0 +1,64 @@ +package dan200.computercraft.test.core.computer + +import dan200.computercraft.api.lua.ILuaAPI +import dan200.computercraft.api.lua.ILuaContext +import dan200.computercraft.api.lua.LuaException +import dan200.computercraft.core.apis.IAPIEnvironment +import dan200.computercraft.test.core.apis.BasicApiEnvironment +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +class LuaTaskRunner : AbstractLuaTaskContext() { + private val eventStream: Channel = Channel(Channel.UNLIMITED) + private val apis = mutableListOf() + + val environment: IAPIEnvironment = object : BasicApiEnvironment(BasicEnvironment()) { + override fun queueEvent(event: String?, vararg args: Any?) { + if (eventStream.trySend(Event(event, args)).isFailure) { + throw IllegalStateException("Queue is full") + } + } + + override fun shutdown() { + super.shutdown() + eventStream.close() + } + } + override val context = + ILuaContext { throw LuaException("Cannot queue main thread task") } + + fun addApi(api: T): T { + super.addApi(api) + apis.add(api) + api.startup() + return api + } + + override fun close() { + environment.shutdown() + } + + private suspend fun run() { + for (event in eventStream) { + queueEvent(event.name, event.args) + } + } + + private class Event(val name: String?, val args: Array) + + companion object { + fun runTest(timeout: Duration = 5.seconds, fn: suspend LuaTaskRunner.() -> Unit) { + runBlocking { + withTimeout(timeout) { + val runner = LuaTaskRunner() + launch { runner.run() } + runner.use { fn(runner) } + } + } + } + } +} diff --git a/src/testMod/java/dan200/computercraft/export/Exporter.java b/src/testMod/java/dan200/computercraft/export/Exporter.java new file mode 100644 index 000000000..ad12f2396 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/export/Exporter.java @@ -0,0 +1,142 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.export; + +import com.google.common.io.MoreFiles; +import com.google.common.io.RecursiveDeleteOption; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import dan200.computercraft.ComputerCraft; +import net.minecraft.client.Minecraft; +import net.minecraft.core.NonNullList; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingRecipe; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.item.crafting.ShapedRecipe; +import net.minecraft.world.item.crafting.ShapelessRecipe; +import net.minecraftforge.registries.ForgeRegistries; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; + +/** + * Provides a {@literal /ccexport } command which exports icons and recipes for all ComputerCraft items. + */ +public class Exporter { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register( + LiteralArgumentBuilder.literal("ccexport") + .then(RequiredArgumentBuilder.argument("path", StringArgumentType.string()) + .executes(c -> { + run(c.getArgument("name", String.class)); + return 0; + }))); + } + + private static void run(String path) { + Path output = new File(path).getAbsoluteFile().toPath(); + if (!Files.isDirectory(output)) { + Minecraft.getInstance().gui.getChat().addMessage(Component.literal("Output path does not exist")); + return; + } + + RenderSystem.assertOnRenderThread(); + try (ImageRenderer renderer = new ImageRenderer()) { + export(output, renderer); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + Minecraft.getInstance().gui.getChat().addMessage(Component.literal("Export finished!")); + } + + private static void export(Path root, ImageRenderer renderer) throws IOException { + JsonDump dump = new JsonDump(); + + Set items = new HashSet<>(); + + // First find all CC items + for (Item item : ForgeRegistries.ITEMS) { + if (ForgeRegistries.ITEMS.getKey(item).getNamespace().equals(ComputerCraft.MOD_ID)) + items.add(item); + } + + // Now find all CC recipes. + for (CraftingRecipe recipe : Minecraft.getInstance().level.getRecipeManager().getAllRecipesFor(RecipeType.CRAFTING)) { + ItemStack result = recipe.getResultItem(); + if (!ForgeRegistries.ITEMS.getKey(result.getItem()).getNamespace().equals(ComputerCraft.MOD_ID)) { + continue; + } + if (result.hasTag()) { + ComputerCraft.log.warn("Skipping recipe {} as it has NBT", recipe.getId()); + continue; + } + + if (recipe instanceof ShapedRecipe shaped) { + JsonDump.Recipe converted = new JsonDump.Recipe(result); + + for (int x = 0; x < shaped.getWidth(); x++) { + for (int y = 0; y < shaped.getHeight(); y++) { + Ingredient ingredient = shaped.getIngredients().get(x + y * shaped.getWidth()); + if (ingredient.isEmpty()) + continue; + + converted.setInput(x + y * 3, ingredient, items); + } + } + + dump.recipes.put(recipe.getId().toString(), converted); + } else if (recipe instanceof ShapelessRecipe shapeless) { + JsonDump.Recipe converted = new JsonDump.Recipe(result); + + NonNullList ingredients = shapeless.getIngredients(); + for (int i = 0; i < ingredients.size(); i++) { + converted.setInput(i, ingredients.get(i), items); + } + + dump.recipes.put(recipe.getId().toString(), converted); + } else { + ComputerCraft.log.info("Don't know how to handle recipe {}", recipe); + } + } + + Path itemDir = root.resolve("items"); + if (Files.exists(itemDir)) MoreFiles.deleteRecursively(itemDir, RecursiveDeleteOption.ALLOW_INSECURE); + + renderer.setupState(); + for (Item item : items) { + ItemStack stack = new ItemStack(item); + ResourceLocation location = ForgeRegistries.ITEMS.getKey(item); + + dump.itemNames.put(location.toString(), stack.getHoverName().getString()); + renderer.captureRender(itemDir.resolve(location.getNamespace()).resolve(location.getPath() + ".png"), + () -> Minecraft.getInstance().getItemRenderer().renderAndDecorateFakeItem(stack, 0, 0) + ); + } + renderer.clearState(); + + try (Writer writer = Files.newBufferedWriter(root.resolve("index.json"))) { + GSON.toJson(dump, writer); + } + } +} diff --git a/src/testMod/java/dan200/computercraft/export/ImageRenderer.java b/src/testMod/java/dan200/computercraft/export/ImageRenderer.java new file mode 100644 index 000000000..a6b6b3d2d --- /dev/null +++ b/src/testMod/java/dan200/computercraft/export/ImageRenderer.java @@ -0,0 +1,74 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.export; + +import com.mojang.blaze3d.pipeline.TextureTarget; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.math.Matrix4f; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.FogRenderer; +import org.lwjgl.opengl.GL12; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Utilities for saving OpenGL output to an image rather than displaying it on the screen. + */ +public class ImageRenderer implements AutoCloseable { + public static final int WIDTH = 64; + public static final int HEIGHT = 64; + + private final TextureTarget framebuffer = new TextureTarget(WIDTH, HEIGHT, true, Minecraft.ON_OSX); + private final NativeImage image = new NativeImage(WIDTH, HEIGHT, Minecraft.ON_OSX); + + private Matrix4f projectionMatrix; + + public ImageRenderer() { + framebuffer.setClearColor(0, 0, 0, 0); + framebuffer.clear(Minecraft.ON_OSX); + } + + public void setupState() { + projectionMatrix = RenderSystem.getProjectionMatrix(); + RenderSystem.setProjectionMatrix(Matrix4f.orthographic(0, 16, 0, 16, 1000, 3000)); + + var transform = RenderSystem.getModelViewStack(); + transform.setIdentity(); + transform.translate(0.0f, 0.0f, -2000.0f); + + FogRenderer.setupNoFog(); + } + + public void clearState() { + RenderSystem.setProjectionMatrix(projectionMatrix); + RenderSystem.getModelViewStack().popPose(); + } + + public void captureRender(Path output, Runnable render) throws IOException { + Files.createDirectories(output.getParent()); + + framebuffer.bindWrite(true); + RenderSystem.clear(GL12.GL_COLOR_BUFFER_BIT | GL12.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX); + render.run(); + framebuffer.unbindWrite(); + + framebuffer.bindRead(); + image.downloadTexture(0, false); + image.flipY(); + framebuffer.unbindRead(); + + image.writeToFile(output); + } + + @Override + public void close() { + image.close(); + framebuffer.destroyBuffers(); + } +} diff --git a/src/testMod/java/dan200/computercraft/export/JsonDump.java b/src/testMod/java/dan200/computercraft/export/JsonDump.java new file mode 100644 index 000000000..863745dc3 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/export/JsonDump.java @@ -0,0 +1,65 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.export; + +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraftforge.registries.ForgeRegistries; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +public class JsonDump { + public Map itemNames = new TreeMap<>(); + public Map recipes = new TreeMap<>(); + + public static class Recipe { + private static final Set canonicalItem = new HashSet<>(Arrays.asList( + Items.GLASS_PANE, Items.STONE, Items.CHEST + )); + public final String[][] inputs = new String[9][]; + public String output; + public int count; + + public Recipe(ItemStack output) { + this.output = ForgeRegistries.ITEMS.getKey(output.getItem()).toString(); + count = output.getCount(); + } + + public void setInput(int pos, Ingredient ingredient, Set trackedItems) { + if (ingredient.isEmpty()) + return; + + ItemStack[] items = ingredient.getItems(); + + // First try to simplify some tags to something easier. + for (ItemStack stack : items) { + Item item = stack.getItem(); + if (!canonicalItem.contains(item)) + continue; + + trackedItems.add(item); + inputs[pos] = new String[]{ForgeRegistries.ITEMS.getKey(item).toString()}; + return; + } + + String[] itemIds = new String[items.length]; + for (int i = 0; i < items.length; i++) { + Item item = items[i].getItem(); + trackedItems.add(item); + itemIds[i] = ForgeRegistries.ITEMS.getKey(item).toString(); + } + Arrays.sort(itemIds); + + inputs[pos] = itemIds; + } + } +} diff --git a/src/testMod/java/dan200/computercraft/gametest/api/ComputerState.java b/src/testMod/java/dan200/computercraft/gametest/api/ComputerState.java new file mode 100644 index 000000000..0e63f8f67 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/gametest/api/ComputerState.java @@ -0,0 +1,44 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.api; + +import dan200.computercraft.gametest.core.TestAPI; +import net.minecraft.gametest.framework.GameTestAssertException; +import net.minecraft.gametest.framework.GameTestSequence; + +import javax.annotation.Nonnull; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Assertion state of a computer. + * + * @see TestAPI For the Lua interface for this. + * @see TestExtensionsKt#thenComputerOk(GameTestSequence, String, String) + */ +public class ComputerState { + public static final String DONE = "DONE"; + + protected static final Map lookup = new ConcurrentHashMap<>(); + + protected final Set markers = new HashSet<>(); + protected String error; + + public static ComputerState get(String label) { + return lookup.get(label); + } + + public boolean isDone(@Nonnull String marker) { + return markers.contains(marker); + } + + public void check(@Nonnull String marker) { + if (!markers.contains(marker)) throw new IllegalStateException("Not yet at " + marker); + if (error != null) throw new GameTestAssertException(error); + } +} diff --git a/src/testMod/java/dan200/computercraft/gametest/api/GameTestHolder.java b/src/testMod/java/dan200/computercraft/gametest/api/GameTestHolder.java new file mode 100644 index 000000000..e0cc3a6a4 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/gametest/api/GameTestHolder.java @@ -0,0 +1,23 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.api; + +import net.minecraft.gametest.framework.GameTest; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks classes containing {@linkplain GameTest game tests}. + *

+ * This is used by Forge to automatically load and test classes. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface GameTestHolder { +} diff --git a/src/testMod/java/dan200/computercraft/gametest/core/CCTestCommand.java b/src/testMod/java/dan200/computercraft/gametest/core/CCTestCommand.java new file mode 100644 index 000000000..b8d961bf3 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/gametest/core/CCTestCommand.java @@ -0,0 +1,117 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.core; + +import com.mojang.brigadier.CommandDispatcher; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.mixin.gametest.TestCommandAccessor; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTestRegistry; +import net.minecraft.gametest.framework.StructureUtils; +import net.minecraft.gametest.framework.TestFunction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.level.block.entity.StructureBlockEntity; +import net.minecraft.world.level.storage.LevelResource; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; + +import static dan200.computercraft.shared.command.builder.HelpingArgumentBuilder.choice; +import static net.minecraft.commands.Commands.literal; + +/** + * Helper commands for importing/exporting the computer directory. + */ +class CCTestCommand { + public static final LevelResource LOCATION = new LevelResource(ComputerCraft.MOD_ID); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(choice("cctest") + .then(literal("import").executes(context -> { + importFiles(context.getSource().getServer()); + return 0; + })) + .then(literal("export").executes(context -> { + exportFiles(context.getSource().getServer()); + + for (TestFunction function : GameTestRegistry.getAllTestFunctions()) { + TestCommandAccessor.callExportTestStructure(context.getSource(), function.getStructureName()); + } + return 0; + })) + .then(literal("regen-structures").executes(context -> { + for (TestFunction function : GameTestRegistry.getAllTestFunctions()) { + dispatcher.execute("test import " + function.getTestName(), context.getSource()); + TestCommandAccessor.callExportTestStructure(context.getSource(), function.getStructureName()); + } + return 0; + })) + + .then(literal("marker").executes(context -> { + ServerPlayer player = context.getSource().getPlayerOrException(); + BlockPos pos = StructureUtils.findNearestStructureBlock(player.blockPosition(), 15, player.getLevel()); + if (pos == null) return error(context.getSource(), "No nearby test"); + + StructureBlockEntity structureBlock = (StructureBlockEntity) player.getLevel().getBlockEntity(pos); + TestFunction info = GameTestRegistry.getTestFunction(structureBlock.getStructurePath()); + + // Kill the existing armor stand + player + .getLevel().getEntities(EntityType.ARMOR_STAND, x -> x.isAlive() && x.getName().getString().equals(info.getTestName())) + .forEach(Entity::kill); + + // And create a new one + CompoundTag nbt = new CompoundTag(); + nbt.putBoolean("Marker", true); + nbt.putBoolean("Invisible", true); + ArmorStand armorStand = EntityType.ARMOR_STAND.create(player.getLevel()); + armorStand.readAdditionalSaveData(nbt); + armorStand.copyPosition(player); + armorStand.setCustomName(Component.literal(info.getTestName())); + player.getLevel().addFreshEntity(armorStand); + return 0; + })) + ); + } + + public static void importFiles(MinecraftServer server) { + try { + Copier.replicate(getSourceComputerPath(), getWorldComputerPath(server)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + static void exportFiles(MinecraftServer server) { + try { + Copier.replicate(getWorldComputerPath(server), getSourceComputerPath()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static Path getWorldComputerPath(MinecraftServer server) { + return server.getWorldPath(LOCATION).resolve("computer").resolve("0"); + } + + private static Path getSourceComputerPath() { + return TestHooks.getSourceDir().resolve("computer"); + } + + private static int error(CommandSourceStack source, String message) { + source.sendFailure(Component.literal(message).withStyle(ChatFormatting.RED)); + return 0; + } +} diff --git a/src/testMod/java/dan200/computercraft/gametest/core/ClientTestEvents.java b/src/testMod/java/dan200/computercraft/gametest/core/ClientTestEvents.java new file mode 100644 index 000000000..9956894b9 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/gametest/core/ClientTestEvents.java @@ -0,0 +1,21 @@ +package dan200.computercraft.gametest.core; + +import com.google.common.collect.Lists; +import net.minecraft.client.gui.components.toasts.Toast; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.ToastAddEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.util.List; + +@Mod.EventBusSubscriber(modid = TestMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT) +public class ClientTestEvents { + public static final List toasts = Lists.newArrayList(); + + @SubscribeEvent + public static void onToast(ToastAddEvent event) { + toasts.add(event.getToast()); + } + +} diff --git a/src/testMod/java/dan200/computercraft/gametest/core/Copier.java b/src/testMod/java/dan200/computercraft/gametest/core/Copier.java new file mode 100644 index 000000000..65351c637 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/gametest/core/Copier.java @@ -0,0 +1,55 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.core; + +import com.google.common.io.MoreFiles; +import com.google.common.io.RecursiveDeleteOption; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.function.Predicate; + +final class Copier extends SimpleFileVisitor { + private final Path sourceDir; + private final Path targetDir; + private final Predicate predicate; + + private Copier(Path sourceDir, Path targetDir, Predicate predicate) { + this.sourceDir = sourceDir; + this.targetDir = targetDir; + this.predicate = predicate; + } + + public static void copy(Path from, Path to) throws IOException { + Files.walkFileTree(from, new Copier(from, to, p -> true)); + } + + public static void replicate(Path from, Path to) throws IOException { + replicate(from, to, p -> true); + } + + public static void replicate(Path from, Path to, Predicate check) throws IOException { + if (Files.exists(to)) MoreFiles.deleteRecursively(to, RecursiveDeleteOption.ALLOW_INSECURE); + Files.walkFileTree(from, new Copier(from, to, check)); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException { + if (predicate.test(file)) Files.copy(file, targetDir.resolve(sourceDir.relativize(file))); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attributes) throws IOException { + Path newDir = targetDir.resolve(sourceDir.relativize(dir)); + Files.createDirectories(newDir); + return FileVisitResult.CONTINUE; + } +} diff --git a/src/testMod/java/dan200/computercraft/gametest/core/MinecraftExtensions.java b/src/testMod/java/dan200/computercraft/gametest/core/MinecraftExtensions.java new file mode 100644 index 000000000..1a21f8d9a --- /dev/null +++ b/src/testMod/java/dan200/computercraft/gametest/core/MinecraftExtensions.java @@ -0,0 +1,15 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.core; + +import net.minecraft.client.Minecraft; + +/** + * Extensions to {@link Minecraft}, injected via mixin. + */ +public interface MinecraftExtensions { + boolean computercraft$isRenderingStable(); +} diff --git a/src/testMod/java/dan200/computercraft/gametest/core/TestAPI.java b/src/testMod/java/dan200/computercraft/gametest/core/TestAPI.java new file mode 100644 index 000000000..7c5d12362 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/gametest/core/TestAPI.java @@ -0,0 +1,100 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.core; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.IComputerSystem; +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.lua.LuaFunction; +import dan200.computercraft.gametest.api.ComputerState; +import dan200.computercraft.gametest.api.TestExtensionsKt; +import dan200.computercraft.shared.computer.core.ServerContext; +import de.srendi.advancedperipherals.common.util.LuaConverter; +import net.minecraft.gametest.framework.GameTestSequence; +import net.minecraftforge.server.ServerLifecycleHooks; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.util.Optional; + +/** + * API exposed to computers to help write tests. + *

+ * Note, we extend this API within startup file of computers (see {@code cctest.lua}). + * + * @see TestExtensionsKt#thenComputerOk(GameTestSequence, String, String) To check tests on the computer have passed. + */ +public class TestAPI extends ComputerState implements ILuaAPI { + private static final Logger LOG = LoggerFactory.getLogger(TestAPI.class); + + private final IComputerSystem system; + private @Nullable String label; + + TestAPI(IComputerSystem system) { + this.system = system; + } + + @Override + public void startup() { + if (label == null) label = system.getLabel(); + if (label == null) { + label = "#" + system.getID(); + LOG.warn("Computer {} has no label", label); + } + + LOG.info("Computer '{}' has turned on.", label); + markers.clear(); + error = null; + lookup.put(label, this); + } + + @Override + public void shutdown() { + LOG.info("Computer '{}' has shut down.", label); + if (lookup.get(label) == this) lookup.remove(label); + } + + @Override + public String[] getNames() { + return new String[]{ "test" }; + } + + @LuaFunction + public final void fail(String message) throws LuaException { + LOG.error("Computer '{}' failed with {}", label, message); + if (markers.contains(ComputerState.DONE)) throw new LuaException("Cannot call fail/ok multiple times."); + markers.add(ComputerState.DONE); + error = message; + throw new LuaException(message); + } + + @LuaFunction + public final void ok(Optional marker) throws LuaException { + var actualMarker = marker.orElse(ComputerState.DONE); + if (markers.contains(ComputerState.DONE) || markers.contains(actualMarker)) { + throw new LuaException("Cannot call fail/ok multiple times."); + } + + markers.add(actualMarker); + } + + @LuaFunction + public final void log(String message) { + LOG.info("[Computer '{}'] {}", label, message); + } + + @LuaFunction + public final Object getComputerPosition() { + return ServerContext.get(ServerLifecycleHooks.getCurrentServer()).registry().getComputers().stream() + .filter(computer -> computer.getLabel() != null && computer.getLabel().equals(label)) + .findFirst() + .map(computer -> LuaConverter.posToObject(computer.getPosition())) + .orElse(null); + } + +} diff --git a/src/testMod/java/dan200/computercraft/gametest/core/TestEvents.java b/src/testMod/java/dan200/computercraft/gametest/core/TestEvents.java new file mode 100644 index 000000000..af5bfe9a1 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/gametest/core/TestEvents.java @@ -0,0 +1,21 @@ +package dan200.computercraft.gametest.core; + +import net.minecraft.core.BlockPos; +import net.minecraftforge.event.level.NoteBlockEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.util.HashMap; +import java.util.Map; + +@Mod.EventBusSubscriber(modid = TestMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class TestEvents { + public static final Map triggeredNoteBlocks = new HashMap<>(); + + @SubscribeEvent + public static void onNoteBlockTrigger(NoteBlockEvent.Play event) { + if (event.getLevel().isClientSide()) return; + triggeredNoteBlocks.put(event.getPos(), triggeredNoteBlocks.getOrDefault(event.getPos(), 0) + 1); + } + +} diff --git a/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java b/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java new file mode 100644 index 000000000..187f446a0 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java @@ -0,0 +1,84 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.core; + +import dan200.computercraft.export.Exporter; +import dan200.computercraft.gametest.api.GameTestHolder; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RegisterClientCommandsEvent; +import net.minecraftforge.client.event.ScreenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.RegisterGameTestsEvent; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.forgespi.language.ModFileScanData; +import net.minecraftforge.gametest.PrefixGameTestTemplate; +import org.objectweb.asm.Type; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Locale; +import java.util.function.Consumer; + +@Mod(TestMod.MOD_ID) +public class TestMod { + + public static final String MOD_ID = "advancedperipheralstest"; + + public TestMod() { + TestHooks.init(); + + var bus = MinecraftForge.EVENT_BUS; + bus.addListener(EventPriority.LOW, (ServerStartedEvent e) -> TestHooks.onServerStarted(e.getServer())); + bus.addListener((RegisterCommandsEvent e) -> CCTestCommand.register(e.getDispatcher())); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> TestMod::onInitializeClient); + + var modBus = FMLJavaModLoadingContext.get().getModEventBus(); + modBus.addListener((RegisterGameTestsEvent event) -> { + var holder = Type.getType(GameTestHolder.class); + ModList.get().getAllScanData().stream() + .map(ModFileScanData::getAnnotations) + .flatMap(Collection::stream) + .filter(a -> holder.equals(a.annotationType())) + .forEach(x -> registerClass(x.clazz().getClassName(), event::register)); + }); + } + + private static void onInitializeClient() { + var bus = MinecraftForge.EVENT_BUS; + + bus.addListener((TickEvent.ServerTickEvent e) -> { + if (e.phase == TickEvent.Phase.START) ClientTestHooks.onServerTick(e.getServer()); + }); + bus.addListener((ScreenEvent.Opening e) -> { + if (ClientTestHooks.onOpenScreen(e.getScreen())) e.setCanceled(true); + }); + bus.addListener((RegisterClientCommandsEvent e) -> Exporter.register(e.getDispatcher())); + } + + private static Class loadClass(String name) { + try { + return Class.forName(name, true, TestMod.class.getClassLoader()); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private static void registerClass(String className, Consumer fallback) { + var klass = loadClass(className); + for (var method : klass.getDeclaredMethods()) { + TestHooks.registerTest(klass, method, fallback); + } + } +} diff --git a/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestHelperAccessor.java b/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestHelperAccessor.java new file mode 100644 index 000000000..7a92267e6 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestHelperAccessor.java @@ -0,0 +1,22 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.mixin.gametest; + +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.gametest.framework.GameTestInfo; +import net.minecraft.world.phys.AABB; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(GameTestHelper.class) +public interface GameTestHelperAccessor { + @Invoker + AABB callGetBounds(); + + @Accessor + GameTestInfo getTestInfo(); +} diff --git a/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestInfoAccessor.java b/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestInfoAccessor.java new file mode 100644 index 000000000..258be4718 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestInfoAccessor.java @@ -0,0 +1,16 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.mixin.gametest; + +import net.minecraft.gametest.framework.GameTestInfo; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(GameTestInfo.class) +public interface GameTestInfoAccessor { + @Invoker("getTick") + long computercraft$getTick(); +} diff --git a/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceAccessor.java b/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceAccessor.java new file mode 100644 index 000000000..a23a707fb --- /dev/null +++ b/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceAccessor.java @@ -0,0 +1,17 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.mixin.gametest; + +import net.minecraft.gametest.framework.GameTestInfo; +import net.minecraft.gametest.framework.GameTestSequence; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(GameTestSequence.class) +public interface GameTestSequenceAccessor { + @Accessor + GameTestInfo getParent(); +} diff --git a/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java b/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java new file mode 100644 index 000000000..86f3e5d16 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java @@ -0,0 +1,48 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.mixin.gametest; + +import dan200.computercraft.gametest.core.TestHooks; +import net.minecraft.gametest.framework.GameTestAssertException; +import net.minecraft.gametest.framework.GameTestInfo; +import net.minecraft.gametest.framework.GameTestSequence; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(GameTestSequence.class) +class GameTestSequenceMixin { + @Shadow + @Final + GameTestInfo parent; + + /** + * Override {@link GameTestSequence#tickAndContinue(long)} to catch non-{@link GameTestAssertException} failures. + * + * @author Jonathan Coates + * @param ticks The current tick. + * @reason There's no sense doing this in a more compatible way for game tests. + */ + @Overwrite + public void tickAndContinue(long ticks) { + try { + tick(ticks); + } catch (GameTestAssertException ignored) { + // Mimic the original behaviour. + } catch (AssertionError e) { + parent.fail(e); + } catch (Exception e) { + // Fail the test, rather than crashing the server. + TestHooks.LOG.error("{} threw unexpected exception", parent.getTestName(), e); + parent.fail(e); + } + } + + @Shadow + private void tick(long tick) { + } +} diff --git a/src/testMod/java/dan200/computercraft/mixin/gametest/TestCommandAccessor.java b/src/testMod/java/dan200/computercraft/mixin/gametest/TestCommandAccessor.java new file mode 100644 index 000000000..8f87246a1 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/mixin/gametest/TestCommandAccessor.java @@ -0,0 +1,19 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.mixin.gametest; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.gametest.framework.TestCommand; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(TestCommand.class) +public interface TestCommandAccessor { + @Invoker + static int callExportTestStructure(CommandSourceStack source, String structure) { + return 0; + } +} diff --git a/src/testMod/java/dan200/computercraft/mixin/gametest/client/LevelSummaryMixin.java b/src/testMod/java/dan200/computercraft/mixin/gametest/client/LevelSummaryMixin.java new file mode 100644 index 000000000..41b3548b8 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/mixin/gametest/client/LevelSummaryMixin.java @@ -0,0 +1,19 @@ +package dan200.computercraft.mixin.gametest.client; + +import net.minecraft.world.level.storage.LevelSummary; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +/** + * Used to suppress the "Worlds using Experimental Settings are not supported" warning + * when loading a world in GameTest. + */ +@Mixin(LevelSummary.class) +public class LevelSummaryMixin { + + @Overwrite + public boolean isExperimental() { + return false; + } + +} diff --git a/src/testMod/java/dan200/computercraft/mixin/gametest/client/MinecraftMixin.java b/src/testMod/java/dan200/computercraft/mixin/gametest/client/MinecraftMixin.java new file mode 100644 index 000000000..141bf1525 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/mixin/gametest/client/MinecraftMixin.java @@ -0,0 +1,55 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.mixin.gametest.client; + +import dan200.computercraft.gametest.core.MinecraftExtensions; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.LevelRenderer; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import javax.annotation.Nullable; +import java.util.concurrent.atomic.AtomicBoolean; + +@Mixin(Minecraft.class) +class MinecraftMixin implements MinecraftExtensions { + @Final + @Shadow + public LevelRenderer levelRenderer; + + @Shadow + @Nullable + public ClientLevel level; + + @Shadow + @Nullable + public LocalPlayer player; + + @Unique + private final AtomicBoolean isStable = new AtomicBoolean(false); + + @Inject(method = "runTick", at = @At("TAIL")) + @SuppressWarnings("unused") + private void updateStable(boolean render, CallbackInfo ci) { + isStable.set( + level != null && player != null && + levelRenderer.isChunkCompiled(player.blockPosition()) && levelRenderer.countRenderedChunks() > 10 && + levelRenderer.hasRenderedAllChunks() + ); + } + + @Override + public boolean computercraft$isRenderingStable() { + return isStable.get(); + } +} diff --git a/src/testMod/java/dan200/computercraft/mixin/gametest/client/WorldOpenFlowsMixin.java b/src/testMod/java/dan200/computercraft/mixin/gametest/client/WorldOpenFlowsMixin.java new file mode 100644 index 000000000..c55a72e1c --- /dev/null +++ b/src/testMod/java/dan200/computercraft/mixin/gametest/client/WorldOpenFlowsMixin.java @@ -0,0 +1,30 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.mixin.gametest.client; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.worldselection.WorldOpenFlows; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(WorldOpenFlows.class) +public class WorldOpenFlowsMixin { + /** + * Never prompt for backup/experimental options when running tests. + * + * @param screen The current menu. + * @param level The level to load. + * @param customised Whether this rule uses legacy customised worldgen options. + * @param action The action run to load the world. + * @author SquidDev + * @reason Makes it easier to run tests. We can switch to an @Inject if this becomes a problem. + */ + @Overwrite + @SuppressWarnings("unused") + private void askForBackup(Screen screen, String level, boolean customised, Runnable action) { + action.run(); + } +} diff --git a/src/testMod/java/de/srendi/advancedperipherals/gametest/TestGameTests.java b/src/testMod/java/de/srendi/advancedperipherals/gametest/TestGameTests.java deleted file mode 100644 index 11a9bc84e..000000000 --- a/src/testMod/java/de/srendi/advancedperipherals/gametest/TestGameTests.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.srendi.advancedperipherals.gametest; - -import net.minecraft.gametest.framework.GameTest; -import net.minecraft.gametest.framework.GameTestHelper; -import net.minecraftforge.gametest.GameTestHolder; -import net.minecraftforge.gametest.PrefixGameTestTemplate; - -@GameTestHolder("advancedperipheralstest") -public class TestGameTests { - - @PrefixGameTestTemplate(false) - @GameTest(templateNamespace = "advancedperipheralstest") - public static void envDetectorTest(GameTestHelper helper) { - helper.succeed(); - } - -} diff --git a/src/testMod/java/de/srendi/advancedperipherals/test/mixin/RsBaseBlockMixin.java b/src/testMod/java/de/srendi/advancedperipherals/test/mixin/RsBaseBlockMixin.java new file mode 100644 index 000000000..a17bb2d51 --- /dev/null +++ b/src/testMod/java/de/srendi/advancedperipherals/test/mixin/RsBaseBlockMixin.java @@ -0,0 +1,38 @@ +package de.srendi.advancedperipherals.test.mixin; + +import com.refinedmods.refinedstorage.block.BaseBlock; +import com.refinedmods.refinedstorage.block.BlockDirection; +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +/** + * Used to fix the orientation of RS Blocks when they are loaded from a structure block. + */ +@Mixin(BaseBlock.class) +public class RsBaseBlockMixin { + + @Shadow + public BlockDirection getDirection() { + return null; + } + + @Overwrite + public BlockState rotate(BlockState state, Rotation rot) { + BlockDirection dir = this.getDirection(); + if (dir == BlockDirection.NONE) return state; + + Direction newDirection = switch (rot) { + case NONE -> state.getValue(dir.getProperty()); + case CLOCKWISE_90 -> dir.cycle(state.getValue(dir.getProperty()).getClockWise().getClockWise()); + case CLOCKWISE_180 -> dir.cycle(state.getValue(dir.getProperty()).getClockWise().getClockWise().getClockWise()); + case COUNTERCLOCKWISE_90 -> dir.cycle(state.getValue(dir.getProperty())); + }; + + return state.setValue(dir.getProperty(), newDirection); + } + +} diff --git a/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientGameTest.kt b/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientGameTest.kt new file mode 100644 index 000000000..2527d3159 --- /dev/null +++ b/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientGameTest.kt @@ -0,0 +1,33 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.api + +import net.minecraft.gametest.framework.GameTest + +/** + * Similar to [GameTest], this annotation defines a method which runs under Minecraft's gametest sequence. + * + * Unlike standard game tests, client game tests are only registered when running under the Minecraft client, and run + * sequentially rather than in parallel. + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class ClientGameTest( + /** + * The template to use for this test, identical to [GameTest.template] + */ + val template: String = "", + + /** + * The timeout for this test, identical to [GameTest.timeoutTicks]. + */ + val timeoutTicks: Int = Timeouts.DEFAULT, + + /** + * The tag associated with this test, denoting when it should run. + */ + val tag: String = TestTags.CLIENT, +) diff --git a/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientTestExtensions.kt b/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientTestExtensions.kt new file mode 100644 index 000000000..7c8147ab7 --- /dev/null +++ b/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientTestExtensions.kt @@ -0,0 +1,136 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.api + +import dan200.computercraft.gametest.core.MinecraftExtensions +import dan200.computercraft.mixin.gametest.GameTestSequenceAccessor +import net.minecraft.client.Minecraft +import net.minecraft.client.Screenshot +import net.minecraft.client.gui.screens.inventory.MenuAccess +import net.minecraft.core.BlockPos +import net.minecraft.gametest.framework.GameTestAssertException +import net.minecraft.gametest.framework.GameTestHelper +import net.minecraft.gametest.framework.GameTestSequence +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.entity.EntityType +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.inventory.MenuType +import net.minecraftforge.registries.ForgeRegistries +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ExecutionException +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Attempt to guess whether all chunks have been rendered. + */ +fun Minecraft.isRenderingStable(): Boolean = (this as MinecraftExtensions).`computercraft$isRenderingStable`() + +/** + * Run a task on the client. + */ +fun GameTestSequence.thenOnClient(task: ClientTestHelper.() -> Unit): GameTestSequence { + var future: CompletableFuture? = null + thenExecute { future = Minecraft.getInstance().submit { task(ClientTestHelper()) } } + thenWaitUntil { if (!future!!.isDone) throw GameTestAssertException("Not done task yet") } + thenExecute { + try { + future!!.get() + } catch (e: ExecutionException) { + throw e.cause ?: e + } + } + return this +} + +/** + * Take a screenshot of the current game state. + */ +fun GameTestSequence.thenScreenshot(name: String? = null, showGui: Boolean = false): GameTestSequence { + val suffix = if (name == null) "" else "-$name" + val test = (this as GameTestSequenceAccessor).parent + val fullName = "${test.testName}$suffix" + + // Wait until all chunks have been rendered and we're idle for an extended period. + var counter = 0 + thenWaitUntil { + if (Minecraft.getInstance().isRenderingStable()) { + val idleFor = ++counter + if (idleFor <= 20) throw GameTestAssertException("Only idle for $idleFor ticks") + } else { + counter = 0 + throw GameTestAssertException("Waiting for client to finish rendering") + } + } + + // Now disable the GUI, take a screenshot and reenable it. Sleep a little afterwards to ensure the render thread + // has caught up. + thenOnClient { minecraft.options.hideGui = !showGui } + thenIdle(2) + + // Take a screenshot and wait for it to have finished. + val hasScreenshot = AtomicBoolean() + thenOnClient { screenshot("$fullName.png") { hasScreenshot.set(true) } } + thenWaitUntil { if (!hasScreenshot.get()) throw GameTestAssertException("Screenshot does not exist") } + thenOnClient { minecraft.options.hideGui = false } + + return this +} + +/** + * "Reset" the current player, ensuring. + */ +fun ServerPlayer.setupForTest() { + if (containerMenu != inventoryMenu) closeContainer() +} + +/** + * Position the player at an armor stand. + */ +fun GameTestHelper.positionAtArmorStand() { + val stand = getEntity(EntityType.ARMOR_STAND) + val player = level.randomPlayer ?: throw GameTestAssertException("Player does not exist") + + player.setupForTest() + player.connection.teleport(stand.x, stand.y, stand.z, stand.yRot, stand.xRot) +} + +/** + * Position the player at a given coordinate. + */ +fun GameTestHelper.positionAt(pos: BlockPos, yRot: Float = 0.0f, xRot: Float = 0.0f) { + val absolutePos = absolutePos(pos) + val player = level.randomPlayer ?: throw GameTestAssertException("Player does not exist") + + player.setupForTest() + player.connection.teleport(absolutePos.x + 0.5, absolutePos.y + 0.5, absolutePos.z + 0.5, yRot, xRot) +} + +/** + * The equivalent of a [GameTestHelper] on the client. + */ +class ClientTestHelper { + val minecraft: Minecraft = Minecraft.getInstance() + + fun screenshot(name: String, callback: () -> Unit = {}) { + Screenshot.grab(minecraft.gameDirectory, name, minecraft.mainRenderTarget) { callback() } + } + + /** + * Get the currently open [AbstractContainerMenu], ensuring it is of a specific type. + */ + fun getOpenMenu(type: MenuType): T { + fun getName(type: MenuType<*>) = ForgeRegistries.MENU_TYPES.getKey(type) + + val screen = minecraft.screen + @Suppress("UNCHECKED_CAST") + when { + screen == null -> throw GameTestAssertException("Expected a ${getName(type)} menu, but no screen is open") + screen !is MenuAccess<*> -> throw GameTestAssertException("Expected a ${getName(type)} menu, but a $screen is open") + screen.menu.type != type -> throw GameTestAssertException("Expected a ${getName(type)} menu, but a ${getName(screen.menu.type)} is open") + else -> return screen.menu as T + } + } +} diff --git a/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt b/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt new file mode 100644 index 000000000..16874ee2f --- /dev/null +++ b/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt @@ -0,0 +1,263 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.api + +import dan200.computercraft.gametest.core.ManagedComputers +import dan200.computercraft.mixin.gametest.GameTestHelperAccessor +import dan200.computercraft.mixin.gametest.GameTestSequenceAccessor +import dan200.computercraft.shared.Peripherals +import dan200.computercraft.test.core.computer.LuaTaskContext +import net.minecraft.commands.arguments.blocks.BlockInput +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.gametest.framework.* +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.Container +import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.EntityType +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.block.state.properties.Property +import net.minecraftforge.registries.ForgeRegistries + +/** + * Globally usable structures. + * + * @see GameTest.template + */ +object Structures { + /** The "default" structure, a 5x5 area with a polished Andesite floor */ + const val DEFAULT = "default" +} + +/** Pre-set in-game times */ +object Times { + const val NOON: Long = 6000 +} + +/** + * Custom timeouts for various test types. + * + * @see GameTest.timeoutTicks + */ +object Timeouts { + const val SECOND: Int = 20 + + const val DEFAULT: Int = SECOND * 5 + + const val COMPUTER_TIMEOUT: Int = SECOND * 15 +} + +/** + * Equivalent to [GameTestSequence.thenExecute], but which won't run the next steps if the parent fails. + */ +fun GameTestSequence.thenExecuteFailFast(task: Runnable): GameTestSequence = + thenExecute(task).thenWaitUntil { + val failure = (this as GameTestSequenceAccessor).parent.error + if (failure != null) throw failure + } + +/** + * Wait until a computer has finished running and check it is OK. + */ +fun GameTestSequence.thenComputerOk(name: String? = null, marker: String = ComputerState.DONE): GameTestSequence { + val label = (this as GameTestSequenceAccessor).parent.testName + (if (name == null) "" else ".$name") + + thenWaitUntil { + val computer = ComputerState.get(label) + if (computer == null || !computer.isDone(marker)) throw GameTestAssertException("Computer '$label' has not reached $marker yet.") + } + thenExecuteFailFast { ComputerState.get(label)!!.check(marker) } + return this +} + +/** + * Run a task on a computer but don't wait for it to finish. + */ +fun GameTestSequence.thenStartComputer( + name: String? = null, + action: suspend LuaTaskContext.() -> Unit +): GameTestSequence { + val test = (this as GameTestSequenceAccessor).parent + val label = test.testName + (if (name == null) "" else ".$name") + return thenExecuteFailFast { ManagedComputers.enqueue(test, label, action) } +} + +/** + * Run a task on a computer and wait for it to finish. + */ +fun GameTestSequence.thenOnComputer(name: String? = null, action: suspend LuaTaskContext.() -> Unit): GameTestSequence { + val test = (this as GameTestSequenceAccessor).parent + val label = test.testName + (if (name == null) "" else ".$name") + var monitor: ManagedComputers.Monitor? = null + thenExecuteFailFast { monitor = ManagedComputers.enqueue(test, label, action) } + thenWaitUntil { if (!monitor!!.isFinished) throw GameTestAssertException("Computer '$label' has not finished yet.") } + thenExecuteFailFast { monitor!!.check() } + return this +} + +/** + * Create a new game test sequence + */ +fun GameTestHelper.sequence(run: GameTestSequence.() -> Unit) { + val sequence = startSequence() + run(sequence) + sequence.thenSucceed() +} + +/** + * A custom instance of [GameTestAssertPosException] which allows for longer error messages. + */ +private class VerboseGameTestAssertPosException( + message: String, + absolutePos: BlockPos, + relativePos: BlockPos, + tick: Long +) : + GameTestAssertPosException(message, absolutePos, relativePos, tick) { + override fun getMessageToShowAtBlock(): String = message!!.lineSequence().first() +} + +/** + * Fail this test. Unlike [GameTestHelper.fail], this trims the in-game error message to the first line. + */ +private fun GameTestHelper.failVerbose(message: String, pos: BlockPos): Nothing { + throw VerboseGameTestAssertPosException(message, absolutePos(pos), pos, tick) +} + +/** Fail with an optional context message. */ +private fun GameTestHelper.fail(message: String?, detail: String, pos: BlockPos): Nothing { + failVerbose(if (message.isNullOrEmpty()) detail else "$message: $detail", pos) +} + +/** + * A version of [GameTestHelper.assertBlockState] which also includes the current block state. + */ +fun GameTestHelper.assertBlockIs(pos: BlockPos, predicate: (BlockState) -> Boolean, message: String = "") { + val state = getBlockState(pos) + if (!predicate(state)) fail(message, state.toString(), pos) +} + +/** + * A version of [GameTestHelper.assertBlockProperty] which includes the current block state in the error message. + */ +fun > GameTestHelper.assertBlockHas( + pos: BlockPos, + property: Property, + value: T, + message: String = "" +) { + val state = getBlockState(pos) + if (!state.hasProperty(property)) { + val id = ForgeRegistries.BLOCKS.getKey(state.block) + fail(message, "block $id does not have property ${property.name}", pos) + } else if (state.getValue(property) != value) { + fail(message, "${property.name} is ${state.getValue(property)}, expected $value", pos) + } +} + +/** + * Assert a container contains exactly these items and no more. + * + * @param pos The position of the container. + * @param items The list of items this container must contain. This should be equal to the expected contents of the + * first `n` slots - the remaining are required to be empty. + */ +fun GameTestHelper.assertContainerExactly(pos: BlockPos, items: List) = + when (val container = getBlockEntity(pos) ?: failVerbose("Expected a container at $pos, found nothing", pos)) { + is Container -> assertContainerExactlyImpl(pos, container, items) + else -> failVerbose("Expected a container at $pos, found ${getName(container.type)}", pos) + } + +/** + * Assert an container contains exactly these items and no more. + * + * @param entity The entity containing these items. + * @param items The list of items this container must contain. This should be equal to the expected contents of the + * first `n` slots - the remaining are required to be empty. + */ +fun GameTestHelper.assertContainerExactly(entity: T, items: List) where T : Entity, T : Container = + assertContainerExactlyImpl(entity.blockPosition(), entity, items) + +private fun GameTestHelper.assertContainerExactlyImpl(pos: BlockPos, container: Container, items: List) { + val slot = (0 until container.containerSize).indexOfFirst { slot -> + val expected = if (slot >= items.size) ItemStack.EMPTY else items[slot] + !ItemStack.matches(container.getItem(slot), expected) + } + + if (slot >= 0) { + failVerbose( + """ + Items do not match (first mismatch at slot $slot). + Expected: $items + Container: ${(0 until container.containerSize).map { container.getItem(it) }.dropLastWhile { it.isEmpty }} + """.trimIndent(), + pos, + ) + } +} + +fun GameTestHelper.assertPeripheral(pos: BlockPos, direction: Direction = Direction.UP, type: String) { + val peripheral = Peripherals.getPeripheral(level, absolutePos(pos), direction) {} + when { + peripheral == null -> fail("No peripheral at position", pos) + peripheral.type != type -> fail("Peripheral is of type ${peripheral.type}, expected $type", pos) + } +} + +fun GameTestHelper.assertNoPeripheral(pos: BlockPos, direction: Direction = Direction.UP) { + val peripheral = Peripherals.getPeripheral(level, absolutePos(pos), direction) {} + if (peripheral != null) fail("Expected no peripheral, got a ${peripheral.type}", pos) +} + +private fun getName(type: BlockEntityType<*>): ResourceLocation = ForgeRegistries.BLOCK_ENTITY_TYPES.getKey(type)!! + +/** + * Get a [BlockEntity] of a specific type. + */ +fun GameTestHelper.getBlockEntity(pos: BlockPos, type: BlockEntityType): T { + val tile = getBlockEntity(pos) + @Suppress("UNCHECKED_CAST") + return when { + tile == null -> failVerbose("Expected ${getName(type)}, but no tile was there", pos) + tile.type != type -> failVerbose("Expected ${getName(type)} but got ${getName(tile.type)}", pos) + else -> tile as T + } +} + +/** + * Get all entities of a specific type within the test structure. + */ +fun GameTestHelper.getEntities(type: EntityType): List { + val info = (this as GameTestHelperAccessor).testInfo + return level.getEntities(type, info.structureBounds!!) { it.isAlive } +} + +/** + * Get an [Entity] inside the game structure, requiring there to be a single one. + */ +fun GameTestHelper.getEntity(type: EntityType): T { + val entities = getEntities(type) + when (entities.size) { + 0 -> throw GameTestAssertException("No $type entities") + 1 -> return entities[0] + else -> throw GameTestAssertException("Multiple $type entities (${entities.size} in bounding box)") + } +} + +/** + * Set a block within the test structure. + */ +fun GameTestHelper.setBlock(pos: BlockPos, state: BlockInput) = state.place(level, absolutePos(pos), 3) + +/** + * Modify a block state within the test. + */ +fun GameTestHelper.modifyBlock(pos: BlockPos, modify: (BlockState) -> BlockState) { + setBlock(pos, modify(getBlockState(pos))) +} diff --git a/src/testMod/kotlin/dan200/computercraft/gametest/api/TestTags.kt b/src/testMod/kotlin/dan200/computercraft/gametest/api/TestTags.kt new file mode 100644 index 000000000..a0b999d1c --- /dev/null +++ b/src/testMod/kotlin/dan200/computercraft/gametest/api/TestTags.kt @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.gametest.api + +/** + * "Tags" associated with each test, denoting whether a specific test should be registered for the current Minecraft + * session. + * + * This is used to only run some tests on the client, or when a specific mod is loaded. + */ +object TestTags { + const val COMMON = "common" + const val CLIENT = "client" + + private val tags: Set = System.getProperty("advancedperipheralstest.tags", COMMON).split(',').toSet() + + fun isEnabled(tag: String) = tags.contains(tag) +} diff --git a/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt b/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt new file mode 100644 index 000000000..505407067 --- /dev/null +++ b/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt @@ -0,0 +1,189 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.core + +import dan200.computercraft.gametest.api.Timeouts +import dan200.computercraft.gametest.api.isRenderingStable +import dan200.computercraft.gametest.api.setupForTest +import net.minecraft.client.CloudStatus +import net.minecraft.client.Minecraft +import net.minecraft.client.ParticleStatus +import net.minecraft.client.gui.screens.AccessibilityOptionsScreen +import net.minecraft.client.gui.screens.Screen +import net.minecraft.client.gui.screens.TitleScreen +import net.minecraft.client.tutorial.TutorialSteps +import net.minecraft.core.BlockPos +import net.minecraft.core.Registry +import net.minecraft.core.RegistryAccess +import net.minecraft.gametest.framework.* +import net.minecraft.server.MinecraftServer +import net.minecraft.sounds.SoundSource +import net.minecraft.util.RandomSource +import net.minecraft.world.Difficulty +import net.minecraft.world.level.* +import net.minecraft.world.level.block.Rotation +import net.minecraft.world.level.levelgen.presets.WorldPresets +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import kotlin.system.exitProcess + +/** + * Client-side hooks for game tests. + * + * This mirrors Minecraft's + */ +object ClientTestHooks { + private val LOG: Logger = LoggerFactory.getLogger(ClientTestHooks::class.java) + + private const val LEVEL_NAME = "test" + + /** + * Time (in ticks) that we wait after the client joins the world + */ + private const val STARTUP_DELAY = 5 * Timeouts.SECOND + + /** + * Whether our client-side game test driver is enabled. + */ + private val enabled: Boolean = System.getProperty("advancedperipheralstest.client") != null + + private var loadedWorld: Boolean = false + + @JvmStatic + fun onOpenScreen(screen: Screen): Boolean = when { + enabled && !loadedWorld && (screen is TitleScreen || screen is AccessibilityOptionsScreen) -> { + loadedWorld = true + openWorld(screen) + true + } + + else -> false + } + + /** + * Open or create our test world immediately on game launch. + */ + private fun openWorld(screen: Screen) { + val minecraft = Minecraft.getInstance() + + // Clear some options before we get any further. + minecraft.options.autoJump().set(false) + minecraft.options.cloudStatus().set(CloudStatus.OFF) + minecraft.options.particles().set(ParticleStatus.MINIMAL) + minecraft.options.tutorialStep = TutorialSteps.NONE + minecraft.options.renderDistance().set(6) + minecraft.options.gamma().set(1.0) + minecraft.options.setSoundCategoryVolume(SoundSource.MUSIC, 0f) + minecraft.options.setSoundCategoryVolume(SoundSource.AMBIENT, 0f) + + if (minecraft.levelSource.levelExists(LEVEL_NAME)) { + LOG.info("World already exists, opening.") + minecraft.createWorldOpenFlows().loadLevel(screen, LEVEL_NAME) + } else { + LOG.info("World does not exist, creating it.") + val rules = GameRules() + rules.getRule(GameRules.RULE_DOMOBSPAWNING).set(false, null) + rules.getRule(GameRules.RULE_DAYLIGHT).set(false, null) + rules.getRule(GameRules.RULE_WEATHER_CYCLE).set(false, null) + + val registryAccess = RegistryAccess.builtinCopy().freeze() + minecraft.createWorldOpenFlows().createFreshLevel( + LEVEL_NAME, + LevelSettings("Test Level", GameType.CREATIVE, false, Difficulty.EASY, true, rules, DataPackConfig.DEFAULT), + registryAccess, + registryAccess.registryOrThrow(Registry.WORLD_PRESET_REGISTRY).getHolderOrThrow(WorldPresets.FLAT).value().createWorldGenSettings(RandomSource.create().nextLong(), false, false) + ) + } + } + + private var testTracker: MultipleTestTracker? = null + private var hasFinished: Boolean = false + private var startupDelay: Int = STARTUP_DELAY + + @JvmStatic + fun onServerTick(server: MinecraftServer) { + if (!enabled || hasFinished) return + + val testTracker = when (val tracker = this.testTracker) { + null -> { + if (server.overworld().players().isEmpty()) return + if (!Minecraft.getInstance().isRenderingStable()) return + if (startupDelay >= 0) { + // TODO: Is there a better way? Maybe set a flag when the client starts rendering? + startupDelay-- + return + } + + LOG.info("Server ready, starting.") + + val tests = GameTestRunner.runTestBatches( + GameTestRunner.groupTestsIntoBatches(GameTestRegistry.getAllTestFunctions()), + BlockPos(0, -60, 0), + Rotation.NONE, + server.overworld(), + GameTestTicker.SINGLETON, + 8, + ) + val testTracker = MultipleTestTracker(tests) + testTracker.addListener( + object : GameTestListener { + fun testFinished() { + for (it in server.playerList.players) it.setupForTest() + } + + override fun testPassed(test: GameTestInfo) = testFinished() + override fun testFailed(test: GameTestInfo) = testFinished() + override fun testStructureLoaded(test: GameTestInfo) = Unit + }, + ) + + LOG.info("{} tests are now running!", testTracker.totalCount) + this.testTracker = testTracker + testTracker + } + + else -> tracker + } + + if (server.overworld().gameTime % 20L == 0L) LOG.info(testTracker.progressBar) + + if (testTracker.isDone) { + hasFinished = true + LOG.info(testTracker.progressBar) + + GlobalTestReporter.finish() + LOG.info("========= {} GAME TESTS COMPLETE ======================", testTracker.totalCount) + if (testTracker.hasFailedRequired()) { + LOG.info("{} required tests failed :(", testTracker.failedRequiredCount) + for (test in testTracker.failedRequired) LOG.info(" - {}", test.testName) + } else { + LOG.info("All {} required tests passed :)", testTracker.totalCount) + } + if (testTracker.hasFailedOptional()) { + LOG.info("{} optional tests failed", testTracker.failedOptionalCount) + for (test in testTracker.failedOptional) LOG.info(" - {}", test.testName) + } + LOG.info("====================================================") + + // Stop Minecraft *from the client thread*. We need to do this to avoid deadlocks in stopping the server. + val minecraft = Minecraft.getInstance() + minecraft.execute { + LOG.info("Stopping client.") + minecraft.level!!.disconnect() + minecraft.clearLevel() + minecraft.stop() + + exitProcess( + when { + testTracker.totalCount == 0 -> 1 + testTracker.hasFailedRequired() -> 2 + else -> 0 + }, + ) + } + } + } +} diff --git a/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt b/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt new file mode 100644 index 000000000..6cfe36f9b --- /dev/null +++ b/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt @@ -0,0 +1,143 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.core + +import dan200.computercraft.api.lua.ILuaAPI +import dan200.computercraft.core.apis.OSAPI +import dan200.computercraft.core.lua.CobaltLuaMachine +import dan200.computercraft.core.lua.ILuaMachine +import dan200.computercraft.core.lua.MachineEnvironment +import dan200.computercraft.core.lua.MachineResult +import dan200.computercraft.gametest.api.thenOnComputer +import dan200.computercraft.mixin.gametest.GameTestInfoAccessor +import dan200.computercraft.shared.computer.core.ServerContext +import dan200.computercraft.test.core.computer.KotlinLuaMachine +import dan200.computercraft.test.core.computer.LuaTaskContext +import net.minecraft.gametest.framework.GameTestAssertException +import net.minecraft.gametest.framework.GameTestAssertPosException +import net.minecraft.gametest.framework.GameTestInfo +import net.minecraft.gametest.framework.GameTestSequence +import org.apache.logging.log4j.LogManager +import java.io.InputStream +import java.util.* +import java.util.concurrent.ConcurrentLinkedDeque +import java.util.concurrent.atomic.AtomicReference + +/** + * Provides a custom [ILuaMachine] which allows computers to run Kotlin or Lua code, depending on their ID. + * + * This allows writing game tests which consume Lua APIs, without having the overhead of starting a new computer for + * each test. + * + * @see GameTestSequence.thenOnComputer + */ +object ManagedComputers : ILuaMachine.Factory { + private val LOGGER = LogManager.getLogger(ManagedComputers::class.java) + private val computers: MutableMap Unit>> = mutableMapOf() + + internal fun enqueue(test: GameTestInfo, label: String, task: suspend LuaTaskContext.() -> Unit): Monitor { + val monitor = Monitor(test, label) + computers.computeIfAbsent(label) { ConcurrentLinkedDeque() }.add { + try { + LOGGER.info("Running $label") + task() + monitor.result.set(Result.success(Unit)) + } catch (e: Throwable) { + if (e !is AssertionError) LOGGER.error("Computer $label failed", e) + monitor.result.set(Result.failure(e)) + throw e + } finally { + LOGGER.info("Finished $label") + } + } + + ServerContext.get(test.level.server).registry().computers + .firstOrNull { it.label == label }?.queueEvent("test_wakeup") + + return monitor + } + + override fun create(environment: MachineEnvironment): ILuaMachine = DelegateMachine(environment) + + private class DelegateMachine(private val environment: MachineEnvironment) : ILuaMachine { + private val apis = mutableListOf() + private var delegate: ILuaMachine? = null + + override fun addAPI(api: ILuaAPI) { + val delegate = this.delegate + if (delegate != null) return delegate.addAPI(api) + + apis.add(api) + + if (api is OSAPI) { + val newMachine = if (api.computerID != 1) { + CobaltLuaMachine(environment) + } else if (api.computerLabel != null) { + KotlinMachine(environment, api.computerLabel[0] as String) + } else { + LOGGER.error("Kotlin Lua machine must have a label") + CobaltLuaMachine(environment) + } + + this.delegate = newMachine + for (api in apis) newMachine.addAPI(api) + } + } + + override fun loadBios(bios: InputStream): MachineResult { + val delegate = this.delegate ?: return MachineResult.error("Computer not created") + return delegate.loadBios(bios) + } + + override fun handleEvent(eventName: String?, arguments: Array?): MachineResult { + val delegate = this.delegate ?: return MachineResult.error("Computer not created") + return delegate.handleEvent(eventName, arguments) + } + + override fun printExecutionState(out: StringBuilder) { + delegate?.printExecutionState(out) + } + + override fun close() { + delegate?.close() + } + } + + private class KotlinMachine(environment: MachineEnvironment, private val label: String) : + KotlinLuaMachine(environment) { + override fun getTask(): (suspend KotlinLuaMachine.() -> Unit)? = computers[label]?.poll() + } + + class Monitor(private val test: GameTestInfo, private val label: String) { + internal val result = AtomicReference>() + + val isFinished + get() = result.get() != null + + fun check() { + val result = result.get() ?: fail("Computer $label did not finish") + val error = result.exceptionOrNull() + if (error != null) fail(error.message ?: error.toString()) + } + + private fun fail(message: String): Nothing { + val computer = + ServerContext.get(test.level.server).registry().computers.firstOrNull { it.label == label } + if (computer == null) { + throw GameTestAssertException(message) + } else { + val pos = computer.position + val relativePos = pos.subtract(test.structureBlockPos) + throw GameTestAssertPosException( + message, + pos, + relativePos, + (test as GameTestInfoAccessor).`computercraft$getTick`() + ) + } + } + } +} diff --git a/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt b/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt new file mode 100644 index 000000000..ab1c77de6 --- /dev/null +++ b/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt @@ -0,0 +1,143 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.core + +import dan200.computercraft.api.ComputerCraftAPI +import dan200.computercraft.gametest.* +import dan200.computercraft.gametest.api.ClientGameTest +import dan200.computercraft.gametest.api.TestTags +import dan200.computercraft.gametest.api.Times +import dan200.computercraft.shared.computer.core.ServerContext +import net.minecraft.core.BlockPos +import net.minecraft.gametest.framework.* +import net.minecraft.server.MinecraftServer +import net.minecraft.world.level.GameRules +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.File +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Method +import java.lang.reflect.Modifier +import java.nio.file.Path +import java.nio.file.Paths +import java.util.function.Consumer +import javax.xml.parsers.ParserConfigurationException + +object TestHooks { + @JvmField + val LOG: Logger = LoggerFactory.getLogger(TestHooks::class.java) + + @JvmStatic + val sourceDir: Path = Paths.get(System.getProperty("advancedperipheralstest.sources")).normalize().toAbsolutePath() + + @JvmStatic + fun init() { + ServerContext.luaMachine = ManagedComputers + ComputerCraftAPI.registerAPIFactory(::TestAPI) + StructureUtils.testStructuresDir = sourceDir.resolve("structures").toString() + + // Set up our test reporter if configured. + val outputPath = System.getProperty("advancedperipheralstest.gametest-report") + if (outputPath != null) { + try { + GlobalTestReporter.replaceWith( + MultiTestReporter( + JunitTestReporter(File(outputPath)), + LogTestReporter(), + ), + ) + } catch (e: ParserConfigurationException) { + throw RuntimeException(e) + } + } + } + + @JvmStatic + fun onServerStarted(server: MinecraftServer) { + val rules = server.gameRules + rules.getRule(GameRules.RULE_DAYLIGHT).set(false, server) + server.overworld().dayTime = Times.NOON + + LOG.info("Cleaning up after last run") + GameTestRunner.clearAllTests(server.overworld(), BlockPos(0, -60, 0), GameTestTicker.SINGLETON, 200) + + // Delete server context and add one with a mutable machine factory. This allows us to set the factory for + // specific test batches without having to reset all computers. + for (computer in ServerContext.get(server).registry().computers) { + val label = if (computer.label == null) "#" + computer.id else computer.label!! + LOG.warn("Unexpected computer {}", label) + } + + LOG.info("Importing files") + CCTestCommand.importFiles(server) + } + + private val isCi = System.getenv("CI") != null + + /** + * Adjust the timeout of a test. This makes it 1.5 times longer when run under CI, as CI servers are less powerful + * than our own. + */ + private fun adjustTimeout(timeout: Int): Int = if (isCi) timeout + (timeout / 2) else timeout + + @JvmStatic + fun registerTest(testClass: Class<*>, method: Method, fallbackRegister: Consumer) { + val className = testClass.simpleName.lowercase() + val testName = className + "." + method.name.lowercase() + + method.getAnnotation(GameTest::class.java)?.let { testInfo -> + if (!TestTags.isEnabled(TestTags.COMMON)) return + + GameTestRegistry.getAllTestFunctions().add( + TestFunction( + testInfo.batch, testName, testInfo.template.ifEmpty { testName }, + StructureUtils.getRotationForRotationSteps(testInfo.rotationSteps), + adjustTimeout(testInfo.timeoutTicks), + testInfo.setupTicks, + testInfo.required, testInfo.requiredSuccesses, testInfo.attempts, + ) { value -> safeInvoke(method, value) }, + ) + GameTestRegistry.getAllTestClassNames().add(testClass.simpleName) + return + } + + method.getAnnotation(ClientGameTest::class.java)?.let { testInfo -> + if (!TestTags.isEnabled(testInfo.tag)) return + + GameTestRegistry.getAllTestFunctions().add( + TestFunction( + testName, + testName, + testInfo.template.ifEmpty { testName }, + adjustTimeout(testInfo.timeoutTicks), + 0, + true, + ) { value -> safeInvoke(method, value) }, + ) + GameTestRegistry.getAllTestClassNames().add(testClass.simpleName) + return + } + + fallbackRegister.accept(method) + } + + private fun safeInvoke(method: Method, value: Any) { + try { + var instance: Any? = null + if (!Modifier.isStatic(method.modifiers)) { + instance = method.declaringClass.getConstructor().newInstance() + } + method.invoke(instance, value) + } catch (e: InvocationTargetException) { + when (val cause = e.cause) { + is RuntimeException -> throw cause + else -> throw RuntimeException(cause) + } + } catch (e: ReflectiveOperationException) { + throw RuntimeException(e) + } + } +} diff --git a/src/testMod/kotlin/dan200/computercraft/gametest/core/TestReporters.kt b/src/testMod/kotlin/dan200/computercraft/gametest/core/TestReporters.kt new file mode 100644 index 000000000..4d9d4bf89 --- /dev/null +++ b/src/testMod/kotlin/dan200/computercraft/gametest/core/TestReporters.kt @@ -0,0 +1,48 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.gametest.core + +import net.minecraft.gametest.framework.GameTestInfo +import net.minecraft.gametest.framework.JUnitLikeTestReporter +import net.minecraft.gametest.framework.TestReporter +import java.io.File +import java.io.IOException +import java.nio.file.Files +import javax.xml.transform.TransformerException + +/** + * A test reporter which delegates to a list of other reporters. + */ +class MultiTestReporter(private val reporters: List) : TestReporter { + constructor(vararg reporters: TestReporter) : this(listOf(*reporters)) + + override fun onTestFailed(test: GameTestInfo) { + for (reporter in reporters) reporter.onTestFailed(test) + } + + override fun onTestSuccess(test: GameTestInfo) { + for (reporter in reporters) reporter.onTestSuccess(test) + } + + override fun finish() { + for (reporter in reporters) reporter.finish() + } +} + +/** + * Reports tests to a JUnit XML file. This is equivalent to [JUnitLikeTestReporter], except it ensures the destination + * directory exists. + */ +class JunitTestReporter constructor(destination: File) : JUnitLikeTestReporter(destination) { + override fun save(file: File) { + try { + Files.createDirectories(file.toPath().parent) + } catch (e: IOException) { + throw TransformerException("Failed to create parent directory", e) + } + super.save(file) + } +} diff --git a/src/testMod/kotlin/de/srendi/advancedperipherals/test/APClientTestExtensions.kt b/src/testMod/kotlin/de/srendi/advancedperipherals/test/APClientTestExtensions.kt new file mode 100644 index 000000000..985c59db5 --- /dev/null +++ b/src/testMod/kotlin/de/srendi/advancedperipherals/test/APClientTestExtensions.kt @@ -0,0 +1,33 @@ +package de.srendi.advancedperipherals.test + +import net.minecraft.client.Minecraft +import net.minecraft.gametest.framework.GameTestAssertException +import net.minecraft.gametest.framework.GameTestHelper +import net.minecraft.network.chat.Component +import java.util.function.Predicate + +fun GameTestHelper.chatContains(filter: Predicate) = Minecraft.getInstance().gui.chat.allMessages.any { filter.test(it.content) } + +fun GameTestHelper.assertChatContains(component: Component) { + if (!chatContains { it == component }) { + throw GameTestAssertException("Expected chat to contain $component") + } +} + +fun GameTestHelper.assertChatContains(filter: Predicate) { + if (!chatContains(filter)) { + throw GameTestAssertException("Expected chat to contain message matching filter") + } +} + +fun GameTestHelper.assertChatNotContains(component: Component) { + if (chatContains { it == component }) { + throw GameTestAssertException("Expected chat to not contain $component") + } +} + +fun GameTestHelper.assertChatNotContains(filter: Predicate) { + if (chatContains(filter)) { + throw GameTestAssertException("Expected chat to not contain message matching filter") + } +} \ No newline at end of file diff --git a/src/testMod/kotlin/de/srendi/advancedperipherals/test/APTestExtension.kt b/src/testMod/kotlin/de/srendi/advancedperipherals/test/APTestExtension.kt new file mode 100644 index 000000000..a0bf1a9e2 --- /dev/null +++ b/src/testMod/kotlin/de/srendi/advancedperipherals/test/APTestExtension.kt @@ -0,0 +1,28 @@ +package de.srendi.advancedperipherals.test + +import net.minecraft.gametest.framework.GameTestHelper + +fun isInRange(value: Double, start: Double, end: Double): Boolean { + val EPSILON = 1e-7 + + return value >= start - EPSILON && value < end + EPSILON +} + +fun GameTestHelper.assertDoubleInRange(value: Double, start: Double, end: Double, message: String) { + if (!(isInRange(value, start, end))) { + fail("$message, is $value, should be between $start and $end") + } +} + +fun GameTestHelper.assertDoubleIs(value: Double, compare: Double, message: String) { + if (!(isInRange(value, compare, compare))) { + fail("$message, is $value, should be $compare") + } +} + +fun GameTestHelper.assertDoubleIsNot(value: Double, compare: Double, message: String) { + if ((isInRange(value, compare, compare))) { + fail("$message, is $value, should be $compare") + } +} + diff --git a/src/testMod/kotlin/de/srendi/advancedperipherals/test/ChatBoxTest.kt b/src/testMod/kotlin/de/srendi/advancedperipherals/test/ChatBoxTest.kt new file mode 100644 index 000000000..245063187 --- /dev/null +++ b/src/testMod/kotlin/de/srendi/advancedperipherals/test/ChatBoxTest.kt @@ -0,0 +1,172 @@ +package de.srendi.advancedperipherals.test + +import dan200.computercraft.gametest.api.* +import dan200.computercraft.gametest.api.Timeouts.SECOND +import dan200.computercraft.gametest.core.ClientTestEvents +import net.minecraft.ChatFormatting +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.components.toasts.SystemToast +import net.minecraft.core.BlockPos +import net.minecraft.gametest.framework.GameTestHelper +import net.minecraft.network.chat.ClickEvent +import net.minecraft.network.chat.Component +import java.util.function.BiPredicate + +@GameTestHolder +class ChatBoxTest { + + private fun getFormattedMessage(bracketColor: String, openBracket: Char, closeBracket: Char, prefix: String, message: String): Component { + return Component.literal("$bracketColor$openBracket§r") + .append(prefix) + .append("$bracketColor$closeBracket§r ") + .append( + Component.literal("Red ").withStyle(ChatFormatting.RED) + .append(Component.literal("Bold ").withStyle(ChatFormatting.BOLD).withStyle(ChatFormatting.WHITE)) + .append(Component.literal("Click ").withStyle(ChatFormatting.UNDERLINE).withStyle(ChatFormatting.WHITE) + .withStyle { it.withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, "https://advancedperipherals.madefor.cc/")) }) + .append(Component.literal(message).withStyle(ChatFormatting.ITALIC).withStyle(ChatFormatting.AQUA)) + ) + } + + private fun getFormattedToast(bracketColor: ChatFormatting?, openBracket: Char, closeBracket: Char, prefix: String, message: String): List { + val prefixComponents = if (bracketColor != null) { + listOf( + Component.literal("$openBracket").withStyle(bracketColor), + Component.literal(prefix), + Component.literal("$closeBracket").withStyle(bracketColor), + Component.literal(" ") + ) + } else { + listOf( + Component.literal("$openBracket$prefix$closeBracket ") + ) + } + + return prefixComponents + listOf( + Component.literal("Red ").withStyle(ChatFormatting.RED), + Component.literal("Bold ").withStyle(ChatFormatting.WHITE).withStyle(ChatFormatting.BOLD), + Component.literal("Click ").withStyle(ChatFormatting.WHITE).withStyle(ChatFormatting.UNDERLINE) + .withStyle { it.withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, "https://advancedperipherals.madefor.cc/")) }, + Component.literal(message).withStyle(ChatFormatting.AQUA).withStyle(ChatFormatting.ITALIC) + ) + } + + private fun containsToast(filter: BiPredicate>): Boolean { + return ClientTestEvents.toasts.filterIsInstance().any { + val sink = ComponentFormattedCharSink() + it.messageLines.forEachIndexed { index, line -> + line.accept(sink) + if (index < it.messageLines.size - 1) { + sink.accept(0, sink.currentStyle!!, ' '.code) + } + } + + filter.test(it.title, sink.getComponents()) + } + } + + private fun assertContainsToast(title: Component, components: List) { + if (!containsToast { toastTitle, toastComponents -> toastTitle == title && toastComponents == components }) { + throw AssertionError("Toast with title $title and components $components not found") + } + } + + private fun assertNotContainsToast(title: Component, components: List) { + if (containsToast { toastTitle, toastComponents -> toastTitle == title && toastComponents == components }) { + throw AssertionError("Toast with title $title and components $components found") + } + } + + private fun assertNotContainsToast(filter: BiPredicate>) { + if (containsToast(filter)) { + throw AssertionError("Toast matching filter found") + } + } + + @ClientGameTest(timeoutTicks = 60 * SECOND) + fun chatBox(context: GameTestHelper) = context.sequence { + thenExecute { context.positionAt(BlockPos(2, 6, 2), 90f) } + thenOnClient { + Minecraft.getInstance().gui.chat.clearMessages(false) + ClientTestEvents.toasts.clear() + } + thenComputerOk() + + thenOnClient { + // sendMessage + context.assertChatContains(Component.literal("[§r").append("AP").append("]§r ").append("Default message")) + context.assertChatContains(Component.literal("[§r").append("GameTest").append("]§r ").append("Message with prefix")) + context.assertChatContains(Component.literal("<§r").append("GameTest").append(">§r ").append("Message with brackets")) + context.assertChatContains(Component.literal("§a<§r").append("GameTest").append("§a>§r ").append("Message with bracket color")) + context.assertChatNotContains(Component.literal("§a<§r").append("GameTest").append("§a>§r ").append("Message with short range")) + context.assertChatNotContains { it.toString().contains("Message with invalid brackets") } + + // sendMessageToPlayer + context.assertChatContains(Component.literal("[§r").append("AP").append("]§r ").append("Default message to player")) + context.assertChatContains(Component.literal("[§r").append("GameTest").append("]§r ").append("Message with prefix to player")) + context.assertChatContains(Component.literal("<§r").append("GameTest").append(">§r ").append("Message with brackets to player")) + context.assertChatContains(Component.literal("§a<§r").append("GameTest").append("§a>§r ").append("Message with bracket color to player")) + context.assertChatNotContains(Component.literal("§a<§r").append("GameTest").append("§a>§r ").append("Message with short range to player")) + context.assertChatNotContains { it.toString().contains("Message with invalid brackets to player") } + context.assertChatNotContains(Component.literal("[§r").append("AP").append("]§r ").append("Default message to invalid player")) + + // sendFormattedMessage + context.assertChatContains(getFormattedMessage("", '[', ']', "AP", "Default formatted message")) + context.assertChatContains(getFormattedMessage("", '[', ']', "GameTest", "Formatted message with prefix")) + context.assertChatContains(getFormattedMessage("", '<', '>', "GameTest", "Formatted message with brackets")) + context.assertChatContains(getFormattedMessage("§a", '<', '>', "GameTest", "Formatted message with bracket color")) + context.assertChatNotContains(getFormattedMessage("§a", '<', '>', "GameTest", "Formatted message with short range")) + context.assertChatNotContains { it.toString().contains("Formatted message with invalid brackets") } + + // sendFormattedMessageToPlayer + context.assertChatContains(getFormattedMessage("", '[', ']', "AP", "Default formatted message to player")) + context.assertChatContains(getFormattedMessage("", '[', ']', "GameTest", "Formatted message with prefix to player")) + context.assertChatContains(getFormattedMessage("", '<', '>', "GameTest", "Formatted message with brackets to player")) + context.assertChatContains(getFormattedMessage("§a", '<', '>', "GameTest", "Formatted message with bracket color to player")) + context.assertChatNotContains(getFormattedMessage("§a", '<', '>', "GameTest", "Formatted message with short range to player")) + context.assertChatNotContains { it.toString().contains("Formatted message with invalid brackets to player") } + context.assertChatNotContains(getFormattedMessage("", '[', ']', "AP", "Default formatted message to invalid player")) + + // sendToastToPlayer + val defaultToastTitle = Component.literal("Toast Title") + assertContainsToast(defaultToastTitle, listOf(Component.literal("[AP] Default toast to player"))) + assertContainsToast(defaultToastTitle, listOf(Component.literal("[GameTest] Toast with prefix to player"))) + assertContainsToast(defaultToastTitle, listOf(Component.literal(" Toast with brackets to player"))) + assertContainsToast(defaultToastTitle, listOf( + Component.literal("<").withStyle(ChatFormatting.GREEN), + Component.literal("GameTest"), + Component.literal(">").withStyle(ChatFormatting.GREEN), + Component.literal(" Toast with bracket color to player") + )) + assertNotContainsToast(defaultToastTitle, listOf( + Component.literal("<").withStyle(ChatFormatting.GREEN), + Component.literal("GameTest"), + Component.literal(">").withStyle(ChatFormatting.GREEN), + Component.literal(" Toast with short range to player") + )) + assertNotContainsToast { title, components -> title == defaultToastTitle && components.any { it.toString().contains("Toast with invalid brackets to player") } } + assertNotContainsToast(defaultToastTitle, listOf(Component.literal("[AP] Default toast to invalid player"))) + + // sendFormattedToastToPlayer + val formattedToastTitle = Component.literal("Formatted Toast Title").withStyle(ChatFormatting.DARK_PURPLE) + assertContainsToast(formattedToastTitle, getFormattedToast(null, '[', ']', "AP", "Default formatted toast to player")) + assertContainsToast(formattedToastTitle, getFormattedToast(null, '[', ']', "GameTest", "Formatted toast with prefix to player")) + assertContainsToast(formattedToastTitle, getFormattedToast(null, '<', '>', "GameTest", "Formatted toast with brackets to player")) + assertContainsToast(formattedToastTitle, getFormattedToast(ChatFormatting.GREEN, '<', '>', "GameTest", "Formatted toast with bracket color to player")) + assertNotContainsToast(formattedToastTitle, getFormattedToast(ChatFormatting.GREEN, '<', '>', "GameTest", "Formatted toast with short range to player")) + assertNotContainsToast { title, components -> title == formattedToastTitle && components.any { it.toString().contains("Formatted toast with invalid brackets to player") } } + assertNotContainsToast(formattedToastTitle, getFormattedToast(null, '[', ']', "AP", "Default formatted toast to invalid player")) + } + } + + @ClientGameTest + fun chatBox_Events(context: GameTestHelper) = context.sequence { + thenIdle(20) + thenOnClient { Minecraft.getInstance().player!!.chatSigned("This is a normal chat message", null) } + thenIdle(20) + thenOnClient { Minecraft.getInstance().player!!.chatSigned("\$This is a hidden chat message", null) } + thenIdle(20) + thenComputerOk() + } + +} \ No newline at end of file diff --git a/src/testMod/kotlin/de/srendi/advancedperipherals/test/ComponentFormattedCharSink.kt b/src/testMod/kotlin/de/srendi/advancedperipherals/test/ComponentFormattedCharSink.kt new file mode 100644 index 000000000..bc62f38b9 --- /dev/null +++ b/src/testMod/kotlin/de/srendi/advancedperipherals/test/ComponentFormattedCharSink.kt @@ -0,0 +1,44 @@ +package de.srendi.advancedperipherals.test + +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.Style +import net.minecraft.util.FormattedCharSink + +class ComponentFormattedCharSink : FormattedCharSink { + var currentStyle: Style? = null; + private var currentText: String = ""; + private val components = mutableListOf(); + + override fun accept(pPositionInCurrentSequence: Int, pStyle: Style, pCodePoint: Int): Boolean { + if (currentStyle?.equals(pStyle) == false) { + if (currentText.isNotEmpty()) { + components.add(Component.literal(currentText).withStyle(simplifyStyle(currentStyle!!))) + currentText = "" + } + } + + currentStyle = pStyle + currentText += String(Character.toChars(pCodePoint)) + + return true + } + + fun getComponents(): List{ + if (currentText.isNotEmpty()) { + components.add(Component.literal(currentText).withStyle(simplifyStyle(currentStyle!!))) + currentText = "" + } + + return components + } + + private fun simplifyStyle(style: Style): Style { + return style + .withBold(if (style.isBold) true else null) + .withItalic(if (style.isItalic) true else null) + .withUnderlined(if (style.isUnderlined) true else null) + .withStrikethrough(if (style.isStrikethrough) true else null) + .withObfuscated(if (style.isObfuscated) true else null) + } + +} \ No newline at end of file diff --git a/src/testMod/kotlin/de/srendi/advancedperipherals/test/ModIntegrTest.kt b/src/testMod/kotlin/de/srendi/advancedperipherals/test/ModIntegrTest.kt new file mode 100644 index 000000000..dd4dda8a8 --- /dev/null +++ b/src/testMod/kotlin/de/srendi/advancedperipherals/test/ModIntegrTest.kt @@ -0,0 +1,125 @@ +package de.srendi.advancedperipherals.test + +import dan200.computercraft.gametest.api.GameTestHolder +import dan200.computercraft.gametest.api.sequence +import dan200.computercraft.gametest.api.thenComputerOk +import dan200.computercraft.gametest.api.thenOnComputer +import dan200.computercraft.gametest.core.TestEvents +import mekanism.common.lib.radiation.RadiationManager +import net.minecraft.core.BlockPos +import net.minecraft.gametest.framework.GameTest +import net.minecraft.gametest.framework.GameTestHelper +import net.minecraft.world.InteractionHand +import net.minecraft.world.entity.EntityType +import net.minecraft.world.entity.animal.allay.Allay +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items + +@GameTestHolder +class ModIntegrTest { + + @GameTest + fun botaniaFlower(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest + fun botaniaManaPool(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest + fun botaniaSpreader(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest(timeoutTicks = 300) + fun minecraftBeacon(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest + fun minecraftNoteBlock(context: GameTestHelper) = context.sequence { + thenExecute { TestEvents.triggeredNoteBlocks.clear() } + thenComputerOk() + thenExecute { + val successBlock = BlockPos(2, 2, 1) + val failBlock = BlockPos(2, 2, 3) + + if (TestEvents.triggeredNoteBlocks.getOrDefault(context.absolutePos(successBlock), 0) != 1) + context.fail("Note Block should have played one time", successBlock) + + if (TestEvents.triggeredNoteBlocks.getOrDefault(context.absolutePos(failBlock), 0) != 0) + context.fail("Note Block should not have played", failBlock) + } + } + + @GameTest(timeoutTicks = 300) + fun minecraftNoteBlock_Triggering_Allay(context: GameTestHelper) = context.sequence { + // test if playNote triggers an allay + // related issue: https://github.com/IntelligenceModding/AdvancedPeripherals/issues/603 + + val item = Items.DIAMOND + var allay: Allay? = null + thenExecute { + allay = context.spawn(EntityType.ALLAY, 2, 3, 2) + allay?.setItemInHand(InteractionHand.MAIN_HAND, ItemStack(item)) + + context.spawnItem(item, 2f, 3f, 2f) + } + + thenWaitUntil { context.assertEntityNotPresent(EntityType.ITEM) } + thenWaitUntil { + if (allay?.inventory?.getItem(0)?.count != 1) + context.fail("Expected Allay to pick up item") + } + thenOnComputer { callPeripheral("left", "playNote") } + thenWaitUntil { context.assertEntityPresent(EntityType.ITEM) } + thenWaitUntil { + if (allay?.inventory?.getItem(0)?.count != 0) + context.fail("Expected Allay to drop item") + } + } + + @GameTest(setupTicks = 60) + fun mekanismradiation(context: GameTestHelper) = context.sequence { + RadiationManager.INSTANCE.clearSources() + + var radiation: Array? = null; + var rawRadiation = 0.0; + thenOnComputer { + radiation = callPeripheral("right", "getRadiation") + rawRadiation = callPeripheral("right", "getRadiationRaw")?.get(0) as Double + } + thenWaitUntil { + if (radiation?.get(0) == null) + context.fail("Radiation not set") + val value = (radiation?.get(0) as Map<*, *>)["radiation"] + if (value == null) { + context.fail("Radiation not found") + } + context.assertDoubleIs(value.toString().toDouble(), 99.9999, "Radiation incorrect") + + context.assertDoubleIs(rawRadiation, 1.0E-07, "Raw radiation incorrect") + } + thenExecute { + context.destroyBlock(BlockPos(3,2,2)) + } + thenOnComputer { + radiation = callPeripheral("right", "getRadiation") + rawRadiation = callPeripheral("right", "getRadiationRaw")?.get(0) as Double + } + thenWaitUntil { + if (radiation?.get(0) == null) + context.fail("Radiation not set") + val value = (radiation?.get(0) as Map<*, *>)["radiation"] + if (value == null) { + context.fail("Radiation not found") + } + context.assertDoubleInRange(value.toString().toDouble(), 2.49,2.51,"Radiation incorrect") + + context.assertDoubleInRange(rawRadiation, 2.49,2.51,"Raw radiation incorrect") + RadiationManager.INSTANCE.clearSources() + } + } +} \ No newline at end of file diff --git a/src/testMod/kotlin/de/srendi/advancedperipherals/test/PeripheralTest.kt b/src/testMod/kotlin/de/srendi/advancedperipherals/test/PeripheralTest.kt new file mode 100644 index 000000000..03cc155b0 --- /dev/null +++ b/src/testMod/kotlin/de/srendi/advancedperipherals/test/PeripheralTest.kt @@ -0,0 +1,60 @@ +package de.srendi.advancedperipherals.test + +import dan200.computercraft.gametest.api.GameTestHolder +import dan200.computercraft.gametest.api.sequence +import dan200.computercraft.gametest.api.thenComputerOk +import net.minecraft.gametest.framework.GameTest +import net.minecraft.gametest.framework.GameTestHelper + +@GameTestHolder +class PeripheralTest { + + @GameTest + fun environment(context: GameTestHelper) = context.sequence { + context.level.setWeatherParameters(6000, 0, false, false); + thenComputerOk(); + } + + // The ME System needs to boot up, so we use a sleep() in the specific lua script + // We set the timeoutTicks to 300 so the test does not fail + @GameTest(timeoutTicks = 600) + fun meCrafting(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest(timeoutTicks = 600) + fun meStorage(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest(timeoutTicks = 600) + fun meTransfer(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest + fun blockReader(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest + fun nbtStorage(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest(timeoutTicks = 300) + fun rsIntegrator(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest(timeoutTicks = 300) + fun geoScanner(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + + @GameTest(timeoutTicks = 300) + fun energyDet(context: GameTestHelper) = context.sequence { + thenComputerOk(); + } + +} \ No newline at end of file diff --git a/src/testMod/resources/META-INF/accesstransformer.cfg b/src/testMod/resources/META-INF/accesstransformer.cfg index 8fcc2e072..a8570dd52 100644 --- a/src/testMod/resources/META-INF/accesstransformer.cfg +++ b/src/testMod/resources/META-INF/accesstransformer.cfg @@ -4,4 +4,8 @@ public net.minecraft.gametest.framework.GameTestHelper m_177448_()Lnet/minecraft public net.minecraft.gametest.framework.GameTestHelper f_127595_ # testInfo public net.minecraft.gametest.framework.GameTestSequence f_127774_ # parent -public net.minecraft.gametest.framework.MultipleTestTracker f_127798_ # tests \ No newline at end of file +public net.minecraft.gametest.framework.MultipleTestTracker f_127798_ # tests + +public net.minecraft.client.gui.components.ChatComponent f_93760_ # allMessages +public net.minecraft.client.gui.components.toasts.SystemToast f_94821_ # title +public net.minecraft.client.gui.components.toasts.SystemToast f_94822_ # messageLines \ No newline at end of file diff --git a/src/testMod/resources/META-INF/mods.toml b/src/testMod/resources/META-INF/mods.toml index 66aca9170..f4095a7d5 100644 --- a/src/testMod/resources/META-INF/mods.toml +++ b/src/testMod/resources/META-INF/mods.toml @@ -1,18 +1,25 @@ -modLoader = "javafml" -loaderVersion = "[30,)" +modLoader="javafml" +loaderVersion="[30,)" -issueTrackerURL = "https://github.com/SquidDev-CC/CC-Tweaked/issues" -displayURL = "https://github.com/SquidDev-CC/CC-Tweaked" -logoFile = "pack.png" +issueTrackerURL="https://github.com/cc-tweaked/CC-Tweaked/issues" +displayURL="https://github.com/cc-tweaked/CC-Tweaked" +logoFile="pack.png" -credits = "Created by Daniel Ratcliffe (@DanTwoHundred)" -authors = "Daniel Ratcliffe, Aaron Mills, SquidDev" -license = "ComputerCraft Public License (https://raw.githubusercontent.com/dan200/ComputerCraft/master/LICENSE)" +credits="Created by Daniel Ratcliffe (@DanTwoHundred)" +authors="Daniel Ratcliffe, Aaron Mills, SquidDev" +license="ComputerCraft Public License (https://raw.githubusercontent.com/dan200/ComputerCraft/master/LICENSE)" [[mods]] -modId = "aptest" -version = "1.0.0" -displayName = "CC: Tweaked test framework" -description = ''' -A test framework for ensuring AP works correctly. Copied from CC:T code +modId="advancedperipheralstest" +version="1.0.0" +displayName="CC: Tweaked test framework" +description=''' +A test framework for ensuring CC: Tweaked works correctly. ''' + +[[dependencies.cctest]] +modId="computercraft" +mandatory=true +versionRange="[1.0,)" +ordering="AFTER" +side="BOTH" diff --git a/src/testMod/resources/advancedperipheralstest.mixins.json b/src/testMod/resources/advancedperipheralstest.mixins.json new file mode 100644 index 000000000..782eaa715 --- /dev/null +++ b/src/testMod/resources/advancedperipheralstest.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "package": "de.srendi.advancedperipherals.test.mixin", + "minVersion": "0.8", + "compatibilityLevel": "JAVA_17", + "injectors": { + "defaultRequire": 1 + }, + "mixins": [ + "RsBaseBlockMixin" + ] +} diff --git a/src/testMod/resources/ccgametest.mixins.json b/src/testMod/resources/ccgametest.mixins.json new file mode 100644 index 000000000..299086dc9 --- /dev/null +++ b/src/testMod/resources/ccgametest.mixins.json @@ -0,0 +1,21 @@ +{ + "required": true, + "package": "dan200.computercraft.mixin.gametest", + "minVersion": "0.8", + "compatibilityLevel": "JAVA_17", + "injectors": { + "defaultRequire": 1 + }, + "mixins": [ + "GameTestHelperAccessor", + "GameTestInfoAccessor", + "GameTestSequenceAccessor", + "GameTestSequenceMixin", + "TestCommandAccessor" + ], + "client": [ + "client.MinecraftMixin", + "client.WorldOpenFlowsMixin", + "client.LevelSummaryMixin" + ] +} diff --git a/src/testMod/server-files/computers/computer/0/startup.lua b/src/testMod/resources/data/advancedperipheralstest/computer/startup.lua similarity index 59% rename from src/testMod/server-files/computers/computer/0/startup.lua rename to src/testMod/resources/data/advancedperipheralstest/computer/startup.lua index b0b815745..b67ad6af7 100644 --- a/src/testMod/server-files/computers/computer/0/startup.lua +++ b/src/testMod/resources/data/advancedperipheralstest/computer/startup.lua @@ -4,6 +4,14 @@ if label == nil then return test.fail("Label a computer to use it.") end local fn, err = loadfile("tests/" .. label .. ".lua", nil, _ENV) if not fn then return test.fail(err) end +local source = "@" .. label .. ".lua" +debug.sethook(function() + local i = debug.getinfo(2, "lS") + if i.source == source and i.currentline then + test.log("At line " .. i.currentline) + end +end, "l") + local ok, err = pcall(fn) if not ok then return test.fail(err) end diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/chatboxtest.chatbox.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/chatboxtest.chatbox.lua new file mode 100644 index 000000000..f6e7dfafe --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/chatboxtest.chatbox.lua @@ -0,0 +1,97 @@ +--- +--- Advanced Peripherals Chat Box tests +--- Covers `sendMessage`, `sendMessageToPlayer`, `sendToastToPlayer`, `sendFormattedToastToPlayer`, +--- `sendFormattedMessage`, `sendFormattedMessageToPlayer` +--- + +sleep(3) + +chatBox = peripheral.find("chat_box") +test.assert(chatBox, "Peripheral not found") +config = chatBox.getConfiguration() + +function assertCooldown() + local currentCooldown = chatBox.getOperationCooldown("chatMessage") + test.assert(currentCooldown > 0 and currentCooldown <= config["chatMessage"]["cooldown"], "Cooldown should be active after a message was sent") + + while chatBox.getOperationCooldown("chatMessage") > 0 do + sleep(0.1) + end +end + +function assertSendMessage(fn, ...) + local message = arg[1] + test.assert(fn(unpack(arg)), message .. " should be sent") + assertCooldown() +end + +function assertFailedSendMessage(fn, ...) + local message = arg[1] + test.assert(not fn(unpack(arg)), message .. " should not be sent") + -- test.eq(0, chatBox.getOperationCooldown("chatMessage"), "Cooldown should not be active after a failed message") failed messages still trigger cooldown, maybe a bug? + assertCooldown() -- TODO Remove when fixed +end + +function getFormattedMessage(message) + local message = { + { text = "Red ", color = "red" }, + { text = "Bold ", color = "white", bold = true }, + { text = "Click ", underlined = true, color = "white", clickEvent = { action = "open_url", value = "https://advancedperipherals.madefor.cc/" } }, + { text = message, color = "aqua", italic = true } + } + local json = textutils.serialiseJSON(message) + return json +end + +-- Test sendMessage in different formats +assertSendMessage(chatBox.sendMessage, "Default message") +assertSendMessage(chatBox.sendMessage, "Message with prefix", "GameTest") +assertSendMessage(chatBox.sendMessage, "Message with brackets", "GameTest", "<>") +assertSendMessage(chatBox.sendMessage, "Message with bracket color", "GameTest", "<>", "&a") +assertSendMessage(chatBox.sendMessage, "Message with short range", "GameTest", "<>", "&a", 3) +assertFailedSendMessage(chatBox.sendMessage, "Message with invalid brackets", "GameTest", "<") + +-- Test sendMessageToPlayer in different formats +assertSendMessage(chatBox.sendMessageToPlayer, "Default message to player", "Dev") +assertSendMessage(chatBox.sendMessageToPlayer, "Message with prefix to player", "Dev", "GameTest") +assertSendMessage(chatBox.sendMessageToPlayer, "Message with brackets to player", "Dev", "GameTest", "<>") +assertSendMessage(chatBox.sendMessageToPlayer, "Message with bracket color to player", "Dev", "GameTest", "<>", "&a") +assertSendMessage(chatBox.sendMessageToPlayer, "Message with short range to player", "Dev", "GameTest", "<>", "&a", 3) +assertFailedSendMessage(chatBox.sendMessageToPlayer, "Message with invalid brackets to player", "Dev", "GameTest", "<") +assertFailedSendMessage(chatBox.sendMessageToPlayer, "Default message to invalid player", "InvalidPlayer") + +-- Test sendFormattedMessage in different formats +assertSendMessage(chatBox.sendFormattedMessage, getFormattedMessage("Default formatted message")) +assertSendMessage(chatBox.sendFormattedMessage, getFormattedMessage("Formatted message with prefix"), "GameTest") +assertSendMessage(chatBox.sendFormattedMessage, getFormattedMessage("Formatted message with brackets"), "GameTest", "<>") +assertSendMessage(chatBox.sendFormattedMessage, getFormattedMessage("Formatted message with bracket color"), "GameTest", "<>", "&a") +assertSendMessage(chatBox.sendFormattedMessage, getFormattedMessage("Formatted message with short range"), "GameTest", "<>", "&a", 3) +assertFailedSendMessage(chatBox.sendFormattedMessage, getFormattedMessage("Formatted message with invalid brackets"), "GameTest", "<") + +-- Test sendFormattedMessageToPlayer in different formats +assertSendMessage(chatBox.sendFormattedMessageToPlayer, getFormattedMessage("Default formatted message to player"), "Dev") +assertSendMessage(chatBox.sendFormattedMessageToPlayer, getFormattedMessage("Formatted message with prefix to player"), "Dev", "GameTest") +assertSendMessage(chatBox.sendFormattedMessageToPlayer, getFormattedMessage("Formatted message with brackets to player"), "Dev", "GameTest", "<>") +assertSendMessage(chatBox.sendFormattedMessageToPlayer, getFormattedMessage("Formatted message with bracket color to player"), "Dev", "GameTest", "<>", "&a") +assertSendMessage(chatBox.sendFormattedMessageToPlayer, getFormattedMessage("Formatted message with short range to player"), "Dev", "GameTest", "<>", "&a", 3) +assertFailedSendMessage(chatBox.sendFormattedMessageToPlayer, getFormattedMessage("Formatted message with invalid brackets to player"), "Dev", "GameTest", "<") +assertFailedSendMessage(chatBox.sendFormattedMessageToPlayer, getFormattedMessage("Default formatted message to invalid player"), "InvalidPlayer") + +-- Test sendToastToPlayer in different formats +assertSendMessage(chatBox.sendToastToPlayer, "Default toast to player", "Toast Title", "Dev") +assertSendMessage(chatBox.sendToastToPlayer, "Toast with prefix to player", "Toast Title", "Dev", "GameTest") +assertSendMessage(chatBox.sendToastToPlayer, "Toast with brackets to player", "Toast Title", "Dev", "GameTest", "<>") +assertSendMessage(chatBox.sendToastToPlayer, "Toast with bracket color to player", "Toast Title", "Dev", "GameTest", "<>", "&a") +assertSendMessage(chatBox.sendToastToPlayer, "Toast with short range to player", "Toast Title", "Dev", "GameTest", "<>", "&a", 3) +assertFailedSendMessage(chatBox.sendToastToPlayer, "Toast with invalid brackets to player", "Toast Title", "Dev", "GameTest", "<") +assertFailedSendMessage(chatBox.sendToastToPlayer, "Default toast to invalid player", "Toast Title", "InvalidPlayer") + +-- Test sendFormattedToastToPlayer in different formats +formattedToastTitle = textutils.serialiseJSON({ { text = "Formatted Toast Title", color = "dark_purple" } }) +assertSendMessage(chatBox.sendFormattedToastToPlayer, getFormattedMessage("Default formatted toast to player"), formattedToastTitle, "Dev") +assertSendMessage(chatBox.sendFormattedToastToPlayer, getFormattedMessage("Formatted toast with prefix to player"), formattedToastTitle, "Dev", "GameTest") +assertSendMessage(chatBox.sendFormattedToastToPlayer, getFormattedMessage("Formatted toast with brackets to player"), formattedToastTitle, "Dev", "GameTest", "<>") +assertSendMessage(chatBox.sendFormattedToastToPlayer, getFormattedMessage("Formatted toast with bracket color to player"), formattedToastTitle, "Dev", "GameTest", "<>", "&a") +assertSendMessage(chatBox.sendFormattedToastToPlayer, getFormattedMessage("Formatted toast with short range to player"),formattedToastTitle, "Dev", "GameTest", "<>", "&a", 3) +assertFailedSendMessage(chatBox.sendFormattedToastToPlayer, getFormattedMessage("Formatted toast with invalid brackets to player"), formattedToastTitle, "Dev", "GameTest", "<") +assertFailedSendMessage(chatBox.sendFormattedToastToPlayer, getFormattedMessage("Default formatted toast to invalid player"), formattedToastTitle, "InvalidPlayer") diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/chatboxtest.chatbox_events.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/chatboxtest.chatbox_events.lua new file mode 100644 index 000000000..3332524a4 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/chatboxtest.chatbox_events.lua @@ -0,0 +1,19 @@ +--- +--- Advanced Peripherals Chat Box tests +--- Covers `sendMessage`, `sendMessageToPlayer`, `sendToastToPlayer`, `sendFormattedToastToPlayer`, +--- `sendFormattedMessage`, `sendFormattedMessageToPlayer` +--- + +chatBox = peripheral.find("chat_box") +test.assert(chatBox, "Peripheral not found") + +function assertMessage(msg, hidden) + local event, username, message, uuid, isHidden = os.pullEvent("chat") + test.eq("chat", event, "Event should be 'chat'") + test.eq("Dev", username, "Username of sender should be 'Dev'") + test.eq(msg, message, "Message should be '" .. msg .. "'") + test.eq(hidden, isHidden, "Message should be " .. (hidden and "hidden" or "visible")) +end + +assertMessage("This is a normal chat message", false) +assertMessage("This is a hidden chat message", true) diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.botaniaflower.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.botaniaflower.lua new file mode 100644 index 000000000..bb2d265d6 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.botaniaflower.lua @@ -0,0 +1,41 @@ +--- +--- Advanced Peripherals tests for the Botania integration on Flowers +--- Covers `getMana`, `getMaxMana`, `isFloating`, +--- `isOnEnchantedSoil`, `isEmpty`, `isFull` +--- + +-- Test for Entropinnyum (Flower has full mana store and is on enchanted soil) +test.eq("mana_flower", peripheral.getType("left"), "Peripheral should be manaFlower") +entropinnyum = peripheral.wrap("left") +test.assert(entropinnyum, "Peripheral not found") + +test.eq(entropinnyum.getMaxMana(), entropinnyum.getMana(), "Entropinnyum should have full mana") +test.eq(6500, entropinnyum.getMaxMana(), "Entropinnyum should have a max mana of 6500") +test.assert(entropinnyum.isOnEnchantedSoil(), "Entropinnyum should be on enchanted soil") +test.assert(not entropinnyum.isFloating(), "Entropinnyum should not be floating") +-- test.assert(entropinnyum.isFull(), "Entropinnyum should be full") TODO: uncomment for 1.20.1 AP versions +-- test.assert(not entropinnyum.isEmpty(), "Entropinnyum should not be empty") TODO: uncomment for 1.20.1 AP versions + +-- Test for Endoflame (Flower has no mana stored and is on normal soil) +test.eq("mana_flower", peripheral.getType("right"), "Peripheral should be manaFlower") +endoflame = peripheral.wrap("right") +test.assert(endoflame, "Peripheral not found") + +test.eq(0, endoflame.getMana(), "Endoflame should have no mana") +test.eq(300, endoflame.getMaxMana(), "Endoflame should have a max mana of 300") +test.assert(not endoflame.isOnEnchantedSoil(), "Endoflame should not be on enchanted soil") +test.assert(not endoflame.isFloating(), "Endoflame should not be floating") +-- test.assert(not endoflame.isFull(), "Endoflame should not be full") TODO: uncomment for 1.20.1 AP versions +-- test.assert(endoflame.isEmpty(), "Endoflame should be empty") TODO: uncomment for 1.20.1 AP versions + +-- Test for Kekimurus (Flower has 1800 mana stored and is floating) +test.eq("mana_flower", peripheral.getType("back"), "Peripheral should be manaFlower") +kekimurus = peripheral.wrap("back") +test.assert(kekimurus, "Peripheral not found") + +test.eq(1800, kekimurus.getMana(), "Kekimurus should have 1800 mana") +test.eq(9001, kekimurus.getMaxMana(), "Kekimurus should have a max mana of 9001") +test.assert(not kekimurus.isOnEnchantedSoil(), "Kekimurus should not be on enchanted soil") +test.assert(kekimurus.isFloating(), "Kekimurus should be floating") +-- test.assert(not kekimurus.isFull(), "Kekimurus should not be full") TODO: uncomment for 1.20.1 AP versions +-- test.assert(not kekimurus.isEmpty(), "Kekimurus should not be empty") TODO: uncomment for 1.20.1 AP versions diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.botaniamanapool.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.botaniamanapool.lua new file mode 100644 index 000000000..fb53d7dc0 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.botaniamanapool.lua @@ -0,0 +1,40 @@ +--- +--- Advanced Peripherals tests for the Botania integration on Mana Pools +--- Covers `getMana`, `getMaxMana`, `getManaNeeded`, +--- `isEmpty`, `isFull` +--- + +-- TODO Add tests for canChargeItem, hasItems and getItems in 1.20.1 AP versions + +-- Test Fabulous Mana Pool (should be empty) +test.eq("mana_pool", peripheral.getType("left"), "Peripheral should be manaPool") +fabulous = peripheral.wrap("left") +test.assert(fabulous, "Peripheral not found") + +test.eq(0, fabulous.getMana(), "Mana should be 0") +test.eq(1000000, fabulous.getMaxMana(), "Max mana should be 1000000") +test.eq(1000000, fabulous.getManaNeeded(), "Mana needed should be 1000000") +-- test.assert(fabulous.isEmpty(), "Mana pool should be empty") TODO method currently not implemented +test.assert(not fabulous.isFull(), "Mana pool should not be full") + +-- Test Mana Pool (should have 36000 mana) +test.eq("mana_pool", peripheral.getType("right"), "Peripheral should be manaPool") +manaPool = peripheral.wrap("right") +test.assert(manaPool, "Peripheral not found") + +test.eq(36000, manaPool.getMana(), "Mana should be 36000") +test.eq(1000000, manaPool.getMaxMana(), "Max mana should be 1000000") +test.eq(964000, manaPool.getManaNeeded(), "Mana needed should be 964000") +-- test.assert(not manaPool.isEmpty(), "Mana pool should not be empty") TODO method currently not implemented +test.assert(not manaPool.isFull(), "Mana pool should not be full") + +-- Test Creative Mana Pool (should have 1000000 mana) +test.eq("mana_pool", peripheral.getType("back"), "Peripheral should be manaPool") +creative = peripheral.wrap("back") +test.assert(creative, "Peripheral not found") + +test.eq(1000000, creative.getMana(), "Mana should be 1000000") +test.eq(1000000, creative.getMaxMana(), "Max mana should be 1000000") +test.eq(0, creative.getManaNeeded(), "Mana needed should be 0") +-- test.assert(not creative.isEmpty(), "Mana pool should not be empty") TODO method currently not implemented +test.assert(creative.isFull(), "Mana pool should be full") diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.botaniaspreader.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.botaniaspreader.lua new file mode 100644 index 000000000..269e79c88 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.botaniaspreader.lua @@ -0,0 +1,66 @@ +--- +--- Advanced Peripherals tests for the Botania integration on Mana Spreaders +--- Covers `getMana`, `getMaxMana`, `getVariant`, +--- `isEmpty`, `isFull`, `getBounding` +--- + +-- TODO Add tests for hasLens and getLens in 1.20.1 AP versions + +-- Test basic Mana Spreader that is empty and directed towards a Mana Pool +test.eq("mana_spreader", peripheral.getType("left"), "Peripheral should be manaSpreader") +spreader = peripheral.wrap("left") +test.assert(spreader, "Peripheral not found") + +test.eq(0, spreader.getMana(), "Mana should be 0") +test.eq(1000, spreader.getMaxMana(), "Max mana should be 1000") +test.eq("MANA", spreader.getVariant(), "Variant should be MANA") +-- test.assert(spreader.isEmpty(), "Mana Spreader should be empty") TODO: method returns the wrong value currently +test.assert(not spreader.isFull(), "Mana Spreader should not be full") + +bounding = spreader.getBounding() +computerPos = test.getComputerPosition() +test.assert(bounding, "Spreader binding should be returned") +test.assert(computerPos, "Computer position should be returned") + +test.eq(computerPos.x + 1, bounding.x, "Bounding x should be set to Mana pool (+1 relative to computer)") +test.eq(computerPos.y, bounding.y, "Bounding y should be set to Mana pool (same as computer)") +test.eq(computerPos.z - 1, bounding.z, "Bounding z should be set to Mana pool (-1 relative to computer") + +-- Test Gaia Mana Spreader that is full +test.eq("mana_spreader", peripheral.getType("right"), "Peripheral should be manaSpreader") +gaiaSpreader = peripheral.wrap("right") +test.assert(gaiaSpreader, "Peripheral not found") + +test.eq(6400, gaiaSpreader.getMana(), "Mana should be 6400") +test.eq(6400, gaiaSpreader.getMaxMana(), "Max mana should be 6400") +test.eq("GAIA", gaiaSpreader.getVariant(), "Variant should be GAIA") +-- test.assert(not gaiaSpreader.isEmpty(), "Mana Spreader should not be empty") TODO: method returns the wrong value currently +test.assert(gaiaSpreader.isFull(), "Mana Spreader should be full") + +test.assert(not gaiaSpreader.getBounding(), "Mana Spreader should not be bound to anything") + +-- Test Elven Mana Spreader that has 177 mana +test.eq("mana_spreader", peripheral.getType("back"), "Peripheral should be manaSpreader") +elvenSpreader = peripheral.wrap("back") +test.assert(elvenSpreader, "Peripheral not found") + +test.eq(177, elvenSpreader.getMana(), "Mana should be 177") +test.eq(1000, elvenSpreader.getMaxMana(), "Max mana should be 1000") +test.eq("ELVEN", elvenSpreader.getVariant(), "Variant should be ELVEN") +-- test.assert(not elvenSpreader.isEmpty(), "Mana Spreader should not be empty") TODO: method returns the wrong value currently +test.assert(not elvenSpreader.isFull(), "Mana Spreader should not be full") + +test.assert(not elvenSpreader.getBounding(), "Mana Spreader should not be bound to anything") + +-- Test Pulse Mana Spreader that is empty +test.eq("mana_spreader", peripheral.getType("top"), "Peripheral should be manaSpreader") +pulseSpreader = peripheral.wrap("top") +test.assert(pulseSpreader, "Peripheral not found") + +test.eq(0, pulseSpreader.getMana(), "Mana should be 0") +test.eq(1000, pulseSpreader.getMaxMana(), "Max mana should be 1000") +test.eq("REDSTONE", pulseSpreader.getVariant(), "Variant should be REDSTONE") +-- test.assert(pulseSpreader.isEmpty(), "Mana Spreader should be empty") TODO: method returns the wrong value currently +test.assert(not pulseSpreader.isFull(), "Mana Spreader should not be full") + +test.assert(not pulseSpreader.getBounding(), "Mana Spreader should not be bound to anything") diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.minecraftbeacon.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.minecraftbeacon.lua new file mode 100644 index 000000000..ee97dc0a4 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.minecraftbeacon.lua @@ -0,0 +1,34 @@ +--- +--- Advanced Peripherals tests for the Minecraft integration on Beacons +--- Covers `getLevel`, `getPrimaryEffect`, `getSecondaryEffect` +--- + +-- Wait until the beacon structure is formed +sleep(4) + +-- Test left beacon, level 4, primary and secondary effect set to haste +test.eq("beacon", peripheral.getType("left"), "Peripheral should be beacon") +beacon = peripheral.wrap("left") +test.assert(beacon, "Peripheral not found") + +test.eq(4, beacon.getLevel(), "Level should be 4") +test.eq("effect.minecraft.haste", beacon.getPrimaryEffect(), "Primary effect should be haste") +test.eq("effect.minecraft.haste", beacon.getSecondaryEffect(), "Secondary effect should be haste") + +-- Test right beacon, level 4, primary effect set to speed, secondary effect not set +test.eq("beacon", peripheral.getType("right"), "Peripheral should be beacon") +beacon = peripheral.wrap("right") +test.assert(beacon, "Peripheral not found") + +test.eq(4, beacon.getLevel(), "Level should be 4") +test.eq("effect.minecraft.speed", beacon.getPrimaryEffect(), "Primary effect should be haste") +test.eq("none", beacon.getSecondaryEffect(), "Secondary effect should be none") + +-- Test top beacon, level 0, primary and secondary effect not set +test.eq("beacon", peripheral.getType("top"), "Peripheral should be beacon") +beacon = peripheral.wrap("top") +test.assert(beacon, "Peripheral not found") + +test.eq(0, beacon.getLevel(), "Level should be 0") +test.eq("none", beacon.getPrimaryEffect(), "Primary effect should be none") +test.eq("none", beacon.getSecondaryEffect(), "Secondary effect should be none") diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.minecraftnoteblock.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.minecraftnoteblock.lua new file mode 100644 index 000000000..3bf3ceec7 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/modintegrtest.minecraftnoteblock.lua @@ -0,0 +1,24 @@ +--- +--- Advanced Peripherals tests for the Minecraft integration on Note Blocks +--- Covers `playNote`, `getNote`, `changeNoteBy`, `changeNote` +--- + +test.eq("note_block", peripheral.getType("left"), "Peripheral should be noteBlock") +noteBlock = peripheral.wrap("left") +test.assert(noteBlock, "Peripheral not found") + +test.eq(4, noteBlock.getNote(), "Note should be 4") +test.eq(24, noteBlock.changeNoteBy(24), "Note should be 24 after setting it to 24") +test.eq(24, noteBlock.getNote(), "Note should be 24") +test.eq(0, noteBlock.changeNote(), "Note should be 0 after cycling it") +test.eq(0, noteBlock.getNote(), "Note should be 0") +test.eq(1, noteBlock.changeNote(), "Note should be 1 after cycling it") +test.eq(1, noteBlock.getNote(), "Note should be 1") +noteBlock.playNote() + +-- this note block has a block above it, so it should not play a note +test.eq("note_block", peripheral.getType("right"), "Peripheral should be noteBlock") +silentNoteBlock = peripheral.wrap("right") +test.assert(silentNoteBlock, "Peripheral not found") + +silentNoteBlock.playNote() \ No newline at end of file diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.blockreader.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.blockreader.lua new file mode 100644 index 000000000..c4e9edbc3 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.blockreader.lua @@ -0,0 +1,54 @@ +--- +--- Advanced Peripherals Block Reader tests +--- Covers `getBlockName`, `getBlockData`, `getBlockStates`, `isTileEntity` +--- + +function tableLength(T) + local count = 0 + for _ in pairs(T) do count = count + 1 end + return count +end + +-- Test Block Reader functions on a simple block +test.eq("block_reader", peripheral.getType("left"), "Peripheral should be blockReader") +simpleReader = peripheral.wrap("left") +test.assert(simpleReader, "Peripheral not found") + +test.eq("minecraft:polished_andesite", simpleReader.getBlockName(), "Block Name should be polished_andesite") +test.eq(nil, simpleReader.getBlockData(), "Block Data should be nil") +test.assert(not simpleReader.isTileEntity(), "Block should not be a TileEntity") +test.eq(0, tableLength(simpleReader.getBlockStates()), "Block State should be empty") + +-- Test Block Reader functions on a stair block +test.eq("block_reader", peripheral.getType("back"), "Peripheral should be blockReader") +stairReader = peripheral.wrap("back") +test.assert(stairReader, "Peripheral not found") + +test.eq("minecraft:polished_andesite_stairs", stairReader.getBlockName(), "Block Name should be polished_andesite") +test.eq(nil, stairReader.getBlockData(), "Block Data should be nil") +test.assert(not stairReader.isTileEntity(), "Block should not be a TileEntity") +test.eq(4, tableLength(stairReader.getBlockStates()), "Block State should not be empty") + +test.eq("east", stairReader.getBlockStates()["facing"], "Stair Facing should be east") +test.eq("bottom", stairReader.getBlockStates()["half"], "Stair Half should be bottom") +test.eq("straight", stairReader.getBlockStates()["shape"], "Stair Shape should be straight") +test.assert(not stairReader.getBlockStates()["waterlogged"], "Stair Waterlogged should be false") + +-- Test Block Reader functions on a sign block +test.eq("block_reader", peripheral.getType("right"), "Peripheral should be blockReader") +signReader = peripheral.wrap("right") +test.assert(signReader, "Peripheral not found") + +test.eq("minecraft:oak_sign", signReader.getBlockName(), "Block Name should be polished_andesite") +test.neq(nil, signReader.getBlockData(), "Block Data should not be nil") +test.assert(signReader.isTileEntity(), "Block should be a TileEntity") +test.eq(2, tableLength(signReader.getBlockStates()), "Block State should not be empty") + +test.eq(4, signReader.getBlockStates()["rotation"], "Sign Rotation should be 4") +test.assert(not signReader.getBlockStates()["waterlogged"], "Sign Waterlogged should be false") + +test.eq("black", signReader.getBlockData()["Color"], "Sign Color should be black") +test.eq("{\"text\":\"this\"}", signReader.getBlockData()["Text1"], "Sign Text1 should be 'this'") +test.eq("{\"text\":\"is a\"}", signReader.getBlockData()["Text2"], "Sign Text2 should be 'is a'") +test.eq("{\"text\":\"test\"}", signReader.getBlockData()["Text3"], "Sign Text3 should be 'test'") +test.eq("{\"text\":\"sign\"}", signReader.getBlockData()["Text4"], "Sign Text4 should be 'sign'") diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.energydet.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.energydet.lua new file mode 100644 index 000000000..4c442e746 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.energydet.lua @@ -0,0 +1,22 @@ +--- +--- Advanced Peripherals Energy Detector tests +--- Covers `getTransferRate`, `setTransferRateLimit`, `getTransferRateLimit` +--- + +test.eq("energy_detector", peripheral.getType("right"), "Peripheral should be energyDetector") + +det = peripheral.wrap("right") +det.setTransferRateLimit(0) +sleep(0.5) +test.eq(0, det.getTransferRate(), "Transfer Rate should be 0") +test.eq(0, det.getTransferRateLimit(), "Transfer Rate Limit should be 0") + +det.setTransferRateLimit(100) +sleep(0.5) +test.eq(100, det.getTransferRate(), "Transfer Rate should be 100") +test.eq(100, det.getTransferRateLimit(), "Transfer Rate Limit should be 100") + +det.setTransferRateLimit(4000000) +sleep(0.5) +test.eq(100000, det.getTransferRate(), "Transfer Rate should be 100000") -- Rate limit from the cables +test.eq(4000000, det.getTransferRateLimit(), "Transfer Rate Limit should be 4000000") diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.environment.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.environment.lua new file mode 100644 index 000000000..ac7cfda8f --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.environment.lua @@ -0,0 +1,5 @@ +detector = peripheral.find("environment_detector") +test.assert(detector, true, "Peripheral not found") + +isRaining = detector.isRaining() +test.eq(false, isRaining, "It should not rain") diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.geoscanner.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.geoscanner.lua new file mode 100644 index 000000000..f1b4861e4 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.geoscanner.lua @@ -0,0 +1,83 @@ +--- +--- Advanced Peripherals Geo Scanner tests +--- Covers `getFuelLevel`, `getMaxFuelLevel`, `cost`, +--- `scan`, `chunkAnalyze`, `getOperationCooldown` +--- + +-- TODO: replace getOperationCooldown with getScanCooldown once the function is implemented + +function testBlockAt(result, x, y, z, expectedName, expectedTag) + local blockEntry = nil + for _, entry in ipairs(result) do + if entry.x == x and entry.y == y and entry.z == z then + blockEntry = entry + break + end + end + + test.assert(blockEntry, ("Block at %d, %d, %d not found"):format(x, y, z)) + test.eq(expectedName, blockEntry["name"], ("Block at %d, %d, %d has the wrong name"):format(x, y, z)) + + local tagFound = false + for _, tag in ipairs(blockEntry["tags"]) do + if tag == expectedTag then + tagFound = true + break + end + end + test.assert(tagFound, ("Block at %d, %d, %d has the wrong tags"):format(x, y, z)) +end + +scanner = peripheral.find("geo_scanner") +test.assert(scanner, "Peripheral not found") + +config = scanner.getConfiguration() +fuelEnabled = scanner.getMaxFuelLevel() > 0 + +-- Test scan costs +test.eq(0, scanner.cost(1), "Scans with a range of 1 should be free") +test.eq(0, scanner.cost(config["scanBlocks"]["maxFreeRadius"]), "Scans with the maximum free radius should be free") +test.assert(scanner.cost(config["scanBlocks"]["maxFreeRadius"] + 1) > 0, "Scans with a radius larger than the maximum free radius should cost fuel") + +test.assert(scanner.cost(config["scanBlocks"]["maxCostRadius"]) > 0, "Scans with the maximum cost radius should cost fuel") +test.assert(scanner.cost(config["scanBlocks"]["maxCostRadius"] + 1) == nil, "Scans with a radius larger than the maximum cost radius should not be possible") + +-- Test scan results +scanResult = scanner.scan(1) +test.assert(scanResult, "Scan result should not be nil") + +currentCooldown = scanner.getOperationCooldown("scanBlocks") +test.assert(currentCooldown > 0 and currentCooldown <= config["scanBlocks"]["cooldown"], "Cooldown should be active after a scan") + +testBlockAt(scanResult, 0, 0, 0, "advancedperipherals:geo_scanner", "minecraft:block/minecraft:mineable/pickaxe") +testBlockAt(scanResult, 0, -1, 0, "computercraft:computer_advanced", "minecraft:block/minecraft:mineable/pickaxe") +testBlockAt(scanResult, 0, 1, 0, "minecraft:iron_ore", "minecraft:block/forge:ores/iron") +testBlockAt(scanResult, 0, -1, 1, "minecraft:polished_diorite", "minecraft:block/minecraft:mineable/pickaxe") +testBlockAt(scanResult, 0, -1, -1, "minecraft:polished_andesite", "minecraft:block/minecraft:mineable/pickaxe") +testBlockAt(scanResult, 1, -1, 0, "minecraft:polished_granite", "minecraft:block/minecraft:mineable/pickaxe") + +while scanner.getOperationCooldown("scanBlocks") > 0 do + sleep(0.25) +end + +-- Test chunk analysis with ores +chunkResult = scanner.chunkAnalyze() + +currentCooldown = scanner.getOperationCooldown("scanBlocks") +test.assert(currentCooldown > 0 and currentCooldown <= config["scanBlocks"]["cooldown"], "Cooldown should be active after a chunk analysis") + +test.assert(chunkResult, "Chunk analysis result should not be nil") +test.eq(1, chunkResult["minecraft:iron_ore"], "Iron ore count should be 1") +test.eq(2, chunkResult["minecraft:gold_ore"], "Gold ore count should be 2") +test.eq(3, chunkResult["minecraft:diamond_ore"], "Diamond ore count should be 3") + +while scanner.getOperationCooldown("scanBlocks") > 0 do + sleep(0.25) +end + +-- Test fuel consumption +if fuelEnabled then + scanResult = scanner.scan(config["scanBlocks"]["maxFreeRadius"] + 1) + test.assert(scanResult, "Scan result should not be nil") + test.eq(scanner.getMaxFuelLevel() - scanner.cost(config["scanBlocks"]["maxFreeRadius"] + 1), scanner.getFuelLevel(), "Fuel level should be reduced after a scan") +end diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.mecrafting.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.mecrafting.lua new file mode 100644 index 000000000..800f7b3c4 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.mecrafting.lua @@ -0,0 +1,86 @@ +--- +--- Advanced Peripherals ME Bridge crafting tests +--- Covers `isConnected`, `getEnergyUsage`, `isItemCrafting`, `isItemCraftable`, +--- `getItem`, `craftItem`, `listCraftableFluid`, `craftFluid`, `getCraftingCPUs`, +--- `getFluid`, `isFluidCrafting` +--- + +sleep(4) +bridge = peripheral.wrap("bottom") +test.assert(bridge, "Peripheral not found") + +isOnline = bridge.isConnected() +test.assert(isOnline, "Bridge is not connected/system is not online") + +validEnergy = bridge.getEnergyUsage() > 24 +test.assert(validEnergy, tostring(bridge.getEnergyUsage()) .. " Consumption does not seem right") + +-- The count is used for the crafting filter +waterFilter = {name="minecraft:water"} +stickFilter = {name="minecraft:stick", count=5} +planksFilter = {name="minecraft:oak_planks"} +logFilter = {name="minecraft:oak_log"} + +isItemCrafting = bridge.isItemCrafting(stickFilter) +test.assert(isItemCrafting == false, "There shouldn't be a crafting job") + +-- Should be true. We have a pattern for it. +isItemCraftable = bridge.isItemCraftable(stickFilter) +test.assert(isItemCraftable == true, "Stick should be craftable") + +-- Logs should not be craftable, we don't have a pattern for it +isItemCraftable = bridge.getItem(logFilter).isCraftable +-- We use == false here since the not keyword would also return true if the value is nil +test.assert(isItemCraftable == false, "Log should not be craftable") + +-- Planks are craftable +isItemCraftable = bridge.getItem(planksFilter).isCraftable +test.assert(isItemCraftable == true, "Planks should be craftable") + +stickCount = bridge.getItem(stickFilter).amount +test.assert(stickCount == 0, "We should not have sticks") + +craftingSuccessful = bridge.craftItem(stickFilter) +test.assert(craftingSuccessful, "Crafting failed") + +sleep(0.15) + +isItemCrafting = bridge.isItemCrafting(stickFilter) +test.assert(isItemCrafting, "There should be a crafting job") +-- Wait for the crafting to finish +sleep(1) + +-- A filter for 5 sticks should craft 8 sticks since we get 4 sticks per recipe +stickCount = bridge.getItem(stickFilter).amount +test.assert(stickCount == 8, "We should have 8 sticks") + +-- There is no getFluid function, so we need to do it like this +waterCount = bridge.getFluid({name="minecraft:water"}) +test.assert(waterCount, "We should not have water") + +craftingSuccessful = bridge.craftFluid(waterFilter) +test.assert(craftingSuccessful, "Crafting failed") + +-- We can't test if the amount of the water has increased since we don't have an actual way to craft water in vanilla. +-- We just have the pattern which uses a dummy recipe with one log to craft 1B water. The log ist just transferred to a chest + +-- But we can test if there is a job +sleep(0.5) +isFluidCrafting = bridge.isFluidCrafting(waterFilter) +test.assert(isFluidCrafting, "There should be a crafting job") + +cpus = bridge.getCraftingCPUs() +test.assert(#cpus == 3, "There should be three CPUs") + +cpuOne = nil +for i, cpu in ipairs(cpus) do + if cpu.name == "CPUOne" then + cpuOne = cpu + end +end +test.assert(cpuOne.isBusy == false, "CPU 1 should not be busy") +test.assert(cpuOne.storage == 131072, "CPU 1 should have 131072 bytes of storage") +test.assert(cpuOne.coProcessors == 1, "CPU 1 should have one CO-CPU") +test.assert(cpuOne.name == "CPUOne", "CPU 1 name should be CPUOne") +test.assert(cpuOne.selectionMode == "PLAYER_ONLY", "CPU 1 selectionMode should be PLAYER_ONLY") + diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.mestorage.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.mestorage.lua new file mode 100644 index 000000000..e1111ebfe --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.mestorage.lua @@ -0,0 +1,78 @@ +--- +--- Advanced Peripherals ME Bridge storage tests +--- Covers `isConnected`, `getEnergyUsage`, `getMaxEnergyStorage`, `getEnergyStorage`, +--- `getTotalItemStorage`, `getTotalFluidStorage`, `getUsedItemStorage`, `getUsedFluidStorage`, +--- `getAvailableItemStorage`, `getAvailableFluidStorage`, `listCells`, +--- + +sleep(4) +bridge = peripheral.wrap("bottom") +test.assert(bridge, "Peripheral not found") + +isOnline = bridge.isConnected() +test.assert(isOnline, "Bridge is not connected/system is not online") + +energyUsage = bridge.getEnergyUsage() +test.assert(energyUsage > 18, tostring(energyUsage) .. " Consumption does not seem right") + +maxEnergyStorage = bridge.getEnergyCapacity() +test.assert(maxEnergyStorage == 1608800, tostring(maxEnergyStorage) .. " Max Energy Storage does not seem right") + +energyStorage = bridge.getStoredEnergy() +test.assert(energyStorage > 1590000, tostring(energyStorage) .. " Energy Storage does not seem right") + +totalItemStorage = bridge.getTotalItemStorage() +test.assert(totalItemStorage == 65536, "Total item storage is not valid") + +totalFluidStorage = bridge.getTotalFluidStorage() +test.assert(totalFluidStorage == 65536, "Total fluid storage is not valid") + +usedItemStorage = bridge.getUsedItemStorage() +test.assert(usedItemStorage == 1088, "Used item storage is not valid") + +usedFluidStorage = bridge.getUsedFluidStorage() +test.assert(usedFluidStorage == 1025, "Used fluid storage is not valid") + +availableItemStorage = bridge.getAvailableItemStorage() +test.assert(availableItemStorage == 64448, "Available item storage is not valid") + +availableFluidStorage = bridge.getAvailableFluidStorage() +test.assert(availableFluidStorage == 64511, "Available fluid storage is not valid") + +cells = bridge.listCells() +test.assert(#cells == 2, "There should be 2 cells") +print(textutils.serialize(cells[1])) +itemCell = nil +fluidCell = nil +for _, cell in pairs(cells) do + if cell.type == "ae2:i" then + itemCell = cell + end +end +for _, cell in pairs(cells) do + if cell.type == "ae2:f" then + fluidCell = cell + end +end + +test.assert(itemCell, "Item cell not found") +test.assert(fluidCell, "Fluid cell not found") + +itemCellBytes = itemCell.bytes +print(textutils.serialize(itemCell)) +test.assert(itemCellBytes == 65536, "Item cell bytes is not valid") + +fluidCellBytes = fluidCell.bytes +test.assert(fluidCellBytes == 65536, "Fluid cell bytes is not valid") + +itemCellItem = itemCell.item.name +test.assert(itemCellItem == "ae2:item_storage_cell_64k", "Item cell item not found") + +fluidCellItem = fluidCell.item.name +test.assert(fluidCellItem == "ae2:fluid_storage_cell_64k", "Fluid cell item not found") + +itemCellBytesPerType = itemCell.bytesPerType +test.assert(itemCellBytesPerType == 512, "Item cell bytes per type is not valid") + +fluidCellBytesPerType = fluidCell.bytesPerType +test.assert(fluidCellBytesPerType == 512, "Fluid cell bytes per type is not valid") diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.metransfer.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.metransfer.lua new file mode 100644 index 000000000..2b46d3308 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.metransfer.lua @@ -0,0 +1,83 @@ +--- +--- Advanced Peripherals ME Bridge transfer tests +--- Covers `isConnected`, `getEnergyUsage`, `getItem`, `exportItemToPeripheral`, `exportItem`, +--- `importItem`, `importItemFromPeripheral` +--- + +sleep(4) +bridge = peripheral.wrap("me_bridge_20") +chest = peripheral.wrap("minecraft:chest_2") +test.assert(bridge, "Peripheral not found") +test.assert(chest, "Chest not found") + +isOnline = bridge.isConnected() +test.assert(isOnline, "Bridge is not connected/system is not online") + +energyUsage = bridge.getEnergyUsage() +test.assert(energyUsage > 17, tostring(energyUsage) .. " Consumption does not seem right") + +planksFilter = {name="minecraft:oak_planks", count=5} +logFilter = {name="minecraft:oak_log", count=8} + +exported, err = bridge.exportItem(planksFilter, peripheral.getName(chest)) +test.assert(exported == 5, "Export failed 5 planks to chest ".. (err or "")) + +planksItem = bridge.getItem(planksFilter) +test.assert(planksItem.amount == 251, "We should have 251 planks") + +chestPlanks = nil +for slot, item in pairs(chest.list()) do + if item.name == "minecraft:oak_planks" then + chestPlanks = item + end +end + +test.assert(chestPlanks, "Planks not found in chest") +test.assert(chestPlanks.count == 5, "We should have 5 planks in the chest") + +exported, err = bridge.exportItem(planksFilter, "front") +test.assert(exported == 5, "Export failed 5 planks to front:".. (err or "")) + +planksItem = bridge.getItem(planksFilter) +test.assert(planksItem.amount == 246, "We should have 246 planks") + +for slot, item in pairs(chest.list()) do + if item.name == "minecraft:oak_planks" then + chestPlanks = item + end +end + +test.assert(chestPlanks, "Planks not found in chest") +test.assert(chestPlanks.count == 10, "We should have 5 planks in the chest") + +imported, err = bridge.importItem(logFilter, peripheral.getName(chest)) +test.assert(imported == 8, "import failed 8 logs to chest".. (err or "")) + +logsItem = bridge.getItem(logFilter) +test.assert(logsItem.amount == 264, "We should have 264 logs") + +chestLogs = nil +for slot, item in pairs(chest.list()) do + if item.name == "minecraft:oak_log" then + chestLogs = item + end +end + +test.assert(chestLogs, "Logs not found in chest") +test.assert(chestLogs.count == 56, "We should have 56 logs in the chest") + +imported, err = bridge.importItem(logFilter, "south") +test.assert(imported == 8, "import failed 8 logs from south".. (err or "")) + +logsItem = bridge.getItem(logFilter) +test.assert(logsItem.amount == 272, "We should have 272 logs") + +chestLogs = nil +for slot, item in pairs(chest.list()) do + if item.name == "minecraft:oak_log" then + chestLogs = item + end +end + +test.assert(chestLogs, "Logs not found in chest") +test.assert(chestLogs.count == 48, "We should have 48 logs in the chest") \ No newline at end of file diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.nbtstorage.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.nbtstorage.lua new file mode 100644 index 000000000..6fcc6a357 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.nbtstorage.lua @@ -0,0 +1,38 @@ +--- +--- Advanced Peripherals NBT Storage tests +--- Covers `read`, `writeJson`, `writeTable` +--- + +TEST_STRING = "Hello, World!" +TEST_NUMBER = 42 +TEST_FLOAT = 3.14 +TEST_VALUE = "AP Game Test" +TEST_JSON_VALUE = "AP Game Test JSON" + +test.eq("nbt_storage", peripheral.getType("left"), "Peripheral should be nbtStorage") +storage = peripheral.wrap("left") +test.assert(storage, "Peripheral not found") + +-- Read data from the test structure and verify it +stored = storage.read() +test.assert(stored, "Storage should not be nil") +test.eq(TEST_STRING, stored["test_string"], ("Stored string should be '%s'"):format(TEST_STRING)) +test.eq(TEST_NUMBER, stored["test_number"], ("Stored number should be %d"):format(TEST_NUMBER)) +test.eq(TEST_FLOAT, stored["test_float"], ("Stored float should be %f"):format(TEST_FLOAT)) + +-- Write a table to the storage and verify it +storage.writeTable({ + test_value = TEST_VALUE, +}) + +stored = storage.read() +test.assert(stored, "Storage should not be nil") +test.eq(TEST_VALUE, stored["test_value"], ("Stored value should be '%s'"):format(TEST_VALUE)) + +-- Write a JSON string to the storage and verify it +success = storage.writeJson(textutils.serializeJSON({test_value = TEST_JSON_VALUE})) +test.assert(success, "Storage writeJson should return true") + +stored = storage.read() +test.assert(stored, "Storage should not be nil") +test.eq(TEST_JSON_VALUE, stored["test_value"], ("Stored value should be '%s'"):format(TEST_JSON_VALUE)) diff --git a/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.rsintegrator.lua b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.rsintegrator.lua new file mode 100644 index 000000000..a983e2040 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/computer/tests/peripheraltest.rsintegrator.lua @@ -0,0 +1,37 @@ +--- +--- Advanced Peripherals Redstone Integrator tests +--- Covers `getInput`, `getOutput`, `getAnalogInput`, +--- `getAnalogOutput`, `setOutput`, `setAnalogOutput` +--- + +test.eq("redstone_integrator", peripheral.getType("left"), "Peripheral should be redstone_integrator") +test.eq("redstone_integrator", peripheral.getType("right"), "Peripheral should be redstone_integrator") + +first = peripheral.wrap("left") +test.assert(first, "Peripheral not found") + +second = peripheral.wrap("right") +test.assert(second, "Peripheral not found") + +-- Test input for Redstone Block (full strength) +test.eq(15, second.getAnalogInput("back"), "Analog input should be 15 for Redstone Block") +test.assert(second.getInput("back"), "Digital input should be true for Redstone Block") + +-- Test output on the right integrator +second.setOutput("front", true) +sleep(1) +test.assert(second.getOutput("front"), "Digital output should be true") + +-- Test analog input on the left integrator (wired from the right integrator) +test.eq(13, first.getAnalogInput("front"), "Analog input should be 13") + +-- Test analog output on the right integrator +second.setAnalogOutput("front", 10) +sleep(1) +test.eq(10, second.getAnalogOutput("front"), "Analog output should be 10") + +-- Test analog input on the left integrator (wired from the right integrator) +test.eq(8, first.getAnalogInput("front"), "Analog input should be 8") + +-- Reset redstone output +second.setOutput("front", false) diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/chatboxtest.chatbox.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/chatboxtest.chatbox.snbt new file mode 100644 index 000000000..017725869 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/chatboxtest.chatbox.snbt @@ -0,0 +1,138 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [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: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "advancedperipherals:chat_box{orientation:west_up}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[Chat Box]"}, Items: [], id: "advancedperipherals:chat_box", peripheralSettings: {cooldowns: {chatMessage: 1717353228235L}}}}, + {pos: [2, 1, 3], state: "computercraft:computer_advanced{facing:west,state:on}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "chatboxtest.chatbox", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [2, 4, 0], state: "minecraft:air"}, + {pos: [2, 4, 1], state: "minecraft:air"}, + {pos: [2, 4, 2], state: "minecraft:polished_andesite"}, + {pos: [2, 4, 3], state: "minecraft:air"}, + {pos: [2, 4, 4], 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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "computercraft:computer_advanced{facing:west,state:on}", + "advancedperipherals:chat_box{orientation:west_up}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/chatboxtest.chatbox_events.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/chatboxtest.chatbox_events.snbt new file mode 100644 index 000000000..c3bc156f8 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/chatboxtest.chatbox_events.snbt @@ -0,0 +1,138 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [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: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "advancedperipherals:chat_box{orientation:west_up}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[Chat Box]"}, Items: [], id: "advancedperipherals:chat_box", peripheralSettings: {cooldowns: {chatMessage: 1717353228235L}}}}, + {pos: [2, 1, 3], state: "computercraft:computer_advanced{facing:west,state:on}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "chatboxtest.chatbox_events", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "computercraft:computer_advanced{facing:west,state:on}", + "advancedperipherals:chat_box{orientation:west_up}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.botaniaflower.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.botaniaflower.snbt new file mode 100644 index 000000000..02829a44b --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.botaniaflower.snbt @@ -0,0 +1,142 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [2, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 1], state: "botania:enchanted_soil"}, + {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 3], state: "minecraft:grass_block{snowy:false}"}, + {pos: [2, 0, 4], 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: [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: [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: [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: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "botania:entropinnyum", nbt: {ForgeCaps: {}, id: "botania:entropinnyum", mana: 6500, ticksExisted: 52900}}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:west,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "modintegrtest.botaniaflower", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "botania:endoflame", nbt: {ForgeCaps: {}, burnTime: 0, id: "botania:endoflame", mana: 0, ticksExisted: 9109}}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "botania:floating_kekimurus{waterlogged:false}", nbt: {ForgeCaps: {}, floating: {islandType: "GRASS"}, id: "botania:kekimurus", mana: 1800, ticksExisted: 26663}}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "botania:enchanted_soil", + "minecraft:grass_block{snowy:false}", + "minecraft:air", + "botania:entropinnyum", + "computercraft:computer_advanced{facing:west,state:blinking}", + "botania:endoflame", + "botania:floating_kekimurus{waterlogged:false}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.botaniamanapool.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.botaniamanapool.snbt new file mode 100644 index 000000000..0952833af --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.botaniamanapool.snbt @@ -0,0 +1,140 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [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: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "botania:fabulous_pool{color:none,waterlogged:false}", nbt: {ForgeCaps: {}, canAccept: 1b, canSpare: 1b, id: "botania:mana_pool", inputKey: "", mana: 0, manaCap: 1000000, outputKey: "", outputting: 0b}}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:west,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "modintegrtest.botaniamanapool", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "botania:mana_pool{color:none,waterlogged:false}", nbt: {ForgeCaps: {}, canAccept: 1b, canSpare: 1b, id: "botania:mana_pool", inputKey: "", mana: 36000, manaCap: 1000000, outputKey: "", outputting: 0b}}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "botania:creative_pool{color:none,waterlogged:false}", nbt: {ForgeCaps: {}, canAccept: 1b, canSpare: 1b, id: "botania:mana_pool", inputKey: "", mana: 1000000, manaCap: 1000000, outputKey: "", outputting: 1b}}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "botania:fabulous_pool{color:none,waterlogged:false}", + "computercraft:computer_advanced{facing:west,state:blinking}", + "botania:mana_pool{color:none,waterlogged:false}", + "botania:creative_pool{color:none,waterlogged:false}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.botaniaspreader.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.botaniaspreader.snbt new file mode 100644 index 000000000..4e42dbe4a --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.botaniaspreader.snbt @@ -0,0 +1,142 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [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: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "botania:mana_spreader{has_scaffolding:false,waterlogged:false}", nbt: {ForgeCaps: {}, Items: [], canShootBurst: 1b, forceClientBindingX: 10, forceClientBindingY: 60, forceClientBindingZ: -9, id: "botania:mana_spreader", inputKey: "", lastPingbackX: 0.0d, lastPingbackY: -2.147483648E9d, lastPingbackZ: 0.0d, mana: 0, mapmakerOverrideEnabled: 0b, mmForcedColor: 2162464, mmForcedGravity: 0.0f, mmForcedManaLossPerTick: 4.0f, mmForcedManaPayload: 160, mmForcedTicksBeforeManaLoss: 60, mmForcedVelocityMultiplier: 1.0f, outputKey: "", paddingColor: -1, pingbackTicks: 0, requestUpdate: 0b, rotationX: 180.0f, rotationY: -14.036243f, uuid: [I; -1335544995, 1721978540, -1824636534, -1356366853]}}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:west,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "modintegrtest.botaniaspreader", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "botania:gaia_spreader{has_scaffolding:false,waterlogged:false}", nbt: {ForgeCaps: {}, Items: [], canShootBurst: 1b, forceClientBindingX: 0, forceClientBindingY: -2147483648, forceClientBindingZ: 0, id: "botania:mana_spreader", inputKey: "", lastPingbackX: 0.0d, lastPingbackY: -2.147483648E9d, lastPingbackZ: 0.0d, mana: 6400, mapmakerOverrideEnabled: 0b, mmForcedColor: 2162464, mmForcedGravity: 0.0f, mmForcedManaLossPerTick: 4.0f, mmForcedManaPayload: 160, mmForcedTicksBeforeManaLoss: 60, mmForcedVelocityMultiplier: 1.0f, outputKey: "", paddingColor: -1, pingbackTicks: 0, requestUpdate: 0b, rotationX: 0.0f, rotationY: 90.0f, uuid: [I; 488019582, 1505248039, -1662086631, -1403515204]}}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "botania:mana_pool{color:none,waterlogged:false}", nbt: {ForgeCaps: {}, canAccept: 1b, canSpare: 1b, id: "botania:mana_pool", inputKey: "", mana: 0, manaCap: 1000000, outputKey: "", outputting: 0b}}, + {pos: [3, 1, 2], state: "botania:elven_spreader{has_scaffolding:false,waterlogged:false}", nbt: {ForgeCaps: {}, Items: [], canShootBurst: 1b, forceClientBindingX: 0, forceClientBindingY: -2147483648, forceClientBindingZ: 0, id: "botania:mana_spreader", inputKey: "", lastPingbackX: 0.0d, lastPingbackY: -2.147483648E9d, lastPingbackZ: 0.0d, mana: 177, mapmakerOverrideEnabled: 0b, mmForcedColor: 2162464, mmForcedGravity: 0.0f, mmForcedManaLossPerTick: 4.0f, mmForcedManaPayload: 160, mmForcedTicksBeforeManaLoss: 60, mmForcedVelocityMultiplier: 1.0f, outputKey: "", paddingColor: -1, pingbackTicks: 0, requestUpdate: 0b, rotationX: 0.0f, rotationY: 90.0f, uuid: [I; -360645062, -1521991436, -1469095441, -381296848]}}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], 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: [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: [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: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "botania:redstone_spreader{has_scaffolding:false,waterlogged:false}", nbt: {ForgeCaps: {}, Items: [], canShootBurst: 1b, forceClientBindingX: 0, forceClientBindingY: -2147483648, forceClientBindingZ: 0, id: "botania:mana_spreader", inputKey: "", lastPingbackX: 0.0d, lastPingbackY: -2.147483648E9d, lastPingbackZ: 0.0d, mana: 0, mapmakerOverrideEnabled: 0b, mmForcedColor: 2162464, mmForcedGravity: 0.0f, mmForcedManaLossPerTick: 4.0f, mmForcedManaPayload: 160, mmForcedTicksBeforeManaLoss: 60, mmForcedVelocityMultiplier: 1.0f, outputKey: "", paddingColor: -1, pingbackTicks: 0, requestUpdate: 0b, rotationX: 0.0f, rotationY: 90.0f, uuid: [I; 758151075, -302429323, -1148181277, 1481371264]}}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "botania:mana_spreader{has_scaffolding:false,waterlogged:false}", + "computercraft:computer_advanced{facing:west,state:blinking}", + "botania:gaia_spreader{has_scaffolding:false,waterlogged:false}", + "botania:mana_pool{color:none,waterlogged:false}", + "botania:elven_spreader{has_scaffolding:false,waterlogged:false}", + "botania:redstone_spreader{has_scaffolding:false,waterlogged:false}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.mekanismradiation.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.mekanismradiation.snbt new file mode 100644 index 000000000..35eee5557 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.mekanismradiation.snbt @@ -0,0 +1,139 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "advancedperipherals:environment_detector{orientation:north_up}", nbt: {ForgeCaps: {}, Items: [], id: "advancedperipherals:environment_detector"}}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 1, ForgeCaps: {}, Label: "modintegrtest.mekanismradiation", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "mekanism:radioactive_waste_barrel{active:false,facing:north,fluid_logged:empty}", nbt: {ForgeCaps: {}, GasTanks: [{Tank: 0b, stored: {amount: 1000L, gasName: "mekanism:spent_nuclear_waste"}}], activeState: 0b, componentFrequency: {}, currentRedstone: 14, id: "mekanism:radioactive_waste_barrel", redstone: 0b, updateDelay: 0}}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "advancedperipherals:environment_detector{orientation:north_up}", + "computercraft:computer_advanced{facing:north,state:blinking}", + "mekanism:radioactive_waste_barrel{active:false,facing:north,fluid_logged:empty}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.minecraftbeacon.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.minecraftbeacon.snbt new file mode 100644 index 000000000..64f2fd44a --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.minecraftbeacon.snbt @@ -0,0 +1,707 @@ +{ + DataVersion: 3120, + size: [11, 7, 9], + 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: [0, 0, 8], 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: [1, 0, 8], 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: [2, 0, 8], 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: [3, 0, 8], 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: [4, 0, 8], 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: [5, 0, 8], 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: [6, 0, 8], 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: [7, 0, 8], state: "minecraft:polished_andesite"}, + {pos: [8, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [8, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [8, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [8, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [8, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [8, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [8, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [8, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [8, 0, 8], state: "minecraft:polished_andesite"}, + {pos: [9, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [9, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [9, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [9, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [9, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [9, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [9, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [9, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [9, 0, 8], state: "minecraft:polished_andesite"}, + {pos: [10, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [10, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [10, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [10, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [10, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [10, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [10, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [10, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [10, 0, 8], state: "minecraft:polished_andesite"}, + {pos: [0, 1, 0], state: "minecraft:iron_block"}, + {pos: [0, 1, 1], state: "minecraft:iron_block"}, + {pos: [0, 1, 2], state: "minecraft:iron_block"}, + {pos: [0, 1, 3], state: "minecraft:iron_block"}, + {pos: [0, 1, 4], state: "minecraft:iron_block"}, + {pos: [0, 1, 5], state: "minecraft:iron_block"}, + {pos: [0, 1, 6], state: "minecraft:iron_block"}, + {pos: [0, 1, 7], state: "minecraft:iron_block"}, + {pos: [0, 1, 8], state: "minecraft:iron_block"}, + {pos: [1, 1, 0], state: "minecraft:iron_block"}, + {pos: [1, 1, 1], state: "minecraft:iron_block"}, + {pos: [1, 1, 2], state: "minecraft:iron_block"}, + {pos: [1, 1, 3], state: "minecraft:iron_block"}, + {pos: [1, 1, 4], state: "minecraft:iron_block"}, + {pos: [1, 1, 5], state: "minecraft:iron_block"}, + {pos: [1, 1, 6], state: "minecraft:iron_block"}, + {pos: [1, 1, 7], state: "minecraft:iron_block"}, + {pos: [1, 1, 8], state: "minecraft:iron_block"}, + {pos: [2, 1, 0], state: "minecraft:iron_block"}, + {pos: [2, 1, 1], state: "minecraft:iron_block"}, + {pos: [2, 1, 2], state: "minecraft:iron_block"}, + {pos: [2, 1, 3], state: "minecraft:iron_block"}, + {pos: [2, 1, 4], state: "minecraft:iron_block"}, + {pos: [2, 1, 5], state: "minecraft:iron_block"}, + {pos: [2, 1, 6], state: "minecraft:iron_block"}, + {pos: [2, 1, 7], state: "minecraft:iron_block"}, + {pos: [2, 1, 8], state: "minecraft:iron_block"}, + {pos: [3, 1, 0], state: "minecraft:iron_block"}, + {pos: [3, 1, 1], state: "minecraft:iron_block"}, + {pos: [3, 1, 2], state: "minecraft:iron_block"}, + {pos: [3, 1, 3], state: "minecraft:iron_block"}, + {pos: [3, 1, 4], state: "minecraft:iron_block"}, + {pos: [3, 1, 5], state: "minecraft:iron_block"}, + {pos: [3, 1, 6], state: "minecraft:iron_block"}, + {pos: [3, 1, 7], state: "minecraft:iron_block"}, + {pos: [3, 1, 8], state: "minecraft:iron_block"}, + {pos: [4, 1, 0], state: "minecraft:iron_block"}, + {pos: [4, 1, 1], state: "minecraft:iron_block"}, + {pos: [4, 1, 2], state: "minecraft:iron_block"}, + {pos: [4, 1, 3], state: "minecraft:iron_block"}, + {pos: [4, 1, 4], state: "minecraft:iron_block"}, + {pos: [4, 1, 5], state: "minecraft:iron_block"}, + {pos: [4, 1, 6], state: "minecraft:iron_block"}, + {pos: [4, 1, 7], state: "minecraft:iron_block"}, + {pos: [4, 1, 8], state: "minecraft:iron_block"}, + {pos: [5, 1, 0], state: "minecraft:iron_block"}, + {pos: [5, 1, 1], state: "minecraft:iron_block"}, + {pos: [5, 1, 2], state: "minecraft:iron_block"}, + {pos: [5, 1, 3], state: "minecraft:iron_block"}, + {pos: [5, 1, 4], state: "minecraft:iron_block"}, + {pos: [5, 1, 5], state: "minecraft:iron_block"}, + {pos: [5, 1, 6], state: "minecraft:iron_block"}, + {pos: [5, 1, 7], state: "minecraft:iron_block"}, + {pos: [5, 1, 8], state: "minecraft:iron_block"}, + {pos: [6, 1, 0], state: "minecraft:iron_block"}, + {pos: [6, 1, 1], state: "minecraft:iron_block"}, + {pos: [6, 1, 2], state: "minecraft:iron_block"}, + {pos: [6, 1, 3], state: "minecraft:iron_block"}, + {pos: [6, 1, 4], state: "minecraft:iron_block"}, + {pos: [6, 1, 5], state: "minecraft:iron_block"}, + {pos: [6, 1, 6], state: "minecraft:iron_block"}, + {pos: [6, 1, 7], state: "minecraft:iron_block"}, + {pos: [6, 1, 8], state: "minecraft:iron_block"}, + {pos: [7, 1, 0], state: "minecraft:iron_block"}, + {pos: [7, 1, 1], state: "minecraft:iron_block"}, + {pos: [7, 1, 2], state: "minecraft:iron_block"}, + {pos: [7, 1, 3], state: "minecraft:iron_block"}, + {pos: [7, 1, 4], state: "minecraft:iron_block"}, + {pos: [7, 1, 5], state: "minecraft:iron_block"}, + {pos: [7, 1, 6], state: "minecraft:iron_block"}, + {pos: [7, 1, 7], state: "minecraft:iron_block"}, + {pos: [7, 1, 8], state: "minecraft:iron_block"}, + {pos: [8, 1, 0], state: "minecraft:iron_block"}, + {pos: [8, 1, 1], state: "minecraft:iron_block"}, + {pos: [8, 1, 2], state: "minecraft:iron_block"}, + {pos: [8, 1, 3], state: "minecraft:iron_block"}, + {pos: [8, 1, 4], state: "minecraft:iron_block"}, + {pos: [8, 1, 5], state: "minecraft:iron_block"}, + {pos: [8, 1, 6], state: "minecraft:iron_block"}, + {pos: [8, 1, 7], state: "minecraft:iron_block"}, + {pos: [8, 1, 8], state: "minecraft:iron_block"}, + {pos: [9, 1, 0], state: "minecraft:iron_block"}, + {pos: [9, 1, 1], state: "minecraft:iron_block"}, + {pos: [9, 1, 2], state: "minecraft:iron_block"}, + {pos: [9, 1, 3], state: "minecraft:iron_block"}, + {pos: [9, 1, 4], state: "minecraft:iron_block"}, + {pos: [9, 1, 5], state: "minecraft:iron_block"}, + {pos: [9, 1, 6], state: "minecraft:iron_block"}, + {pos: [9, 1, 7], state: "minecraft:iron_block"}, + {pos: [9, 1, 8], state: "minecraft:iron_block"}, + {pos: [10, 1, 0], state: "minecraft:iron_block"}, + {pos: [10, 1, 1], state: "minecraft:iron_block"}, + {pos: [10, 1, 2], state: "minecraft:iron_block"}, + {pos: [10, 1, 3], state: "minecraft:iron_block"}, + {pos: [10, 1, 4], state: "minecraft:iron_block"}, + {pos: [10, 1, 5], state: "minecraft:iron_block"}, + {pos: [10, 1, 6], state: "minecraft:iron_block"}, + {pos: [10, 1, 7], state: "minecraft:iron_block"}, + {pos: [10, 1, 8], state: "minecraft:iron_block"}, + {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: [0, 2, 8], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:iron_block"}, + {pos: [1, 2, 2], state: "minecraft:iron_block"}, + {pos: [1, 2, 3], state: "minecraft:iron_block"}, + {pos: [1, 2, 4], state: "minecraft:iron_block"}, + {pos: [1, 2, 5], state: "minecraft:iron_block"}, + {pos: [1, 2, 6], state: "minecraft:iron_block"}, + {pos: [1, 2, 7], state: "minecraft:iron_block"}, + {pos: [1, 2, 8], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:iron_block"}, + {pos: [2, 2, 2], state: "minecraft:iron_block"}, + {pos: [2, 2, 3], state: "minecraft:iron_block"}, + {pos: [2, 2, 4], state: "minecraft:iron_block"}, + {pos: [2, 2, 5], state: "minecraft:iron_block"}, + {pos: [2, 2, 6], state: "minecraft:iron_block"}, + {pos: [2, 2, 7], state: "minecraft:iron_block"}, + {pos: [2, 2, 8], state: "minecraft:air"}, + {pos: [3, 2, 0], state: "minecraft:air"}, + {pos: [3, 2, 1], state: "minecraft:iron_block"}, + {pos: [3, 2, 2], state: "minecraft:iron_block"}, + {pos: [3, 2, 3], state: "minecraft:iron_block"}, + {pos: [3, 2, 4], state: "minecraft:iron_block"}, + {pos: [3, 2, 5], state: "minecraft:iron_block"}, + {pos: [3, 2, 6], state: "minecraft:iron_block"}, + {pos: [3, 2, 7], state: "minecraft:iron_block"}, + {pos: [3, 2, 8], state: "minecraft:air"}, + {pos: [4, 2, 0], state: "minecraft:air"}, + {pos: [4, 2, 1], state: "minecraft:iron_block"}, + {pos: [4, 2, 2], state: "minecraft:iron_block"}, + {pos: [4, 2, 3], state: "minecraft:iron_block"}, + {pos: [4, 2, 4], state: "minecraft:iron_block"}, + {pos: [4, 2, 5], state: "minecraft:iron_block"}, + {pos: [4, 2, 6], state: "minecraft:iron_block"}, + {pos: [4, 2, 7], state: "minecraft:iron_block"}, + {pos: [4, 2, 8], state: "minecraft:air"}, + {pos: [5, 2, 0], state: "minecraft:air"}, + {pos: [5, 2, 1], state: "minecraft:iron_block"}, + {pos: [5, 2, 2], state: "minecraft:iron_block"}, + {pos: [5, 2, 3], state: "minecraft:iron_block"}, + {pos: [5, 2, 4], state: "minecraft:iron_block"}, + {pos: [5, 2, 5], state: "minecraft:iron_block"}, + {pos: [5, 2, 6], state: "minecraft:iron_block"}, + {pos: [5, 2, 7], state: "minecraft:iron_block"}, + {pos: [5, 2, 8], state: "minecraft:air"}, + {pos: [6, 2, 0], state: "minecraft:air"}, + {pos: [6, 2, 1], state: "minecraft:iron_block"}, + {pos: [6, 2, 2], state: "minecraft:iron_block"}, + {pos: [6, 2, 3], state: "minecraft:iron_block"}, + {pos: [6, 2, 4], state: "minecraft:iron_block"}, + {pos: [6, 2, 5], state: "minecraft:iron_block"}, + {pos: [6, 2, 6], state: "minecraft:iron_block"}, + {pos: [6, 2, 7], state: "minecraft:iron_block"}, + {pos: [6, 2, 8], state: "minecraft:air"}, + {pos: [7, 2, 0], state: "minecraft:air"}, + {pos: [7, 2, 1], state: "minecraft:iron_block"}, + {pos: [7, 2, 2], state: "minecraft:iron_block"}, + {pos: [7, 2, 3], state: "minecraft:iron_block"}, + {pos: [7, 2, 4], state: "minecraft:iron_block"}, + {pos: [7, 2, 5], state: "minecraft:iron_block"}, + {pos: [7, 2, 6], state: "minecraft:iron_block"}, + {pos: [7, 2, 7], state: "minecraft:iron_block"}, + {pos: [7, 2, 8], state: "minecraft:air"}, + {pos: [8, 2, 0], state: "minecraft:air"}, + {pos: [8, 2, 1], state: "minecraft:iron_block"}, + {pos: [8, 2, 2], state: "minecraft:iron_block"}, + {pos: [8, 2, 3], state: "minecraft:iron_block"}, + {pos: [8, 2, 4], state: "minecraft:iron_block"}, + {pos: [8, 2, 5], state: "minecraft:iron_block"}, + {pos: [8, 2, 6], state: "minecraft:iron_block"}, + {pos: [8, 2, 7], state: "minecraft:iron_block"}, + {pos: [8, 2, 8], state: "minecraft:air"}, + {pos: [9, 2, 0], state: "minecraft:air"}, + {pos: [9, 2, 1], state: "minecraft:iron_block"}, + {pos: [9, 2, 2], state: "minecraft:iron_block"}, + {pos: [9, 2, 3], state: "minecraft:iron_block"}, + {pos: [9, 2, 4], state: "minecraft:iron_block"}, + {pos: [9, 2, 5], state: "minecraft:iron_block"}, + {pos: [9, 2, 6], state: "minecraft:iron_block"}, + {pos: [9, 2, 7], state: "minecraft:iron_block"}, + {pos: [9, 2, 8], state: "minecraft:air"}, + {pos: [10, 2, 0], state: "minecraft:air"}, + {pos: [10, 2, 1], state: "minecraft:air"}, + {pos: [10, 2, 2], state: "minecraft:air"}, + {pos: [10, 2, 3], state: "minecraft:air"}, + {pos: [10, 2, 4], state: "minecraft:air"}, + {pos: [10, 2, 5], state: "minecraft:air"}, + {pos: [10, 2, 6], state: "minecraft:air"}, + {pos: [10, 2, 7], state: "minecraft:air"}, + {pos: [10, 2, 8], 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: [0, 3, 8], 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: [1, 3, 8], state: "minecraft:air"}, + {pos: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "minecraft:air"}, + {pos: [2, 3, 2], state: "minecraft:iron_block"}, + {pos: [2, 3, 3], state: "minecraft:iron_block"}, + {pos: [2, 3, 4], state: "minecraft:iron_block"}, + {pos: [2, 3, 5], state: "minecraft:iron_block"}, + {pos: [2, 3, 6], state: "minecraft:iron_block"}, + {pos: [2, 3, 7], state: "minecraft:air"}, + {pos: [2, 3, 8], state: "minecraft:air"}, + {pos: [3, 3, 0], state: "minecraft:air"}, + {pos: [3, 3, 1], state: "minecraft:air"}, + {pos: [3, 3, 2], state: "minecraft:iron_block"}, + {pos: [3, 3, 3], state: "minecraft:iron_block"}, + {pos: [3, 3, 4], state: "minecraft:iron_block"}, + {pos: [3, 3, 5], state: "minecraft:iron_block"}, + {pos: [3, 3, 6], state: "minecraft:iron_block"}, + {pos: [3, 3, 7], state: "minecraft:air"}, + {pos: [3, 3, 8], state: "minecraft:air"}, + {pos: [4, 3, 0], state: "minecraft:air"}, + {pos: [4, 3, 1], state: "minecraft:air"}, + {pos: [4, 3, 2], state: "minecraft:iron_block"}, + {pos: [4, 3, 3], state: "minecraft:iron_block"}, + {pos: [4, 3, 4], state: "minecraft:iron_block"}, + {pos: [4, 3, 5], state: "minecraft:iron_block"}, + {pos: [4, 3, 6], state: "minecraft:iron_block"}, + {pos: [4, 3, 7], state: "minecraft:air"}, + {pos: [4, 3, 8], state: "minecraft:air"}, + {pos: [5, 3, 0], state: "minecraft:air"}, + {pos: [5, 3, 1], state: "minecraft:air"}, + {pos: [5, 3, 2], state: "minecraft:iron_block"}, + {pos: [5, 3, 3], state: "minecraft:iron_block"}, + {pos: [5, 3, 4], state: "minecraft:iron_block"}, + {pos: [5, 3, 5], state: "minecraft:iron_block"}, + {pos: [5, 3, 6], state: "minecraft:iron_block"}, + {pos: [5, 3, 7], state: "minecraft:air"}, + {pos: [5, 3, 8], state: "minecraft:air"}, + {pos: [6, 3, 0], state: "minecraft:air"}, + {pos: [6, 3, 1], state: "minecraft:air"}, + {pos: [6, 3, 2], state: "minecraft:iron_block"}, + {pos: [6, 3, 3], state: "minecraft:iron_block"}, + {pos: [6, 3, 4], state: "minecraft:iron_block"}, + {pos: [6, 3, 5], state: "minecraft:iron_block"}, + {pos: [6, 3, 6], state: "minecraft:iron_block"}, + {pos: [6, 3, 7], state: "minecraft:air"}, + {pos: [6, 3, 8], state: "minecraft:air"}, + {pos: [7, 3, 0], state: "minecraft:air"}, + {pos: [7, 3, 1], state: "minecraft:air"}, + {pos: [7, 3, 2], state: "minecraft:iron_block"}, + {pos: [7, 3, 3], state: "minecraft:iron_block"}, + {pos: [7, 3, 4], state: "minecraft:iron_block"}, + {pos: [7, 3, 5], state: "minecraft:iron_block"}, + {pos: [7, 3, 6], state: "minecraft:iron_block"}, + {pos: [7, 3, 7], state: "minecraft:air"}, + {pos: [7, 3, 8], state: "minecraft:air"}, + {pos: [8, 3, 0], state: "minecraft:air"}, + {pos: [8, 3, 1], state: "minecraft:air"}, + {pos: [8, 3, 2], state: "minecraft:iron_block"}, + {pos: [8, 3, 3], state: "minecraft:iron_block"}, + {pos: [8, 3, 4], state: "minecraft:iron_block"}, + {pos: [8, 3, 5], state: "minecraft:iron_block"}, + {pos: [8, 3, 6], state: "minecraft:iron_block"}, + {pos: [8, 3, 7], state: "minecraft:air"}, + {pos: [8, 3, 8], state: "minecraft:air"}, + {pos: [9, 3, 0], state: "minecraft:air"}, + {pos: [9, 3, 1], state: "minecraft:air"}, + {pos: [9, 3, 2], state: "minecraft:air"}, + {pos: [9, 3, 3], state: "minecraft:air"}, + {pos: [9, 3, 4], state: "minecraft:air"}, + {pos: [9, 3, 5], state: "minecraft:air"}, + {pos: [9, 3, 6], state: "minecraft:air"}, + {pos: [9, 3, 7], state: "minecraft:air"}, + {pos: [9, 3, 8], state: "minecraft:air"}, + {pos: [10, 3, 0], state: "minecraft:air"}, + {pos: [10, 3, 1], state: "minecraft:air"}, + {pos: [10, 3, 2], state: "minecraft:air"}, + {pos: [10, 3, 3], state: "minecraft:air"}, + {pos: [10, 3, 4], state: "minecraft:air"}, + {pos: [10, 3, 5], state: "minecraft:air"}, + {pos: [10, 3, 6], state: "minecraft:air"}, + {pos: [10, 3, 7], state: "minecraft:air"}, + {pos: [10, 3, 8], 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: [0, 4, 8], 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: [1, 4, 8], 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: [2, 4, 8], 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:iron_block"}, + {pos: [3, 4, 4], state: "minecraft:iron_block"}, + {pos: [3, 4, 5], state: "minecraft:iron_block"}, + {pos: [3, 4, 6], state: "minecraft:air"}, + {pos: [3, 4, 7], state: "minecraft:air"}, + {pos: [3, 4, 8], 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:iron_block"}, + {pos: [4, 4, 4], state: "minecraft:iron_block"}, + {pos: [4, 4, 5], state: "minecraft:iron_block"}, + {pos: [4, 4, 6], state: "minecraft:air"}, + {pos: [4, 4, 7], state: "minecraft:air"}, + {pos: [4, 4, 8], 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:iron_block"}, + {pos: [5, 4, 4], state: "minecraft:iron_block"}, + {pos: [5, 4, 5], state: "minecraft:iron_block"}, + {pos: [5, 4, 6], state: "minecraft:air"}, + {pos: [5, 4, 7], state: "minecraft:air"}, + {pos: [5, 4, 8], 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:iron_block"}, + {pos: [6, 4, 4], state: "minecraft:iron_block"}, + {pos: [6, 4, 5], state: "minecraft:iron_block"}, + {pos: [6, 4, 6], state: "minecraft:air"}, + {pos: [6, 4, 7], state: "minecraft:air"}, + {pos: [6, 4, 8], 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:iron_block"}, + {pos: [7, 4, 4], state: "minecraft:iron_block"}, + {pos: [7, 4, 5], state: "minecraft:iron_block"}, + {pos: [7, 4, 6], state: "minecraft:air"}, + {pos: [7, 4, 7], state: "minecraft:air"}, + {pos: [7, 4, 8], state: "minecraft:air"}, + {pos: [8, 4, 0], state: "minecraft:air"}, + {pos: [8, 4, 1], state: "minecraft:air"}, + {pos: [8, 4, 2], state: "minecraft:air"}, + {pos: [8, 4, 3], state: "minecraft:air"}, + {pos: [8, 4, 4], state: "minecraft:air"}, + {pos: [8, 4, 5], state: "minecraft:air"}, + {pos: [8, 4, 6], state: "minecraft:air"}, + {pos: [8, 4, 7], state: "minecraft:air"}, + {pos: [8, 4, 8], state: "minecraft:air"}, + {pos: [9, 4, 0], state: "minecraft:air"}, + {pos: [9, 4, 1], state: "minecraft:air"}, + {pos: [9, 4, 2], state: "minecraft:air"}, + {pos: [9, 4, 3], state: "minecraft:air"}, + {pos: [9, 4, 4], state: "minecraft:air"}, + {pos: [9, 4, 5], state: "minecraft:air"}, + {pos: [9, 4, 6], state: "minecraft:air"}, + {pos: [9, 4, 7], state: "minecraft:air"}, + {pos: [9, 4, 8], state: "minecraft:air"}, + {pos: [10, 4, 0], state: "minecraft:air"}, + {pos: [10, 4, 1], state: "minecraft:air"}, + {pos: [10, 4, 2], state: "minecraft:air"}, + {pos: [10, 4, 3], state: "minecraft:air"}, + {pos: [10, 4, 4], state: "minecraft:air"}, + {pos: [10, 4, 5], state: "minecraft:air"}, + {pos: [10, 4, 6], state: "minecraft:air"}, + {pos: [10, 4, 7], state: "minecraft:air"}, + {pos: [10, 4, 8], 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: [0, 5, 8], 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: [1, 5, 8], 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: [2, 5, 8], 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: [3, 5, 8], 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:beacon", nbt: {ForgeCaps: {}, Levels: 4, Primary: 1, Secondary: -1, id: "minecraft:beacon"}}, + {pos: [4, 5, 5], state: "minecraft:air"}, + {pos: [4, 5, 6], state: "minecraft:air"}, + {pos: [4, 5, 7], state: "minecraft:air"}, + {pos: [4, 5, 8], 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: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "modintegrtest.minecraftbeacon", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [5, 5, 5], state: "minecraft:air"}, + {pos: [5, 5, 6], state: "minecraft:air"}, + {pos: [5, 5, 7], state: "minecraft:air"}, + {pos: [5, 5, 8], 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:beacon", nbt: {ForgeCaps: {}, Levels: 4, Primary: 3, Secondary: 3, id: "minecraft:beacon"}}, + {pos: [6, 5, 5], state: "minecraft:air"}, + {pos: [6, 5, 6], state: "minecraft:air"}, + {pos: [6, 5, 7], state: "minecraft:air"}, + {pos: [6, 5, 8], 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: [7, 5, 8], state: "minecraft:air"}, + {pos: [8, 5, 0], state: "minecraft:air"}, + {pos: [8, 5, 1], state: "minecraft:air"}, + {pos: [8, 5, 2], state: "minecraft:air"}, + {pos: [8, 5, 3], state: "minecraft:air"}, + {pos: [8, 5, 4], state: "minecraft:air"}, + {pos: [8, 5, 5], state: "minecraft:air"}, + {pos: [8, 5, 6], state: "minecraft:air"}, + {pos: [8, 5, 7], state: "minecraft:air"}, + {pos: [8, 5, 8], state: "minecraft:air"}, + {pos: [9, 5, 0], state: "minecraft:air"}, + {pos: [9, 5, 1], state: "minecraft:air"}, + {pos: [9, 5, 2], state: "minecraft:air"}, + {pos: [9, 5, 3], state: "minecraft:air"}, + {pos: [9, 5, 4], state: "minecraft:air"}, + {pos: [9, 5, 5], state: "minecraft:air"}, + {pos: [9, 5, 6], state: "minecraft:air"}, + {pos: [9, 5, 7], state: "minecraft:air"}, + {pos: [9, 5, 8], state: "minecraft:air"}, + {pos: [10, 5, 0], state: "minecraft:air"}, + {pos: [10, 5, 1], state: "minecraft:air"}, + {pos: [10, 5, 2], state: "minecraft:air"}, + {pos: [10, 5, 3], state: "minecraft:air"}, + {pos: [10, 5, 4], state: "minecraft:air"}, + {pos: [10, 5, 5], state: "minecraft:air"}, + {pos: [10, 5, 6], state: "minecraft:air"}, + {pos: [10, 5, 7], state: "minecraft:air"}, + {pos: [10, 5, 8], 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: [0, 6, 8], 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: [1, 6, 8], 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: [2, 6, 8], 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: [3, 6, 8], 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: [4, 6, 8], 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:beacon", nbt: {ForgeCaps: {}, Levels: 0, Primary: -1, Secondary: -1, id: "minecraft:beacon"}}, + {pos: [5, 6, 5], state: "minecraft:air"}, + {pos: [5, 6, 6], state: "minecraft:air"}, + {pos: [5, 6, 7], state: "minecraft:air"}, + {pos: [5, 6, 8], 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: [6, 6, 8], 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: [7, 6, 8], state: "minecraft:air"}, + {pos: [8, 6, 0], state: "minecraft:air"}, + {pos: [8, 6, 1], state: "minecraft:air"}, + {pos: [8, 6, 2], state: "minecraft:air"}, + {pos: [8, 6, 3], state: "minecraft:air"}, + {pos: [8, 6, 4], state: "minecraft:air"}, + {pos: [8, 6, 5], state: "minecraft:air"}, + {pos: [8, 6, 6], state: "minecraft:air"}, + {pos: [8, 6, 7], state: "minecraft:air"}, + {pos: [8, 6, 8], state: "minecraft:air"}, + {pos: [9, 6, 0], state: "minecraft:air"}, + {pos: [9, 6, 1], state: "minecraft:air"}, + {pos: [9, 6, 2], state: "minecraft:air"}, + {pos: [9, 6, 3], state: "minecraft:air"}, + {pos: [9, 6, 4], state: "minecraft:air"}, + {pos: [9, 6, 5], state: "minecraft:air"}, + {pos: [9, 6, 6], state: "minecraft:air"}, + {pos: [9, 6, 7], state: "minecraft:air"}, + {pos: [9, 6, 8], state: "minecraft:air"}, + {pos: [10, 6, 0], state: "minecraft:air"}, + {pos: [10, 6, 1], state: "minecraft:air"}, + {pos: [10, 6, 2], state: "minecraft:air"}, + {pos: [10, 6, 3], state: "minecraft:air"}, + {pos: [10, 6, 4], state: "minecraft:air"}, + {pos: [10, 6, 5], state: "minecraft:air"}, + {pos: [10, 6, 6], state: "minecraft:air"}, + {pos: [10, 6, 7], state: "minecraft:air"}, + {pos: [10, 6, 8], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:iron_block", + "minecraft:air", + "minecraft:beacon", + "computercraft:computer_advanced{facing:north,state:blinking}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.minecraftnoteblock.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.minecraftnoteblock.snbt new file mode 100644 index 000000000..e32bcfbcf --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.minecraftnoteblock.snbt @@ -0,0 +1,139 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [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: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:note_block{instrument:basedrum,note:4,powered:false}"}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:west,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "modintegrtest.minecraftnoteblock", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "minecraft:note_block{instrument:basedrum,note:0,powered:false}"}, + {pos: [2, 1, 4], 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: [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: [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: [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: [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:polished_andesite"}, + {pos: [2, 2, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:note_block{instrument:basedrum,note:4,powered:false}", + "minecraft:note_block{instrument:basedrum,note:0,powered:false}", + "minecraft:air", + "computercraft:computer_advanced{facing:west,state:blinking}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.minecraftnoteblock_triggering_allay.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.minecraftnoteblock_triggering_allay.snbt new file mode 100644 index 000000000..5344fa82e --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/modintegrtest.minecraftnoteblock_triggering_allay.snbt @@ -0,0 +1,140 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [0, 1, 0], state: "minecraft:glass"}, + {pos: [0, 1, 1], state: "minecraft:glass"}, + {pos: [0, 1, 2], state: "minecraft:glass"}, + {pos: [0, 1, 3], state: "minecraft:glass"}, + {pos: [0, 1, 4], state: "minecraft:glass"}, + {pos: [1, 1, 0], state: "minecraft:glass"}, + {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:glass"}, + {pos: [2, 1, 0], state: "minecraft:glass"}, + {pos: [2, 1, 1], state: "minecraft:note_block{instrument:basedrum,note:4,powered:false}"}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:west,state:blinking}", nbt: {ComputerId: 1, ForgeCaps: {}, Label: "modintegrtest.minecraftnoteblock_triggering_allay", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], state: "minecraft:glass"}, + {pos: [3, 1, 0], state: "minecraft:glass"}, + {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:glass"}, + {pos: [4, 1, 0], state: "minecraft:glass"}, + {pos: [4, 1, 1], state: "minecraft:glass"}, + {pos: [4, 1, 2], state: "minecraft:glass"}, + {pos: [4, 1, 3], state: "minecraft:glass"}, + {pos: [4, 1, 4], state: "minecraft:glass"}, + {pos: [0, 2, 0], state: "minecraft:glass"}, + {pos: [0, 2, 1], state: "minecraft:glass"}, + {pos: [0, 2, 2], state: "minecraft:glass"}, + {pos: [0, 2, 3], state: "minecraft:glass"}, + {pos: [0, 2, 4], state: "minecraft:glass"}, + {pos: [1, 2, 0], state: "minecraft:glass"}, + {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:glass"}, + {pos: [2, 2, 0], state: "minecraft:glass"}, + {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:glass"}, + {pos: [3, 2, 0], state: "minecraft:glass"}, + {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:glass"}, + {pos: [4, 2, 0], state: "minecraft:glass"}, + {pos: [4, 2, 1], state: "minecraft:glass"}, + {pos: [4, 2, 2], state: "minecraft:glass"}, + {pos: [4, 2, 3], state: "minecraft:glass"}, + {pos: [4, 2, 4], state: "minecraft:glass"}, + {pos: [0, 3, 0], state: "minecraft:glass"}, + {pos: [0, 3, 1], state: "minecraft:glass"}, + {pos: [0, 3, 2], state: "minecraft:glass"}, + {pos: [0, 3, 3], state: "minecraft:glass"}, + {pos: [0, 3, 4], state: "minecraft:glass"}, + {pos: [1, 3, 0], state: "minecraft:glass"}, + {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:glass"}, + {pos: [2, 3, 0], state: "minecraft:glass"}, + {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:glass"}, + {pos: [3, 3, 0], state: "minecraft:glass"}, + {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:glass"}, + {pos: [4, 3, 0], state: "minecraft:glass"}, + {pos: [4, 3, 1], state: "minecraft:glass"}, + {pos: [4, 3, 2], state: "minecraft:glass"}, + {pos: [4, 3, 3], state: "minecraft:glass"}, + {pos: [4, 3, 4], state: "minecraft:glass"}, + {pos: [0, 4, 0], state: "minecraft:glass"}, + {pos: [0, 4, 1], state: "minecraft:glass"}, + {pos: [0, 4, 2], state: "minecraft:glass"}, + {pos: [0, 4, 3], state: "minecraft:glass"}, + {pos: [0, 4, 4], state: "minecraft:glass"}, + {pos: [1, 4, 0], state: "minecraft:glass"}, + {pos: [1, 4, 1], state: "minecraft:glass"}, + {pos: [1, 4, 2], state: "minecraft:glass"}, + {pos: [1, 4, 3], state: "minecraft:glass"}, + {pos: [1, 4, 4], state: "minecraft:glass"}, + {pos: [2, 4, 0], state: "minecraft:glass"}, + {pos: [2, 4, 1], state: "minecraft:glass"}, + {pos: [2, 4, 2], state: "minecraft:glass"}, + {pos: [2, 4, 3], state: "minecraft:glass"}, + {pos: [2, 4, 4], state: "minecraft:glass"}, + {pos: [3, 4, 0], state: "minecraft:glass"}, + {pos: [3, 4, 1], state: "minecraft:glass"}, + {pos: [3, 4, 2], state: "minecraft:glass"}, + {pos: [3, 4, 3], state: "minecraft:glass"}, + {pos: [3, 4, 4], state: "minecraft:glass"}, + {pos: [4, 4, 0], state: "minecraft:glass"}, + {pos: [4, 4, 1], state: "minecraft:glass"}, + {pos: [4, 4, 2], state: "minecraft:glass"}, + {pos: [4, 4, 3], state: "minecraft:glass"}, + {pos: [4, 4, 4], state: "minecraft:glass"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:glass", + "minecraft:note_block{instrument:basedrum,note:4,powered:false}", + "minecraft:note_block{instrument:basedrum,note:0,powered:false}", + "minecraft:air", + "computercraft:computer_advanced{facing:west,state:blinking}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.blockreader.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.blockreader.snbt new file mode 100644 index 000000000..6cf8472da --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.blockreader.snbt @@ -0,0 +1,140 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [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: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "advancedperipherals:block_reader{orientation:up_east}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[Block Reader]"}, Items: [], id: "advancedperipherals:block_reader"}}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:west,state:off}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "peripheraltest.blockreader", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "advancedperipherals:block_reader{orientation:up_east}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[Block Reader]"}, Items: [], id: "advancedperipherals:block_reader"}}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "advancedperipherals:block_reader{orientation:up_east}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[Block Reader]"}, Items: [], id: "advancedperipherals:block_reader"}}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], 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: [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: [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: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:polished_andesite"}, + {pos: [2, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 3], state: "minecraft:oak_sign{rotation:4,waterlogged:false}", nbt: {Color: "black", ForgeCaps: {}, GlowingText: 0b, Text1: '{"text":"this"}', Text2: '{"text":"is a"}', Text3: '{"text":"test"}', Text4: '{"text":"sign"}', id: "minecraft:sign"}}, + {pos: [2, 2, 4], state: "minecraft:air"}, + {pos: [3, 2, 0], state: "minecraft:air"}, + {pos: [3, 2, 1], state: "minecraft:air"}, + {pos: [3, 2, 2], state: "minecraft:polished_andesite_stairs{facing:east,half:bottom,shape:straight,waterlogged:false}"}, + {pos: [3, 2, 3], state: "minecraft:air"}, + {pos: [3, 2, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "minecraft:polished_andesite_stairs{facing:east,half:bottom,shape:straight,waterlogged:false}", + "advancedperipherals:block_reader{orientation:up_east}", + "computercraft:computer_advanced{facing:west,state:off}", + "minecraft:oak_sign{rotation:4,waterlogged:false}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.energydet.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.energydet.snbt new file mode 100644 index 000000000..fbb06aa29 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.energydet.snbt @@ -0,0 +1,141 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [1, 1, 0], state: "powah:energy_cell_niotic{waterlogged:false}", nbt: {ForgeCaps: {}, Items: [], Size: 2, energy_stored_main_energy: 16400L, id: "powah:energy_cell", redstone_mode: 0, side_transfer_type: [I; 0, 0, 0, 0, 0, 0], variant: 4}}, + {pos: [1, 1, 1], state: "powah:energy_cable_nitro{down:false,east:false,north:false,south:false,up:false,waterlogged:false,west:false}", nbt: {ForgeCaps: {}, cs: 12b, energy_capacity_main_energy: 0L, energy_stored_main_energy: 0L, id: "powah:energy_cable", redstone_mode: 0, side_transfer_type: [I; 0, 0, 1, 2, 0, 0], variant: 6}}, + {pos: [1, 1, 2], state: "advancedperipherals:energy_detector{orientation:south_up}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[Energy Detector]"}, Items: [], id: "advancedperipherals:energy_detector", rateLimit: 100}}, + {pos: [1, 1, 3], state: "powah:energy_cable_nitro{down:false,east:false,north:false,south:false,up:false,waterlogged:false,west:false}", nbt: {ForgeCaps: {}, cs: 12b, energy_capacity_main_energy: 0L, energy_stored_main_energy: 0L, id: "powah:energy_cable", redstone_mode: 0, side_transfer_type: [I; 0, 0, 1, 2, 0, 0], variant: 6}}, + {pos: [1, 1, 4], state: "powah:energy_cell_creative{waterlogged:false}", nbt: {ForgeCaps: {}, Items: [], Size: 2, energy_capacity_main_energy: 9000000000000000000L, energy_stored_main_energy: 9000000000000000000L, id: "powah:energy_cell", redstone_mode: 0, side_transfer_type: [I; 1, 1, 1, 1, 1, 1], variant: 7}}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "peripheraltest.energydet", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "powah:energy_cell_niotic{waterlogged:false}", + "powah:energy_cable_nitro{down:false,east:false,north:false,south:false,up:false,waterlogged:false,west:false}", + "advancedperipherals:energy_detector{orientation:south_up}", + "powah:energy_cell_creative{waterlogged:false}", + "computercraft:computer_advanced{facing:north,state:blinking}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.environment.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.environment.snbt new file mode 100644 index 000000000..be7577190 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.environment.snbt @@ -0,0 +1,138 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "peripheraltest.environment", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "advancedperipherals:environment_detector{orientation:up_south}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[Environment Detector]"}, Items: [], id: "advancedperipherals:environment_detector"}}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "computercraft:computer_advanced{facing:north,state:blinking}", + "advancedperipherals:environment_detector{orientation:up_south}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.geoscanner.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.geoscanner.snbt new file mode 100644 index 000000000..fd6a9bf08 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.geoscanner.snbt @@ -0,0 +1,243 @@ +{ + DataVersion: 3120, + size: [5, 9, 5], + 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: [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: [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: [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: [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: [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: [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: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:polished_andesite"}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:west,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "peripheraltest.geoscanner", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "minecraft:polished_diorite"}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "minecraft:polished_granite"}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], 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: [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: [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: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "advancedperipherals:geo_scanner{orientation:west_up}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[Geo Scanner]"}, Items: [], energy: 100000000, id: "advancedperipherals:geo_scanner", peripheralSettings: {FUEL_CONSUMING_RATE: 1, cooldowns: {scanBlocks: 1715469234280L}}}}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], 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: [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: [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: [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: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "minecraft:air"}, + {pos: [2, 3, 2], state: "minecraft:iron_ore"}, + {pos: [2, 3, 3], state: "minecraft:air"}, + {pos: [2, 3, 4], 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: [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: [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: [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: [2, 4, 0], state: "minecraft:air"}, + {pos: [2, 4, 1], state: "minecraft:air"}, + {pos: [2, 4, 2], state: "minecraft:gold_ore"}, + {pos: [2, 4, 3], state: "minecraft:air"}, + {pos: [2, 4, 4], 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: [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: [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: [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: [2, 5, 0], state: "minecraft:air"}, + {pos: [2, 5, 1], state: "minecraft:air"}, + {pos: [2, 5, 2], state: "minecraft:gold_ore"}, + {pos: [2, 5, 3], state: "minecraft:air"}, + {pos: [2, 5, 4], 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: [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: [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: [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: [2, 6, 0], state: "minecraft:air"}, + {pos: [2, 6, 1], state: "minecraft:air"}, + {pos: [2, 6, 2], state: "minecraft:diamond_ore"}, + {pos: [2, 6, 3], state: "minecraft:air"}, + {pos: [2, 6, 4], 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: [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: [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: [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: [2, 7, 0], state: "minecraft:air"}, + {pos: [2, 7, 1], state: "minecraft:air"}, + {pos: [2, 7, 2], state: "minecraft:diamond_ore"}, + {pos: [2, 7, 3], state: "minecraft:air"}, + {pos: [2, 7, 4], 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: [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: [0, 8, 0], state: "minecraft:air"}, + {pos: [0, 8, 1], state: "minecraft:air"}, + {pos: [0, 8, 2], state: "minecraft:air"}, + {pos: [0, 8, 3], state: "minecraft:air"}, + {pos: [0, 8, 4], state: "minecraft:air"}, + {pos: [1, 8, 0], state: "minecraft:air"}, + {pos: [1, 8, 1], state: "minecraft:air"}, + {pos: [1, 8, 2], state: "minecraft:air"}, + {pos: [1, 8, 3], state: "minecraft:air"}, + {pos: [1, 8, 4], state: "minecraft:air"}, + {pos: [2, 8, 0], state: "minecraft:air"}, + {pos: [2, 8, 1], state: "minecraft:air"}, + {pos: [2, 8, 2], state: "minecraft:diamond_ore"}, + {pos: [2, 8, 3], state: "minecraft:air"}, + {pos: [2, 8, 4], state: "minecraft:air"}, + {pos: [3, 8, 0], state: "minecraft:air"}, + {pos: [3, 8, 1], state: "minecraft:air"}, + {pos: [3, 8, 2], state: "minecraft:air"}, + {pos: [3, 8, 3], state: "minecraft:air"}, + {pos: [3, 8, 4], state: "minecraft:air"}, + {pos: [4, 8, 0], state: "minecraft:air"}, + {pos: [4, 8, 1], state: "minecraft:air"}, + {pos: [4, 8, 2], state: "minecraft:air"}, + {pos: [4, 8, 3], state: "minecraft:air"}, + {pos: [4, 8, 4], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:polished_diorite", + "minecraft:polished_granite", + "minecraft:iron_ore", + "minecraft:gold_ore", + "minecraft:diamond_ore", + "minecraft:air", + "computercraft:computer_advanced{facing:west,state:blinking}", + "advancedperipherals:geo_scanner{orientation:west_up}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.mecrafting.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.mecrafting.snbt new file mode 100644 index 000000000..5b346e824 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.mecrafting.snbt @@ -0,0 +1,275 @@ +{ + DataVersion: 3120, + size: [5, 10, 5], + 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: [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: [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: [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: [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: [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:anvil{facing:west}"}, + {pos: [0, 1, 4], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "ae2:drive", nbt: {ForgeCaps: {}, forward: "NORTH", id: "ae2:drive", inv: {item0: {Count: 1b, id: "ae2:item_storage_cell_64k", tag: {amts: [L; 256L, 256L], ic: 512L, keys: [{"#c": "ae2:i", id: "minecraft:oak_log"}, {"#c": "ae2:i", id: "minecraft:oak_planks"}]}}, item1: {Count: 1b, id: "ae2:fluid_storage_cell_64k", tag: {}}, item2: {}, item3: {}, item4: {}, item5: {}, item6: {}, item7: {}, item8: {}, item9: {}}, priority: 0, proxy: {g: 21L, k: -1L, p: 0}, up: "UP", visual: {cell0: {id: "ae2:item_storage_cell_64k", state: "not_empty"}, cell1: {id: "ae2:fluid_storage_cell_64k", state: "empty"}, online: 1b}}}, + {pos: [1, 1, 2], state: "advancedperipherals:me_bridge{orientation:up_east}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[ME Bridge]"}, Items: [], id: "advancedperipherals:me_bridge"}}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "ae2:controller{state:online,type:block}", nbt: {ForgeCaps: {}, id: "ae2:controller", internalCurrentPower: 0.0d, proxy: {g: 21L, k: -1L, p: 0}, visual: {}}}, + {pos: [2, 1, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsNorth: 0, channelsUp: 0, channelsWest: 0, connections: ["up", "north", "west"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", visual: {}}}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "ae2:creative_energy_cell", nbt: {ForgeCaps: {}, forward: "WEST", id: "ae2:creative_energy_cell", proxy: {g: 21L, k: -1L, p: 0}, up: "UP", visual: {}}}, + {pos: [3, 1, 2], state: "minecraft:air"}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], 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: [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: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "peripheraltest.mecrafting", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [1, 2, 3], state: "minecraft:air"}, + {pos: [1, 2, 4], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "ae2:cable_bus{light_level:9,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_cable", visual: {channelsDown: 0, channelsUp: 0, connections: ["down", "up"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {blankPattern: [{Count: 59b, Slot: 0, id: "ae2:blank_pattern"}], encodedInputs: [{"#": 1L, "#c": "ae2:i", id: "minecraft:oak_log"}], encodedOutputs: [{"#": 1000L, "#c": "ae2:f", id: "minecraft:water"}], filter_type: "ALL", gn: {g: 21L, k: -1L, p: 0}, id: "ae2:pattern_encoding_terminal", mode: "PROCESSING", sort_by: "AMOUNT", sort_direction: "ASCENDING", spin: 0b, substitute: 0b, substituteFluids: 1b, view_mode: "ALL", visual: {missingChannel: 0b, powered: 1b}}, visual: {}}}, + {pos: [2, 2, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, channelsUp: 0, connections: ["down", "up"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {id: "ae2:cable_anchor", visual: {}}, visual: {}}}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], 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: [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: [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: [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: "ae2:crafting_accelerator{formed:true,powered:true}", nbt: {ForgeCaps: {}, core: 0b, customName: "CPUOne", id: "ae2:crafting_unit", proxy: {g: 21L, k: -1L, p: 0}, visual: {}}}, + {pos: [1, 3, 4], state: "minecraft:air"}, + {pos: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "ae2:cable_bus{light_level:9,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_cable", visual: {channelsDown: 0, connections: ["down"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {filter_type: "ALL", gn: {g: 21L, k: -1L, p: 0}, id: "ae2:crafting_terminal", sort_by: "NAME", sort_direction: "ASCENDING", spin: 0b, view_mode: "ALL", visual: {missingChannel: 0b, powered: 1b}}, visual: {}}}, + {pos: [2, 3, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, channelsUp: 0, connections: ["down", "up"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {id: "ae2:cable_anchor", visual: {}}, visual: {}}}, + {pos: [2, 3, 3], state: "minecraft:air"}, + {pos: [2, 3, 4], 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: [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: [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: [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: "ae2:64k_crafting_storage{formed:true,powered:true}", nbt: {ForgeCaps: {}, core: 1b, crafting_scheduling_mode: "PLAYER_ONLY", customName: "CPUOne", id: "ae2:crafting_storage", inventory: [], proxy: {g: 21L, k: -1L, p: 0}, visual: {}}}, + {pos: [1, 4, 4], state: "minecraft:air"}, + {pos: [2, 4, 0], state: "minecraft:air"}, + {pos: [2, 4, 1], state: "minecraft:air"}, + {pos: [2, 4, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, channelsSouth: 0, channelsUp: 0, connections: ["down", "up", "south"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", visual: {}}}, + {pos: [2, 4, 3], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsEast: 0, channelsNorth: 0, channelsSouth: 0, channelsWest: 0, connections: ["north", "south", "west", "east"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", visual: {}}}, + {pos: [2, 4, 4], state: "ae2:64k_crafting_storage{formed:true,powered:true}", nbt: {ForgeCaps: {}, core: 1b, crafting_scheduling_mode: "ANY", id: "ae2:crafting_storage", inventory: [], proxy: {g: 21L, k: -1L, p: 0}, visual: {}}}, + {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: "ae2:64k_crafting_storage{formed:true,powered:true}", nbt: {ForgeCaps: {}, core: 1b, crafting_scheduling_mode: "ANY", id: "ae2:crafting_storage", inventory: [], proxy: {g: 21L, k: -1L, p: 0}, visual: {}}}, + {pos: [3, 4, 4], 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: [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: [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: "ae2:64k_crafting_storage{formed:true,powered:true}", nbt: {ForgeCaps: {}, core: 0b, customName: "CPUOne", id: "ae2:crafting_storage", proxy: {g: 21L, k: -1L, p: 0}, visual: {}}}, + {pos: [1, 5, 4], state: "minecraft:air"}, + {pos: [2, 5, 0], state: "minecraft:air"}, + {pos: [2, 5, 1], state: "minecraft:air"}, + {pos: [2, 5, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, channelsUp: 0, connections: ["down", "up"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", visual: {}}}, + {pos: [2, 5, 3], state: "minecraft:air"}, + {pos: [2, 5, 4], state: "ae2:64k_crafting_storage{formed:true,powered:true}", nbt: {ForgeCaps: {}, core: 0b, id: "ae2:crafting_storage", proxy: {g: 21L, k: -1L, p: 0}, visual: {}}}, + {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: "ae2:64k_crafting_storage{formed:true,powered:true}", nbt: {ForgeCaps: {}, core: 0b, id: "ae2:crafting_storage", proxy: {g: 21L, k: -1L, p: 0}, visual: {}}}, + {pos: [3, 5, 4], 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: [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: [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: [2, 6, 0], state: "minecraft:air"}, + {pos: [2, 6, 1], state: "minecraft:air"}, + {pos: [2, 6, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, channelsUp: 0, connections: ["down", "up"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", visual: {}}}, + {pos: [2, 6, 3], state: "minecraft:air"}, + {pos: [2, 6, 4], 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: [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: [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: [1, 7, 0], state: "minecraft:air"}, + {pos: [1, 7, 1], state: "minecraft:chest{facing:north,type:single,waterlogged:false}", nbt: {ForgeCaps: {}, Items: [], id: "minecraft:chest"}}, + {pos: [1, 7, 2], state: "ae2:pattern_provider{omnidirectional:false}", nbt: {ForgeCaps: {}, blocking_mode: "NO", forward: "SOUTH", id: "ae2:pattern_provider", lock_crafting_mode: "NONE", omniDirectional: 0b, pattern_access_terminal: "YES", patterns: [{Count: 1b, Slot: 0, id: "ae2:crafting_pattern", tag: {in: [{}, {}, {}, {}, {Count: 1b, id: "minecraft:oak_planks"}, {Count: 1b, id: "minecraft:oak_planks"}, {}, {}, {}], out: {Count: 1b, id: "minecraft:oak_pressure_plate"}, recipe: "minecraft:oak_pressure_plate", substitute: 0b, substituteFluids: 1b}}], priority: 0, proxy: {g: 21L, k: -1L, p: 0}, returnInv: [], sendList: [], up: "UP", visual: {}}}, + {pos: [1, 7, 3], state: "ae2:molecular_assembler{powered:true}", nbt: {ForgeCaps: {}, forward: "SOUTH", id: "ae2:molecular_assembler", inv: {item0: {}, item1: {}, item10: {}, item2: {}, item3: {}, item4: {}, item5: {}, item6: {}, item7: {}, item8: {}, item9: {}}, proxy: {g: 21L, k: -1L, p: 0}, up: "UP", visual: {}}}, + {pos: [1, 7, 4], state: "minecraft:air"}, + {pos: [2, 7, 0], state: "minecraft:air"}, + {pos: [2, 7, 1], state: "ae2:pattern_provider{omnidirectional:false}", nbt: {ForgeCaps: {}, blocking_mode: "NO", forward: "WEST", id: "ae2:pattern_provider", lock_crafting_mode: "NONE", omniDirectional: 0b, pattern_access_terminal: "YES", patterns: [{Count: 1b, Slot: 0, id: "ae2:processing_pattern", tag: {in: [{"#": 1L, "#c": "ae2:i", id: "minecraft:oak_log"}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], out: [{"#": 1000L, "#c": "ae2:f", id: "minecraft:water"}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]}}], priority: 0, proxy: {g: 21L, k: -1L, p: 0}, returnInv: [], sendList: [], up: "UP", visual: {}}}, + {pos: [2, 7, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, channelsEast: 0, channelsNorth: 0, channelsSouth: 0, channelsWest: 0, connections: ["down", "north", "south", "west", "east"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", visual: {}}}, + {pos: [2, 7, 3], state: "ae2:pattern_provider{omnidirectional:true}", nbt: {ForgeCaps: {}, blocking_mode: "NO", forward: "NORTH", id: "ae2:pattern_provider", lock_crafting_mode: "NONE", omniDirectional: 1b, pattern_access_terminal: "YES", patterns: [{Count: 1b, Slot: 0, id: "ae2:crafting_pattern", tag: {in: [{}, {}, {}, {}, {}, {Count: 1b, id: "minecraft:oak_planks"}, {}, {}, {Count: 1b, id: "minecraft:oak_planks"}], out: {Count: 4b, id: "minecraft:stick"}, recipe: "minecraft:stick", substitute: 0b, substituteFluids: 1b}}], priority: 0, proxy: {g: 21L, k: -1L, p: 0}, returnInv: [], sendList: [], up: "UP", visual: {}}}, + {pos: [2, 7, 4], state: "minecraft:air"}, + {pos: [3, 7, 0], state: "minecraft:air"}, + {pos: [3, 7, 1], state: "minecraft:air"}, + {pos: [3, 7, 2], state: "ae2:pattern_provider{omnidirectional:false}", nbt: {ForgeCaps: {}, blocking_mode: "NO", forward: "SOUTH", id: "ae2:pattern_provider", lock_crafting_mode: "NONE", omniDirectional: 0b, pattern_access_terminal: "YES", patterns: [{Count: 1b, Slot: 0, id: "ae2:crafting_pattern", tag: {in: [{}, {}, {}, {Count: 1b, id: "minecraft:oak_log"}, {}, {}, {}, {}, {}], out: {Count: 4b, id: "minecraft:oak_planks"}, recipe: "minecraft:oak_planks", substitute: 0b, substituteFluids: 1b}}], priority: 0, proxy: {g: 21L, k: -1L, p: 0}, returnInv: [], sendList: [], up: "UP", visual: {}}}, + {pos: [3, 7, 3], state: "ae2:molecular_assembler{powered:true}", nbt: {ForgeCaps: {}, forward: "SOUTH", id: "ae2:molecular_assembler", inv: {item0: {}, item1: {}, item10: {}, item2: {}, item3: {}, item4: {}, item5: {}, item6: {}, item7: {}, item8: {}, item9: {}}, proxy: {g: 21L, k: -1L, p: 0}, up: "UP", visual: {}}}, + {pos: [3, 7, 4], 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: [0, 8, 0], state: "minecraft:air"}, + {pos: [0, 8, 1], state: "minecraft:air"}, + {pos: [0, 8, 2], state: "minecraft:air"}, + {pos: [0, 8, 3], state: "minecraft:air"}, + {pos: [0, 8, 4], state: "minecraft:air"}, + {pos: [1, 8, 0], state: "minecraft:air"}, + {pos: [1, 8, 1], state: "minecraft:air"}, + {pos: [1, 8, 2], state: "minecraft:air"}, + {pos: [1, 8, 3], state: "minecraft:air"}, + {pos: [1, 8, 4], state: "minecraft:air"}, + {pos: [2, 8, 0], state: "minecraft:air"}, + {pos: [2, 8, 1], state: "minecraft:air"}, + {pos: [2, 8, 2], state: "minecraft:air"}, + {pos: [2, 8, 3], state: "minecraft:air"}, + {pos: [2, 8, 4], state: "minecraft:air"}, + {pos: [3, 8, 0], state: "minecraft:air"}, + {pos: [3, 8, 1], state: "minecraft:air"}, + {pos: [3, 8, 2], state: "minecraft:air"}, + {pos: [3, 8, 3], state: "minecraft:air"}, + {pos: [3, 8, 4], state: "minecraft:air"}, + {pos: [4, 8, 0], state: "minecraft:air"}, + {pos: [4, 8, 1], state: "minecraft:air"}, + {pos: [4, 8, 2], state: "minecraft:air"}, + {pos: [4, 8, 3], state: "minecraft:air"}, + {pos: [4, 8, 4], state: "minecraft:air"}, + {pos: [0, 9, 0], state: "minecraft:air"}, + {pos: [0, 9, 1], state: "minecraft:air"}, + {pos: [0, 9, 2], state: "minecraft:air"}, + {pos: [0, 9, 3], state: "minecraft:air"}, + {pos: [0, 9, 4], state: "minecraft:air"}, + {pos: [1, 9, 0], state: "minecraft:air"}, + {pos: [1, 9, 1], state: "minecraft:air"}, + {pos: [1, 9, 2], state: "minecraft:air"}, + {pos: [1, 9, 3], state: "minecraft:air"}, + {pos: [1, 9, 4], state: "minecraft:air"}, + {pos: [2, 9, 0], state: "minecraft:air"}, + {pos: [2, 9, 1], state: "minecraft:air"}, + {pos: [2, 9, 2], state: "minecraft:air"}, + {pos: [2, 9, 3], state: "minecraft:air"}, + {pos: [2, 9, 4], state: "minecraft:air"}, + {pos: [3, 9, 0], state: "minecraft:air"}, + {pos: [3, 9, 1], state: "minecraft:air"}, + {pos: [3, 9, 2], state: "minecraft:air"}, + {pos: [3, 9, 3], state: "minecraft:air"}, + {pos: [3, 9, 4], state: "minecraft:air"}, + {pos: [4, 9, 0], state: "minecraft:air"}, + {pos: [4, 9, 1], state: "minecraft:air"}, + {pos: [4, 9, 2], state: "minecraft:air"}, + {pos: [4, 9, 3], state: "minecraft:air"}, + {pos: [4, 9, 4], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "minecraft:anvil{facing:west}", + "ae2:drive", + "advancedperipherals:me_bridge{orientation:up_east}", + "ae2:controller{state:online,type:block}", + "ae2:cable_bus{light_level:0,waterlogged:false}", + "ae2:creative_energy_cell", + "computercraft:computer_advanced{facing:north,state:blinking}", + "ae2:cable_bus{light_level:9,waterlogged:false}", + "ae2:crafting_accelerator{formed:true,powered:true}", + "ae2:64k_crafting_storage{formed:true,powered:true}", + "minecraft:chest{facing:north,type:single,waterlogged:false}", + "ae2:pattern_provider{omnidirectional:false}", + "ae2:molecular_assembler{powered:true}", + "ae2:pattern_provider{omnidirectional:true}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.mestorage.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.mestorage.snbt new file mode 100644 index 000000000..211cc3153 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.mestorage.snbt @@ -0,0 +1,270 @@ +{ + DataVersion: 3120, + size: [5, 10, 5], + 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: [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: [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: [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: [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: [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: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "ae2:drive", nbt: {ForgeCaps: {}, forward: "NORTH", id: "ae2:drive", inv: {item0: {Count: 1b, id: "ae2:item_storage_cell_64k", tag: {amts: [L; 256L, 256L], ic: 512L, keys: [{"#c": "ae2:i", id: "minecraft:oak_log"}, {"#c": "ae2:i", id: "minecraft:oak_planks"}]}}, item1: {Count: 1b, id: "ae2:fluid_storage_cell_64k", tag: {amts: [L; 1000L, 1000L], ic: 2000L, keys: [{"#c": "ae2:f", id: "minecraft:water"}, {"#c": "ae2:f", id: "minecraft:lava"}]}}, item2: {}, item3: {}, item4: {}, item5: {}, item6: {}, item7: {}, item8: {}, item9: {}}, priority: 0, proxy: {g: 21L, k: -1L, p: 0}, up: "UP", visual: {cell0: {id: "ae2:item_storage_cell_64k", state: "not_empty"}, cell1: {id: "ae2:fluid_storage_cell_64k", state: "not_empty"}, online: 1b}}}, + {pos: [1, 1, 2], state: "advancedperipherals:me_bridge{orientation:up_east}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[ME Bridge]"}, Items: [], id: "advancedperipherals:me_bridge"}}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "ae2:controller{state:online,type:block}", nbt: {ForgeCaps: {}, id: "ae2:controller", internalCurrentPower: 0.0d, proxy: {g: 21L, k: -1L, p: 0}, visual: {}}}, + {pos: [2, 1, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsNorth: 0, channelsSouth: 0, channelsUp: 0, channelsWest: 0, connections: ["up", "north", "south", "west"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", visual: {}}}, + {pos: [2, 1, 3], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_cable", visual: {channelsNorth: 0, connections: ["north"], missingChannel: 0b, powered: 1b}}, east: {access: "READ_WRITE", filter_on_extract: "YES", fuzzy_mode: "IGNORE_ALL", gn: {g: 21L, k: -1L, p: 0}, id: "ae2:storage_bus", priority: 0, storage_filter: "EXTRACTABLE_ONLY", visual: {missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", south: {access: "READ_WRITE", filter_on_extract: "YES", fuzzy_mode: "IGNORE_ALL", gn: {g: 21L, k: -1L, p: 0}, id: "ae2:storage_bus", priority: 0, storage_filter: "EXTRACTABLE_ONLY", visual: {missingChannel: 0b, powered: 1b}}, visual: {}}}, + {pos: [2, 1, 4], state: "ae2:sky_stone_tank", nbt: {Amount: 1000, FluidName: "minecraft:lava", ForgeCaps: {}, forward: "WEST", id: "ae2:sky_tank", up: "UP", visual: {}}}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "ae2:dense_energy_cell{fullness:4}", nbt: {ForgeCaps: {}, id: "ae2:dense_energy_cell", internalCurrentPower: 1599133.46875d, proxy: {g: 21L, k: -1L, p: 0}, visual: {}}}, + {pos: [3, 1, 2], state: "minecraft:air"}, + {pos: [3, 1, 3], state: "minecraft:chest{facing:west,type:single,waterlogged:false}", nbt: {ForgeCaps: {}, Items: [{Count: 32b, Slot: 0b, id: "ae2:storage_bus"}], id: "minecraft:chest"}}, + {pos: [3, 1, 4], 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: [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: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "peripheraltest.mestorage", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [1, 2, 3], state: "minecraft:air"}, + {pos: [1, 2, 4], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "ae2:cable_bus{light_level:9,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_cable", visual: {channelsDown: 0, channelsUp: 0, connections: ["down", "up"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {blankPattern: [{Count: 59b, Slot: 0, id: "ae2:blank_pattern"}], encodedInputs: [{"#": 1L, "#c": "ae2:i", id: "minecraft:oak_log"}], encodedOutputs: [{"#": 1000L, "#c": "ae2:f", id: "minecraft:water"}], filter_type: "ALL", gn: {g: 21L, k: -1L, p: 0}, id: "ae2:pattern_encoding_terminal", mode: "PROCESSING", sort_by: "AMOUNT", sort_direction: "DESCENDING", spin: 0b, substitute: 0b, substituteFluids: 1b, view_mode: "ALL", visual: {missingChannel: 0b, powered: 1b}}, south: {id: "ae2:cable_anchor", visual: {}}, visual: {}}}, + {pos: [2, 2, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, channelsUp: 0, connections: ["down", "up"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {id: "ae2:cable_anchor", visual: {}}, south: {id: "ae2:cable_anchor", visual: {}}, visual: {}}}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], 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: [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: [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: [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: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "ae2:cable_bus{light_level:9,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_cable", visual: {channelsDown: 0, connections: ["down"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {filter_type: "ALL", gn: {g: 21L, k: -1L, p: 0}, id: "ae2:crafting_terminal", sort_by: "AMOUNT", sort_direction: "DESCENDING", spin: 0b, view_mode: "ALL", visual: {missingChannel: 0b, powered: 1b}}, south: {id: "ae2:cable_anchor", visual: {}}, visual: {}}}, + {pos: [2, 3, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 21L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, connections: ["down"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {id: "ae2:cable_anchor", visual: {}}, south: {id: "ae2:cable_anchor", visual: {}}, visual: {}}}, + {pos: [2, 3, 3], state: "minecraft:air"}, + {pos: [2, 3, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [0, 8, 0], state: "minecraft:air"}, + {pos: [0, 8, 1], state: "minecraft:air"}, + {pos: [0, 8, 2], state: "minecraft:air"}, + {pos: [0, 8, 3], state: "minecraft:air"}, + {pos: [0, 8, 4], state: "minecraft:air"}, + {pos: [1, 8, 0], state: "minecraft:air"}, + {pos: [1, 8, 1], state: "minecraft:air"}, + {pos: [1, 8, 2], state: "minecraft:air"}, + {pos: [1, 8, 3], state: "minecraft:air"}, + {pos: [1, 8, 4], state: "minecraft:air"}, + {pos: [2, 8, 0], state: "minecraft:air"}, + {pos: [2, 8, 1], state: "minecraft:air"}, + {pos: [2, 8, 2], state: "minecraft:air"}, + {pos: [2, 8, 3], state: "minecraft:air"}, + {pos: [2, 8, 4], state: "minecraft:air"}, + {pos: [3, 8, 0], state: "minecraft:air"}, + {pos: [3, 8, 1], state: "minecraft:air"}, + {pos: [3, 8, 2], state: "minecraft:air"}, + {pos: [3, 8, 3], state: "minecraft:air"}, + {pos: [3, 8, 4], state: "minecraft:air"}, + {pos: [4, 8, 0], state: "minecraft:air"}, + {pos: [4, 8, 1], state: "minecraft:air"}, + {pos: [4, 8, 2], state: "minecraft:air"}, + {pos: [4, 8, 3], state: "minecraft:air"}, + {pos: [4, 8, 4], state: "minecraft:air"}, + {pos: [0, 9, 0], state: "minecraft:air"}, + {pos: [0, 9, 1], state: "minecraft:air"}, + {pos: [0, 9, 2], state: "minecraft:air"}, + {pos: [0, 9, 3], state: "minecraft:air"}, + {pos: [0, 9, 4], state: "minecraft:air"}, + {pos: [1, 9, 0], state: "minecraft:air"}, + {pos: [1, 9, 1], state: "minecraft:air"}, + {pos: [1, 9, 2], state: "minecraft:air"}, + {pos: [1, 9, 3], state: "minecraft:air"}, + {pos: [1, 9, 4], state: "minecraft:air"}, + {pos: [2, 9, 0], state: "minecraft:air"}, + {pos: [2, 9, 1], state: "minecraft:air"}, + {pos: [2, 9, 2], state: "minecraft:air"}, + {pos: [2, 9, 3], state: "minecraft:air"}, + {pos: [2, 9, 4], state: "minecraft:air"}, + {pos: [3, 9, 0], state: "minecraft:air"}, + {pos: [3, 9, 1], state: "minecraft:air"}, + {pos: [3, 9, 2], state: "minecraft:air"}, + {pos: [3, 9, 3], state: "minecraft:air"}, + {pos: [3, 9, 4], state: "minecraft:air"}, + {pos: [4, 9, 0], state: "minecraft:air"}, + {pos: [4, 9, 1], state: "minecraft:air"}, + {pos: [4, 9, 2], state: "minecraft:air"}, + {pos: [4, 9, 3], state: "minecraft:air"}, + {pos: [4, 9, 4], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "ae2:drive", + "advancedperipherals:me_bridge{orientation:up_east}", + "ae2:controller{state:online,type:block}", + "ae2:cable_bus{light_level:0,waterlogged:false}", + "ae2:sky_stone_tank", + "ae2:dense_energy_cell{fullness:4}", + "minecraft:chest{facing:west,type:single,waterlogged:false}", + "computercraft:computer_advanced{facing:north,state:blinking}", + "ae2:cable_bus{light_level:9,waterlogged:false}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.metransfer.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.metransfer.snbt new file mode 100644 index 000000000..c3a6101d9 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.metransfer.snbt @@ -0,0 +1,275 @@ +{ + DataVersion: 3120, + size: [5, 10, 5], + 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: [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: [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: [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: [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: [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: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "ae2:drive", nbt: {ForgeCaps: {}, forward: "NORTH", id: "ae2:drive", inv: {item0: {Count: 1b, id: "ae2:item_storage_cell_64k", tag: {amts: [L; 256L, 256L], ic: 512L, keys: [{"#c": "ae2:i", id: "minecraft:oak_log"}, {"#c": "ae2:i", id: "minecraft:oak_planks"}]}}, item1: {Count: 1b, id: "ae2:fluid_storage_cell_64k", tag: {}}, item2: {}, item3: {}, item4: {}, item5: {}, item6: {}, item7: {}, item8: {}, item9: {}}, priority: 0, proxy: {g: 1054L, k: -1L, p: 0}, up: "UP", visual: {cell0: {id: "ae2:item_storage_cell_64k", state: "not_empty"}, cell1: {id: "ae2:fluid_storage_cell_64k", state: "empty"}, online: 1b}}}, + {pos: [1, 1, 2], state: "computercraft:cable{cable:true,down:false,east:false,modem:up_off_peripheral,north:false,south:true,up:true,waterlogged:false,west:false}", nbt: {ForgeCaps: {}, PeirpheralAccess: 1b, PeripheralId: 3, PeripheralType: "computer", id: "computercraft:cable"}}, + {pos: [1, 1, 3], state: "computercraft:cable{cable:true,down:false,east:true,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}", nbt: {ForgeCaps: {}, PeirpheralAccess: 0b, id: "computercraft:cable"}}, + {pos: [1, 1, 4], state: "computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:false,up:true,waterlogged:false,west:false}", nbt: {ForgeCaps: {}, PeirpheralAccess: 0b, id: "computercraft:cable"}}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "ae2:controller{state:online,type:block}", nbt: {ForgeCaps: {}, id: "ae2:controller", internalCurrentPower: 0.0d, proxy: {g: 1054L, k: -1L, p: 0}, visual: {}}}, + {pos: [2, 1, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 1054L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsNorth: 0, channelsUp: 0, connections: ["up", "north"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", visual: {}}}, + {pos: [2, 1, 3], state: "computercraft:cable{cable:true,down:false,east:false,modem:up_off_peripheral,north:false,south:false,up:true,waterlogged:false,west:true}", nbt: {ForgeCaps: {}, PeirpheralAccess: 1b, PeripheralId: 20, PeripheralType: "me_bridge", id: "computercraft:cable"}}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "ae2:creative_energy_cell", nbt: {ForgeCaps: {}, forward: "NORTH", id: "ae2:creative_energy_cell", proxy: {g: 1054L, k: -1L, p: 0}, up: "UP", visual: {}}}, + {pos: [3, 1, 2], state: "minecraft:air"}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], 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: [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: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "peripheraltest.metransfer", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [1, 2, 3], state: "ae2:sky_stone_tank", nbt: {ForgeCaps: {}, forward: "SOUTH", id: "ae2:sky_tank", up: "UP", visual: {}}}, + {pos: [1, 2, 4], state: "computercraft:wired_modem_full{modem:false,peripheral:true}", nbt: {ForgeCaps: {}, PeripheralAccess: 1b, PeripheralId1: 19, PeripheralId2: 1, PeripheralId5: 2, PeripheralType1: "me_bridge", PeripheralType2: "ae2:sky_tank", PeripheralType5: "minecraft:chest", id: "computercraft:wired_modem_full"}}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "ae2:cable_bus{light_level:9,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 1054L, k: -1L, p: 0}, id: "ae2:white_smart_cable", visual: {channelsDown: 0, channelsUp: 0, connections: ["down", "up"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {filter_type: "ALL", gn: {g: 1054L, k: -1L, p: 0}, id: "ae2:pattern_encoding_terminal", mode: "CRAFTING", sort_by: "NAME", sort_direction: "ASCENDING", spin: 0b, substitute: 0b, substituteFluids: 1b, view_mode: "ALL", visual: {missingChannel: 0b, powered: 1b}}, south: {id: "ae2:cable_anchor", visual: {}}, visual: {}}}, + {pos: [2, 2, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 1054L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, channelsSouth: 0, channelsUp: 0, connections: ["down", "up", "south"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {id: "ae2:cable_anchor", visual: {}}, visual: {}}}, + {pos: [2, 2, 3], state: "advancedperipherals:me_bridge{orientation:south_up}", nbt: {ForgeCaps: {}, Items: [], id: "advancedperipherals:me_bridge"}}, + {pos: [2, 2, 4], state: "minecraft:chest{facing:south,type:single,waterlogged:false}", nbt: {ForgeCaps: {}, Items: [{Count: 64b, Slot: 26b, id: "minecraft:oak_log"}], id: "minecraft:chest"}}, + {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: [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: [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: [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: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 1054L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsEast: 0, connections: ["east"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", visual: {}}}, + {pos: [1, 3, 4], state: "minecraft:air"}, + {pos: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "ae2:cable_bus{light_level:9,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 1054L, k: -1L, p: 0}, id: "ae2:white_smart_cable", visual: {channelsDown: 0, connections: ["down"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {filter_type: "ALL", gn: {g: 1054L, k: -1L, p: 0}, id: "ae2:crafting_terminal", sort_by: "NAME", sort_direction: "ASCENDING", spin: 0b, view_mode: "ALL", visual: {missingChannel: 0b, powered: 1b}}, south: {id: "ae2:cable_anchor", visual: {}}, visual: {}}}, + {pos: [2, 3, 2], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 1054L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, channelsSouth: 0, connections: ["down", "south"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", north: {id: "ae2:cable_anchor", visual: {}}, visual: {}}}, + {pos: [2, 3, 3], state: "ae2:cable_bus{light_level:0,waterlogged:false}", nbt: {ForgeCaps: {}, cable: {gn: {g: 1054L, k: -1L, p: 0}, id: "ae2:white_smart_dense_cable", visual: {channelsDown: 0, channelsNorth: 0, channelsWest: 0, connections: ["down", "north", "west"], missingChannel: 0b, powered: 1b}}, hasRedstone: 2, id: "ae2:cable_bus", visual: {}}}, + {pos: [2, 3, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [0, 8, 0], state: "minecraft:air"}, + {pos: [0, 8, 1], state: "minecraft:air"}, + {pos: [0, 8, 2], state: "minecraft:air"}, + {pos: [0, 8, 3], state: "minecraft:air"}, + {pos: [0, 8, 4], state: "minecraft:air"}, + {pos: [1, 8, 0], state: "minecraft:air"}, + {pos: [1, 8, 1], state: "minecraft:air"}, + {pos: [1, 8, 2], state: "minecraft:air"}, + {pos: [1, 8, 3], state: "minecraft:air"}, + {pos: [1, 8, 4], state: "minecraft:air"}, + {pos: [2, 8, 0], state: "minecraft:air"}, + {pos: [2, 8, 1], state: "minecraft:air"}, + {pos: [2, 8, 2], state: "minecraft:air"}, + {pos: [2, 8, 3], state: "minecraft:air"}, + {pos: [2, 8, 4], state: "minecraft:air"}, + {pos: [3, 8, 0], state: "minecraft:air"}, + {pos: [3, 8, 1], state: "minecraft:air"}, + {pos: [3, 8, 2], state: "minecraft:air"}, + {pos: [3, 8, 3], state: "minecraft:air"}, + {pos: [3, 8, 4], state: "minecraft:air"}, + {pos: [4, 8, 0], state: "minecraft:air"}, + {pos: [4, 8, 1], state: "minecraft:air"}, + {pos: [4, 8, 2], state: "minecraft:air"}, + {pos: [4, 8, 3], state: "minecraft:air"}, + {pos: [4, 8, 4], state: "minecraft:air"}, + {pos: [0, 9, 0], state: "minecraft:air"}, + {pos: [0, 9, 1], state: "minecraft:air"}, + {pos: [0, 9, 2], state: "minecraft:air"}, + {pos: [0, 9, 3], state: "minecraft:air"}, + {pos: [0, 9, 4], state: "minecraft:air"}, + {pos: [1, 9, 0], state: "minecraft:air"}, + {pos: [1, 9, 1], state: "minecraft:air"}, + {pos: [1, 9, 2], state: "minecraft:air"}, + {pos: [1, 9, 3], state: "minecraft:air"}, + {pos: [1, 9, 4], state: "minecraft:air"}, + {pos: [2, 9, 0], state: "minecraft:air"}, + {pos: [2, 9, 1], state: "minecraft:air"}, + {pos: [2, 9, 2], state: "minecraft:air"}, + {pos: [2, 9, 3], state: "minecraft:air"}, + {pos: [2, 9, 4], state: "minecraft:air"}, + {pos: [3, 9, 0], state: "minecraft:air"}, + {pos: [3, 9, 1], state: "minecraft:air"}, + {pos: [3, 9, 2], state: "minecraft:air"}, + {pos: [3, 9, 3], state: "minecraft:air"}, + {pos: [3, 9, 4], state: "minecraft:air"}, + {pos: [4, 9, 0], state: "minecraft:air"}, + {pos: [4, 9, 1], state: "minecraft:air"}, + {pos: [4, 9, 2], state: "minecraft:air"}, + {pos: [4, 9, 3], state: "minecraft:air"}, + {pos: [4, 9, 4], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "ae2:drive", + "computercraft:cable{cable:true,down:false,east:false,modem:up_off_peripheral,north:false,south:true,up:true,waterlogged:false,west:false}", + "computercraft:cable{cable:true,down:false,east:true,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}", + "computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:false,up:true,waterlogged:false,west:false}", + "ae2:controller{state:online,type:block}", + "ae2:cable_bus{light_level:0,waterlogged:false}", + "computercraft:cable{cable:true,down:false,east:false,modem:up_off_peripheral,north:false,south:false,up:true,waterlogged:false,west:true}", + "ae2:creative_energy_cell", + "computercraft:computer_advanced{facing:north,state:blinking}", + "ae2:sky_stone_tank", + "computercraft:wired_modem_full{modem:false,peripheral:true}", + "ae2:cable_bus{light_level:9,waterlogged:false}", + "advancedperipherals:me_bridge{orientation:south_up}", + "minecraft:chest{facing:south,type:single,waterlogged:false}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.nbtstorage.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.nbtstorage.snbt new file mode 100644 index 000000000..0f0515b89 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.nbtstorage.snbt @@ -0,0 +1,138 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [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: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "advancedperipherals:nbt_storage{orientation:up_east}", nbt: {ForgeCaps: {}, ForgeData: {CustomName: "[NBT Storage]"}, Items: [], id: "advancedperipherals:nbt_storage", storedData: {test_float: 3.14d, test_number: 42.0d, test_string: "Hello, World!"}}}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:west,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "peripheraltest.nbtstorage", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "advancedperipherals:nbt_storage{orientation:up_east}", + "computercraft:computer_advanced{facing:west,state:blinking}" + ] +} diff --git a/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.rsintegrator.snbt b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.rsintegrator.snbt new file mode 100644 index 000000000..0c084d522 --- /dev/null +++ b/src/testMod/resources/data/advancedperipheralstest/structures/peripheraltest.rsintegrator.snbt @@ -0,0 +1,142 @@ +{ + DataVersion: 3120, + size: [5, 5, 5], + 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: [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: [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: [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: [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: [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: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:redstone_wire{east:side,north:none,power:0,south:side,west:none}"}, + {pos: [1, 1, 2], state: "minecraft:redstone_wire{east:side,north:side,power:0,south:side,west:none}"}, + {pos: [1, 1, 3], state: "minecraft:redstone_wire{east:side,north:side,power:0,south:none,west:none}"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "advancedperipherals:redstone_integrator{orientation:west_up}", nbt: {DOWNPower: 0, EASTPower: 0, ForgeCaps: {}, ForgeData: {CustomName: "[Redstone Integrator]"}, Items: [], NORTHPower: 0, SOUTHPower: 0, UPPower: 0, WESTPower: 0, id: "advancedperipherals:redstone_integrator"}}, + {pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:west,state:blinking}", nbt: {ComputerId: 0, ForgeCaps: {}, Label: "peripheraltest.rsintegrator", On: 1b, id: "computercraft:computer_advanced"}}, + {pos: [2, 1, 3], state: "advancedperipherals:redstone_integrator{orientation:west_up}", nbt: {DOWNPower: 0, EASTPower: 0, ForgeCaps: {}, ForgeData: {CustomName: "[Redstone Integrator]"}, Items: [], NORTHPower: 0, SOUTHPower: 0, UPPower: 0, WESTPower: 0, id: "advancedperipherals:redstone_integrator"}}, + {pos: [2, 1, 4], 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:redstone_block"}, + {pos: [3, 1, 4], 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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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: [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"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:redstone_block", + "minecraft:air", + "minecraft:redstone_wire{east:side,north:none,power:0,south:side,west:none}", + "minecraft:redstone_wire{east:side,north:side,power:0,south:side,west:none}", + "minecraft:redstone_wire{east:side,north:side,power:0,south:none,west:none}", + "advancedperipherals:redstone_integrator{orientation:west_up}", + "computercraft:computer_advanced{facing:west,state:blinking}" + ] +} diff --git a/src/testMod/resources/data/computercraft/lua/rom/autorun/cctest.lua b/src/testMod/resources/data/computercraft/lua/rom/autorun/cctest.lua new file mode 100644 index 000000000..ecf36c4e9 --- /dev/null +++ b/src/testMod/resources/data/computercraft/lua/rom/autorun/cctest.lua @@ -0,0 +1,25 @@ +--- Extend the test API with some convenience functions. +-- +-- It's much easier to declare these in Lua rather than Java. + +function test.assert(ok, ...) + if ok then return ... end + + test.fail(... and tostring(...) or "Assertion failed") +end + +function test.eq(expected, actual, msg) + if expected == actual then return end + + local message = ("Assertion failed:\nExpected %s,\ngot %s"):format(expected, actual) + if msg then message = ("%s - %s"):format(msg, message) end + test.fail(message) +end + +function test.neq(expected, actual, msg) + if expected ~= actual then return end + + local message = ("Assertion failed:\nExpected something different to %s"):format(expected) + if msg then message = ("%s - %s"):format(msg, message) end + test.fail(message) +end diff --git a/src/testMod/resources/pack.mcmeta b/src/testMod/resources/pack.mcmeta index d5db9991c..9ed247d36 100755 --- a/src/testMod/resources/pack.mcmeta +++ b/src/testMod/resources/pack.mcmeta @@ -1,6 +1,6 @@ { - "pack": { - "pack_format": 4, - "description": "CC: Test" - } + "pack": { + "pack_format": 7, + "description": "CC: Test" + } } diff --git a/src/testMod/server-files/computers/computer/0/tests/automata.weak.lua b/src/testMod/server-files/computers/computer/0/tests/automata.weak.lua deleted file mode 100644 index f1f43ad47..000000000 --- a/src/testMod/server-files/computers/computer/0/tests/automata.weak.lua +++ /dev/null @@ -1,25 +0,0 @@ -local p = peripheral.find("weakAutomata") -test.assert(p, "Not automata found") -local firstLookResult = p.lookAtBlock() -test.eq("minecraft:cobblestone", firstLookResult.name) -test.neq(0, #(firstLookResult.tags), "Cobblestone has empty tags? Really") -local dig, digErr = p.digBlock() -test.eq(nil, digErr, "Dig finished with error") -test.eq(true, dig, "Dig should be successful") -local scanResult, scanErr = p.scanItems() -test.eq(nil, scanErr, "Errors on scan") -test.eq(1, #scanResult, "Scan result bigger then 1?") -test.eq("[Cobblestone]", scanResult[1].name, "Scan found not cobblestone") -local suckResult, suckError = p.collectItems(1) -test.eq(nil, suckError, "Suck finished with error") -test.eq(true, suckResult, "Suck should be successful") -test.eq({ count = 1, name = "minecraft:cobblestone" }, turtle.getItemDetail(3), "Problem with suck result details?") -local secondLookResult = p.lookAtBlock() -test.eq("minecraft:stone_bricks", secondLookResult.name, "Second look at block with problems") -test.neq(0, #(secondLookResult.tags), "stone bricks has empty tags? Really") -turtle.select(2) -local useResult, useError = p.useOnBlock() -test.eq("PASS", useError, "Use on block finished with error") -test.eq(true, useResult, "Use on block should be successful") -local lookAtEntityResult = p.lookAtEntity() -test.eq("Cow", lookAtEntityResult.name, "Problem with entity") diff --git a/src/testMod/server-files/computers/computer/0/tests/botania.manapool.lua b/src/testMod/server-files/computers/computer/0/tests/botania.manapool.lua deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/testMod/server-files/computers/computer/0/tests/peripherals.geoscanner.lua b/src/testMod/server-files/computers/computer/0/tests/peripherals.geoscanner.lua deleted file mode 100644 index 8e92b5a7f..000000000 --- a/src/testMod/server-files/computers/computer/0/tests/peripherals.geoscanner.lua +++ /dev/null @@ -1,9 +0,0 @@ -local p = peripheral.find("geoScanner") -test.assert(p, "There is no scanner") -local result, err = p.scan(3) -test.eq(nil, err, "Err should be nil") -test.assert(result, "There is no scan result") -sleep(2.5) -result, err = p.chunkAnalyze() -test.eq(nil, err, "Err should be nil") -test.eq(result, {}, "Chunk analyze result should be empty") diff --git a/src/testMod/server-files/computers/ids.json b/src/testMod/server-files/computers/ids.json deleted file mode 100644 index 9c8f668d7..000000000 --- a/src/testMod/server-files/computers/ids.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "computer": 0, - "peripheral.monitor": 1, - "peripheral.printer": 1 -} diff --git a/src/testMod/server-files/eula.txt b/src/testMod/server-files/eula.txt deleted file mode 100644 index e6765d6c9..000000000 --- a/src/testMod/server-files/eula.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Automatically generated EULA. Please don't use this for a real server. -eula=true diff --git a/src/testMod/server-files/server.properties b/src/testMod/server-files/server.properties deleted file mode 100644 index 60384302f..000000000 --- a/src/testMod/server-files/server.properties +++ /dev/null @@ -1,45 +0,0 @@ -# Minecraft server properties -allow-flight=false -allow-nether=true -broadcast-console-to-ops=true -broadcast-rcon-to-ops=true -difficulty=easy -enable-command-block=true -enable-query=false -enable-rcon=false -enforce-whitelist=false -force-gamemode=false -function-permission-level=2 -gamemode=creative -generate-structures=false -generator-settings= -hardcore=false -level-name=world -level-seed= -level-type=flat -max-build-height=256 -max-players=20 -max-tick-time=60000 -max-world-size=29999984 -motd=A testing server -network-compression-threshold=256 -online-mode=false -op-permission-level=4 -player-idle-timeout=0 -prevent-proxy-connections=false -pvp=true -query.port=25565 -rcon.password= -rcon.port=25575 -resource-pack= -resource-pack-sha1= -server-ip= -server-port=25565 -snooper-enabled=true -spawn-animals=true -spawn-monsters=true -spawn-npcs=true -spawn-protection=16 -use-native-transport=true -view-distance=10 -white-list=false diff --git a/src/testMod/server-files/structures/automata.weak.snbt b/src/testMod/server-files/structures/automata.weak.snbt deleted file mode 100644 index afbeb645c..000000000 --- a/src/testMod/server-files/structures/automata.weak.snbt +++ /dev/null @@ -1,155 +0,0 @@ -{ - size: [3, 3, 3], - entities: [], - 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: [1, 0, 0], - state: "minecraft:polished_andesite" - }, - { - pos: [1, 0, 1], - state: "minecraft:polished_andesite" - }, - { - pos: [1, 0, 2], - 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: [0, 1, 1], - state: "minecraft:polished_andesite" - }, - { - pos: [1, 1, 1], - state: "minecraft:cobblestone" - }, - { - pos: [1, 1, 2], - state: "minecraft:stone_bricks" - }, - { - pos: [2, 1, 1], - state: "minecraft:polished_andesite" - }, - { - pos: [0, 2, 1], - state: "minecraft:polished_andesite" - }, - { - pos: [1, 2, 0], - state: "minecraft:polished_andesite" - }, - { - pos: [1, 2, 2], - state: "minecraft:polished_andesite" - }, - { - pos: [2, 2, 1], - state: "minecraft:polished_andesite" - }, - { - pos: [0, 1, 0], - state: "minecraft:air" - }, - { - pos: [0, 1, 2], - state: "minecraft:air" - }, - { - pos: [2, 1, 0], - state: "minecraft:air" - }, - { - pos: [2, 1, 2], - state: "minecraft:air" - }, - { - pos: [0, 2, 0], - state: "minecraft:air" - }, - { - pos: [0, 2, 2], - state: "minecraft:air" - }, - { - pos: [1, 2, 1], - state: "minecraft:air" - }, - { - pos: [2, 2, 0], - state: "minecraft:air" - }, - { - pos: [2, 2, 2], - state: "minecraft:air" - }, - { - nbt: { - Owner: { - UpperId: 4039158846114182220L, - LowerId: -6876936588741668278L, - Name: "Dev" - }, - Fuel: 51197, - RightUpgrade: "advancedperipherals:weak_automata", - Slot: 0, - Items: [ - { - Slot: 0b, - id: "minecraft:netherite_pickaxe", - Count: 1b, - tag: { - Damage: 0 - } - }, - { - Slot: 1b, - id: "minecraft:cow_spawn_egg", - Count: 1b - } - ], - id: "computercraft:turtle_advanced", - RightUpgradeNbt: { - FUEL_CONSUMING_RATE: 1, - rotationCharge: 0 - }, - Label: "automata.weak", - ComputerId: 0, - On: 1b - }, - pos: [1, 1, 0], - state: "computercraft:turtle_advanced{facing:south}" - } - ], - palette: [ - "minecraft:polished_andesite", - "minecraft:cobblestone", - "minecraft:stone_bricks", - "minecraft:air", - "computercraft:turtle_advanced{facing:south}" - ], - DataVersion: 2730 -} diff --git a/src/testMod/server-files/structures/dummytest.passing.snbt b/src/testMod/server-files/structures/dummytest.passing.snbt deleted file mode 100644 index c53a423ca..000000000 --- a/src/testMod/server-files/structures/dummytest.passing.snbt +++ /dev/null @@ -1,11 +0,0 @@ -{ - DataVersion: 2730, - size: [1, 1, 1], - data: [ - {pos: [0, 0, 0], state: "minecraft:cobblestone"}, - ], - entities: [], - palette: [ - "minecraft:cobblestone" - ] -} \ No newline at end of file diff --git a/src/testMod/server-files/structures/peripherals.geoscanner.snbt b/src/testMod/server-files/structures/peripherals.geoscanner.snbt deleted file mode 100644 index 3eeb076da..000000000 --- a/src/testMod/server-files/structures/peripherals.geoscanner.snbt +++ /dev/null @@ -1,20 +0,0 @@ -{ - DataVersion: 2730, - size: [2, 2, 2], - data: [ - {pos: [0, 0, 0], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, Label: "peripherals.geoscanner", On: 1b, id: "computercraft:computer_advanced"}}, - {pos: [1, 0, 0], state: "advancedperipherals:geo_scanner"}, - {pos: [0, 1, 0], state: "minecraft:air"}, - {pos: [0, 0, 1], state: "minecraft:air"}, - {pos: [1, 1, 0], state: "minecraft:air"}, - {pos: [1, 0, 1], state: "minecraft:air"}, - {pos: [0, 1, 1], state: "minecraft:air"}, - {pos: [1, 1, 1], state: "minecraft:air"}, - ], - entities: [], - palette: [ - "computercraft:computer_advanced{facing:north,state:blinking}", - "advancedperipherals:geo_scanner", - "minecraft:air" - ] -} \ No newline at end of file