mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-04-21 10:13:19 +00:00
Merge branch 'mc-1.20.x' into mc-1.21.x
As part of this, we also rewrite some of the turtle placing code, and how it uses the turtle_can_use tag: Minecraft 1.21 cleaned up the item/block clicking code a little bit, splitting Block.use into Block.useItemOn and Block.useWithoutItem. The first of these is pretty much exactly what we wanted in the first place, so the tag was kinda redundant and we commented it out in the 1.21 update. This was never meant to be a long-term fix, but time has gone by anyway. We now check that tag, and call useWithoutItem() if present — effectively restoring the previous behaviour. Fixes #2011
This commit is contained in:
commit
4710ee5bcc
.editorconfig.gitignore
buildSrc/src/main/kotlin
gradle.propertiesgradle
projects
common
build.gradle.kts
src
datagen/java/dan200/computercraft/data
generated/resources/data/computercraft/tags/block
main
java/dan200/computercraft/shared
peripheral/printer
platform
pocket/core
turtle
resources/assets/computercraft/lang
test/java/dan200/computercraft
testMod
kotlin/dan200/computercraft/gametest
resources/data/cctest/structures
core
build.gradle.kts
src/main
java/dan200/computercraft/core/apis/http/request
resources/data/computercraft/lua/rom/help
fabric
forge
web
@ -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
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,6 +27,7 @@
|
||||
*.iml
|
||||
.idea
|
||||
.gradle
|
||||
.kotlin
|
||||
*.DS_Store
|
||||
|
||||
/.classpath
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ publishing {
|
||||
register<MavenPublication>("maven") {
|
||||
artifactId = base.archivesName.get()
|
||||
from(components["java"])
|
||||
suppressAllPomMetadataWarnings()
|
||||
|
||||
pom {
|
||||
name = "CC: Tweaked"
|
||||
|
@ -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<String> =
|
||||
gitProvider("<no git branch>", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() }
|
||||
@ -64,17 +57,11 @@ abstract class CCTweakedExtension(
|
||||
*/
|
||||
val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java)
|
||||
|
||||
/**
|
||||
* Dependencies excluded from published artifacts.
|
||||
*/
|
||||
private val excludedDeps: ListProperty<Dependency> = 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 <T> jacoco(task: NamedDomainObjectProvider<T>) 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 <T> gitProvider(default: T, command: List<String>, process: (String) -> T): Provider<T> {
|
||||
val baseResult = project.providers.exec {
|
||||
commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command
|
||||
|
@ -22,19 +22,19 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin
|
||||
|
||||
abstract class DependencyCheck : DefaultTask() {
|
||||
@get:Input
|
||||
abstract val configuration: ListProperty<Configuration>
|
||||
protected abstract val dependencies: ListProperty<DependencyResult>
|
||||
|
||||
/**
|
||||
* A mapping of module coordinates (`group:module`) to versions, overriding the requested version.
|
||||
*/
|
||||
@get:Input
|
||||
abstract val overrides: MapProperty<String, String>
|
||||
protected abstract val overrides: MapProperty<String, String>
|
||||
|
||||
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<Configuration>) {
|
||||
// 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) {
|
||||
|
@ -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<Spec<MavenDependency>>()
|
||||
|
||||
fun exclude(spec: Spec<MavenDependency>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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) }
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"]
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(
|
||||
|
2
projects/common/src/generated/resources/data/computercraft/tags/block/turtle_can_use.json
generated
2
projects/common/src/generated/resources/data/computercraft/tags/block/turtle_can_use.json
generated
@ -1 +1 @@
|
||||
{"values": ["#minecraft:beehives", "#minecraft:cauldrons", "minecraft:composter"]}
|
||||
{"values": []}
|
||||
|
3
projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java
3
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() {
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* 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<BlockState> 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.
|
||||
* <p>
|
||||
* 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 {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
@ -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": "タスク",
|
||||
|
@ -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",
|
||||
|
@ -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": "Включить веб-сокеты",
|
||||
|
@ -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",
|
||||
|
@ -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": "任务",
|
||||
|
@ -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<BlockState> canUseBlock) {
|
||||
public UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) {
|
||||
throw new UnsupportedOperationException("Cannot interact with the world inside tests");
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
137
projects/common/src/testMod/resources/data/cctest/structures/printer_test.no_print_when_full.snbt
generated
Normal file
137
projects/common/src/testMod/resources/data/cctest/structures/printer_test.no_print_when_full.snbt
generated
Normal file
File diff suppressed because one or more lines are too long
138
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_into_composter_non_adjacent.snbt
generated
Normal file
138
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_into_composter_non_adjacent.snbt
generated
Normal file
@ -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}"
|
||||
]
|
||||
}
|
@ -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 {
|
||||
|
@ -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<HttpObject> implements Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpRequestHandler.class);
|
||||
|
||||
/**
|
||||
* Same as {@link io.netty.handler.codec.MessageAggregator}.
|
||||
*/
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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<BlockState> 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<T> implements RegistrationHelper<T> {
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<BlockState> 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<R>(DeferredRegister<R> registry) implements RegistrationHelper<R> {
|
||||
|
@ -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<Javadoc>("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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user