diff --git a/.editorconfig b/.editorconfig index 92cfba65e..11d6673ca 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,11 +18,6 @@ ij_any_if_brace_force = if_multiline ij_any_for_brace_force = if_multiline ij_any_spaces_within_array_initializer_braces = true -ij_kotlin_allow_trailing_comma = true -ij_kotlin_allow_trailing_comma_on_call_site = true -ij_kotlin_method_parameters_wrap = off -ij_kotlin_call_parameters_wrap = off - [*.md] trim_trailing_whitespace = false @@ -31,3 +26,16 @@ indent_size = 2 [*.yml] indent_size = 2 + +[{*.kt,*.kts}] +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_continuation_indent_size = 4 +ij_kotlin_spaces_around_equality_operators = true + +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true + +# Prefer to handle these manually +ij_kotlin_method_parameters_wrap = off +ij_kotlin_call_parameters_wrap = off +ij_kotlin_extends_list_wrap = off diff --git a/.gitignore b/.gitignore index 675c66cf6..7170eed48 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ *.iml .idea .gradle +.kotlin *.DS_Store /.classpath diff --git a/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts b/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts index 091e39c97..54414f366 100644 --- a/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts @@ -227,6 +227,5 @@ idea.module { // Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes. // This is required for Loom, and we patch Forge's run configurations to work there. - // TODO: Submit a patch to Forge to support ProjectRootManager. inheritOutputDirs = true } diff --git a/buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts b/buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts index 51a8dd943..a3792d67d 100644 --- a/buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts +++ b/buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts @@ -12,6 +12,7 @@ publishing { register("maven") { artifactId = base.archivesName.get() from(components["java"]) + suppressAllPomMetadataWarnings() pom { name = "CC: Tweaked" diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt index f97bc8084..7f73f04fa 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt @@ -11,13 +11,10 @@ import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Dependency -import org.gradle.api.attributes.TestSuiteType -import org.gradle.api.file.FileSystemOperations import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Provider import org.gradle.api.provider.SetProperty -import org.gradle.api.reporting.ReportingExtension import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.compile.JavaCompile @@ -25,7 +22,6 @@ import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.language.jvm.tasks.ProcessResources import org.gradle.process.JavaForkOptions -import org.gradle.testing.jacoco.plugins.JacocoCoverageReport import org.gradle.testing.jacoco.plugins.JacocoPluginExtension import org.gradle.testing.jacoco.plugins.JacocoTaskExtension import org.gradle.testing.jacoco.tasks.JacocoReport @@ -36,10 +32,7 @@ import java.io.IOException import java.net.URI import java.util.regex.Pattern -abstract class CCTweakedExtension( - private val project: Project, - private val fs: FileSystemOperations, -) { +abstract class CCTweakedExtension(private val project: Project) { /** Get the current git branch. */ val gitBranch: Provider = gitProvider("", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() } @@ -64,17 +57,11 @@ abstract class CCTweakedExtension( */ val sourceDirectories: SetProperty = project.objects.setProperty(SourceSetReference::class.java) - /** - * Dependencies excluded from published artifacts. - */ - private val excludedDeps: ListProperty = project.objects.listProperty(Dependency::class.java) - /** All source sets referenced by this project. */ val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } } init { sourceDirectories.finalizeValueOnRead() - excludedDeps.finalizeValueOnRead() project.afterEvaluate { sourceDirectories.disallowChanges() } } @@ -169,23 +156,18 @@ abstract class CCTweakedExtension( } fun jacoco(task: NamedDomainObjectProvider) where T : Task, T : JavaForkOptions { - val classDump = project.layout.buildDirectory.dir("jacocoClassDump/${task.name}") val reportTaskName = "jacoco${task.name.capitalise()}Report" val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java) task.configure { finalizedBy(reportTaskName) - - doFirst("Clean class dump directory") { fs.delete { delete(classDump) } } - jacoco.applyTo(this) - extensions.configure(JacocoTaskExtension::class.java) { - includes = listOf("dan200.computercraft.*") - classDumpDir = classDump.get().asFile - // Older versions of modlauncher don't include a protection domain (and thus no code - // source). Jacoco skips such classes by default, so we need to explicitly include them. - isIncludeNoLocationClasses = true + extensions.configure(JacocoTaskExtension::class.java) { + excludes = listOf( + "dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime. + "dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them. + ) } } @@ -194,15 +176,11 @@ abstract class CCTweakedExtension( description = "Generates code coverage report for the ${task.name} task." executionData(task.get()) - classDirectories.from(classDump) - // Don't want to use sourceSets(...) here as we have a custom class directory. - for (ref in sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories) - } - - project.extensions.configure(ReportingExtension::class.java) { - reports.register("${task.name}CodeCoverageReport", JacocoCoverageReport::class.java) { - testType.set(TestSuiteType.INTEGRATION_TEST) + // Don't want to use sourceSets(...) here as we don't use all class directories. + for (ref in this@CCTweakedExtension.sourceDirectories.get()) { + sourceDirectories.from(ref.sourceSet.allSource.sourceDirectories) + if (ref.classes) classDirectories.from(ref.sourceSet.output) } } } @@ -242,20 +220,6 @@ abstract class CCTweakedExtension( ).resolve().single() } - /** - * Exclude a dependency from being published in Maven. - */ - fun exclude(dep: Dependency) { - excludedDeps.add(dep) - } - - /** - * Configure a [MavenDependencySpec]. - */ - fun configureExcludes(spec: MavenDependencySpec) { - for (dep in excludedDeps.get()) spec.exclude(dep) - } - private fun gitProvider(default: T, command: List, process: (String) -> T): Provider { val baseResult = project.providers.exec { commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/DependencyCheck.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/DependencyCheck.kt index 78987ff72..9f7f41960 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/DependencyCheck.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/DependencyCheck.kt @@ -22,19 +22,19 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin abstract class DependencyCheck : DefaultTask() { @get:Input - abstract val configuration: ListProperty + protected abstract val dependencies: ListProperty /** * A mapping of module coordinates (`group:module`) to versions, overriding the requested version. */ @get:Input - abstract val overrides: MapProperty + protected abstract val overrides: MapProperty init { description = "Check :core's dependencies are consistent with Minecraft's." group = LifecycleBasePlugin.VERIFICATION_GROUP - configuration.finalizeValueOnRead() + dependencies.finalizeValueOnRead() overrides.finalizeValueOnRead() } @@ -45,13 +45,19 @@ abstract class DependencyCheck : DefaultTask() { overrides.putAll(project.provider { mutableMapOf(module.get().module.toString() to version) }) } + /** + * Add a configuration to check. + */ + fun configuration(configuration: Provider) { + // We can't store the Configuration in the cache, so store the resolved dependencies instead. + dependencies.addAll(configuration.map { it.incoming.resolutionResult.allDependencies }) + } + @TaskAction fun run() { var ok = true - for (configuration in configuration.get()) { - configuration.incoming.resolutionResult.allDependencies { - if (!check(this@allDependencies)) ok = false - } + for (configuration in dependencies.get()) { + if (!check(configuration)) ok = false } if (!ok) { diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt deleted file mode 100644 index e54ad74c3..000000000 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package cc.tweaked.gradle - -import org.gradle.api.artifacts.Dependency -import org.gradle.api.artifacts.MinimalExternalModuleDependency -import org.gradle.api.artifacts.ProjectDependency -import org.gradle.api.plugins.BasePluginExtension -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.specs.Spec - -/** - * A dependency in a POM file. - */ -data class MavenDependency(val groupId: String?, val artifactId: String?, val version: String?, val scope: String?) - -/** - * A spec specifying which dependencies to include/exclude. - */ -class MavenDependencySpec { - private val excludeSpecs = mutableListOf>() - - fun exclude(spec: Spec) { - excludeSpecs.add(spec) - } - - fun exclude(dep: Dependency) { - exclude { - // We have to cheat a little for project dependencies, as the project name doesn't match the artifact group. - val name = when (dep) { - is ProjectDependency -> dep.dependencyProject.extensions.getByType(BasePluginExtension::class.java).archivesName.get() - else -> dep.name - } - (dep.group.isNullOrEmpty() || dep.group == it.groupId) && - (name.isNullOrEmpty() || name == it.artifactId) && - (dep.version.isNullOrEmpty() || dep.version == it.version) - } - } - - fun exclude(dep: MinimalExternalModuleDependency) { - exclude { - dep.module.group == it.groupId && dep.module.name == it.artifactId - } - } - - fun isIncluded(dep: MavenDependency) = !excludeSpecs.any { it.isSatisfiedBy(dep) } -} - -/** - * Configure dependencies present in this publication's POM file. - * - * While this approach is very ugly, it's the easiest way to handle it! - */ -fun MavenPublication.mavenDependencies(action: MavenDependencySpec.() -> Unit) { - val spec = MavenDependencySpec() - action(spec) - - pom.withXml { - val dependencies = XmlUtil.findChild(asNode(), "dependencies") ?: return@withXml - dependencies.children().map { it as groovy.util.Node }.forEach { - val dep = MavenDependency( - groupId = XmlUtil.findChild(it, "groupId")?.text(), - artifactId = XmlUtil.findChild(it, "artifactId")?.text(), - version = XmlUtil.findChild(it, "version")?.text(), - scope = XmlUtil.findChild(it, "scope")?.text(), - ) - - if (!spec.isIncluded(dep)) it.parent().remove(it) - } - } -} diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt index 6dbe9fbd3..a4caa5dec 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt @@ -99,8 +99,8 @@ class MinecraftConfigurations private constructor(private val project: Project) val checkDependencyConsistency = project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) { // We need to check both the main and client classpath *configurations*, as the actual configuration - configuration.add(configurations.named(main.runtimeClasspathConfigurationName)) - configuration.add(configurations.named(client.runtimeClasspathConfigurationName)) + configuration(configurations.named(main.runtimeClasspathConfigurationName)) + configuration(configurations.named(client.runtimeClasspathConfigurationName)) } project.tasks.named("check") { dependsOn(checkDependencyConsistency) } } diff --git a/gradle.properties b/gradle.properties index edefdaf2c..1b3b55631 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,9 +10,8 @@ kotlin.jvm.target.validation.mode=error neogradle.subsystems.conventions.runs.enabled=false -# Mod properties isUnstable=true -modVersion=1.114.2 +modVersion=1.114.3 # Minecraft properties: We want to configure this here so we can read it in settings.gradle mcVersion=1.21.1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c197c93c..b3ba6a34a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,8 @@ create-fabric = "0.5.1-f-build.1467+mc1.20.1" # Testing hamcrest = "2.2" jqwik = "1.8.2" -junit = "5.10.1" +junit = "5.11.4" +junitPlatform = "1.11.4" jmh = "1.37" # Build tools @@ -132,6 +133,7 @@ jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" } junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" } +junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitPlatform" } slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } jmh = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } jmh-processor = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } @@ -190,7 +192,7 @@ externalMods-fabric-runtime = ["jei-fabric", "modmenu"] # Testing test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"] -testRuntime = ["junit-jupiter-engine", "jqwik-engine"] +testRuntime = ["junit-jupiter-engine", "junit-platform-launcher", "jqwik-engine"] # Build tools teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"] diff --git a/projects/common/build.gradle.kts b/projects/common/build.gradle.kts index 8441a1a50..cddd9afee 100644 --- a/projects/common/build.gradle.kts +++ b/projects/common/build.gradle.kts @@ -129,4 +129,7 @@ val runExampleData by tasks.registering(MergeTrees::class) { configureForDatagen(sourceSets.examples.get(), "src/examples/generatedResources") } -tasks.withType(GenerateModuleMetadata::class).configureEach { isEnabled = false } +// We can't create accurate module metadata for our additional capabilities, so disable it. +project.tasks.withType(GenerateModuleMetadata::class.java).configureEach { + isEnabled = false +} diff --git a/projects/common/src/datagen/java/dan200/computercraft/data/TagProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/TagProvider.java index e0e60f990..8b81ae1f7 100644 --- a/projects/common/src/datagen/java/dan200/computercraft/data/TagProvider.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/TagProvider.java @@ -60,10 +60,7 @@ class TagProvider { tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).addTag(BlockTags.WOOL).add(Blocks.COBWEB); - tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE) - .addTag(BlockTags.BEEHIVES) - .addTag(BlockTags.CAULDRONS) - .add(Blocks.COMPOSTER); + tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE); // Make all blocks aside from command computer mineable. tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add( diff --git a/projects/common/src/generated/resources/data/computercraft/tags/block/turtle_can_use.json b/projects/common/src/generated/resources/data/computercraft/tags/block/turtle_can_use.json index 18c71801e..9783d2443 100644 --- a/projects/common/src/generated/resources/data/computercraft/tags/block/turtle_can_use.json +++ b/projects/common/src/generated/resources/data/computercraft/tags/block/turtle_can_use.json @@ -1 +1 @@ -{"values": ["#minecraft:beehives", "#minecraft:cauldrons", "minecraft:composter"]} +{"values": []} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java index 0b324e380..549167b3a 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java @@ -175,8 +175,7 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple } private boolean canInputPage() { - var inkStack = inventory.get(0); - return !inkStack.isEmpty() && isInk(inkStack) && getPaperLevel() > 0; + return getInkLevel() > 0 && getPaperLevel() > 0; } private boolean inputPage() { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java b/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java index c80f3f1fc..eaf8c70a6 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java @@ -33,6 +33,7 @@ import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -42,7 +43,6 @@ import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; import java.util.List; import java.util.function.Consumer; -import java.util.function.Predicate; /** * Abstraction layer for Forge and Fabric. See implementations for more details. @@ -283,20 +283,40 @@ public interface PlatformHelper { boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos); /** - * Place an item against a block. - *

- * Implementations should largely mirror {@link ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)} - * (including any loader-specific modifications), except the call to {@link BlockState#useItemOn(ItemStack, Level, Player, InteractionHand, BlockHitResult)} - * should only be evaluated when {@code canUseBlock} evaluates to true. - * - * @param player The player which is placing this item. - * @param stack The item to place. - * @param hit The collision with the block we're placing against. - * @param canUseBlock Test whether the block should be interacted with first. - * @return Whether any interaction occurred. - * @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult) + * The result of attempting to use an item on a block. */ - InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate canUseBlock); + sealed interface UseOnResult { + /** + * This interaction was intercepted by an event, and handled. + * + * @param result The result of using an item on a block. + */ + record Handled(InteractionResult result) implements UseOnResult { + } + + /** + * This result was not handled, and should be handled by the caller. + * + * @param block Whether the block may be used ({@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)}). + * @param item Whether the item may be used on the block ({@link ItemStack#useOn(UseOnContext)}). + * @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult) + */ + record Continue(boolean block, boolean item) implements UseOnResult { + } + } + + /** + * Run mod-loader specific code before placing an item against a block. + *

+ * This should dispatch any mod-loader specific events that are fired when clicking a block. It does necessarily + * handle the actual clicking of the block — see {@link UseOnResult.Handled} and {@link UseOnResult.Continue}. + * + * @param player The player which is placing this item. + * @param stack The item to place. + * @param hit The collision with the block we're placing against. + * @return Whether any interaction occurred. + */ + UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit); final class Instance { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java index 16dc81a5d..d9b8c9196 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java @@ -16,7 +16,7 @@ import net.minecraft.world.phys.Vec3; /** * An object that holds a pocket computer item. */ -public sealed interface PocketHolder permits PocketHolder.EntityHolder { +public sealed interface PocketHolder { /** * The level this holder is in. * @@ -54,7 +54,7 @@ public sealed interface PocketHolder permits PocketHolder.EntityHolder { /** * An {@link Entity} holding a pocket computer. */ - sealed interface EntityHolder extends PocketHolder permits PocketHolder.PlayerHolder, PocketHolder.ItemEntityHolder { + sealed interface EntityHolder extends PocketHolder { /** * Get the entity holding this pocket computer. * diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java index 0df008103..df99a3336 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java @@ -21,6 +21,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; +import net.minecraft.world.ItemInteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.*; import net.minecraft.world.item.context.BlockPlaceContext; @@ -202,18 +203,33 @@ public class TurtlePlaceCommand implements TurtleCommand { * @return If this item was deployed. */ private static InteractionResult doDeployOnBlock(ItemStack stack, TurtlePlayer turtlePlayer, BlockHitResult hit, boolean adjacent) { - var result = PlatformHelper.get().useOn( - turtlePlayer.player(), stack, hit, - adjacent ? x -> x.is(ComputerCraftTags.Blocks.TURTLE_CAN_USE) : x -> false - ); - if (result != InteractionResult.PASS) return result; + var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit); + switch (result) { + case PlatformHelper.UseOnResult.Handled handled -> { + if (handled.result() != InteractionResult.PASS) return handled.result(); + } + case PlatformHelper.UseOnResult.Continue canUse -> { + var player = turtlePlayer.player(); + var block = player.level().getBlockState(hit.getBlockPos()); + if (adjacent && canUse.block()) { + var useItemOnResult = block.useItemOn(stack, player.level(), player, InteractionHand.MAIN_HAND, hit); + if (useItemOnResult.consumesAction()) return useItemOnResult.result(); - var level = turtlePlayer.player().level(); + if (useItemOnResult == ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION && block.is(ComputerCraftTags.Blocks.TURTLE_CAN_USE)) { + var useWithoutItemResult = block.useWithoutItem(player.level(), player, hit); + if (useWithoutItemResult.consumesAction()) return useWithoutItemResult; + } + } + + var useOnResult = stack.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit)); + if (useOnResult != InteractionResult.PASS) return useOnResult; + } + } // We special case some items which we allow to place "normally". Yes, this is very ugly. var item = stack.getItem(); if (item instanceof BucketItem || item instanceof PlaceOnWaterBlockItem || stack.is(ComputerCraftTags.Items.TURTLE_CAN_PLACE)) { - return turtlePlayer.player().gameMode.useItem(turtlePlayer.player(), level, stack, InteractionHand.MAIN_HAND); + return turtlePlayer.player().gameMode.useItem(turtlePlayer.player(), turtlePlayer.player().level(), stack, InteractionHand.MAIN_HAND); } return InteractionResult.PASS; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java index afac649d5..65c059fc5 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java @@ -36,6 +36,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.Projectile; import net.minecraft.world.entity.projectile.ProjectileDeflection; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; @@ -303,7 +304,7 @@ public class TurtleTool extends AbstractTurtleUpgrade { * @return Whether the tool was successfully used. * @see PlatformHelper#hasToolUsage(ItemStack) */ - private boolean useTool(ServerLevel level, ITurtleAccess turtle, TurtlePlayer turtlePlayer, ItemStack stack, Direction direction) { + private static boolean useTool(ServerLevel level, ITurtleAccess turtle, TurtlePlayer turtlePlayer, ItemStack stack, Direction direction) { var position = turtle.getPosition().relative(direction); // Allow digging one extra block below the turtle, as you can't till dirt/flatten grass if there's a block // above. @@ -314,8 +315,12 @@ public class TurtleTool extends AbstractTurtleUpgrade { } var hit = TurtlePlaceCommand.getHitResult(position, direction.getOpposite()); - var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit, x -> false); - return result.consumesAction(); + var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit); + return switch (result) { + case PlatformHelper.UseOnResult.Handled handled -> handled.result().consumesAction(); + case PlatformHelper.UseOnResult.Continue canUse -> + canUse.item() && stack.useOn(new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit)).consumesAction(); + }; } private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) { diff --git a/projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json b/projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json index 582892135..9161daa72 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json @@ -205,8 +205,10 @@ "item.computercraft.treasure_disk": "Disketa", "itemGroup.computercraft": "ComputerCraft", "tag.item.computercraft.computer": "Počítače", + "tag.item.computercraft.dyeable": "Obarvitelné předměty", "tag.item.computercraft.monitor": "Monitory", "tag.item.computercraft.turtle": "Roboti", + "tag.item.computercraft.turtle_can_place": "Roboty-umístitelné předměty", "tag.item.computercraft.wired_modem": "Drátové modemy", "tracking_field.computercraft.avg": "%s (průměr)", "tracking_field.computercraft.computer_tasks.name": "Úlohy", diff --git a/projects/common/src/main/resources/assets/computercraft/lang/ja_jp.json b/projects/common/src/main/resources/assets/computercraft/lang/ja_jp.json index 83230c752..8b5634fe9 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/ja_jp.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/ja_jp.json @@ -205,8 +205,10 @@ "item.computercraft.treasure_disk": "フロッピーディスク", "itemGroup.computercraft": "ComputerCraft", "tag.item.computercraft.computer": "コンピューター", + "tag.item.computercraft.dyeable": "染色可能なアイテム", "tag.item.computercraft.monitor": "モニター", "tag.item.computercraft.turtle": "タートル", + "tag.item.computercraft.turtle_can_place": "タートルが設置可能なアイテム", "tag.item.computercraft.wired_modem": "有線モデム", "tracking_field.computercraft.avg": "%s (平均)", "tracking_field.computercraft.computer_tasks.name": "タスク", diff --git a/projects/common/src/main/resources/assets/computercraft/lang/pt_br.json b/projects/common/src/main/resources/assets/computercraft/lang/pt_br.json index 56ab8174c..25df945d2 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/pt_br.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/pt_br.json @@ -82,26 +82,35 @@ "gui.computercraft.config.disabled_generic_methods.tooltip": "Uma lista de métodos genéricos ou fontes de métodos a serem desativados. Métodos genéricos são aqueles adicionados a um bloco ou entidade de bloco quando não há um provedor de periféricos explícito. Isso inclui métodos de inventário (ou seja, inventory.getItemDetail, inventory.pushItems) e, se estiver usando Forge, os métodos fluid_storage e energy_storage. \nOs métodos nesta lista podem ser um grupo inteiro de métodos (computercraft:inventory) ou um único método (computercraft:inventory#pushItems).\n", "gui.computercraft.config.execution": "Execução", "gui.computercraft.config.execution.computer_threads": "Threads por computador", + "gui.computercraft.config.execution.computer_threads.tooltip": "Defina o número de threads que os computadores podem utilizar. Um número maior significa que mais computadores podem ser executados ao mesmo tempo, mas pode causar lag. Observe que alguns mods podem não funcionar com uma contagem de threads superior a 1. Use com cautela.", "gui.computercraft.config.execution.max_main_computer_time": "Limite de tempo do computador por tick do servidor", + "gui.computercraft.config.execution.max_main_computer_time.tooltip": "O tempo máximo ideal que um computador pode executar em um tick, em milissegundos. \nObserve que é bastante provável que ultrapassemos esse limite, pois não há como saber exatamente quanto tempo uma operação levará - isso visa ser o limite superior do tempo médio.", "gui.computercraft.config.execution.max_main_global_time": "Limite de tempo global por tick do servidor", + "gui.computercraft.config.execution.max_main_global_time.tooltip": "O tempo máximo que pode ser gasto executando tarefas em um único tick, em milissegundos. \nObserve que é bastante provável que ultrapassemos esse limite, pois não há como saber exatamente quanto tempo uma operação levará - isso visa ser o limite superior do tempo médio.", "gui.computercraft.config.execution.tooltip": "Controla o comportamento de execução dos computadores. Isso é principalmente destinado ao ajuste fino dos servidores e, geralmente, não deve ser alterado.", "gui.computercraft.config.floppy_space_limit": "Limite de espaço dos Disquetes (bytes)", "gui.computercraft.config.floppy_space_limit.tooltip": "O limite de espaço em disco para disquete em bytes.", "gui.computercraft.config.http": "HTTP", "gui.computercraft.config.http.bandwidth": "Largura de banda", "gui.computercraft.config.http.bandwidth.global_download": "Limite de download global", + "gui.computercraft.config.http.bandwidth.global_download.tooltip": "O número de bytes que podem ser baixados em um segundo. Isso é compartilhado entre todos os computadores. (bytes/s).", "gui.computercraft.config.http.bandwidth.global_upload": "Limite de upload global", + "gui.computercraft.config.http.bandwidth.global_upload.tooltip": "O número de bytes que podem ser enviados em um segundo. Isso é compartilhado entre todos os computadores. (bytes/s).", "gui.computercraft.config.http.bandwidth.tooltip": "Limita a banda usada pelos computadores.", "gui.computercraft.config.http.enabled": "Habilitar a biblioteca de HTTP", "gui.computercraft.config.http.enabled.tooltip": "Ative a API \"http\" nos Computadores. Desativar isso também desativa os programas \"pastebin\" e \"wget\", dos quais muitos usuários dependem. É recomendado deixar isso ativado e usar a opção de configuração \"rules\" para impor um controle mais detalhado.", "gui.computercraft.config.http.max_requests": "Limite de conexões paralelas", + "gui.computercraft.config.http.max_requests.tooltip": "O número de solicitações Http que um computador pode fazer ao mesmo tempo. Solicitações adicionais serão enfileiradas e enviadas quando as solicitações em execução forem concluídas. Defina como 0 para ilimitado.", "gui.computercraft.config.http.max_websockets": "Limite de conexões websocket", + "gui.computercraft.config.http.max_websockets.tooltip": "O número de websockets que um computador pode ter abertos ao mesmo tempo.", "gui.computercraft.config.http.proxy": "Proxy", "gui.computercraft.config.http.proxy.host": "Nome de host", "gui.computercraft.config.http.proxy.host.tooltip": "O nome do host ou endereço IP do servidor proxy.", "gui.computercraft.config.http.proxy.port": "Porta", + "gui.computercraft.config.http.proxy.port.tooltip": "A porta do servidor proxy.", "gui.computercraft.config.http.proxy.tooltip": "Tunéis de solicitações HTTP e websocket através de um servidor proxy. Afeta apenas as regras HTTP com \"use_proxy\" definido como verdadeiro (desligado por padrão). Se a autenticação for necessária para o proxy, crie um arquivo \"computercraft-proxy.pw\" no mesmo diretório que \"computercraft-server.toml\", contendo o nome de usuário e a senha separados por dois pontos, por exemplo, \"meuusuario:minhasenha\". Para proxies SOCKS4, apenas o nome de usuário é necessário.", "gui.computercraft.config.http.proxy.type": "Tipo de proxy", + "gui.computercraft.config.http.proxy.type.tooltip": "O tipo de proxy para usar.", "gui.computercraft.config.http.rules": "Regras Permitir/Negar", "gui.computercraft.config.http.rules.tooltip": "Uma lista de regras que controlam o comportamento da API \"http\" para domínios ou IPs específicos. Cada regra corresponde a um nome de host e uma porta opcional, e então define várias propriedades para a solicitação. As regras são avaliadas em ordem, o que significa que regras anteriores sobrescrevem as posteriores.\n\nPropriedades válidas:\n- \"host\" (obrigatório): O domínio ou endereço IP que esta regra corresponde. Isso pode ser um nome de domínio (\"pastebin.com\"), um curinga (\"*.pastebin.com\") ou notação CIDR (\"127.0.0.0/8\").\n- \"port\" (opcional): Corresponder apenas a solicitações para uma porta específica, como 80 ou 443.\n\n- \"action\" (opcional): Se permitir ou negar esta solicitação.\n- \"max_download\" (opcional): O tamanho máximo (em bytes) que um computador pode baixar nesta solicitação.\n- \"max_upload\" (opcional): O tamanho máximo (em bytes) que um computador pode enviar em uma solicitação.\n- \"max_websocket_message\" (opcional): O tamanho máximo (em bytes) que um computador pode enviar ou receber em um pacote websocket.\n- \"use_proxy\" (opcional): Habilitar o uso do proxy HTTP/SOCKS se estiver configurado.", "gui.computercraft.config.http.tooltip": "Controla a API HTTP", diff --git a/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json b/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json index f20146b7c..c1491e199 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json @@ -74,25 +74,34 @@ "gui.computercraft.config.default_computer_settings.tooltip": "Разделенный запятыми список системных настроек по умолчанию на новых компьютерах.\nНапример: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nотключит всё автодополнение.", "gui.computercraft.config.execution": "Выполнение", "gui.computercraft.config.execution.computer_threads": "Потоки компьютера", + "gui.computercraft.config.execution.computer_threads.tooltip": "Устанавливает количество потоков, на которых работают компьютеры. Большее число\nозначает, что больше компьютеров сможет работать одновременно, но может привести к лагу.\nОбратите внимание, что некоторые моды могут не работать с более чем одним потоком. Используйте с осторожностью.", + "gui.computercraft.config.execution.max_main_computer_time.tooltip": "Идеальный максимум времени, которое отведено компьютеру на выполнение задач, в миллисекундах.\nМы вполне возможно выйдем за этот лимит, так как невозможно предсказать сколько\nвремени будет затрачено на выполнение задач, это лишь верхний лимит среднего значения времени.", "gui.computercraft.config.execution.max_main_global_time": "Глобальный лимит времени на тик сервера", + "gui.computercraft.config.execution.max_main_global_time.tooltip": "Максимум времени, которое может быть потрачено на выполнение задач за один тик, в \nмиллисекундах. \nМы вполне возможно выйдем за этот лимит, так как невозможно предсказать сколько\nвремени будет затрачено на выполнение задач, это лишь верхний лимит среднего значения времени.", "gui.computercraft.config.execution.tooltip": "Контролирует поведение выполнения задач компьютеров. Эта настройка преднезначается для \nтонкой настройки серверов, и в основном не должна быть изменена.", "gui.computercraft.config.floppy_space_limit": "Лимит места на дискетах (байты)", "gui.computercraft.config.floppy_space_limit.tooltip": "Лимит места для хранения информации на дискетах, в байтах.", "gui.computercraft.config.http": "HTTP", "gui.computercraft.config.http.bandwidth": "Пропускная способность", "gui.computercraft.config.http.bandwidth.global_download": "Глобальный лимит на скачивание", + "gui.computercraft.config.http.bandwidth.global_download.tooltip": "Количество байтов, которое можно скачать за секунду. Все компьютеры делят эту пропускную способность. (байты в секунду)", "gui.computercraft.config.http.bandwidth.global_upload": "Глобальный лимит загрузки", + "gui.computercraft.config.http.bandwidth.global_upload.tooltip": "Количество байтов, которое можно загрузить за секунду. Все компьютеры делят эту пропускную способность. (байты в секунду)", "gui.computercraft.config.http.bandwidth.tooltip": "Ограничивает пропускную способность, используемую компьютерами.", "gui.computercraft.config.http.enabled": "Включить HTTP API", "gui.computercraft.config.http.enabled.tooltip": "Включить API \"http\" на Компьютерах. Это также отключает программы \"pastebin\" и \"wget\", \nкоторые нужны многим пользователям. Рекомендуется оставить это включенным и использовать \nконфиг \"rules\" для более тонкой настройки.", "gui.computercraft.config.http.max_requests": "Максимум одновременных запросов", + "gui.computercraft.config.http.max_requests.tooltip": "Количество http-запросов, которые компьютер может сделать одновременно. Дополнительные запросы \nбудут поставлены в очередь, и отправлены когда существующие запросы будут выполнены. Установите на 0 для \nнеограниченных запросов.", "gui.computercraft.config.http.max_websockets": "Максимум одновременных веб-сокетов", + "gui.computercraft.config.http.max_websockets.tooltip": "Количество одновременно открытых веб-сокетов, которые может иметь компьютер. Установите на 0 для неограниченных веб-сокетов.", "gui.computercraft.config.http.proxy": "Proxy", "gui.computercraft.config.http.proxy.host": "Имя хоста", "gui.computercraft.config.http.proxy.host.tooltip": "Имя хоста или IP-адрес прокси-сервера.", "gui.computercraft.config.http.proxy.port": "Порт", + "gui.computercraft.config.http.proxy.port.tooltip": "Порт прокси-сервера.", "gui.computercraft.config.http.proxy.tooltip": "Туннелирует HTTP-запросы и запросы websocket через прокси-сервер. Влияет только на HTTP\nправила с параметром \"use_proxy\" в значении true (отключено по умолчанию).\nЕсли для прокси-сервера требуется аутентификация, создайте \"computercraft-proxy.pw\"\nфайл в том же каталоге, что и \"computercraft-server.toml\", содержащий имя\nпользователя и пароль, разделенные двоеточием, например \"myuser:mypassword\". Для\nпрокси-серверов SOCKS4 требуется только имя пользователя.", "gui.computercraft.config.http.proxy.type": "Тип прокси-сервера", + "gui.computercraft.config.http.proxy.type.tooltip": "Тип используемого прокси-сервера.", "gui.computercraft.config.http.rules": "Разрешающие/запрещающие правила", "gui.computercraft.config.http.rules.tooltip": "Список правил, которые контролируют поведение «http» API для определенных доменов или\nIP-адресов. Каждое правило представляет собой элемент с «узлом» для сопоставления и набором\nсвойств. Правила оцениваются по порядку, то есть более ранние правила перевешивают\nболее поздние.\nХост может быть доменным именем (\"pastebin.com\"), wildcard-сертификатом (\"*.pastebin.com\") или\nнотацией CIDR (\"127.0.0.0/8\").\nЕсли правил нет, домен блокируется.", "gui.computercraft.config.http.websocket_enabled": "Включить веб-сокеты", diff --git a/projects/common/src/main/resources/assets/computercraft/lang/tr_tr.json b/projects/common/src/main/resources/assets/computercraft/lang/tr_tr.json index 25edd3c4b..4aa73046c 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/tr_tr.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/tr_tr.json @@ -205,8 +205,10 @@ "item.computercraft.treasure_disk": "Disket", "itemGroup.computercraft": "ComputerCraft", "tag.item.computercraft.computer": "Bilgisayarlar", + "tag.item.computercraft.dyeable": "Boyanabilir eşyalar", "tag.item.computercraft.monitor": "Monitörler", "tag.item.computercraft.turtle": "Turtlelar", + "tag.item.computercraft.turtle_can_place": "Kaplumbağa-yerleştirilebilir eşyalar", "tag.item.computercraft.wired_modem": "Kablolu modemler", "tracking_field.computercraft.avg": "%s (ort.)", "tracking_field.computercraft.computer_tasks.name": "Görevler", diff --git a/projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json b/projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json index 2047644d3..fd940de28 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json @@ -205,8 +205,10 @@ "item.computercraft.treasure_disk": "软盘", "itemGroup.computercraft": "ComputerCraft", "tag.item.computercraft.computer": "计算机", + "tag.item.computercraft.dyeable": "可染色物品", "tag.item.computercraft.monitor": "监视器", "tag.item.computercraft.turtle": "海龟", + "tag.item.computercraft.turtle_can_place": "可放置海龟物品", "tag.item.computercraft.wired_modem": "有线调制解调器", "tracking_field.computercraft.avg": "%s (平均)", "tracking_field.computercraft.computer_tasks.name": "任务", diff --git a/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java b/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java index 6636304bc..80ee6d7f4 100644 --- a/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java +++ b/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java @@ -43,7 +43,6 @@ import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; import java.util.List; import java.util.function.Consumer; -import java.util.function.Predicate; @AutoService({ PlatformHelper.class, ComputerCraftAPIService.class }) public class TestPlatformHelper extends AbstractComputerCraftAPI implements PlatformHelper { @@ -143,7 +142,7 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat } @Override - public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate canUseBlock) { + public UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) { throw new UnsupportedOperationException("Cannot interact with the world inside tests"); } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt index 4034bba06..49cfe5c21 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt @@ -4,13 +4,13 @@ package dan200.computercraft.gametest -import dan200.computercraft.gametest.api.assertBlockHas -import dan200.computercraft.gametest.api.assertExactlyItems -import dan200.computercraft.gametest.api.getBlockEntity -import dan200.computercraft.gametest.api.sequence +import dan200.computercraft.api.lua.Coerced +import dan200.computercraft.api.lua.LuaException +import dan200.computercraft.gametest.api.* import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.media.items.PrintoutData import dan200.computercraft.shared.peripheral.printer.PrinterBlock +import dan200.computercraft.shared.peripheral.printer.PrinterPeripheral import dan200.computercraft.shared.util.DataComponentUtil import net.minecraft.core.BlockPos import net.minecraft.core.component.DataComponents @@ -20,11 +20,12 @@ import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import net.minecraft.world.level.block.RedStoneWireBlock -import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.* +import java.util.* class Printer_Test { /** - * Check comparators can read the contents of the disk drive + * Check comparators can read the contents of the printer */ @GameTest fun Comparator(helper: GameTestHelper) = helper.sequence { @@ -33,19 +34,19 @@ class Printer_Test { // Adding items should provide power thenExecute { - val drive = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get()) - drive.setItem(0, ItemStack(Items.BLACK_DYE)) - drive.setItem(1, ItemStack(Items.PAPER)) - drive.setChanged() + val printer = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get()) + printer.setItem(0, ItemStack(Items.BLACK_DYE)) + printer.setItem(1, ItemStack(Items.PAPER)) + printer.setChanged() } thenIdle(2) thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 1) } // And removing them should reset power. thenExecute { - val drive = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get()) - drive.clearContent() - drive.setChanged() + val printer = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get()) + printer.clearContent() + printer.setChanged() } thenIdle(2) thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 0) } @@ -54,35 +55,127 @@ class Printer_Test { /** * Changing the inventory contents updates the block state */ - @GameTest + @GameTest(template = "printer_test.empty") fun Contents_updates_state(helper: GameTestHelper) = helper.sequence { val pos = BlockPos(2, 2, 2) thenExecute { - val drive = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) + val printer = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) - drive.setItem(1, ItemStack(Items.PAPER)) - drive.setChanged() + printer.setItem(1, ItemStack(Items.PAPER)) + printer.setChanged() helper.assertBlockHas(pos, PrinterBlock.TOP, true, message = "One item in the top row") helper.assertBlockHas(pos, PrinterBlock.BOTTOM, false, message = "One item in the top row") - drive.setItem(7, ItemStack(Items.PAPER)) - drive.setChanged() + printer.setItem(7, ItemStack(Items.PAPER)) + printer.setChanged() helper.assertBlockHas(pos, PrinterBlock.TOP, true, message = "One item in each row") helper.assertBlockHas(pos, PrinterBlock.BOTTOM, true, message = "One item in each row") - drive.setItem(1, ItemStack.EMPTY) - drive.setChanged() + printer.setItem(1, ItemStack.EMPTY) + printer.setChanged() helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "One item in the bottom") helper.assertBlockHas(pos, PrinterBlock.BOTTOM, true, message = "One item in the bottom row") - drive.setItem(7, ItemStack.EMPTY) - drive.setChanged() + printer.setItem(7, ItemStack.EMPTY) + printer.setChanged() helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "Empty") helper.assertBlockHas(pos, PrinterBlock.BOTTOM, false, message = "Empty") } } + /** + * Printing a page + */ + @GameTest(template = "printer_test.empty") + fun Print_page(helper: GameTestHelper) = helper.sequence { + val pos = BlockPos(2, 2, 2) + + thenExecute { + val printer = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) + val peripheral = printer.peripheral() as PrinterPeripheral + + // Try to print with no pages + assertFalse(peripheral.newPage(), "newPage fails with no items") + + // Try to print with just ink + printer.setItem(0, ItemStack(Items.BLUE_DYE)) + printer.setChanged() + assertFalse(peripheral.newPage(), "newPage fails with no paper") + + printer.clearContent() + + // Try to print with just paper + printer.setItem(1, ItemStack(Items.PAPER)) + printer.setChanged() + assertFalse(peripheral.newPage(), "newPage fails with no ink") + + printer.clearContent() + + // Try to print with both items + printer.setItem(0, ItemStack(Items.BLUE_DYE)) + printer.setItem(1, ItemStack(Items.PAPER)) + printer.setChanged() + assertTrue(peripheral.newPage(), "newPage succeeds") + + // newPage() should consume both items and update the block state + helper.assertContainerEmpty(pos) + helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "Empty") + helper.assertBlockHas(pos, PrinterBlock.BOTTOM, false, message = "Empty") + + assertFalse(peripheral.newPage(), "Cannot start a page when already printing") + + peripheral.setPageTitle(Optional.of("New Page")) + peripheral.write(Coerced("Hello, world!")) + peripheral.setCursorPos(5, 2) + peripheral.write(Coerced("Second line")) + + // Try to finish the page + assertTrue(peripheral.endPage(), "endPage prints item") + + // endPage() should + helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "Empty") + helper.assertBlockHas(pos, PrinterBlock.BOTTOM, true, message = "Has pages") + + // And check the inventory matches + val emptyLine = createEmptyLine('b') + val lines = MutableList(PrintoutData.LINES_PER_PAGE) { emptyLine } + lines[0] = lines[0].text("Hello, world! ") + lines[1] = lines[1].text(" Second line ") + + helper.assertContainerExactly( + pos, + listOf( + // Ink + ItemStack.EMPTY, + // Paper + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + // Pages + DataComponentUtil.createStack(ModRegistry.Items.PRINTED_PAGE.get(), ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData("New Page", lines)), + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ), + ) + + val error = assertThrows(LuaException::class.java) { peripheral.endPage() } + assertEquals("Page not started", error.message) + } + } + + /** + * Can't print when full. + */ + @GameTest + fun No_print_when_full(helper: GameTestHelper) = helper.sequence { + val pos = BlockPos(2, 2, 2) + + thenExecute { + val printer = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) + val peripheral = printer.peripheral() as PrinterPeripheral + assertTrue(peripheral.newPage()) + assertFalse(peripheral.endPage(), "Cannot print when full") + } + } + /** * When the block is broken, we drop the contents and an optionally named stack. */ @@ -115,4 +208,10 @@ class Printer_Test { assertEquals("3333333333333333333333333", printout.lines[0].foreground) } } + + private fun createEmptyLine(bg: Char): PrintoutData.Line { + return PrintoutData.Line(" ".repeat(PrintoutData.LINE_LENGTH), bg.toString().repeat(PrintoutData.LINE_LENGTH)) + } + + fun PrintoutData.Line.text(text: String) = PrintoutData.Line(text, foreground) } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt index 778b2f0b9..304e746fe 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt @@ -23,7 +23,8 @@ class Speaker_Test { callPeripheral("right", "playSound", SoundEvents.NOTE_BLOCK_HARP.key().location().toString()) .assertArrayEquals(true) - tryMultipleTimes(2) { // We could technically call this a tick later, so try twice + tryMultipleTimes(2) { + // We could technically call this a tick later, so try twice callPeripheral("right", "playSound", SoundEvents.NOTE_BLOCK_HARP.key().location().toString()) .assertArrayEquals(false) } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt index 062dfbed5..3b7a34c65 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt @@ -31,7 +31,6 @@ import dan200.computercraft.shared.util.WaterloggableHelpers import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.computer.LuaTaskContext import dan200.computercraft.test.core.computer.getApi -import dan200.computercraft.test.shared.ItemStackMatcher.isStack import net.minecraft.core.BlockPos import net.minecraft.core.registries.Registries import net.minecraft.gametest.framework.GameTest @@ -50,7 +49,8 @@ import net.minecraft.world.level.block.FenceBlock import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.properties.BlockStateProperties import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.* +import org.hamcrest.Matchers.array +import org.hamcrest.Matchers.instanceOf import org.junit.jupiter.api.Assertions.* import java.util.* import java.util.concurrent.CopyOnWriteArrayList @@ -142,7 +142,23 @@ class Turtle_Test { ) } thenExecute { - helper.assertBlockIs(BlockPos(2, 2, 2)) { it.block == Blocks.COMPOSTER && it.getValue(ComposterBlock.LEVEL) == 2 } + helper.assertBlockHas(BlockPos(2, 2, 2), ComposterBlock.LEVEL, 2) + } + } + + /** + * Checks that turtles cannot place items into non-adjacent blocks. + * + * See [ComputerCraftTags.Blocks.TURTLE_CAN_USE]. + */ + @GameTest + fun Place_into_composter_non_adjacent(helper: GameTestHelper) = helper.sequence { + thenOnComputer { + turtle.place(ObjectArguments()).await() + .assertArrayEquals(false, "Cannot place item here", message = "Failed to place item") + } + thenExecute { + helper.assertBlockHas(BlockPos(2, 2, 3), ComposterBlock.LEVEL, 0) } } @@ -163,7 +179,7 @@ class Turtle_Test { ) } thenExecute { - helper.assertBlockIs(BlockPos(2, 2, 2)) { it.block == Blocks.BEEHIVE && it.getValue(BeehiveBlock.HONEY_LEVEL) == 0 } + helper.assertBlockHas(BlockPos(2, 2, 2), BeehiveBlock.HONEY_LEVEL, 0) } } @@ -786,15 +802,13 @@ class Turtle_Test { callPeripheral("left", "craft", 1).assertArrayEquals(true) } thenExecute { - val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) - assertThat( - "Inventory is as expected.", - turtle.items, - contains( - isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND_PICKAXE, 1), - isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), + helper.assertContainerExactly( + BlockPos(2, 2, 2), + listOf( + ItemStack(Items.DIAMOND), ItemStack(Items.DIAMOND), ItemStack(Items.DIAMOND), ItemStack(Items.DIAMOND_PICKAXE), + ItemStack.EMPTY, ItemStack(Items.STICK), ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack(Items.STICK), ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ), ) } @@ -807,22 +821,19 @@ class Turtle_Test { */ @GameTest fun Craft_remainder(helper: GameTestHelper) = helper.sequence { - thenOnComputer { - callPeripheral("left", "craft", 1).assertArrayEquals(true) - } thenExecute { val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + assertTrue(TurtleCraftCommand(1).execute(turtle.access).isSuccess, "Crafting succeeded") val turtleStack = ItemStack(ModRegistry.Items.TURTLE_NORMAL.get()) - assertThat( - "Inventory is as expected.", - turtle.items, - contains( - isStack(turtleStack), isStack(Items.WET_SPONGE, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), + helper.assertContainerExactly( + BlockPos(2, 2, 2), + listOf( + turtleStack, ItemStack(Items.WET_SPONGE), ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ), ) } @@ -837,7 +848,8 @@ class Turtle_Test { fun Craft_offset(helper: GameTestHelper) = helper.sequence { for (offset in listOf(0, 1, 4, 5)) { thenExecute { - val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + val turtlePos = BlockPos(2, 2, 2) + val turtle = helper.getBlockEntity(turtlePos, ModRegistry.BlockEntities.TURTLE_NORMAL.get()) // Set up turtle inventory turtle.clearContent() @@ -851,14 +863,13 @@ class Turtle_Test { assertTrue(TurtleCraftCommand(1).execute(turtle.access).isSuccess, "Crafting succeeded") // And check item was crafted - assertThat( - "Inventory is as expected.", - turtle.items, - contains( - isStack(Items.STONE_PICKAXE, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), + helper.assertContainerExactly( + turtlePos, + listOf( + ItemStack(Items.STONE_PICKAXE), ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ), ) } diff --git a/projects/common/src/testMod/resources/data/cctest/structures/printer_test.contents_updates_state.snbt b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.empty.snbt similarity index 100% rename from projects/common/src/testMod/resources/data/cctest/structures/printer_test.contents_updates_state.snbt rename to projects/common/src/testMod/resources/data/cctest/structures/printer_test.empty.snbt diff --git a/projects/common/src/testMod/resources/data/cctest/structures/printer_test.no_print_when_full.snbt b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.no_print_when_full.snbt new file mode 100644 index 000000000..7a742b2fc --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.no_print_when_full.snbt @@ -0,0 +1,137 @@ +{ + DataVersion: 3465, + 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: "computercraft:printer{bottom:true,facing:north,top:true}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "minecraft:black_dye"}, {Count: 1b, Slot: 1b, id: "minecraft:paper"}, {Count: 1b, Slot: 7b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}, {Count: 1b, Slot: 8b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}, {Count: 1b, Slot: 9b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}, {Count: 1b, Slot: 10b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}, {Count: 1b, Slot: 11b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}, {Count: 1b, Slot: 12b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 15, term_textColour_0: "fffffffffffffffffffffffff", term_textColour_1: "fffffffffffffffffffffffff", term_textColour_10: "fffffffffffffffffffffffff", term_textColour_11: "fffffffffffffffffffffffff", term_textColour_12: "fffffffffffffffffffffffff", term_textColour_13: "fffffffffffffffffffffffff", term_textColour_14: "fffffffffffffffffffffffff", term_textColour_15: "fffffffffffffffffffffffff", term_textColour_16: "fffffffffffffffffffffffff", term_textColour_17: "fffffffffffffffffffffffff", term_textColour_18: "fffffffffffffffffffffffff", term_textColour_19: "fffffffffffffffffffffffff", term_textColour_2: "fffffffffffffffffffffffff", term_textColour_20: "fffffffffffffffffffffffff", term_textColour_3: "fffffffffffffffffffffffff", term_textColour_4: "fffffffffffffffffffffffff", term_textColour_5: "fffffffffffffffffffffffff", term_textColour_6: "fffffffffffffffffffffffff", term_textColour_7: "fffffffffffffffffffffffff", term_textColour_8: "fffffffffffffffffffffffff", term_textColour_9: "fffffffffffffffffffffffff", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}}, + {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:printer{bottom:true,facing:north,top:true}" + ] +} diff --git a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_into_composter_non_adjacent.snbt b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_into_composter_non_adjacent.snbt new file mode 100644 index 000000000..cb3dc3225 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_into_composter_non_adjacent.snbt @@ -0,0 +1,138 @@ +{ + DataVersion: 3465, + 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: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:pumpkin_pie"}], Label: "turtle_test.place_into_composter_non_adjacent", On: 1b, Owner: {LowerId: -7298459922670553123L, Name: "Player572", UpperId: -8225029765375707172L}, Slot: 0, id: "computercraft:turtle_normal"}}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 3], state: "minecraft:composter{level:0}"}, + {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", + "minecraft:composter{level:0}", + "computercraft:turtle_normal{facing:south,waterlogged:false}" + ] +} diff --git a/projects/core/build.gradle.kts b/projects/core/build.gradle.kts index 033b0af2f..4655b9e50 100644 --- a/projects/core/build.gradle.kts +++ b/projects/core/build.gradle.kts @@ -40,9 +40,8 @@ dependencies { } tasks.processResources { - filesMatching("data/computercraft/lua/rom/help/credits.md") { - expand(mapOf("gitContributors" to cct.gitContributors.map { it.joinToString("\n") }.get())) - } + var props = mapOf("gitContributors" to cct.gitContributors.get().joinToString("\n")) + filesMatching("data/computercraft/lua/rom/help/credits.md") { expand(props) } } tasks.test { diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java index 005440e59..8ef3a099c 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java @@ -14,8 +14,6 @@ import io.netty.buffer.CompositeByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.Closeable; @@ -30,8 +28,6 @@ import java.util.Objects; import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize; public final class HttpRequestHandler extends SimpleChannelInboundHandler implements Closeable { - private static final Logger LOG = LoggerFactory.getLogger(HttpRequestHandler.class); - /** * Same as {@link io.netty.handler.codec.MessageAggregator}. */ diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md b/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md index 10c8bc9b1..c3f83b3c2 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md @@ -1,3 +1,12 @@ +# New features in CC: Tweaked 1.114.3 + +* `wget` now prints the error that occurred, rather than a generic "Failed" (tizu69). +* Update several translations. + +Several bug fixes: +* Fix `fs.isDriveRoot` returning true for non-existent files. +* Fix possible memory leak when sending terminal contents. + # New features in CC: Tweaked 1.114.2 One bug fix: diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md b/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md index 203143367..a8fc8a0e6 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md @@ -1,6 +1,10 @@ -New features in CC: Tweaked 1.114.2 +New features in CC: Tweaked 1.114.3 -One bug fix: -* Fix OpenGL errors when rendering empty monitors. +* `wget` now prints the error that occurred, rather than a generic "Failed" (tizu69). +* Update several translations. + +Several bug fixes: +* Fix `fs.isDriveRoot` returning true for non-existent files. +* Fix possible memory leak when sending terminal contents. Type "help changelog" to see the full version history. diff --git a/projects/fabric/build.gradle.kts b/projects/fabric/build.gradle.kts index 70894243a..987926348 100644 --- a/projects/fabric/build.gradle.kts +++ b/projects/fabric/build.gradle.kts @@ -61,6 +61,16 @@ configurations { include { extendsFrom(includeRuntimeOnly.get(), includeImplementation.get()) } runtimeOnly { extendsFrom(includeRuntimeOnly.get()) } implementation { extendsFrom(includeImplementation.get()) } + + // Declare a configuration for projects which are on the compile and runtime classpath, but not treated as + // dependencies. This is used for our local projects. + val localImplementation by registering { + isCanBeResolved = false + isCanBeConsumed = false + isVisible = false + } + compileClasspath { extendsFrom(localImplementation.get()) } + runtimeClasspath { extendsFrom(localImplementation.get()) } } dependencies { @@ -90,9 +100,9 @@ dependencies { "includeImplementation"(libs.nightConfig.toml) // Pull in our other projects. See comments in MinecraftConfigurations on this nastiness. - api(commonClasses(project(":fabric-api"))) { cct.exclude(this) } - clientApi(clientClasses(project(":fabric-api"))) { cct.exclude(this) } - implementation(project(":core")) { cct.exclude(this) } + "localImplementation"(project(":core")) + "localImplementation"(commonClasses(project(":fabric-api"))) + clientImplementation(clientClasses(project(":fabric-api"))) annotationProcessorEverywhere(libs.autoService) @@ -211,11 +221,9 @@ loom { } tasks.processResources { - inputs.property("version", modVersion) + var props = mapOf("version" to modVersion) - filesMatching("fabric.mod.json") { - expand(mapOf("version" to modVersion)) - } + filesMatching("fabric.mod.json") { expand(props) } } tasks.jar { @@ -281,17 +289,6 @@ modPublishing { output = tasks.remapJar } -tasks.withType(GenerateModuleMetadata::class).configureEach { isEnabled = false } -publishing { - publications { - named("maven", MavenPublication::class) { - mavenDependencies { - cct.configureExcludes(this) - } - } - } -} - modrinth { required.project("fabric-api") } diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java index 0c770ea5e..40f77ef94 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java @@ -54,7 +54,6 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.*; -import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; @@ -68,7 +67,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Consumer; -import java.util.function.Predicate; import java.util.function.Supplier; @AutoService(PlatformHelper.class) @@ -225,20 +223,10 @@ public class PlatformHelperImpl implements PlatformHelper { } @Override - public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate canUseBlock) { + public UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) { var result = UseBlockCallback.EVENT.invoker().interact(player, player.level(), InteractionHand.MAIN_HAND, hit); - if (result != InteractionResult.PASS) return result; - - var block = player.level().getBlockState(hit.getBlockPos()); - if (!block.isAir() && canUseBlock.test(block)) { - var useResult = block.useItemOn(stack, player.level(), player, InteractionHand.MAIN_HAND, hit); - if (useResult.consumesAction()) return useResult.result(); - - // TODO(1.20.5): Should we do this unconditionally now? Or at least a better way of configuring it. - // TODO(1.20.5: What to do with useWithoutItem - } - - return stack.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit)); + if (result != InteractionResult.PASS) return new UseOnResult.Handled(result); + return new UseOnResult.Continue(true, true); } private static final class RegistrationHelperImpl implements RegistrationHelper { diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts index 28651de20..ce6bdd2b6 100644 --- a/projects/forge/build.gradle.kts +++ b/projects/forge/build.gradle.kts @@ -141,6 +141,16 @@ configurations { isCanBeConsumed = false isCanBeResolved = true } + + // Declare a configuration for projects which are on the compile and runtime classpath, but not treated as + // dependencies. This is used for our local projects. + val localImplementation by registering { + isCanBeResolved = false + isCanBeConsumed = false + isVisible = false + } + compileClasspath { extendsFrom(localImplementation.get()) } + runtimeClasspath { extendsFrom(localImplementation.get()) } } dependencies { @@ -149,13 +159,13 @@ dependencies { clientCompileOnly(variantOf(libs.emi) { classifier("api") }) compileOnly(libs.bundles.externalMods.forge.compile) - runtimeOnly(libs.bundles.externalMods.forge.runtime) { cct.exclude(this) } + clientRuntimeOnly(libs.bundles.externalMods.forge.runtime) compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false } // Depend on our other projects. - api(commonClasses(project(":forge-api"))) { cct.exclude(this) } - clientApi(clientClasses(project(":forge-api"))) { cct.exclude(this) } - implementation(project(":core")) { cct.exclude(this) } + "localImplementation"(project(":core")) + "localImplementation"(commonClasses(project(":forge-api"))) + clientImplementation(clientClasses(project(":forge-api"))) jarJar(libs.cobalt) jarJar(libs.jzlib) @@ -188,12 +198,12 @@ dependencies { // Compile tasks tasks.processResources { - inputs.property("modVersion", modVersion) - inputs.property("neoVersion", libs.versions.neoForge.get()) + var props = mapOf( + "neoVersion" to libs.versions.neoForge.get(), + "file" to mapOf("jarVersion" to modVersion), + ) - filesMatching("META-INF/mods.toml") { - expand(mapOf("neoVersion" to libs.versions.neoForge.get(), "file" to mapOf("jarVersion" to modVersion))) - } + filesMatching("META-INF/neoforge.mods.toml") { expand(props) } } tasks.jar { @@ -249,19 +259,6 @@ tasks.register("checkClient") { dependsOn(runGametestClient, runGametestClientWithIris) } -// Upload tasks - modPublishing { output = tasks.jar } - -publishing { - publications { - named("maven", MavenPublication::class) { - mavenDependencies { - cct.configureExcludes(this) - exclude(libs.jei.forge.get()) - } - } - } -} diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java b/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java index c6d5bc27f..25790666a 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java @@ -65,7 +65,6 @@ import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.function.Consumer; -import java.util.function.Predicate; import java.util.function.Supplier; @AutoService(PlatformHelper.class) @@ -222,25 +221,18 @@ public class PlatformHelperImpl implements PlatformHelper { } @Override - public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate canUseBlock) { - var level = player.level(); + public UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) { var pos = hit.getBlockPos(); var event = CommonHooks.onRightClickBlock(player, InteractionHand.MAIN_HAND, pos, hit); - if (event.isCanceled()) return event.getCancellationResult(); + if (event.isCanceled()) return new UseOnResult.Handled(event.getCancellationResult()); var context = new UseOnContext(player, InteractionHand.MAIN_HAND, hit); if (!event.getUseItem().isFalse()) { var result = stack.onItemUseFirst(context); - if (result != InteractionResult.PASS) return result; + if (result != InteractionResult.PASS) return new UseOnResult.Handled(event.getCancellationResult()); } - var block = level.getBlockState(hit.getBlockPos()); - if (!event.getUseBlock().isFalse() && !block.isAir() && canUseBlock.test(block)) { - var useResult = block.useItemOn(stack, level, player, InteractionHand.MAIN_HAND, hit); - if (useResult.consumesAction()) return useResult.result(); - } - - return event.getUseItem().isFalse() ? InteractionResult.PASS : stack.useOn(context); + return new UseOnResult.Continue(!event.getUseBlock().isFalse(), !event.getUseItem().isFalse()); } private record RegistrationHelperImpl(DeferredRegister registry) implements RegistrationHelper { diff --git a/projects/web/build.gradle.kts b/projects/web/build.gradle.kts index afcb2319f..1f2330ac2 100644 --- a/projects/web/build.gradle.kts +++ b/projects/web/build.gradle.kts @@ -95,14 +95,14 @@ val illuaminateDocs by tasks.registering(cc.tweaked.gradle.IlluaminateExecToDir: // Sources inputs.files(rootProject.fileTree("doc")).withPropertyName("docs") inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom") - inputs.files(project(":common").tasks.named("luaJavadoc")) + inputs.dir(project(":common").tasks.named("luaJavadoc").map { it.destinationDir!! }).withPropertyName("luaJavadoc") // Assets inputs.files(rollup) // Output directory. Also defined in illuaminate.sexp. output = layout.buildDirectory.dir("illuaminate") - args = listOf("doc-gen") + args("doc-gen") workingDir = rootProject.projectDir }