mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-04 07:32:59 +00:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			v1.20.1-1.
			...
			v1.20.4-1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7b9a156abc | ||
| 
						 | 
					0a9e5c78f3 | ||
| 
						 | 
					da5885ef35 | ||
| 
						 | 
					240528cce5 | ||
| 
						 | 
					83f1f86888 | ||
| 
						 | 
					9c202bd1c2 | ||
| 
						 | 
					fc834cd97f | 
@@ -51,9 +51,8 @@ dependencies {
 | 
			
		||||
  compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
 | 
			
		||||
 | 
			
		||||
  // Forge Gradle
 | 
			
		||||
  compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
 | 
			
		||||
  compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion"))
 | 
			
		||||
  runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion"))
 | 
			
		||||
  compileOnly("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion")
 | 
			
		||||
  runtimeOnly("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion")
 | 
			
		||||
 | 
			
		||||
  // Fabric Loom
 | 
			
		||||
  modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion")
 | 
			
		||||
 
 | 
			
		||||
@@ -14,18 +14,14 @@ repositories {
 | 
			
		||||
    mavenCentral()
 | 
			
		||||
    gradlePluginPortal()
 | 
			
		||||
 | 
			
		||||
    maven("https://maven.minecraftforge.net") {
 | 
			
		||||
        name = "Forge"
 | 
			
		||||
    maven("https://maven.neoforged.net/releases") {
 | 
			
		||||
        name = "NeoForge"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroup("net.minecraftforge")
 | 
			
		||||
            includeGroup("net.minecraftforge.gradle")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    maven("https://maven.parchmentmc.org") {
 | 
			
		||||
        name = "Librarian"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroupByRegex("^org\\.parchmentmc.*")
 | 
			
		||||
            includeGroup("net.neoforged")
 | 
			
		||||
            includeGroup("net.neoforged.gradle")
 | 
			
		||||
            includeModule("codechicken", "DiffPatch")
 | 
			
		||||
            includeModule("net.covers1624", "Quack")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -51,10 +47,9 @@ dependencies {
 | 
			
		||||
 | 
			
		||||
    implementation(libs.curseForgeGradle)
 | 
			
		||||
    implementation(libs.fabric.loom)
 | 
			
		||||
    implementation(libs.forgeGradle)
 | 
			
		||||
    implementation(libs.ideaExt)
 | 
			
		||||
    implementation(libs.librarian)
 | 
			
		||||
    implementation(libs.minotaur)
 | 
			
		||||
    implementation(libs.neoGradle.userdev)
 | 
			
		||||
    implementation(libs.vanillaExtract)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,8 @@ import cc.tweaked.gradle.IdeaRunConfigurations
 | 
			
		||||
import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("net.minecraftforge.gradle")
 | 
			
		||||
    // We must apply java-convention after Forge, as we need the fg extension to be present.
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
    id("org.parchmentmc.librarian.forgegradle")
 | 
			
		||||
    id("net.neoforged.gradle.userdev")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
plugins.apply(CCTweakedPlugin::class.java)
 | 
			
		||||
@@ -21,10 +19,20 @@ plugins.apply(CCTweakedPlugin::class.java)
 | 
			
		||||
val mcVersion: String by extra
 | 
			
		||||
 | 
			
		||||
minecraft {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
    mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
 | 
			
		||||
    modIdentifier("computercraft")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
 | 
			
		||||
subsystems {
 | 
			
		||||
    parchment {
 | 
			
		||||
        val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
        minecraftVersion = libs.findVersion("parchmentMc").get().toString()
 | 
			
		||||
        mappingsVersion = libs.findVersion("parchment").get().toString()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
    implementation("net.neoforged:neoforge:${libs.findVersion("neoForge").get()}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MinecraftConfigurations.setup(project)
 | 
			
		||||
@@ -32,13 +40,3 @@ MinecraftConfigurations.setup(project)
 | 
			
		||||
extensions.configure(CCTweakedExtension::class.java) {
 | 
			
		||||
    linters(minecraft = true, loader = "forge")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
    "minecraft"("net.minecraftforge:forge:$mcVersion-${libs.findVersion("forge").get()}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.configureEach {
 | 
			
		||||
    // genIntellijRuns isn't registered until much later, so we need this silly hijinks.
 | 
			
		||||
    if (name == "genIntellijRuns") doLast { IdeaRunConfigurations(project).patch() }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,13 +44,6 @@ repositories {
 | 
			
		||||
 | 
			
		||||
    exclusiveContent {
 | 
			
		||||
        forRepositories(mainMaven)
 | 
			
		||||
 | 
			
		||||
        // Include the ForgeGradle repository if present. This requires that ForgeGradle is already present, which we
 | 
			
		||||
        // enforce in our Forge overlay.
 | 
			
		||||
        val fg =
 | 
			
		||||
            project.extensions.findByType(net.minecraftforge.gradle.userdev.DependencyManagementExtension::class.java)
 | 
			
		||||
        if (fg != null) forRepositories(fg.repository)
 | 
			
		||||
 | 
			
		||||
        filter {
 | 
			
		||||
            includeGroup("cc.tweaked")
 | 
			
		||||
            // Things we mirror
 | 
			
		||||
 
 | 
			
		||||
@@ -4,23 +4,59 @@
 | 
			
		||||
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import net.minecraftforge.gradle.common.util.RunConfig
 | 
			
		||||
import net.minecraftforge.gradle.common.util.runs.setRunConfigInternal
 | 
			
		||||
import net.neoforged.gradle.common.runs.run.RunImpl
 | 
			
		||||
import net.neoforged.gradle.common.runs.tasks.RunExec
 | 
			
		||||
import net.neoforged.gradle.dsl.common.extensions.RunnableSourceSet
 | 
			
		||||
import net.neoforged.gradle.dsl.common.runs.run.Run
 | 
			
		||||
import org.gradle.api.plugins.JavaPluginExtension
 | 
			
		||||
import org.gradle.api.tasks.JavaExec
 | 
			
		||||
import org.gradle.api.tasks.SourceSet
 | 
			
		||||
import org.gradle.jvm.toolchain.JavaToolchainService
 | 
			
		||||
import org.gradle.kotlin.dsl.assign
 | 
			
		||||
import org.gradle.kotlin.dsl.create
 | 
			
		||||
import org.gradle.kotlin.dsl.findByType
 | 
			
		||||
import java.nio.file.Files
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set [JavaExec] task to run a given [RunConfig].
 | 
			
		||||
 *
 | 
			
		||||
 * See also [RunExec].
 | 
			
		||||
 */
 | 
			
		||||
fun JavaExec.setRunConfig(config: RunConfig) {
 | 
			
		||||
    dependsOn("prepareRuns")
 | 
			
		||||
    setRunConfigInternal(project, this, config)
 | 
			
		||||
    doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
 | 
			
		||||
fun JavaExec.setRunConfig(config: Run) {
 | 
			
		||||
    mainClass.set(config.mainClass)
 | 
			
		||||
    workingDir = config.workingDirectory.get().asFile
 | 
			
		||||
    argumentProviders.add { config.programArguments.get() }
 | 
			
		||||
    jvmArgumentProviders.add { config.jvmArguments.get() }
 | 
			
		||||
 | 
			
		||||
    environment(config.environmentVariables.get())
 | 
			
		||||
    systemProperties(config.systemProperties.get())
 | 
			
		||||
 | 
			
		||||
    config.modSources.get().forEach { classpath(it.runtimeClasspath) }
 | 
			
		||||
    classpath(config.classpath)
 | 
			
		||||
    classpath(config.dependencies.get().configuration)
 | 
			
		||||
 | 
			
		||||
    (config as RunImpl).taskDependencies.forEach { dependsOn(it) }
 | 
			
		||||
 | 
			
		||||
    javaLauncher.set(
 | 
			
		||||
        project.extensions.getByType(JavaToolchainService::class.java)
 | 
			
		||||
            .launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a new [Run.modSource] with a specific mod id.
 | 
			
		||||
 */
 | 
			
		||||
fun Run.modSourceAs(sourceSet: SourceSet, mod: String) {
 | 
			
		||||
    // NeoGradle requires a RunnableSourceSet to be present, so we inject it into other project's source sets.
 | 
			
		||||
    val runnable = sourceSet.extensions.findByType<RunnableSourceSet>() ?: run {
 | 
			
		||||
        val extension = sourceSet.extensions.create<RunnableSourceSet>(RunnableSourceSet.NAME, project)
 | 
			
		||||
        extension.modIdentifier = mod
 | 
			
		||||
        extension.modIdentifier.finalizeValueOnRead()
 | 
			
		||||
        extension
 | 
			
		||||
    }
 | 
			
		||||
    if (runnable.modIdentifier.get() != mod) throw IllegalArgumentException("Multiple mod identifiers")
 | 
			
		||||
 | 
			
		||||
    modSource(sourceSet)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import net.minecraftforge.gradle.common.util.RunConfig
 | 
			
		||||
import org.gradle.api.GradleException
 | 
			
		||||
import org.gradle.api.file.FileSystemOperations
 | 
			
		||||
import org.gradle.api.invocation.Gradle
 | 
			
		||||
@@ -65,14 +64,6 @@ abstract class ClientJavaExec : JavaExec() {
 | 
			
		||||
        setTestProperties()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set this task to run a given [RunConfig].
 | 
			
		||||
     */
 | 
			
		||||
    fun setRunConfig(config: RunConfig) {
 | 
			
		||||
        (this as JavaExec).setRunConfig(config)
 | 
			
		||||
        setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Copy configuration from a task with the given name.
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,51 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package net.minecraftforge.gradle.common.util.runs
 | 
			
		||||
 | 
			
		||||
import net.minecraftforge.gradle.common.util.RunConfig
 | 
			
		||||
import org.gradle.api.Project
 | 
			
		||||
import org.gradle.process.CommandLineArgumentProvider
 | 
			
		||||
import org.gradle.process.JavaExecSpec
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.util.function.Supplier
 | 
			
		||||
import java.util.stream.Collectors
 | 
			
		||||
import java.util.stream.Stream
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set up a [JavaExecSpec] to execute a [RunConfig].
 | 
			
		||||
 *
 | 
			
		||||
 * [MinecraftRunTask] sets up all its properties when the task is executed, rather than when configured. As such, it's
 | 
			
		||||
 * not possible to use [cc.tweaked.gradle.copyToFull] like we do for Fabric. Instead, we set up the task manually.
 | 
			
		||||
 *
 | 
			
		||||
 * Unfortunately most of the functionality we need is package-private, and so we have to put our code into the package.
 | 
			
		||||
 */
 | 
			
		||||
internal fun setRunConfigInternal(project: Project, spec: JavaExecSpec, config: RunConfig) {
 | 
			
		||||
    spec.workingDir = File(config.workingDirectory)
 | 
			
		||||
 | 
			
		||||
    spec.mainClass.set(config.main)
 | 
			
		||||
    for (source in config.allSources) spec.classpath(source.runtimeClasspath)
 | 
			
		||||
 | 
			
		||||
    val originalTask = project.tasks.named(config.taskName, MinecraftRunTask::class.java)
 | 
			
		||||
 | 
			
		||||
    // Add argument and JVM argument via providers, to be as lazy as possible with fetching artifacts.
 | 
			
		||||
    val lazyTokens = RunConfigGenerator.configureTokensLazy(
 | 
			
		||||
        project, config, RunConfigGenerator.mapModClassesToGradle(project, config),
 | 
			
		||||
        originalTask.get().minecraftArtifacts,
 | 
			
		||||
        originalTask.get().runtimeClasspathArtifacts,
 | 
			
		||||
    )
 | 
			
		||||
    spec.argumentProviders.add(
 | 
			
		||||
        CommandLineArgumentProvider {
 | 
			
		||||
            RunConfigGenerator.getArgsStream(config, lazyTokens, false).toList()
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
    spec.jvmArgumentProviders.add(
 | 
			
		||||
        CommandLineArgumentProvider {
 | 
			
		||||
            (if (config.isClient) config.jvmArgs + originalTask.get().additionalClientArgs.get() else config.jvmArgs).map { config.replace(lazyTokens, it) } +
 | 
			
		||||
                config.properties.map { (k, v) -> "-D${k}=${config.replace(lazyTokens, v)}" }
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    for ((key, value) in config.environment) spec.environment(key, config.replace(lazyTokens, value))
 | 
			
		||||
}
 | 
			
		||||
@@ -9,8 +9,8 @@ kotlin.stdlib.default.dependency=false
 | 
			
		||||
kotlin.jvm.target.validation.mode=error
 | 
			
		||||
 | 
			
		||||
# Mod properties
 | 
			
		||||
isUnstable=false
 | 
			
		||||
isUnstable=true
 | 
			
		||||
modVersion=1.110.0
 | 
			
		||||
 | 
			
		||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
 | 
			
		||||
mcVersion=1.20.1
 | 
			
		||||
mcVersion=1.20.4
 | 
			
		||||
 
 | 
			
		||||
@@ -7,20 +7,20 @@
 | 
			
		||||
# Minecraft
 | 
			
		||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
 | 
			
		||||
# Remember to update corresponding versions in fabric.mod.json/mods.toml
 | 
			
		||||
fabric-api = "0.86.1+1.20.1"
 | 
			
		||||
fabric-loader = "0.14.21"
 | 
			
		||||
forge = "47.1.0"
 | 
			
		||||
forgeSpi = "7.0.1"
 | 
			
		||||
fabric-api = "0.93.1+1.20.4"
 | 
			
		||||
fabric-loader = "0.15.3"
 | 
			
		||||
neoForge = "20.4.210"
 | 
			
		||||
neoForgeSpi = "8.0.1"
 | 
			
		||||
mixin = "0.8.5"
 | 
			
		||||
parchment = "2023.08.20"
 | 
			
		||||
parchmentMc = "1.20.1"
 | 
			
		||||
yarn = "1.20.1+build.10"
 | 
			
		||||
parchment = "2023.12.31"
 | 
			
		||||
parchmentMc = "1.20.3"
 | 
			
		||||
yarn = "1.20.4+build.3"
 | 
			
		||||
 | 
			
		||||
# Core dependencies (these versions are tied to the version Minecraft uses)
 | 
			
		||||
fastutil = "8.5.9"
 | 
			
		||||
guava = "31.1-jre"
 | 
			
		||||
netty = "4.1.82.Final"
 | 
			
		||||
slf4j = "2.0.1"
 | 
			
		||||
fastutil = "8.5.12"
 | 
			
		||||
guava = "32.1.2-jre"
 | 
			
		||||
netty = "4.1.97.Final"
 | 
			
		||||
slf4j = "2.0.7"
 | 
			
		||||
 | 
			
		||||
# Core dependencies (independent of Minecraft)
 | 
			
		||||
asm = "9.6"
 | 
			
		||||
@@ -36,14 +36,14 @@ kotlin-coroutines = "1.7.3"
 | 
			
		||||
nightConfig = "3.6.7"
 | 
			
		||||
 | 
			
		||||
# Minecraft mods
 | 
			
		||||
emi = "1.0.8+1.20.1"
 | 
			
		||||
emi = "1.0.30+1.20.4"
 | 
			
		||||
fabricPermissions = "0.3.20230723"
 | 
			
		||||
iris = "1.6.4+1.20"
 | 
			
		||||
jei = "15.2.0.22"
 | 
			
		||||
modmenu = "7.1.0"
 | 
			
		||||
iris = "1.6.14+1.20.4"
 | 
			
		||||
jei = "17.3.0.48"
 | 
			
		||||
modmenu = "9.0.0"
 | 
			
		||||
moreRed = "4.0.0.4"
 | 
			
		||||
oculus = "1.2.5"
 | 
			
		||||
rei = "12.0.626"
 | 
			
		||||
rei = "14.0.688"
 | 
			
		||||
rubidium = "0.6.1"
 | 
			
		||||
sodium = "mc1.20-0.4.10"
 | 
			
		||||
 | 
			
		||||
@@ -56,18 +56,17 @@ jmh = "1.37"
 | 
			
		||||
# Build tools
 | 
			
		||||
cctJavadoc = "1.8.2"
 | 
			
		||||
checkstyle = "10.14.1"
 | 
			
		||||
curseForgeGradle = "1.0.14"
 | 
			
		||||
curseForgeGradle = "1.1.18"
 | 
			
		||||
errorProne-core = "2.23.0"
 | 
			
		||||
errorProne-plugin = "3.1.0"
 | 
			
		||||
fabric-loom = "1.5.7"
 | 
			
		||||
forgeGradle = "6.0.20"
 | 
			
		||||
githubRelease = "2.5.2"
 | 
			
		||||
gradleVersions = "0.50.0"
 | 
			
		||||
ideaExt = "1.1.7"
 | 
			
		||||
illuaminate = "0.1.0-69-gf294ab2"
 | 
			
		||||
librarian = "1.+"
 | 
			
		||||
lwjgl = "3.3.3"
 | 
			
		||||
minotaur = "2.+"
 | 
			
		||||
minotaur = "2.8.7"
 | 
			
		||||
neoGradle = "7.0.100"
 | 
			
		||||
nullAway = "0.9.9"
 | 
			
		||||
spotless = "6.23.3"
 | 
			
		||||
taskTree = "2.1.1"
 | 
			
		||||
@@ -84,7 +83,7 @@ checkerFramework = { module = "org.checkerframework:checker-qual", version.ref =
 | 
			
		||||
cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" }
 | 
			
		||||
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
 | 
			
		||||
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
 | 
			
		||||
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
 | 
			
		||||
neoForgeSpi = { module = "net.neoforged:neoforgespi", version.ref = "neoForgeSpi" }
 | 
			
		||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
 | 
			
		||||
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
 | 
			
		||||
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" }
 | 
			
		||||
@@ -106,9 +105,9 @@ fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fab
 | 
			
		||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
 | 
			
		||||
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
 | 
			
		||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
 | 
			
		||||
jei-api = { module = "mezz.jei:jei-1.20.1-common-api", version.ref = "jei" }
 | 
			
		||||
jei-fabric = { module = "mezz.jei:jei-1.20.1-fabric", version.ref = "jei" }
 | 
			
		||||
jei-forge = { module = "mezz.jei:jei-1.20.1-forge", version.ref = "jei" }
 | 
			
		||||
jei-api = { module = "mezz.jei:jei-1.20.4-common-api", version.ref = "jei" }
 | 
			
		||||
jei-fabric = { module = "mezz.jei:jei-1.20.4-fabric", version.ref = "jei" }
 | 
			
		||||
jei-forge = { module = "mezz.jei:jei-1.20.4-forge", version.ref = "jei" }
 | 
			
		||||
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
 | 
			
		||||
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
 | 
			
		||||
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" }
 | 
			
		||||
@@ -146,11 +145,10 @@ errorProne-core = { module = "com.google.errorprone:error_prone_core", version.r
 | 
			
		||||
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
 | 
			
		||||
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
 | 
			
		||||
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
 | 
			
		||||
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
 | 
			
		||||
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
 | 
			
		||||
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
 | 
			
		||||
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
 | 
			
		||||
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
 | 
			
		||||
neoGradle-userdev = { module = "net.neoforged.gradle:userdev", version.ref = "neoGradle" }
 | 
			
		||||
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
 | 
			
		||||
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
 | 
			
		||||
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
 | 
			
		||||
@@ -165,11 +163,9 @@ vanillaExtract = { module = "cc.tweaked.vanilla-extract:plugin", version.ref = "
 | 
			
		||||
yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" }
 | 
			
		||||
 | 
			
		||||
[plugins]
 | 
			
		||||
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
 | 
			
		||||
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
 | 
			
		||||
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
 | 
			
		||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
 | 
			
		||||
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
 | 
			
		||||
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
 | 
			
		||||
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
 | 
			
		||||
 | 
			
		||||
@@ -180,7 +176,7 @@ kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
 | 
			
		||||
# Minecraft
 | 
			
		||||
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
 | 
			
		||||
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
 | 
			
		||||
externalMods-forge-runtime = ["jei-forge"]
 | 
			
		||||
externalMods-forge-runtime = []
 | 
			
		||||
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
 | 
			
		||||
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,43 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The public API for client-only code.
 | 
			
		||||
 *
 | 
			
		||||
 * @see dan200.computercraft.api.ComputerCraftAPI The main API
 | 
			
		||||
 */
 | 
			
		||||
public final class ComputerCraftAPIClient {
 | 
			
		||||
    private ComputerCraftAPIClient() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This may be called at any point after registry creation, though it is recommended to call it within your client
 | 
			
		||||
     * setup step.
 | 
			
		||||
     *
 | 
			
		||||
     * @param serialiser The turtle upgrade serialiser.
 | 
			
		||||
     * @param modeller   The upgrade modeller.
 | 
			
		||||
     * @param <T>        The type of the turtle upgrade.
 | 
			
		||||
     * @deprecated This method can lead to confusing load behaviour on Forge. Use
 | 
			
		||||
     * {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} on Fabric, or
 | 
			
		||||
     * {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} on Forge.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
 | 
			
		||||
        // TODO(1.20.4): Remove this
 | 
			
		||||
        getInstance().registerTurtleUpgradeModeller(serialiser, modeller);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ComputerCraftAPIClientService getInstance() {
 | 
			
		||||
        return ComputerCraftAPIClientService.get();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
package dan200.computercraft.api.client.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A functional interface to register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
 | 
			
		||||
@@ -22,5 +22,5 @@ public interface RegisterTurtleUpgradeModeller {
 | 
			
		||||
     * @param modeller   The upgrade modeller.
 | 
			
		||||
     * @param <T>        The type of the turtle upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    <T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
 | 
			
		||||
    <T extends ITurtleUpgrade> void register(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import dan200.computercraft.api.client.TransformedModel;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
import net.minecraft.client.resources.model.UnbakedModel;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
@@ -31,30 +30,15 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Obtain the model to be used when rendering a turtle peripheral.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
 | 
			
		||||
     * When the current turtle is {@literal null}, this function should be constant for a given upgrade, side and data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrade The upgrade that you're getting the model for.
 | 
			
		||||
     * @param turtle  Access to the turtle that the upgrade resides on. This will be null when getting item models, unless
 | 
			
		||||
     *                {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
 | 
			
		||||
     * @param turtle  Access to the turtle that the upgrade resides on. This will be null when getting item models.
 | 
			
		||||
     * @param side    Which side of the turtle (left or right) the upgrade resides on.
 | 
			
		||||
     * @return The model that you wish to be used to render your upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Obtain the model to be used when rendering a turtle peripheral.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This is used when rendering the turtle's item model, and so no {@link ITurtleAccess} is available.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrade The upgrade that you're getting the model for.
 | 
			
		||||
     * @param data    Upgrade data instance for current turtle side.
 | 
			
		||||
     * @param side    Which side of the turtle (left or right) the upgrade resides on.
 | 
			
		||||
     * @return The model that you wish to be used to render your upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
 | 
			
		||||
        return getModel(upgrade, (ITurtleAccess) null, side);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a list of models that this turtle modeller depends on.
 | 
			
		||||
@@ -85,19 +69,6 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
        return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.UPGRADE_ITEM;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
 | 
			
		||||
     *
 | 
			
		||||
     * @param left  The model to use on the left.
 | 
			
		||||
     * @param right The model to use on the right.
 | 
			
		||||
     * @param <T>   The type of the turtle upgrade.
 | 
			
		||||
     * @return The constructed modeller.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
 | 
			
		||||
        // TODO(1.21.0): Remove this.
 | 
			
		||||
        return sided((ResourceLocation) left, right);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
 | 
			
		||||
     *
 | 
			
		||||
@@ -109,7 +80,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
 | 
			
		||||
        return new TurtleUpgradeModeller<>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
 | 
			
		||||
            public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
 | 
			
		||||
                return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.impl.client.ClientPlatformHelper;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.joml.Matrix4f;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
@@ -37,16 +36,8 @@ final class TurtleUpgradeModellers {
 | 
			
		||||
 | 
			
		||||
    private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
 | 
			
		||||
        @Override
 | 
			
		||||
        public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
 | 
			
		||||
            return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
 | 
			
		||||
            return getModel(upgrade.getUpgradeItem(data), side);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private TransformedModel getModel(ItemStack stack, TurtleSide side) {
 | 
			
		||||
        public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
 | 
			
		||||
            var stack = upgrade.getUpgradeItem(data);
 | 
			
		||||
            var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
 | 
			
		||||
            if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
 | 
			
		||||
            return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,92 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.network.wired;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A wired network is composed of one of more {@link WiredNode}s, a set of connections between them, and a series
 | 
			
		||||
 * of peripherals.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
 | 
			
		||||
 * there is some path between two nodes then they must be on the same network. {@link WiredNetwork} will automatically
 | 
			
		||||
 * handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
 | 
			
		||||
 * change.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
 | 
			
		||||
 * it is generally preferred to use the methods provided by {@link WiredNode}.
 | 
			
		||||
 *
 | 
			
		||||
 * @see WiredNode#getNetwork()
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.NonExtendable
 | 
			
		||||
public interface WiredNetwork {
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a connection between two nodes.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @param left  The first node to connect
 | 
			
		||||
     * @param right The second node to connect
 | 
			
		||||
     * @return {@code true} if a connection was created or {@code false} if the connection already exists.
 | 
			
		||||
     * @throws IllegalStateException    If neither node is on the network.
 | 
			
		||||
     * @throws IllegalArgumentException If {@code left} and {@code right} are equal.
 | 
			
		||||
     * @see WiredNode#connectTo(WiredNode)
 | 
			
		||||
     * @see WiredNetwork#connect(WiredNode, WiredNode)
 | 
			
		||||
     * @deprecated Use {@link WiredNode#connectTo(WiredNode)}
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    boolean connect(WiredNode left, WiredNode right);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Destroy a connection between this node and another.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @param left  The first node in the connection.
 | 
			
		||||
     * @param right The second node in the connection.
 | 
			
		||||
     * @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
 | 
			
		||||
     * @throws IllegalArgumentException If either node is not on the network.
 | 
			
		||||
     * @throws IllegalArgumentException If {@code left} and {@code right} are equal.
 | 
			
		||||
     * @see WiredNode#disconnectFrom(WiredNode)
 | 
			
		||||
     * @see WiredNetwork#connect(WiredNode, WiredNode)
 | 
			
		||||
     * @deprecated Use {@link WiredNode#disconnectFrom(WiredNode)}
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    boolean disconnect(WiredNode left, WiredNode right);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sever all connections this node has, removing it from this network.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread. You should only call this on nodes
 | 
			
		||||
     * that your network element owns.
 | 
			
		||||
     *
 | 
			
		||||
     * @param node The node to remove
 | 
			
		||||
     * @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
 | 
			
		||||
     * only element.
 | 
			
		||||
     * @throws IllegalArgumentException If the node is not in the network.
 | 
			
		||||
     * @see WiredNode#remove()
 | 
			
		||||
     * @deprecated Use {@link WiredNode#remove()}
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    boolean remove(WiredNode node);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the peripherals a node provides.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread. You should only call this on nodes
 | 
			
		||||
     * that your network element owns.
 | 
			
		||||
     *
 | 
			
		||||
     * @param node        The node to attach peripherals for.
 | 
			
		||||
     * @param peripherals The new peripherals for this node.
 | 
			
		||||
     * @throws IllegalArgumentException If the node is not in the network.
 | 
			
		||||
     * @see WiredNode#updatePeripherals(Map)
 | 
			
		||||
     * @deprecated Use {@link WiredNode#updatePeripherals(Map)}
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    void updatePeripherals(WiredNode node, Map<String, IPeripheral> peripherals);
 | 
			
		||||
}
 | 
			
		||||
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wired nodes act as a layer between {@link WiredElement}s and {@link WiredNetwork}s.
 | 
			
		||||
 * A single node on a wired network.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
 | 
			
		||||
 * methods may be safely used on any thread.
 | 
			
		||||
@@ -32,18 +32,6 @@ public interface WiredNode extends PacketNetwork {
 | 
			
		||||
     */
 | 
			
		||||
    WiredElement getElement();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The network this node is currently connected to. Note that this may change
 | 
			
		||||
     * after any network operation, so it should not be cached.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This node's network.
 | 
			
		||||
     * @deprecated Use the connect/disconnect/remove methods on {@link WiredNode}.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    WiredNetwork getNetwork();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a connection from this node to another.
 | 
			
		||||
     * <p>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,12 @@ import dan200.computercraft.api.network.PacketSender;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An object on a {@link WiredNetwork} capable of sending packets.
 | 
			
		||||
 * An object on a wired network capable of sending packets.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Unlike a regular {@link PacketSender}, this must be associated with the node you are attempting to
 | 
			
		||||
 * to send the packet from.
 | 
			
		||||
 *
 | 
			
		||||
 * @see WiredElement
 | 
			
		||||
 */
 | 
			
		||||
public interface WiredSender extends PacketSender {
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -4,15 +4,12 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper class for pocket computers.
 | 
			
		||||
@@ -90,13 +87,4 @@ public interface IPocketAccess {
 | 
			
		||||
     * entity} changes.
 | 
			
		||||
     */
 | 
			
		||||
    void invalidatePeripheral();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a list of all upgrades for the pocket computer.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A collection of all upgrade names.
 | 
			
		||||
     * @deprecated This is a relic of a previous API, which no longer makes sense with newer versions of ComputerCraft.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    Map<ResourceLocation, IPeripheral> getUpgrades();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,10 @@ package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
@@ -13,16 +17,46 @@ import javax.annotation.Nullable;
 | 
			
		||||
/**
 | 
			
		||||
 * A peripheral which can be equipped to the back side of a pocket computer.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Pocket upgrades are defined in two stages. First, on creates a {@link IPocketUpgrade} subclass and corresponding
 | 
			
		||||
 * {@link PocketUpgradeSerialiser} instance, which are then registered in a Forge registry.
 | 
			
		||||
 * Pocket upgrades are defined in two stages. First, one creates a {@link IPocketUpgrade} subclass and corresponding
 | 
			
		||||
 * {@link UpgradeSerialiser} instance, which are then registered in a registry.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
 | 
			
		||||
 * the upgrade registered internally. See the documentation in {@link PocketUpgradeSerialiser} for details on this process
 | 
			
		||||
 * and where files should be located.
 | 
			
		||||
 * the upgrade automatically registered. It is recommended this is done via {@linkplain PocketUpgradeDataProvider data
 | 
			
		||||
 * generators}.
 | 
			
		||||
 *
 | 
			
		||||
 * @see PocketUpgradeSerialiser For how to register a pocket computer upgrade.
 | 
			
		||||
 * <h2>Example</h2>
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * // We use Forge's DeferredRegister to register our serialiser. Fabric mods may register their serialiser directly.
 | 
			
		||||
 * static final DeferredRegister<UpgradeSerialiser<? extends IPocketUpgrade>> SERIALISERS = DeferredRegister.create(IPocketUpgrade.serialiserRegistryKey(), "my_mod");
 | 
			
		||||
 *
 | 
			
		||||
 * // Register a new upgrade serialiser called "my_upgrade".
 | 
			
		||||
 * public static final RegistryObject<UpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
 | 
			
		||||
 *     SERIALISERS.register("my_upgrade", () -> UpgradeSerialiser.simple(MyUpgrade::new));
 | 
			
		||||
 *
 | 
			
		||||
 * // Then in your constructor
 | 
			
		||||
 * SERIALISERS.register(bus);
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * We can then define a new upgrade using JSON by placing the following in
 | 
			
		||||
 * {@code data/<my_mod>/computercraft/pocket_upgrades/<my_upgrade_id>.json}.
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * {
 | 
			
		||||
 *     "type": my_mod:my_upgrade",
 | 
			
		||||
 * }
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * {@link PocketUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
 | 
			
		||||
 */
 | 
			
		||||
public interface IPocketUpgrade extends UpgradeBase {
 | 
			
		||||
    /**
 | 
			
		||||
     * The registry key for upgrade serialisers.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The registry key.
 | 
			
		||||
     */
 | 
			
		||||
    static ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> serialiserRegistryKey() {
 | 
			
		||||
        return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a peripheral for the pocket computer.
 | 
			
		||||
     * <p>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import net.minecraft.data.DataGenerator;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
 | 
			
		||||
@@ -17,10 +18,11 @@ import java.util.function.Consumer;
 | 
			
		||||
 * {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
 | 
			
		||||
 * generate them.
 | 
			
		||||
 *
 | 
			
		||||
 * @see PocketUpgradeSerialiser
 | 
			
		||||
 * @see IPocketUpgrade
 | 
			
		||||
 * @see UpgradeSerialiser
 | 
			
		||||
 */
 | 
			
		||||
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {
 | 
			
		||||
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade> {
 | 
			
		||||
    public PocketUpgradeDataProvider(PackOutput output) {
 | 
			
		||||
        super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId());
 | 
			
		||||
        super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", IPocketUpgrade.serialiserRegistryKey());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Reads a {@link IPocketUpgrade} from disk and reads/writes it to a network packet.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This follows the same format as {@link dan200.computercraft.api.turtle.TurtleUpgradeSerialiser} - consult the
 | 
			
		||||
 * documentation there for more information.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of pocket computer upgrade this is responsible for serialising.
 | 
			
		||||
 * @see IPocketUpgrade
 | 
			
		||||
 * @see PocketUpgradeDataProvider
 | 
			
		||||
 */
 | 
			
		||||
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID for the associated registry.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The registry key.
 | 
			
		||||
     */
 | 
			
		||||
    static ResourceKey<Registry<PocketUpgradeSerialiser<?>>> registryId() {
 | 
			
		||||
        return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
 | 
			
		||||
     * but for upgrades.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory Generate a new upgrade with a specific ID.
 | 
			
		||||
     * @param <T>     The type of the generated upgrade.
 | 
			
		||||
     * @return The serialiser for this upgrade
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
 | 
			
		||||
        final class Impl extends SimpleSerialiser<T> implements PocketUpgradeSerialiser<T> {
 | 
			
		||||
            private Impl(Function<ResourceLocation, T> constructor) {
 | 
			
		||||
                super(constructor);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Impl(factory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
 | 
			
		||||
     *                {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
 | 
			
		||||
     * @param <T>     The type of the generated upgrade.
 | 
			
		||||
     * @return The serialiser for this upgrade.
 | 
			
		||||
     * @see #simple(Function)  For upgrades whose crafting stack should not vary.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
        final class Impl extends SerialiserWithCraftingItem<T> implements PocketUpgradeSerialiser<T> {
 | 
			
		||||
            private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
                super(factory);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Impl(factory);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,8 +6,12 @@ package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
@@ -16,15 +20,54 @@ import javax.annotation.Nullable;
 | 
			
		||||
 * peripheral.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding
 | 
			
		||||
 * {@link TurtleUpgradeSerialiser} instance, which are then registered in a Forge registry.
 | 
			
		||||
 * {@link UpgradeSerialiser} instance, which are then registered in a registry.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
 | 
			
		||||
 * the upgrade registered internally. See the documentation in {@link TurtleUpgradeSerialiser} for details on this process
 | 
			
		||||
 * and where files should be located.
 | 
			
		||||
 * the upgrade automatically registered. It is recommended this is done via {@linkplain TurtleUpgradeDataProvider data
 | 
			
		||||
 * generators}.
 | 
			
		||||
 *
 | 
			
		||||
 * @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
 | 
			
		||||
 * <h2>Example</h2>
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * // We use Forge's DeferredRegister to register our serialiser. Fabric mods may register their serialiser directly.
 | 
			
		||||
 * static final DeferredRegister<UpgradeSerialiser<? extends ITurtleUpgrade>> SERIALISERS = DeferredRegister.create(ITurtleUpgrade.serialiserRegistryKey(), "my_mod");
 | 
			
		||||
 *
 | 
			
		||||
 * // Register a new upgrade serialiser called "my_upgrade".
 | 
			
		||||
 * public static final RegistryObject<UpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
 | 
			
		||||
 *     SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
 | 
			
		||||
 *
 | 
			
		||||
 * // Then in your constructor
 | 
			
		||||
 * SERIALISERS.register( bus );
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * We can then define a new upgrade using JSON by placing the following in
 | 
			
		||||
 * {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
 | 
			
		||||
 *
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * {
 | 
			
		||||
 *     "type": my_mod:my_upgrade",
 | 
			
		||||
 * }
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Finally, we need to register a model for our upgrade, see
 | 
			
		||||
 * {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information.
 | 
			
		||||
 *
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * // Register our model inside FMLClientSetupEvent
 | 
			
		||||
 * ComputerCraftAPIClient.registerTurtleUpgradeModeller(MY_UPGRADE.get(), TurtleUpgradeModeller.flatItem())
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 */
 | 
			
		||||
public interface ITurtleUpgrade extends UpgradeBase {
 | 
			
		||||
    /**
 | 
			
		||||
     * The registry key for upgrade serialisers.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The registry key.
 | 
			
		||||
     */
 | 
			
		||||
    static ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> serialiserRegistryKey() {
 | 
			
		||||
        return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return whether this turtle adds a tool or a peripheral to the turtle.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,9 @@ package dan200.computercraft.api.turtle;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftTags;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
 | 
			
		||||
import dan200.computercraft.impl.PlatformHelper;
 | 
			
		||||
import net.minecraft.core.registries.Registries;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.RegistryHelper;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.data.DataGenerator;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
@@ -29,13 +30,13 @@ import java.util.function.Consumer;
 | 
			
		||||
 * {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
 | 
			
		||||
 * generate them.
 | 
			
		||||
 *
 | 
			
		||||
 * @see TurtleUpgradeSerialiser
 | 
			
		||||
 * @see ITurtleUpgrade
 | 
			
		||||
 */
 | 
			
		||||
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
 | 
			
		||||
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade> {
 | 
			
		||||
    private static final ResourceLocation TOOL_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "tool");
 | 
			
		||||
 | 
			
		||||
    public TurtleUpgradeDataProvider(PackOutput output) {
 | 
			
		||||
        super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.registryId());
 | 
			
		||||
        super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", ITurtleUpgrade.serialiserRegistryKey());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -57,7 +58,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
 | 
			
		||||
     */
 | 
			
		||||
    public static class ToolBuilder {
 | 
			
		||||
        private final ResourceLocation id;
 | 
			
		||||
        private final TurtleUpgradeSerialiser<?> serialiser;
 | 
			
		||||
        private final UpgradeSerialiser<? extends ITurtleUpgrade> serialiser;
 | 
			
		||||
        private final Item toolItem;
 | 
			
		||||
        private @Nullable String adjective;
 | 
			
		||||
        private @Nullable Item craftingItem;
 | 
			
		||||
@@ -66,7 +67,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
 | 
			
		||||
        private boolean allowEnchantments = false;
 | 
			
		||||
        private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
 | 
			
		||||
 | 
			
		||||
        ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
 | 
			
		||||
        ToolBuilder(ResourceLocation id, UpgradeSerialiser<? extends ITurtleUpgrade> serialiser, Item toolItem) {
 | 
			
		||||
            this.id = id;
 | 
			
		||||
            this.serialiser = serialiser;
 | 
			
		||||
            this.toolItem = toolItem;
 | 
			
		||||
@@ -149,12 +150,12 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
 | 
			
		||||
         *
 | 
			
		||||
         * @param add The callback given to {@link #addUpgrades(Consumer)}.
 | 
			
		||||
         */
 | 
			
		||||
        public void add(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
 | 
			
		||||
        public void add(Consumer<Upgrade<UpgradeSerialiser<? extends ITurtleUpgrade>>> add) {
 | 
			
		||||
            add.accept(new Upgrade<>(id, serialiser, s -> {
 | 
			
		||||
                s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, toolItem).toString());
 | 
			
		||||
                s.addProperty("item", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, toolItem).toString());
 | 
			
		||||
                if (adjective != null) s.addProperty("adjective", adjective);
 | 
			
		||||
                if (craftingItem != null) {
 | 
			
		||||
                    s.addProperty("craftItem", PlatformHelper.get().getRegistryKey(Registries.ITEM, craftingItem).toString());
 | 
			
		||||
                    s.addProperty("craftItem", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, craftingItem).toString());
 | 
			
		||||
                }
 | 
			
		||||
                if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
 | 
			
		||||
                if (breakable != null) s.addProperty("breakable", breakable.location().toString());
 | 
			
		||||
 
 | 
			
		||||
@@ -1,109 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.RecipeSerializer;
 | 
			
		||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Reads a {@link ITurtleUpgrade} from disk and reads/writes it to a network packet.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * If your turtle upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
 | 
			
		||||
 * {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
 | 
			
		||||
 *
 | 
			
		||||
 * <h2>Example (Forge)</h2>
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * static final DeferredRegister<TurtleUpgradeSerialiser<?>> SERIALISERS = DeferredRegister.create( TurtleUpgradeSerialiser.TYPE, "my_mod" );
 | 
			
		||||
 *
 | 
			
		||||
 * // Register a new upgrade serialiser called "my_upgrade".
 | 
			
		||||
 * public static final RegistryObject<TurtleUpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
 | 
			
		||||
 *     SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
 | 
			
		||||
 *
 | 
			
		||||
 * // Then in your constructor
 | 
			
		||||
 * SERIALISERS.register( bus );
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * We can then define a new upgrade using JSON by placing the following in
 | 
			
		||||
 * {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
 | 
			
		||||
 *
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * {
 | 
			
		||||
 *     "type": my_mod:my_upgrade",
 | 
			
		||||
 * }
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Finally, we need to register a model for our upgrade. The way to do this varies on mod loader, see
 | 
			
		||||
 * {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of turtle upgrade this is responsible for serialising.
 | 
			
		||||
 * @see ITurtleUpgrade
 | 
			
		||||
 * @see TurtleUpgradeDataProvider
 | 
			
		||||
 * @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
 | 
			
		||||
 */
 | 
			
		||||
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID for the associated registry.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The registry key.
 | 
			
		||||
     */
 | 
			
		||||
    static ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> registryId() {
 | 
			
		||||
        return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
 | 
			
		||||
     * but for upgrades.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory Generate a new upgrade with a specific ID.
 | 
			
		||||
     * @param <T>     The type of the generated upgrade.
 | 
			
		||||
     * @return The serialiser for this upgrade
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
 | 
			
		||||
        final class Impl extends SimpleSerialiser<T> implements TurtleUpgradeSerialiser<T> {
 | 
			
		||||
            private Impl(Function<ResourceLocation, T> constructor) {
 | 
			
		||||
                super(constructor);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Impl(factory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
 | 
			
		||||
     *                {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
 | 
			
		||||
     * @param <T>     The type of the generated upgrade.
 | 
			
		||||
     * @return The serialiser for this upgrade.
 | 
			
		||||
     * @see #simple(Function)  For upgrades whose crafting stack should not vary.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
        final class Impl extends SerialiserWithCraftingItem<T> implements TurtleUpgradeSerialiser<T> {
 | 
			
		||||
            private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
                super(factory);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Impl(factory);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,7 +9,6 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.impl.PlatformHelper;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
@@ -100,7 +99,7 @@ public interface UpgradeBase {
 | 
			
		||||
     * The default check requires that any non-capability NBT is exactly the same as the
 | 
			
		||||
     * crafting item, but this may be relaxed for your upgrade.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This is based on {@code net.minecraftforge.common.crafting.StrictNBTIngredient}'s check.
 | 
			
		||||
     * This is based on {@code net.neoforged.common.crafting.StrictNBTIngredient}'s check.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
 | 
			
		||||
     *              {@link #getCraftingItem()}.
 | 
			
		||||
@@ -111,12 +110,12 @@ public interface UpgradeBase {
 | 
			
		||||
 | 
			
		||||
        // A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
 | 
			
		||||
        // null one.
 | 
			
		||||
        var shareTag = PlatformHelper.get().getShareTag(stack);
 | 
			
		||||
        var craftingShareTag = PlatformHelper.get().getShareTag(crafting);
 | 
			
		||||
        if (shareTag == craftingShareTag) return true;
 | 
			
		||||
        if (shareTag == null) return Objects.requireNonNull(craftingShareTag).isEmpty();
 | 
			
		||||
        if (craftingShareTag == null) return shareTag.isEmpty();
 | 
			
		||||
        return shareTag.equals(craftingShareTag);
 | 
			
		||||
        var tag = stack.getTag();
 | 
			
		||||
        var craftingTag = crafting.getTag();
 | 
			
		||||
        if (tag == craftingTag) return true;
 | 
			
		||||
        if (tag == null) return Objects.requireNonNull(craftingTag).isEmpty();
 | 
			
		||||
        if (craftingTag == null) return tag.isEmpty();
 | 
			
		||||
        return tag.equals(craftingTag);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -6,19 +6,20 @@ package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import com.google.gson.JsonParseException;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.PlatformHelper;
 | 
			
		||||
import dan200.computercraft.impl.RegistryHelper;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.core.registries.Registries;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.data.CachedOutput;
 | 
			
		||||
import net.minecraft.data.DataProvider;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
@@ -31,31 +32,31 @@ import java.util.function.Function;
 | 
			
		||||
 * the other subclasses.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The base class of upgrades.
 | 
			
		||||
 * @param <R> The upgrade serialiser to register for.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
 | 
			
		||||
public abstract class UpgradeDataProvider<T extends UpgradeBase> implements DataProvider {
 | 
			
		||||
    private final PackOutput output;
 | 
			
		||||
    private final String name;
 | 
			
		||||
    private final String folder;
 | 
			
		||||
    private final ResourceKey<Registry<R>> registry;
 | 
			
		||||
    private final Registry<UpgradeSerialiser<? extends T>> registry;
 | 
			
		||||
 | 
			
		||||
    private @Nullable List<T> upgrades;
 | 
			
		||||
 | 
			
		||||
    protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<R>> registry) {
 | 
			
		||||
    @ApiStatus.Internal
 | 
			
		||||
    protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry) {
 | 
			
		||||
        this.output = output;
 | 
			
		||||
        this.name = name;
 | 
			
		||||
        this.folder = folder;
 | 
			
		||||
        this.registry = registry;
 | 
			
		||||
        this.registry = RegistryHelper.getRegistry(registry);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
 | 
			
		||||
     * Register an upgrade using a {@linkplain UpgradeSerialiser#simple(Function) "simple" serialiser}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id         The ID of the upgrade to create.
 | 
			
		||||
     * @param serialiser The simple serialiser.
 | 
			
		||||
     * @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
 | 
			
		||||
     */
 | 
			
		||||
    public final Upgrade<R> simple(ResourceLocation id, R serialiser) {
 | 
			
		||||
    public final Upgrade<UpgradeSerialiser<? extends T>> simple(ResourceLocation id, UpgradeSerialiser<? extends T> serialiser) {
 | 
			
		||||
        if (!(serialiser instanceof SimpleSerialiser)) {
 | 
			
		||||
            throw new IllegalStateException(serialiser + " must be a simple() seriaiser.");
 | 
			
		||||
        }
 | 
			
		||||
@@ -65,20 +66,20 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
 | 
			
		||||
     * Register an upgrade using a {@linkplain UpgradeSerialiser#simple(Function) simple serialiser}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id         The ID of the upgrade to create.
 | 
			
		||||
     * @param serialiser The simple serialiser.
 | 
			
		||||
     * @param item       The crafting upgrade for this item.
 | 
			
		||||
     * @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
 | 
			
		||||
     */
 | 
			
		||||
    public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser, Item item) {
 | 
			
		||||
    public final Upgrade<UpgradeSerialiser<? extends T>> simpleWithCustomItem(ResourceLocation id, UpgradeSerialiser<? extends T> serialiser, Item item) {
 | 
			
		||||
        if (!(serialiser instanceof SerialiserWithCraftingItem)) {
 | 
			
		||||
            throw new IllegalStateException(serialiser + " must be a simpleWithCustomItem() serialiser.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Upgrade<>(id, serialiser, s ->
 | 
			
		||||
            s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, item).toString())
 | 
			
		||||
            s.addProperty("item", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, item).toString())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -94,7 +95,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
 | 
			
		||||
     *
 | 
			
		||||
     * @param addUpgrade A callback used to register an upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
 | 
			
		||||
    protected abstract void addUpgrades(Consumer<Upgrade<UpgradeSerialiser<? extends T>>> addUpgrade);
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CompletableFuture<?> run(CachedOutput cache) {
 | 
			
		||||
@@ -107,7 +108,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
 | 
			
		||||
            if (!seen.add(upgrade.id())) throw new IllegalStateException("Duplicate upgrade " + upgrade.id());
 | 
			
		||||
 | 
			
		||||
            var json = new JsonObject();
 | 
			
		||||
            json.addProperty("type", PlatformHelper.get().getRegistryKey(registry, upgrade.serialiser()).toString());
 | 
			
		||||
            json.addProperty("type", RegistryHelper.getKeyOrThrow(registry, upgrade.serialiser()).toString());
 | 
			
		||||
            upgrade.serialise().accept(json);
 | 
			
		||||
 | 
			
		||||
            futures.add(DataProvider.saveStable(cache, json, base.resolve(upgrade.id().getNamespace() + "/" + folder + "/" + upgrade.id().getPath() + ".json")));
 | 
			
		||||
@@ -129,9 +130,9 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final R existingSerialiser(ResourceLocation id) {
 | 
			
		||||
        var result = PlatformHelper.get().getRegistryObject(registry, id);
 | 
			
		||||
        if (result == null) throw new IllegalArgumentException("No such serialiser " + registry);
 | 
			
		||||
    public final UpgradeSerialiser<? extends T> existingSerialiser(ResourceLocation id) {
 | 
			
		||||
        var result = registry.get(id);
 | 
			
		||||
        if (result == null) throw new IllegalArgumentException("No such serialiser " + id);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,21 +5,40 @@
 | 
			
		||||
package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.RecipeSerializer;
 | 
			
		||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base interface for upgrade serialisers. This should generally not be implemented directly, instead implementing one
 | 
			
		||||
 * of {@link TurtleUpgradeSerialiser} or {@link PocketUpgradeSerialiser}.
 | 
			
		||||
 * A serialiser for {@link ITurtleUpgrade} or {@link IPocketUpgrade}s.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * However, it may sometimes be useful to implement this if you have some shared logic between upgrade types.
 | 
			
		||||
 * These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This interface is very similar to {@link RecipeSerializer}; each serialiser should correspond to a specific upgrade
 | 
			
		||||
 * class. Upgrades are then read from JSON files in datapacks, allowing multiple instances of the upgrade to be
 | 
			
		||||
 * registered.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * If your upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
 | 
			
		||||
 * {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Upgrades may be data generated via a {@link UpgradeDataProvider} (see {@link TurtleUpgradeDataProvider} and
 | 
			
		||||
 * {@link PocketUpgradeDataProvider}).
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The upgrade that this class can serialise and deserialise.
 | 
			
		||||
 * @see TurtleUpgradeSerialiser
 | 
			
		||||
 * @see PocketUpgradeSerialiser
 | 
			
		||||
 * @see ITurtleUpgrade
 | 
			
		||||
 * @see IPocketUpgrade
 | 
			
		||||
 */
 | 
			
		||||
public interface UpgradeSerialiser<T extends UpgradeBase> {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -49,4 +68,30 @@ public interface UpgradeSerialiser<T extends UpgradeBase> {
 | 
			
		||||
     */
 | 
			
		||||
    void toNetwork(FriendlyByteBuf buffer, T upgrade);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
 | 
			
		||||
     * but for upgrades.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory Generate a new upgrade with a specific ID.
 | 
			
		||||
     * @param <T>     The type of the generated upgrade.
 | 
			
		||||
     * @return The serialiser for this upgrade
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends UpgradeBase> UpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
 | 
			
		||||
        return new SimpleSerialiser<>(factory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
 | 
			
		||||
     *                {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
 | 
			
		||||
     * @param <T>     The type of the generated upgrade.
 | 
			
		||||
     * @return The serialiser for this upgrade.
 | 
			
		||||
     * @see #simple(Function)  For upgrades whose crafting stack should not vary.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends UpgradeBase> UpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
        return new SerialiserWithCraftingItem<>(factory);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,10 +15,11 @@ import dan200.computercraft.api.media.MediaProvider;
 | 
			
		||||
import dan200.computercraft.api.network.PacketNetwork;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredElement;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredNode;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
@@ -67,9 +68,9 @@ public interface ComputerCraftAPIService {
 | 
			
		||||
 | 
			
		||||
    void registerRefuelHandler(TurtleRefuelHandler handler);
 | 
			
		||||
 | 
			
		||||
    ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId();
 | 
			
		||||
    ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId();
 | 
			
		||||
 | 
			
		||||
    ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId();
 | 
			
		||||
    ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId();
 | 
			
		||||
 | 
			
		||||
    DetailRegistry<ItemStack> getItemStackDetailRegistry();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,6 @@ package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
@@ -32,39 +27,6 @@ public interface PlatformHelper {
 | 
			
		||||
        return instance == null ? Services.raise(PlatformHelper.class, Instance.ERROR) : instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the unique ID for a registered object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param registry The registry to look up this object in.
 | 
			
		||||
     * @param object   The object to look up.
 | 
			
		||||
     * @param <T>      The type of object the registry stores.
 | 
			
		||||
     * @return The registered object's ID.
 | 
			
		||||
     * @throws IllegalArgumentException If the registry or object are not registered.
 | 
			
		||||
     */
 | 
			
		||||
    <T> ResourceLocation getRegistryKey(ResourceKey<Registry<T>> registry, T object);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Look up an ID in a registry, returning the registered object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param registry The registry to look up this object in.
 | 
			
		||||
     * @param id       The ID to look up.
 | 
			
		||||
     * @param <T>      The type of object the registry stores.
 | 
			
		||||
     * @return The resolved registry object.
 | 
			
		||||
     * @throws IllegalArgumentException If the registry or object are not registered.
 | 
			
		||||
     */
 | 
			
		||||
    <T> T getRegistryObject(ResourceKey<Registry<T>> registry, ResourceLocation id);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the subset of an {@link ItemStack}'s {@linkplain ItemStack#getTag() tag} which is synced to the client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param item The stack.
 | 
			
		||||
     * @return The item's tag.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    default CompoundTag getShareTag(ItemStack item) {
 | 
			
		||||
        return item.getTag();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a resource condition which requires a mod to be loaded. This should be used by data providers such as
 | 
			
		||||
     * {@link UpgradeDataProvider}.
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,49 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Additioanl functions for working with {@linkplain Registry registries}.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public final class RegistryHelper {
 | 
			
		||||
    private RegistryHelper() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find a registry from a {@link ResourceKey}, throwing if it does not exist.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id  The id of the registry.
 | 
			
		||||
     * @param <T> The contents of the registry
 | 
			
		||||
     * @return The associated registry.
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    public static <T> Registry<T> getRegistry(ResourceKey<Registry<T>> id) {
 | 
			
		||||
        var registry = (Registry<T>) BuiltInRegistries.REGISTRY.get(id.location());
 | 
			
		||||
        if (registry == null) throw new IllegalArgumentException("Unknown registry " + id);
 | 
			
		||||
        return registry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the key of a registry entry, throwing if it is not registered.
 | 
			
		||||
     *
 | 
			
		||||
     * @param registry The registry to look up in.
 | 
			
		||||
     * @param object   The object to look up.
 | 
			
		||||
     * @param <T>      The type of this registry.
 | 
			
		||||
     * @return The ID of this object
 | 
			
		||||
     * @see Registry#getResourceKey(Object)
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> ResourceLocation getKeyOrThrow(Registry<T> registry, T object) {
 | 
			
		||||
        var key = registry.getResourceKey(object);
 | 
			
		||||
        if (key.isEmpty()) throw new IllegalArgumentException(object + " was not registered in " + registry.key());
 | 
			
		||||
        return key.get().location();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,27 +23,27 @@ import java.util.function.BiFunction;
 | 
			
		||||
 * @param <T> The upgrade that this class can serialise and deserialise.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public abstract class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
 | 
			
		||||
public final class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
 | 
			
		||||
    private final BiFunction<ResourceLocation, ItemStack, T> factory;
 | 
			
		||||
 | 
			
		||||
    protected SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
    public SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
        this.factory = factory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final T fromJson(ResourceLocation id, JsonObject object) {
 | 
			
		||||
    public T fromJson(ResourceLocation id, JsonObject object) {
 | 
			
		||||
        var item = GsonHelper.getAsItem(object, "item");
 | 
			
		||||
        return factory.apply(id, new ItemStack(item));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
 | 
			
		||||
    public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
 | 
			
		||||
        var item = buffer.readItem();
 | 
			
		||||
        return factory.apply(id, item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
 | 
			
		||||
    public void toNetwork(FriendlyByteBuf buffer, T upgrade) {
 | 
			
		||||
        buffer.writeItem(upgrade.getCraftingItem());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ import java.util.function.Function;
 | 
			
		||||
 * @param <T> The upgrade that this class can serialise and deserialise.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public abstract class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
 | 
			
		||||
public final class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
 | 
			
		||||
    private final Function<ResourceLocation, T> constructor;
 | 
			
		||||
 | 
			
		||||
    public SimpleSerialiser(Function<ResourceLocation, T> constructor) {
 | 
			
		||||
@@ -29,16 +29,16 @@ public abstract class SimpleSerialiser<T extends UpgradeBase> implements Upgrade
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final T fromJson(ResourceLocation id, JsonObject object) {
 | 
			
		||||
    public T fromJson(ResourceLocation id, JsonObject object) {
 | 
			
		||||
        return constructor.apply(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
 | 
			
		||||
    public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
 | 
			
		||||
        return constructor.apply(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
 | 
			
		||||
    public void toNetwork(FriendlyByteBuf buffer, T upgrade) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@ dependencies {
 | 
			
		||||
    implementation(commonClasses(project(":common-api")))
 | 
			
		||||
    clientImplementation(clientClasses(project(":common-api")))
 | 
			
		||||
 | 
			
		||||
    compileOnly(libs.mixin)
 | 
			
		||||
    compileOnly(libs.bundles.externalMods.common)
 | 
			
		||||
    clientCompileOnly(variantOf(libs.emi) { classifier("api") })
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -108,7 +108,7 @@ public final class ClientHooks {
 | 
			
		||||
     */
 | 
			
		||||
    public static void addBlockDebugInfo(Consumer<String> addText) {
 | 
			
		||||
        var minecraft = Minecraft.getInstance();
 | 
			
		||||
        if (!minecraft.options.renderDebug || minecraft.level == null) return;
 | 
			
		||||
        if (!minecraft.getDebugOverlay().showDebugScreen() || minecraft.level == null) return;
 | 
			
		||||
        if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
 | 
			
		||||
 | 
			
		||||
        var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos());
 | 
			
		||||
@@ -138,7 +138,7 @@ public final class ClientHooks {
 | 
			
		||||
     * @param addText A callback which adds a single line of text.
 | 
			
		||||
     */
 | 
			
		||||
    public static void addGameDebugInfo(Consumer<String> addText) {
 | 
			
		||||
        if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().options.renderDebug) {
 | 
			
		||||
        if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().getDebugOverlay().showDebugScreen()) {
 | 
			
		||||
            addText.accept("[CC:T] Monitor renderer: " + MonitorBlockEntityRenderer.currentRenderer());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,8 @@ import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.color.item.ItemColor;
 | 
			
		||||
import net.minecraft.client.gui.screens.MenuScreens;
 | 
			
		||||
import net.minecraft.client.gui.screens.Screen;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
 | 
			
		||||
import net.minecraft.client.multiplayer.ClientLevel;
 | 
			
		||||
import net.minecraft.client.renderer.ShaderInstance;
 | 
			
		||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
 | 
			
		||||
@@ -43,6 +45,8 @@ import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
 | 
			
		||||
import net.minecraft.server.packs.resources.ResourceProvider;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
import net.minecraft.world.inventory.MenuType;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.ItemLike;
 | 
			
		||||
@@ -82,17 +86,6 @@ public final class ClientRegistry {
 | 
			
		||||
     * @param itemProperties Callback to register item properties.
 | 
			
		||||
     */
 | 
			
		||||
    public static void registerMainThread(RegisterItemProperty itemProperties) {
 | 
			
		||||
        MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
 | 
			
		||||
        MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new);
 | 
			
		||||
        MenuScreens.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
 | 
			
		||||
        MenuScreens.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
 | 
			
		||||
 | 
			
		||||
        MenuScreens.register(ModRegistry.Menus.PRINTER.get(), PrinterScreen::new);
 | 
			
		||||
        MenuScreens.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
 | 
			
		||||
        MenuScreens.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
 | 
			
		||||
 | 
			
		||||
        MenuScreens.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
 | 
			
		||||
 | 
			
		||||
        registerItemProperty(itemProperties, "state",
 | 
			
		||||
            new UnclampedPropertyFunction((stack, world, player, random) -> {
 | 
			
		||||
                var computer = ClientPocketComputers.get(stack);
 | 
			
		||||
@@ -106,6 +99,23 @@ public final class ClientRegistry {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void registerMenuScreens(RegisterMenuScreen register) {
 | 
			
		||||
        register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
 | 
			
		||||
        register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new);
 | 
			
		||||
        register.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
 | 
			
		||||
        register.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
 | 
			
		||||
 | 
			
		||||
        register.register(ModRegistry.Menus.PRINTER.get(), PrinterScreen::new);
 | 
			
		||||
        register.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
 | 
			
		||||
        register.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
 | 
			
		||||
 | 
			
		||||
        register.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public interface RegisterMenuScreen {
 | 
			
		||||
        <M extends AbstractContainerMenu, U extends Screen & MenuAccess<M>> void register(MenuType<? extends M> type, MenuScreens.ScreenConstructor<M, U> factory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void registerTurtleModellers(RegisterTurtleUpgradeModeller register) {
 | 
			
		||||
        register.register(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
 | 
			
		||||
 
 | 
			
		||||
@@ -125,7 +125,6 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
        renderTooltip(graphics, mouseX, mouseY);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,6 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
        renderTooltip(graphics, mouseX, mouseY);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.components.toasts.Toast;
 | 
			
		||||
import net.minecraft.client.gui.components.toasts.ToastComponent;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.util.FormattedCharSequence;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
@@ -18,6 +19,7 @@ import java.util.List;
 | 
			
		||||
 * A {@link Toast} implementation which displays an arbitrary message along with an optional {@link ItemStack}.
 | 
			
		||||
 */
 | 
			
		||||
public class ItemToast implements Toast {
 | 
			
		||||
    private static final ResourceLocation TEXTURE = new ResourceLocation("toast/recipe");
 | 
			
		||||
    public static final Object TRANSFER_NO_RESPONSE_TOKEN = new Object();
 | 
			
		||||
 | 
			
		||||
    private static final long DISPLAY_TIME = 7000L;
 | 
			
		||||
@@ -79,7 +81,7 @@ public class ItemToast implements Toast {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (width == 160 && message.size() <= 1) {
 | 
			
		||||
            graphics.blit(TEXTURE, 0, 0, 0, 64, width, height());
 | 
			
		||||
            graphics.blitSprite(TEXTURE, 0, 0, width, height());
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
            var height = height();
 | 
			
		||||
@@ -109,14 +111,14 @@ public class ItemToast implements Toast {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void renderBackgroundRow(GuiGraphics graphics, int x, int u, int y, int height) {
 | 
			
		||||
        var leftOffset = 5;
 | 
			
		||||
        var leftOffset = u == 0 ? 20 : 5;
 | 
			
		||||
        var rightOffset = Math.min(60, x - leftOffset);
 | 
			
		||||
 | 
			
		||||
        graphics.blit(TEXTURE, 0, y, 0, 32 + u, leftOffset, height);
 | 
			
		||||
        graphics.blitSprite(TEXTURE, 160, 32, 0, u, 0, y, leftOffset, height);
 | 
			
		||||
        for (var k = leftOffset; k < x - rightOffset; k += 64) {
 | 
			
		||||
            graphics.blit(TEXTURE, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height);
 | 
			
		||||
            graphics.blitSprite(TEXTURE, 160, 32, 32, u, k, y, Math.min(64, x - k - rightOffset), height);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        graphics.blit(TEXTURE, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height);
 | 
			
		||||
        graphics.blitSprite(TEXTURE, 160, 32, 160 - rightOffset, u, x - rightOffset, y, rightOffset, height);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -63,9 +63,9 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) {
 | 
			
		||||
        minecraft.player.getInventory().swapPaint(pDelta);
 | 
			
		||||
        return super.mouseScrolled(pMouseX, pMouseY, pDelta);
 | 
			
		||||
    public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
 | 
			
		||||
        minecraft.player.getInventory().swapPaint(scrollX);
 | 
			
		||||
        return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -86,8 +86,6 @@ public final class OptionScreen extends Screen {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
 | 
			
		||||
        // Render the actual texture.
 | 
			
		||||
        graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
 | 
			
		||||
        graphics.blit(BACKGROUND,
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,6 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
        renderTooltip(graphics, mouseX, mouseY);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -64,15 +64,15 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean mouseScrolled(double x, double y, double delta) {
 | 
			
		||||
        if (super.mouseScrolled(x, y, delta)) return true;
 | 
			
		||||
        if (delta < 0) {
 | 
			
		||||
    public boolean mouseScrolled(double x, double y, double deltaX, double deltaY) {
 | 
			
		||||
        if (super.mouseScrolled(x, y, deltaX, deltaY)) return true;
 | 
			
		||||
        if (deltaX < 0) {
 | 
			
		||||
            // Scroll up goes to the next page
 | 
			
		||||
            if (page < pages - 1) page++;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (delta > 0) {
 | 
			
		||||
        if (deltaX > 0) {
 | 
			
		||||
            // Scroll down goes to the previous page
 | 
			
		||||
            if (page > 0) page--;
 | 
			
		||||
            return true;
 | 
			
		||||
@@ -91,14 +91,12 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
    public void renderBackground(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        // We must take the background further back in order to not overlap with our printed pages.
 | 
			
		||||
        graphics.pose().pushPose();
 | 
			
		||||
        graphics.pose().translate(0, 0, -1);
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
        super.renderBackground(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
        graphics.pose().popPose();
 | 
			
		||||
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,10 @@ public class DynamicImageButton extends Button {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        var message = this.message.get();
 | 
			
		||||
        setMessage(message.message());
 | 
			
		||||
        setTooltip(message.tooltip());
 | 
			
		||||
 | 
			
		||||
        var texture = this.texture.get(isHoveredOrFocused());
 | 
			
		||||
 | 
			
		||||
        RenderSystem.disableDepthTest();
 | 
			
		||||
@@ -50,14 +54,6 @@ public class DynamicImageButton extends Button {
 | 
			
		||||
        RenderSystem.enableDepthTest();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        var message = this.message.get();
 | 
			
		||||
        setMessage(message.message());
 | 
			
		||||
        setTooltip(message.tooltip());
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public record HintedMessage(Component message, Tooltip tooltip) {
 | 
			
		||||
        public HintedMessage(Component message, @Nullable Component hint) {
 | 
			
		||||
            this(
 | 
			
		||||
 
 | 
			
		||||
@@ -195,7 +195,7 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
 | 
			
		||||
    public boolean mouseScrolled(double mouseX, double mouseY, double delta, double deltaY) {
 | 
			
		||||
        if (!inTermRegion(mouseX, mouseY)) return false;
 | 
			
		||||
        if (!hasMouseSupport() || delta == 0) return false;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.network.protocol.Packet;
 | 
			
		||||
import net.minecraft.network.protocol.game.ServerGamePacketListener;
 | 
			
		||||
import net.minecraft.network.protocol.common.ServerCommonPacketListener;
 | 
			
		||||
import net.minecraft.sounds.SoundEvent;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
@@ -27,7 +27,7 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
 | 
			
		||||
     * @param message The messsge to convert.
 | 
			
		||||
     * @return The converted message.
 | 
			
		||||
     */
 | 
			
		||||
    Packet<ServerGamePacketListener> createPacket(NetworkMessage<ServerNetworkContext> message);
 | 
			
		||||
    Packet<ServerCommonPacketListener> createPacket(NetworkMessage<ServerNetworkContext> message);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render a {@link BakedModel}, using any loader-specific hooks.
 | 
			
		||||
 
 | 
			
		||||
@@ -125,10 +125,10 @@ public class SpriteRenderer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static float u(TextureAtlasSprite sprite, int x, int width) {
 | 
			
		||||
        return sprite.getU((double) x / width * 16);
 | 
			
		||||
        return sprite.getU((float) x / width);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static float v(TextureAtlasSprite sprite, int y, int height) {
 | 
			
		||||
        return sprite.getV((double) y / height * 16);
 | 
			
		||||
        return sprite.getV((float) y / height);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import com.mojang.blaze3d.platform.MemoryTracker;
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import com.mojang.blaze3d.vertex.*;
 | 
			
		||||
import com.mojang.math.Axis;
 | 
			
		||||
import dan200.computercraft.annotations.ForgeOverride;
 | 
			
		||||
import dan200.computercraft.client.FrameInfo;
 | 
			
		||||
import dan200.computercraft.client.integration.ShaderMod;
 | 
			
		||||
import dan200.computercraft.client.render.RenderTypes;
 | 
			
		||||
@@ -25,6 +26,7 @@ import dan200.computercraft.shared.util.DirectionUtil;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
 | 
			
		||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
 | 
			
		||||
import net.minecraft.world.phys.AABB;
 | 
			
		||||
import org.joml.Matrix3f;
 | 
			
		||||
import org.joml.Matrix4f;
 | 
			
		||||
import org.lwjgl.opengl.GL11;
 | 
			
		||||
@@ -255,6 +257,11 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
 | 
			
		||||
        return Config.monitorDistance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ForgeOverride
 | 
			
		||||
    public AABB getRenderBoundingBox(MonitorBlockEntity monitor) {
 | 
			
		||||
        return monitor.getRenderBoundingBox();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if any monitors were rendered this frame.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
@@ -40,7 +41,7 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
 | 
			
		||||
    public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
 | 
			
		||||
        var active = false;
 | 
			
		||||
        if (turtle != null) {
 | 
			
		||||
            var turtleNBT = turtle.getUpgradeNBTData(side);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,15 +10,13 @@ import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.PlatformHelper;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.RegistryHelper;
 | 
			
		||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
			
		||||
import dan200.computercraft.impl.UpgradeManager;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.WeakHashMap;
 | 
			
		||||
@@ -29,12 +27,10 @@ import java.util.stream.Stream;
 | 
			
		||||
 * A registry of {@link TurtleUpgradeModeller}s.
 | 
			
		||||
 */
 | 
			
		||||
public final class TurtleUpgradeModellers {
 | 
			
		||||
    private static final Logger LOG = LoggerFactory.getLogger(TurtleUpgradeModellers.class);
 | 
			
		||||
 | 
			
		||||
    private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side) ->
 | 
			
		||||
    private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side, data) ->
 | 
			
		||||
        new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity());
 | 
			
		||||
 | 
			
		||||
    private static final Map<TurtleUpgradeSerialiser<?>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
 | 
			
		||||
    private static final Map<UpgradeSerialiser<? extends ITurtleUpgrade>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
 | 
			
		||||
    private static volatile boolean fetchedModels;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -48,15 +44,12 @@ public final class TurtleUpgradeModellers {
 | 
			
		||||
    private TurtleUpgradeModellers() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static <T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
 | 
			
		||||
    public static <T extends ITurtleUpgrade> void register(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
 | 
			
		||||
        if (fetchedModels) {
 | 
			
		||||
            // TODO(1.20.4): Replace with an error.
 | 
			
		||||
            LOG.warn(
 | 
			
		||||
                "Turtle upgrade serialiser {} was registered too late, its models may not be loaded correctly. If you are " +
 | 
			
		||||
                    "the mod author, you may be using a deprecated API - see https://github.com/cc-tweaked/CC-Tweaked/pull/1684 " +
 | 
			
		||||
                    "for further information.",
 | 
			
		||||
                PlatformHelper.get().getRegistryKey(TurtleUpgradeSerialiser.registryId(), serialiser)
 | 
			
		||||
            );
 | 
			
		||||
            throw new IllegalStateException(String.format(
 | 
			
		||||
                "Turtle upgrade serialiser %s must be registered before models are baked.",
 | 
			
		||||
                RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.serialiserRegistryKey()), serialiser)
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (turtleModels.putIfAbsent(serialiser, modeller) != null) {
 | 
			
		||||
@@ -67,13 +60,13 @@ public final class TurtleUpgradeModellers {
 | 
			
		||||
    public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) {
 | 
			
		||||
        @SuppressWarnings("unchecked")
 | 
			
		||||
        var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
 | 
			
		||||
        return modeller.getModel(upgrade, access, side);
 | 
			
		||||
        return modeller.getModel(upgrade, access, side, access.getUpgradeNBTData(side));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
 | 
			
		||||
        @SuppressWarnings("unchecked")
 | 
			
		||||
        var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
 | 
			
		||||
        return modeller.getModel(upgrade, data, side);
 | 
			
		||||
        return modeller.getModel(upgrade, null, side, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,13 +18,12 @@ import dan200.computercraft.shared.computer.metrics.basic.Aggregate;
 | 
			
		||||
import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
 | 
			
		||||
import dan200.computercraft.shared.config.ConfigFile;
 | 
			
		||||
import dan200.computercraft.shared.config.ConfigSpec;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryWrappers;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.data.CachedOutput;
 | 
			
		||||
import net.minecraft.data.DataProvider;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.level.block.Block;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
@@ -282,12 +281,12 @@ public final class LanguageProvider implements DataProvider {
 | 
			
		||||
 | 
			
		||||
    private Stream<String> getExpectedKeys() {
 | 
			
		||||
        return Stream.of(
 | 
			
		||||
            RegistryWrappers.BLOCKS.stream()
 | 
			
		||||
                .filter(x -> RegistryWrappers.BLOCKS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
 | 
			
		||||
                .map(Block::getDescriptionId),
 | 
			
		||||
            RegistryWrappers.ITEMS.stream()
 | 
			
		||||
                .filter(x -> RegistryWrappers.ITEMS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
 | 
			
		||||
                .map(Item::getDescriptionId),
 | 
			
		||||
            BuiltInRegistries.BLOCK.holders()
 | 
			
		||||
                .filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
 | 
			
		||||
                .map(x -> x.value().getDescriptionId()),
 | 
			
		||||
            BuiltInRegistries.ITEM.holders()
 | 
			
		||||
                .filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
 | 
			
		||||
                .map(x -> x.value().getDescriptionId()),
 | 
			
		||||
            turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
 | 
			
		||||
            pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
 | 
			
		||||
            Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,9 @@
 | 
			
		||||
package dan200.computercraft.data;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonElement;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryWrappers;
 | 
			
		||||
import dan200.computercraft.impl.RegistryHelper;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.data.CachedOutput;
 | 
			
		||||
import net.minecraft.data.DataProvider;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
@@ -67,7 +68,7 @@ public class ModelProvider implements DataProvider {
 | 
			
		||||
        blocks.accept(new BlockModelGenerators(addBlockState, addModel, explicitItems::add));
 | 
			
		||||
        items.accept(new ItemModelGenerators(addModel));
 | 
			
		||||
 | 
			
		||||
        for (var block : RegistryWrappers.BLOCKS) {
 | 
			
		||||
        for (var block : BuiltInRegistries.BLOCK) {
 | 
			
		||||
            if (!blockStates.containsKey(block)) continue;
 | 
			
		||||
 | 
			
		||||
            var item = Item.BY_BLOCK.get(block);
 | 
			
		||||
@@ -80,7 +81,7 @@ public class ModelProvider implements DataProvider {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        List<CompletableFuture<?>> futures = new ArrayList<>();
 | 
			
		||||
        saveCollection(output, futures, blockStates, x -> blockStatePath.json(RegistryWrappers.BLOCKS.getKey(x)));
 | 
			
		||||
        saveCollection(output, futures, blockStates, x -> blockStatePath.json(RegistryHelper.getKeyOrThrow(BuiltInRegistries.BLOCK, x)));
 | 
			
		||||
        saveCollection(output, futures, models, modelPath::json);
 | 
			
		||||
        return Util.sequenceFailFast(futures);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,9 @@
 | 
			
		||||
package dan200.computercraft.data;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
@@ -21,7 +22,7 @@ class PocketUpgradeProvider extends PocketUpgradeDataProvider {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void addUpgrades(Consumer<Upgrade<PocketUpgradeSerialiser<?>>> addUpgrade) {
 | 
			
		||||
    protected void addUpgrades(Consumer<Upgrade<UpgradeSerialiser<? extends IPocketUpgrade>>> addUpgrade) {
 | 
			
		||||
        addUpgrade.accept(simpleWithCustomItem(id("speaker"), PocketUpgradeSerialisers.SPEAKER.get(), Items.SPEAKER.get()));
 | 
			
		||||
        simpleWithCustomItem(id("wireless_modem_normal"), PocketUpgradeSerialisers.WIRELESS_MODEM_NORMAL.get(), Items.WIRELESS_MODEM_NORMAL.get()).add(addUpgrade);
 | 
			
		||||
        simpleWithCustomItem(id("wireless_modem_advanced"), PocketUpgradeSerialisers.WIRELESS_MODEM_ADVANCED.get(), Items.WIRELESS_MODEM_ADVANCED.get()).add(addUpgrade);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,34 +5,56 @@
 | 
			
		||||
package dan200.computercraft.data;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import com.google.gson.JsonParseException;
 | 
			
		||||
import com.mojang.authlib.GameProfile;
 | 
			
		||||
import com.mojang.serialization.JsonOps;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeData;
 | 
			
		||||
import dan200.computercraft.core.util.Colour;
 | 
			
		||||
import dan200.computercraft.data.recipe.ShapedSpecBuilder;
 | 
			
		||||
import dan200.computercraft.data.recipe.ShapelessSpecBuilder;
 | 
			
		||||
import dan200.computercraft.impl.RegistryHelper;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.common.IColouredItem;
 | 
			
		||||
import dan200.computercraft.shared.common.ClearColourRecipe;
 | 
			
		||||
import dan200.computercraft.shared.common.ColourableRecipe;
 | 
			
		||||
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
 | 
			
		||||
import dan200.computercraft.shared.media.items.DiskItem;
 | 
			
		||||
import dan200.computercraft.shared.media.recipes.DiskRecipe;
 | 
			
		||||
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
 | 
			
		||||
import dan200.computercraft.shared.platform.PlatformHelper;
 | 
			
		||||
import dan200.computercraft.shared.platform.RecipeIngredients;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryWrappers;
 | 
			
		||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
 | 
			
		||||
import dan200.computercraft.shared.recipe.CustomShapelessRecipe;
 | 
			
		||||
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
 | 
			
		||||
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
 | 
			
		||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
 | 
			
		||||
import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe;
 | 
			
		||||
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
 | 
			
		||||
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
 | 
			
		||||
import dan200.computercraft.shared.util.ColourUtils;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.advancements.Criterion;
 | 
			
		||||
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
 | 
			
		||||
import net.minecraft.advancements.critereon.ItemPredicate;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.core.registries.Registries;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.data.recipes.*;
 | 
			
		||||
import net.minecraft.data.recipes.RecipeCategory;
 | 
			
		||||
import net.minecraft.data.recipes.RecipeOutput;
 | 
			
		||||
import net.minecraft.data.recipes.ShapedRecipeBuilder;
 | 
			
		||||
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.nbt.NbtUtils;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.util.GsonHelper;
 | 
			
		||||
import net.minecraft.world.item.*;
 | 
			
		||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
 | 
			
		||||
import net.minecraft.world.item.crafting.Ingredient;
 | 
			
		||||
import net.minecraft.world.item.crafting.ShapedRecipe;
 | 
			
		||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
 | 
			
		||||
import net.minecraft.world.item.crafting.Recipe;
 | 
			
		||||
import net.minecraft.world.level.ItemLike;
 | 
			
		||||
import net.minecraft.world.level.block.Blocks;
 | 
			
		||||
 | 
			
		||||
@@ -43,7 +65,7 @@ import java.util.function.Consumer;
 | 
			
		||||
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
 | 
			
		||||
import static dan200.computercraft.api.ComputerCraftTags.Items.WIRED_MODEM;
 | 
			
		||||
 | 
			
		||||
class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
    private final RecipeIngredients ingredients = PlatformHelper.get().getRecipeIngredients();
 | 
			
		||||
    private final TurtleUpgradeDataProvider turtleUpgrades;
 | 
			
		||||
    private final PocketUpgradeDataProvider pocketUpgrades;
 | 
			
		||||
@@ -55,40 +77,37 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void buildRecipes(Consumer<FinishedRecipe> add) {
 | 
			
		||||
    public void buildRecipes(RecipeOutput add) {
 | 
			
		||||
        basicRecipes(add);
 | 
			
		||||
        diskColours(add);
 | 
			
		||||
        pocketUpgrades(add);
 | 
			
		||||
        turtleUpgrades(add);
 | 
			
		||||
        turtleOverlays(add);
 | 
			
		||||
 | 
			
		||||
        addSpecial(add, ModRegistry.RecipeSerializers.PRINTOUT.get());
 | 
			
		||||
        addSpecial(add, ModRegistry.RecipeSerializers.DISK.get());
 | 
			
		||||
        addSpecial(add, ModRegistry.RecipeSerializers.DYEABLE_ITEM.get());
 | 
			
		||||
        addSpecial(add, ModRegistry.RecipeSerializers.DYEABLE_ITEM_CLEAR.get());
 | 
			
		||||
        addSpecial(add, ModRegistry.RecipeSerializers.TURTLE_UPGRADE.get());
 | 
			
		||||
        addSpecial(add, ModRegistry.RecipeSerializers.POCKET_COMPUTER_UPGRADE.get());
 | 
			
		||||
        addSpecial(add, new PrintoutRecipe(CraftingBookCategory.MISC));
 | 
			
		||||
        addSpecial(add, new DiskRecipe(CraftingBookCategory.MISC));
 | 
			
		||||
        addSpecial(add, new ColourableRecipe(CraftingBookCategory.MISC));
 | 
			
		||||
        addSpecial(add, new ClearColourRecipe(CraftingBookCategory.MISC));
 | 
			
		||||
        addSpecial(add, new TurtleUpgradeRecipe(CraftingBookCategory.MISC));
 | 
			
		||||
        addSpecial(add, new PocketComputerUpgradeRecipe(CraftingBookCategory.MISC));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a crafting recipe for a disk of every dye colour.
 | 
			
		||||
     *
 | 
			
		||||
     * @param add The callback to add recipes.
 | 
			
		||||
     * @param output The callback to add recipes.
 | 
			
		||||
     */
 | 
			
		||||
    private void diskColours(Consumer<FinishedRecipe> add) {
 | 
			
		||||
    private void diskColours(RecipeOutput output) {
 | 
			
		||||
        for (var colour : Colour.VALUES) {
 | 
			
		||||
            ShapelessRecipeBuilder
 | 
			
		||||
                .shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.DISK.get())
 | 
			
		||||
            ShapelessSpecBuilder
 | 
			
		||||
                .shapeless(RecipeCategory.REDSTONE, DiskItem.createFromIDAndColour(-1, null, colour.getHex()))
 | 
			
		||||
                .requires(ingredients.redstone())
 | 
			
		||||
                .requires(Items.PAPER)
 | 
			
		||||
                .requires(DyeItem.byColor(ofColour(colour)))
 | 
			
		||||
                .group("computercraft:disk")
 | 
			
		||||
                .unlockedBy("has_drive", inventoryChange(ModRegistry.Blocks.DISK_DRIVE.get()))
 | 
			
		||||
                .save(
 | 
			
		||||
                    RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(), add)
 | 
			
		||||
                        .withResultTag(x -> x.putInt(IColouredItem.NBT_COLOUR, colour.getHex())),
 | 
			
		||||
                    new ResourceLocation(ComputerCraftAPI.MOD_ID, "disk_" + (colour.ordinal() + 1))
 | 
			
		||||
                );
 | 
			
		||||
                .build(ImpostorShapelessRecipe::new)
 | 
			
		||||
                .save(output, new ResourceLocation(ComputerCraftAPI.MOD_ID, "disk_" + (colour.ordinal() + 1)));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -101,23 +120,22 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
     *
 | 
			
		||||
     * @param add The callback to add recipes.
 | 
			
		||||
     */
 | 
			
		||||
    private void turtleUpgrades(Consumer<FinishedRecipe> add) {
 | 
			
		||||
    private void turtleUpgrades(RecipeOutput add) {
 | 
			
		||||
        for (var turtleItem : turtleItems()) {
 | 
			
		||||
            var base = turtleItem.create(-1, null, -1, null, null, 0, null);
 | 
			
		||||
            var name = RegistryWrappers.ITEMS.getKey(turtleItem);
 | 
			
		||||
            var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
 | 
			
		||||
 | 
			
		||||
            for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
 | 
			
		||||
                var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
 | 
			
		||||
                ShapedRecipeBuilder
 | 
			
		||||
                    .shaped(RecipeCategory.REDSTONE, result.getItem())
 | 
			
		||||
                ShapedSpecBuilder
 | 
			
		||||
                    .shaped(RecipeCategory.REDSTONE, turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null))
 | 
			
		||||
                    .group(name.toString())
 | 
			
		||||
                    .pattern("#T")
 | 
			
		||||
                    .define('T', base.getItem())
 | 
			
		||||
                    .define('#', upgrade.getCraftingItem().getItem())
 | 
			
		||||
                    .unlockedBy("has_items",
 | 
			
		||||
                        inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
 | 
			
		||||
                    .unlockedBy("has_items", inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
 | 
			
		||||
                    .build(ImpostorShapedRecipe::new)
 | 
			
		||||
                    .save(
 | 
			
		||||
                        RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
 | 
			
		||||
                        add,
 | 
			
		||||
                        name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
 | 
			
		||||
                    );
 | 
			
		||||
            }
 | 
			
		||||
@@ -133,31 +151,30 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
     *
 | 
			
		||||
     * @param add The callback to add recipes.
 | 
			
		||||
     */
 | 
			
		||||
    private void pocketUpgrades(Consumer<FinishedRecipe> add) {
 | 
			
		||||
    private void pocketUpgrades(RecipeOutput add) {
 | 
			
		||||
        for (var pocket : pocketComputerItems()) {
 | 
			
		||||
            var base = pocket.create(-1, null, -1, null);
 | 
			
		||||
            var name = RegistryWrappers.ITEMS.getKey(pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
 | 
			
		||||
            var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
 | 
			
		||||
 | 
			
		||||
            for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
 | 
			
		||||
                var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
 | 
			
		||||
                ShapedRecipeBuilder
 | 
			
		||||
                    .shaped(RecipeCategory.REDSTONE, result.getItem())
 | 
			
		||||
                ShapedSpecBuilder
 | 
			
		||||
                    .shaped(RecipeCategory.REDSTONE, pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade)))
 | 
			
		||||
                    .group(name.toString())
 | 
			
		||||
                    .pattern("#")
 | 
			
		||||
                    .pattern("P")
 | 
			
		||||
                    .define('P', base.getItem())
 | 
			
		||||
                    .define('#', upgrade.getCraftingItem().getItem())
 | 
			
		||||
                    .unlockedBy("has_items",
 | 
			
		||||
                        inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
 | 
			
		||||
                    .unlockedBy("has_items", inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
 | 
			
		||||
                    .build(ImpostorShapedRecipe::new)
 | 
			
		||||
                    .save(
 | 
			
		||||
                        RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
 | 
			
		||||
                        add,
 | 
			
		||||
                        name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
 | 
			
		||||
                    );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void turtleOverlays(Consumer<FinishedRecipe> add) {
 | 
			
		||||
    private void turtleOverlays(RecipeOutput add) {
 | 
			
		||||
        turtleOverlay(add, "turtle_trans_overlay", x -> x
 | 
			
		||||
            .unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
 | 
			
		||||
            .requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE))
 | 
			
		||||
@@ -178,28 +195,24 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void turtleOverlay(Consumer<FinishedRecipe> add, String overlay, Consumer<ShapelessRecipeBuilder> build) {
 | 
			
		||||
    private void turtleOverlay(RecipeOutput add, String overlay, Consumer<ShapelessSpecBuilder> build) {
 | 
			
		||||
        for (var turtleItem : turtleItems()) {
 | 
			
		||||
            var base = turtleItem.create(-1, null, -1, null, null, 0, null);
 | 
			
		||||
            var name = RegistryWrappers.ITEMS.getKey(turtleItem);
 | 
			
		||||
            var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
 | 
			
		||||
 | 
			
		||||
            var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem())
 | 
			
		||||
            var builder = ShapelessSpecBuilder.shapeless(RecipeCategory.REDSTONE, base)
 | 
			
		||||
                .group(name.withSuffix("_overlay").toString())
 | 
			
		||||
                .unlockedBy("has_turtle", inventoryChange(base.getItem()));
 | 
			
		||||
            build.accept(builder);
 | 
			
		||||
            builder
 | 
			
		||||
                .requires(base.getItem())
 | 
			
		||||
                .save(
 | 
			
		||||
                    RecipeWrapper
 | 
			
		||||
                        .wrap(ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get(), add)
 | 
			
		||||
                        .withExtraData(x -> x.addProperty("overlay", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay).toString())),
 | 
			
		||||
                    name.withSuffix("_overlays/" + overlay)
 | 
			
		||||
                );
 | 
			
		||||
                .build(s -> new TurtleOverlayRecipe(s, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay)))
 | 
			
		||||
                .save(add, name.withSuffix("_overlays/" + overlay));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private void basicRecipes(Consumer<FinishedRecipe> add) {
 | 
			
		||||
    private void basicRecipes(RecipeOutput add) {
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Items.CABLE.get(), 6)
 | 
			
		||||
            .pattern(" # ")
 | 
			
		||||
@@ -233,7 +246,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .unlockedBy("has_components", inventoryChange(itemPredicate(ingredients.redstone()), itemPredicate(ingredients.goldIngot())))
 | 
			
		||||
            .save(add);
 | 
			
		||||
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
        ShapedSpecBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
 | 
			
		||||
            .pattern("###")
 | 
			
		||||
            .pattern("#C#")
 | 
			
		||||
@@ -241,10 +254,8 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .define('#', ingredients.goldIngot())
 | 
			
		||||
            .define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
 | 
			
		||||
            .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
 | 
			
		||||
            .save(
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
 | 
			
		||||
                new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade")
 | 
			
		||||
            );
 | 
			
		||||
            .build(ComputerUpgradeRecipe::new)
 | 
			
		||||
            .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade"));
 | 
			
		||||
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.COMPUTER_COMMAND.get())
 | 
			
		||||
@@ -257,7 +268,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .unlockedBy("has_components", inventoryChange(Blocks.COMMAND_BLOCK))
 | 
			
		||||
            .save(add);
 | 
			
		||||
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
        ShapedSpecBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_NORMAL.get())
 | 
			
		||||
            .pattern("###")
 | 
			
		||||
            .pattern("#C#")
 | 
			
		||||
@@ -266,9 +277,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
 | 
			
		||||
            .define('I', ingredients.woodenChest())
 | 
			
		||||
            .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
 | 
			
		||||
            .save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
 | 
			
		||||
            .buildOrThrow(TurtleRecipe::of)
 | 
			
		||||
            .save(add);
 | 
			
		||||
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
        ShapedSpecBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
 | 
			
		||||
            .pattern("###")
 | 
			
		||||
            .pattern("#C#")
 | 
			
		||||
@@ -277,9 +289,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
 | 
			
		||||
            .define('I', ingredients.woodenChest())
 | 
			
		||||
            .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
 | 
			
		||||
            .save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
 | 
			
		||||
            .buildOrThrow(TurtleRecipe::of)
 | 
			
		||||
            .save(add);
 | 
			
		||||
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
        ShapedSpecBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
 | 
			
		||||
            .pattern("###")
 | 
			
		||||
            .pattern("#C#")
 | 
			
		||||
@@ -288,10 +301,8 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .define('C', ModRegistry.Items.TURTLE_NORMAL.get())
 | 
			
		||||
            .define('B', ingredients.goldBlock())
 | 
			
		||||
            .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
 | 
			
		||||
            .save(
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
 | 
			
		||||
                new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade")
 | 
			
		||||
            );
 | 
			
		||||
            .build(ComputerUpgradeRecipe::new)
 | 
			
		||||
            .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade"));
 | 
			
		||||
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.DISK_DRIVE.get())
 | 
			
		||||
@@ -347,7 +358,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .unlockedBy("has_apple", inventoryChange(Items.GOLDEN_APPLE))
 | 
			
		||||
            .save(add);
 | 
			
		||||
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
        ShapedSpecBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
 | 
			
		||||
            .pattern("###")
 | 
			
		||||
            .pattern("#C#")
 | 
			
		||||
@@ -355,10 +366,8 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .define('#', ingredients.goldIngot())
 | 
			
		||||
            .define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
 | 
			
		||||
            .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
 | 
			
		||||
            .save(
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
 | 
			
		||||
                new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade")
 | 
			
		||||
            );
 | 
			
		||||
            .build(ComputerUpgradeRecipe::new)
 | 
			
		||||
            .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade"));
 | 
			
		||||
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.PRINTER.get())
 | 
			
		||||
@@ -425,57 +434,53 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .unlockedBy("has_wireless", inventoryChange(ModRegistry.Blocks.WIRELESS_MODEM_NORMAL.get()))
 | 
			
		||||
            .save(add);
 | 
			
		||||
 | 
			
		||||
        ShapelessRecipeBuilder
 | 
			
		||||
            .shapeless(RecipeCategory.DECORATIONS, Items.PLAYER_HEAD)
 | 
			
		||||
        ShapelessSpecBuilder
 | 
			
		||||
            .shapeless(RecipeCategory.DECORATIONS, playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c"))
 | 
			
		||||
            .requires(ingredients.head())
 | 
			
		||||
            .requires(ModRegistry.Items.MONITOR_NORMAL.get())
 | 
			
		||||
            .unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get()))
 | 
			
		||||
            .save(
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
 | 
			
		||||
                    .withResultTag(playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c")),
 | 
			
		||||
                new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy")
 | 
			
		||||
            );
 | 
			
		||||
            .build(CustomShapelessRecipe::new)
 | 
			
		||||
            .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy"));
 | 
			
		||||
 | 
			
		||||
        ShapelessRecipeBuilder
 | 
			
		||||
            .shapeless(RecipeCategory.DECORATIONS, Items.PLAYER_HEAD)
 | 
			
		||||
        ShapelessSpecBuilder
 | 
			
		||||
            .shapeless(RecipeCategory.DECORATIONS, playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb"))
 | 
			
		||||
            .requires(ingredients.head())
 | 
			
		||||
            .requires(ModRegistry.Items.COMPUTER_ADVANCED.get())
 | 
			
		||||
            .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get()))
 | 
			
		||||
            .save(
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
 | 
			
		||||
                    .withResultTag(playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb")),
 | 
			
		||||
                new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200")
 | 
			
		||||
            );
 | 
			
		||||
            .build(CustomShapelessRecipe::new)
 | 
			
		||||
            .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200"));
 | 
			
		||||
 | 
			
		||||
        ShapelessRecipeBuilder
 | 
			
		||||
        ShapelessSpecBuilder
 | 
			
		||||
            .shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_PAGES.get())
 | 
			
		||||
            .requires(ModRegistry.Items.PRINTED_PAGE.get(), 2)
 | 
			
		||||
            .requires(ingredients.string())
 | 
			
		||||
            .unlockedBy("has_printer", inventoryChange(ModRegistry.Blocks.PRINTER.get()))
 | 
			
		||||
            .save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(), add));
 | 
			
		||||
            .build(ImpostorShapelessRecipe::new)
 | 
			
		||||
            .save(add);
 | 
			
		||||
 | 
			
		||||
        ShapelessRecipeBuilder
 | 
			
		||||
        ShapelessSpecBuilder
 | 
			
		||||
            .shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_BOOK.get())
 | 
			
		||||
            .requires(ingredients.leather())
 | 
			
		||||
            .requires(ModRegistry.Items.PRINTED_PAGE.get(), 1)
 | 
			
		||||
            .requires(ingredients.string())
 | 
			
		||||
            .unlockedBy("has_printer", inventoryChange(ModRegistry.Blocks.PRINTER.get()))
 | 
			
		||||
            .save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(), add));
 | 
			
		||||
            .build(ImpostorShapelessRecipe::new)
 | 
			
		||||
            .save(add);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static DyeColor ofColour(Colour colour) {
 | 
			
		||||
        return DyeColor.byId(15 - colour.ordinal());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static InventoryChangeTrigger.TriggerInstance inventoryChange(TagKey<Item> stack) {
 | 
			
		||||
    private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(TagKey<Item> stack) {
 | 
			
		||||
        return InventoryChangeTrigger.TriggerInstance.hasItems(itemPredicate(stack));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static InventoryChangeTrigger.TriggerInstance inventoryChange(ItemLike... stack) {
 | 
			
		||||
    private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(ItemLike... stack) {
 | 
			
		||||
        return InventoryChangeTrigger.TriggerInstance.hasItems(stack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static InventoryChangeTrigger.TriggerInstance inventoryChange(ItemPredicate... items) {
 | 
			
		||||
    private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(ItemPredicate... items) {
 | 
			
		||||
        return InventoryChangeTrigger.TriggerInstance.hasItems(items);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -488,11 +493,12 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ItemPredicate itemPredicate(Ingredient ingredient) {
 | 
			
		||||
        var json = ingredient.toJson();
 | 
			
		||||
        var json = Util.getOrThrow(Ingredient.CODEC_NONEMPTY.encodeStart(JsonOps.INSTANCE, ingredient), JsonParseException::new);
 | 
			
		||||
        if (!(json instanceof JsonObject object)) throw new IllegalStateException("Unknown ingredient " + json);
 | 
			
		||||
 | 
			
		||||
        if (object.has("item")) {
 | 
			
		||||
            return itemPredicate(ShapedRecipe.itemFromJson(object));
 | 
			
		||||
            var item = Util.getOrThrow(ItemStack.ITEM_WITH_COUNT_CODEC.parse(JsonOps.INSTANCE, object), JsonParseException::new);
 | 
			
		||||
            return itemPredicate(item.getItem());
 | 
			
		||||
        } else if (object.has("tag")) {
 | 
			
		||||
            return itemPredicate(TagKey.create(Registries.ITEM, new ResourceLocation(GsonHelper.getAsString(object, "tag"))));
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -500,15 +506,14 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static CompoundTag playerHead(String name, String uuid) {
 | 
			
		||||
    private static ItemStack playerHead(String name, String uuid) {
 | 
			
		||||
        var item = new ItemStack(Items.PLAYER_HEAD);
 | 
			
		||||
        var owner = NbtUtils.writeGameProfile(new CompoundTag(), new GameProfile(UUID.fromString(uuid), name));
 | 
			
		||||
 | 
			
		||||
        var tag = new CompoundTag();
 | 
			
		||||
        tag.put(PlayerHeadItem.TAG_SKULL_OWNER, owner);
 | 
			
		||||
        return tag;
 | 
			
		||||
        item.getOrCreateTag().put(PlayerHeadItem.TAG_SKULL_OWNER, owner);
 | 
			
		||||
        return item;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void addSpecial(Consumer<FinishedRecipe> add, SimpleCraftingRecipeSerializer<?> special) {
 | 
			
		||||
        SpecialRecipeBuilder.special(special).save(add, RegistryWrappers.RECIPE_SERIALIZERS.getKey(special).toString());
 | 
			
		||||
    private static void addSpecial(RecipeOutput add, Recipe<?> recipe) {
 | 
			
		||||
        add.accept(RegistryHelper.getKeyOrThrow(BuiltInRegistries.RECIPE_SERIALIZER, recipe.getSerializer()), recipe, null);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,93 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.data;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import net.minecraft.data.recipes.FinishedRecipe;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.util.GsonHelper;
 | 
			
		||||
import net.minecraft.world.item.crafting.RecipeSerializer;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adapter for recipes which overrides the serializer and adds custom item NBT.
 | 
			
		||||
 */
 | 
			
		||||
final class RecipeWrapper implements Consumer<FinishedRecipe> {
 | 
			
		||||
    private final Consumer<FinishedRecipe> add;
 | 
			
		||||
    private final RecipeSerializer<?> serializer;
 | 
			
		||||
    private final List<Consumer<JsonObject>> extend = new ArrayList<>(0);
 | 
			
		||||
 | 
			
		||||
    RecipeWrapper(Consumer<FinishedRecipe> add, RecipeSerializer<?> serializer) {
 | 
			
		||||
        this.add = add;
 | 
			
		||||
        this.serializer = serializer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static RecipeWrapper wrap(RecipeSerializer<?> serializer, Consumer<FinishedRecipe> original) {
 | 
			
		||||
        return new RecipeWrapper(original, serializer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public RecipeWrapper withExtraData(Consumer<JsonObject> extra) {
 | 
			
		||||
        extend.add(extra);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public RecipeWrapper withResultTag(@Nullable CompoundTag resultTag) {
 | 
			
		||||
        if (resultTag == null) return this;
 | 
			
		||||
 | 
			
		||||
        extend.add(json -> {
 | 
			
		||||
            var object = GsonHelper.getAsJsonObject(json, "result");
 | 
			
		||||
            object.addProperty("nbt", resultTag.toString());
 | 
			
		||||
        });
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public RecipeWrapper withResultTag(Consumer<CompoundTag> resultTag) {
 | 
			
		||||
        var tag = new CompoundTag();
 | 
			
		||||
        resultTag.accept(tag);
 | 
			
		||||
        return withResultTag(tag);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void accept(FinishedRecipe finishedRecipe) {
 | 
			
		||||
        add.accept(new RecipeImpl(finishedRecipe, serializer, extend));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private record RecipeImpl(
 | 
			
		||||
        FinishedRecipe recipe, RecipeSerializer<?> serializer, List<Consumer<JsonObject>> extend
 | 
			
		||||
    ) implements FinishedRecipe {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void serializeRecipeData(JsonObject jsonObject) {
 | 
			
		||||
            recipe.serializeRecipeData(jsonObject);
 | 
			
		||||
            for (var extender : extend) extender.accept(jsonObject);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public ResourceLocation getId() {
 | 
			
		||||
            return recipe.getId();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public RecipeSerializer<?> getType() {
 | 
			
		||||
            return serializer;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Nullable
 | 
			
		||||
        @Override
 | 
			
		||||
        public JsonObject serializeAdvancement() {
 | 
			
		||||
            return recipe.serializeAdvancement();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Nullable
 | 
			
		||||
        @Override
 | 
			
		||||
        public ResourceLocation getAdvancementId() {
 | 
			
		||||
            return recipe.getAdvancementId();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,8 +5,9 @@
 | 
			
		||||
package dan200.computercraft.data;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftTags;
 | 
			
		||||
import dan200.computercraft.impl.RegistryHelper;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryWrappers;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.data.tags.ItemTagsProvider;
 | 
			
		||||
import net.minecraft.data.tags.TagsProvider;
 | 
			
		||||
import net.minecraft.tags.BlockTags;
 | 
			
		||||
@@ -111,9 +112,9 @@ class TagProvider {
 | 
			
		||||
        TagAppender<T> tag(TagKey<T> tag);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public record TagAppender<T>(RegistryWrappers.RegistryWrapper<T> registry, TagBuilder builder) {
 | 
			
		||||
    public record TagAppender<T>(Registry<T> registry, TagBuilder builder) {
 | 
			
		||||
        public TagAppender<T> add(T object) {
 | 
			
		||||
            builder.addElement(registry.getKey(object));
 | 
			
		||||
            builder.addElement(RegistryHelper.getKeyOrThrow(registry, object));
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,9 @@ package dan200.computercraft.data;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftTags.Blocks;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
@@ -22,7 +23,7 @@ class TurtleUpgradeProvider extends TurtleUpgradeDataProvider {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
 | 
			
		||||
    protected void addUpgrades(Consumer<Upgrade<UpgradeSerialiser<? extends ITurtleUpgrade>>> addUpgrade) {
 | 
			
		||||
        simpleWithCustomItem(id("speaker"), TurtleSerialisers.SPEAKER.get(), Items.SPEAKER.get()).add(addUpgrade);
 | 
			
		||||
        simpleWithCustomItem(vanilla("crafting_table"), TurtleSerialisers.WORKBENCH.get(), net.minecraft.world.item.Items.CRAFTING_TABLE).add(addUpgrade);
 | 
			
		||||
        simpleWithCustomItem(id("wireless_modem_normal"), TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), Items.WIRELESS_MODEM_NORMAL.get()).add(addUpgrade);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,129 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.data.recipe;
 | 
			
		||||
 | 
			
		||||
import com.mojang.serialization.DataResult;
 | 
			
		||||
import dan200.computercraft.shared.recipe.RecipeProperties;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.advancements.AdvancementRequirements;
 | 
			
		||||
import net.minecraft.advancements.AdvancementRewards;
 | 
			
		||||
import net.minecraft.advancements.Criterion;
 | 
			
		||||
import net.minecraft.advancements.critereon.RecipeUnlockedTrigger;
 | 
			
		||||
import net.minecraft.data.recipes.RecipeBuilder;
 | 
			
		||||
import net.minecraft.data.recipes.RecipeCategory;
 | 
			
		||||
import net.minecraft.data.recipes.RecipeOutput;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.Recipe;
 | 
			
		||||
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An abstract base class for creating recipes, in the style of {@link RecipeBuilder}.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <S> The type of this class.
 | 
			
		||||
 * @param <O> The output of this builder.
 | 
			
		||||
 * @see ShapelessSpecBuilder
 | 
			
		||||
 */
 | 
			
		||||
public abstract class AbstractRecipeBuilder<S extends AbstractRecipeBuilder<S, O>, O> {
 | 
			
		||||
    private final RecipeCategory category;
 | 
			
		||||
    protected final ItemStack result;
 | 
			
		||||
    private String group = "";
 | 
			
		||||
    private final Map<String, Criterion<?>> criteria = new LinkedHashMap<>();
 | 
			
		||||
 | 
			
		||||
    protected AbstractRecipeBuilder(RecipeCategory category, ItemStack result) {
 | 
			
		||||
        this.category = category;
 | 
			
		||||
        this.result = result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the group for this recipe.
 | 
			
		||||
     *
 | 
			
		||||
     * @param group The new group.
 | 
			
		||||
     * @return This object, for chaining.
 | 
			
		||||
     */
 | 
			
		||||
    public final S group(String group) {
 | 
			
		||||
        this.group = group;
 | 
			
		||||
        return self();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a criterion to this recipe.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name      The name of the criterion.
 | 
			
		||||
     * @param criterion The criterion to add.
 | 
			
		||||
     * @return This object, for chaining.
 | 
			
		||||
     */
 | 
			
		||||
    public final S unlockedBy(String name, Criterion<?> criterion) {
 | 
			
		||||
        criteria.put(name, criterion);
 | 
			
		||||
        return self();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convert this builder into the output ({@link O}) object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param properties The properties for this recipe.
 | 
			
		||||
     * @return The built object.
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract O build(RecipeProperties properties);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convert this builder into a concrete recipe.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory The recipe's constructor.
 | 
			
		||||
     * @return The "built" recipe.
 | 
			
		||||
     */
 | 
			
		||||
    public final FinishedRecipe build(Function<O, Recipe<?>> factory) {
 | 
			
		||||
        var properties = new RecipeProperties(group, RecipeBuilder.determineBookCategory(category), true);
 | 
			
		||||
        return new FinishedRecipe(factory.apply(build(properties)), result.getItem(), category, criteria);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convert this builder into a concrete recipe.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory The recipe's constructor.
 | 
			
		||||
     * @return The "built" recipe.
 | 
			
		||||
     */
 | 
			
		||||
    public final FinishedRecipe buildOrThrow(Function<O, DataResult<? extends Recipe<?>>> factory) {
 | 
			
		||||
        return build(s -> Util.getOrThrow(factory.apply(s), IllegalStateException::new));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    private S self() {
 | 
			
		||||
        return (S) this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static final class FinishedRecipe {
 | 
			
		||||
        private final Recipe<?> recipe;
 | 
			
		||||
        private final Item result;
 | 
			
		||||
        private final RecipeCategory category;
 | 
			
		||||
        private final Map<String, Criterion<?>> criteria;
 | 
			
		||||
 | 
			
		||||
        private FinishedRecipe(Recipe<?> recipe, Item result, RecipeCategory category, Map<String, Criterion<?>> criteria) {
 | 
			
		||||
            this.recipe = recipe;
 | 
			
		||||
            this.result = result;
 | 
			
		||||
            this.category = category;
 | 
			
		||||
            this.criteria = criteria;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void save(RecipeOutput output, ResourceLocation id) {
 | 
			
		||||
            if (criteria.isEmpty()) throw new IllegalStateException("No way of obtaining recipe " + id);
 | 
			
		||||
            var advancement = output.advancement()
 | 
			
		||||
                .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(id))
 | 
			
		||||
                .rewards(AdvancementRewards.Builder.recipe(id))
 | 
			
		||||
                .requirements(AdvancementRequirements.Strategy.OR);
 | 
			
		||||
            for (var entry : criteria.entrySet()) advancement.addCriterion(entry.getKey(), entry.getValue());
 | 
			
		||||
 | 
			
		||||
            output.accept(id, recipe, advancement.build(id.withPrefix("recipes/" + category.getFolderName() + "/")));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void save(RecipeOutput output) {
 | 
			
		||||
            save(output, RecipeBuilder.getDefaultRecipeId(result));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,71 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.data.recipe;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.recipe.RecipeProperties;
 | 
			
		||||
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
 | 
			
		||||
import net.minecraft.data.recipes.RecipeCategory;
 | 
			
		||||
import net.minecraft.data.recipes.ShapedRecipeBuilder;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.Ingredient;
 | 
			
		||||
import net.minecraft.world.item.crafting.ShapedRecipePattern;
 | 
			
		||||
import net.minecraft.world.level.ItemLike;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A builder for {@link ShapedRecipeSpec}s, much like {@link ShapedRecipeBuilder}.
 | 
			
		||||
 */
 | 
			
		||||
public final class ShapedSpecBuilder extends AbstractRecipeBuilder<ShapedSpecBuilder, ShapedRecipeSpec> {
 | 
			
		||||
    private final List<String> rows = new ArrayList<>();
 | 
			
		||||
    private final Map<Character, Ingredient> key = new LinkedHashMap<>();
 | 
			
		||||
 | 
			
		||||
    private ShapedSpecBuilder(RecipeCategory category, ItemStack result) {
 | 
			
		||||
        super(category, result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ShapedSpecBuilder shaped(RecipeCategory category, ItemStack result) {
 | 
			
		||||
        return new ShapedSpecBuilder(category, result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ShapedSpecBuilder shaped(RecipeCategory category, ItemLike result) {
 | 
			
		||||
        return new ShapedSpecBuilder(category, new ItemStack(result));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShapedSpecBuilder define(char key, Ingredient ingredient) {
 | 
			
		||||
        if (this.key.containsKey(key)) throw new IllegalArgumentException("Symbol '" + key + "' is already defined!");
 | 
			
		||||
        if (key == ' ') throw new IllegalArgumentException("Symbol ' ' (whitespace) is reserved and cannot be defined");
 | 
			
		||||
 | 
			
		||||
        this.key.put(key, ingredient);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShapedSpecBuilder define(char key, TagKey<Item> tag) {
 | 
			
		||||
        return this.define(key, Ingredient.of(tag));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShapedSpecBuilder define(char key, ItemLike item) {
 | 
			
		||||
        return this.define(key, Ingredient.of(item));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShapedSpecBuilder pattern(String pattern) {
 | 
			
		||||
        if (!this.rows.isEmpty() && pattern.length() != this.rows.get(0).length()) {
 | 
			
		||||
            throw new IllegalArgumentException("Pattern must be the same width on every line!");
 | 
			
		||||
        } else {
 | 
			
		||||
            this.rows.add(pattern);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected ShapedRecipeSpec build(RecipeProperties properties) {
 | 
			
		||||
        return new ShapedRecipeSpec(properties, ShapedRecipePattern.of(key, rows), result);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,61 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.data.recipe;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.recipe.RecipeProperties;
 | 
			
		||||
import dan200.computercraft.shared.recipe.ShapelessRecipeSpec;
 | 
			
		||||
import net.minecraft.core.NonNullList;
 | 
			
		||||
import net.minecraft.data.recipes.RecipeCategory;
 | 
			
		||||
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.Ingredient;
 | 
			
		||||
import net.minecraft.world.level.ItemLike;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A builder for {@link ShapelessRecipeSpec}s, much like {@link ShapelessRecipeBuilder}.
 | 
			
		||||
 */
 | 
			
		||||
public final class ShapelessSpecBuilder extends AbstractRecipeBuilder<ShapelessSpecBuilder, ShapelessRecipeSpec> {
 | 
			
		||||
    private final NonNullList<Ingredient> ingredients = NonNullList.create();
 | 
			
		||||
 | 
			
		||||
    private ShapelessSpecBuilder(RecipeCategory category, ItemStack result) {
 | 
			
		||||
        super(category, result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ShapelessSpecBuilder shapeless(RecipeCategory category, ItemStack result) {
 | 
			
		||||
        return new ShapelessSpecBuilder(category, result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ShapelessSpecBuilder shapeless(RecipeCategory category, ItemLike result) {
 | 
			
		||||
        return new ShapelessSpecBuilder(category, new ItemStack(result));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShapelessSpecBuilder requires(Ingredient ingredient, int count) {
 | 
			
		||||
        for (int i = 0; i < count; i++) ingredients.add(ingredient);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShapelessSpecBuilder requires(Ingredient ingredient) {
 | 
			
		||||
        return requires(ingredient, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShapelessSpecBuilder requires(ItemLike item) {
 | 
			
		||||
        return requires(Ingredient.of(new ItemStack(item)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShapelessSpecBuilder requires(ItemLike item, int count) {
 | 
			
		||||
        return requires(Ingredient.of(new ItemStack(item)), count);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShapelessSpecBuilder requires(TagKey<Item> item) {
 | 
			
		||||
        return requires(Ingredient.of(item));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected ShapelessRecipeSpec build(RecipeProperties properties) {
 | 
			
		||||
        return new ShapelessRecipeSpec(properties, ingredients, result);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,10 +15,11 @@ import dan200.computercraft.api.media.MediaProvider;
 | 
			
		||||
import dan200.computercraft.api.network.PacketNetwork;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredElement;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredNode;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.core.filesystem.WritableFileMount;
 | 
			
		||||
import dan200.computercraft.impl.detail.DetailRegistryImpl;
 | 
			
		||||
import dan200.computercraft.impl.network.wired.WiredNodeImpl;
 | 
			
		||||
@@ -44,8 +45,8 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
 | 
			
		||||
    private final DetailRegistry<ItemStack> itemStackDetails = new DetailRegistryImpl<>(ItemDetails::fillBasic);
 | 
			
		||||
    private final DetailRegistry<BlockReference> blockDetails = new DetailRegistryImpl<>(BlockDetails::fillBasic);
 | 
			
		||||
 | 
			
		||||
    protected static final ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
 | 
			
		||||
    protected static final ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
 | 
			
		||||
    protected static final ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
 | 
			
		||||
    protected static final ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
 | 
			
		||||
 | 
			
		||||
    public static @Nullable InputStream getResourceFile(MinecraftServer server, String domain, String subPath) {
 | 
			
		||||
        var manager = server.getResourceManager();
 | 
			
		||||
@@ -116,12 +117,12 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId() {
 | 
			
		||||
    public final ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId() {
 | 
			
		||||
        return turtleUpgradeRegistryId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId() {
 | 
			
		||||
    public final ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId() {
 | 
			
		||||
        return pocketUpgradeRegistryId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,20 +16,18 @@ import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The registry for peripheral providers.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This lives in the {@code impl} package despite it not being part of the public API, in order to mirror Forge's class.
 | 
			
		||||
 */
 | 
			
		||||
public final class Peripherals {
 | 
			
		||||
    private static final GenericPeripheralProvider<Runnable> genericProvider = new GenericPeripheralProvider<>();
 | 
			
		||||
    private static final GenericPeripheralProvider genericProvider = new GenericPeripheralProvider();
 | 
			
		||||
 | 
			
		||||
    private Peripherals() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void addGenericLookup(ComponentLookup<? super Runnable> lookup) {
 | 
			
		||||
    public static void addGenericLookup(ComponentLookup lookup) {
 | 
			
		||||
        genericProvider.registerLookup(lookup);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static @Nullable IPeripheral getGenericPeripheral(ServerLevel level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity, Runnable invalidate) {
 | 
			
		||||
        return genericProvider.getPeripheral(level, pos, side, blockEntity, invalidate);
 | 
			
		||||
    public static @Nullable IPeripheral getGenericPeripheral(ServerLevel level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity) {
 | 
			
		||||
        return genericProvider.getPeripheral(level, pos, side, blockEntity);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,19 +6,18 @@ package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
			
		||||
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
public final class PocketUpgrades {
 | 
			
		||||
    private static final UpgradeManager<PocketUpgradeSerialiser<?>, IPocketUpgrade> registry = new UpgradeManager<>(
 | 
			
		||||
        "pocket computer upgrade", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId()
 | 
			
		||||
    private static final UpgradeManager<IPocketUpgrade> registry = new UpgradeManager<>(
 | 
			
		||||
        "pocket computer upgrade", "computercraft/pocket_upgrades", IPocketUpgrade.serialiserRegistryKey()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    private PocketUpgrades() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static UpgradeManager<PocketUpgradeSerialiser<?>, IPocketUpgrade> instance() {
 | 
			
		||||
    public static UpgradeManager<IPocketUpgrade> instance() {
 | 
			
		||||
        return registry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,19 +6,18 @@ package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
public final class TurtleUpgrades {
 | 
			
		||||
    private static final UpgradeManager<TurtleUpgradeSerialiser<?>, ITurtleUpgrade> registry = new UpgradeManager<>(
 | 
			
		||||
        "turtle upgrade", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.registryId()
 | 
			
		||||
    private static final UpgradeManager<ITurtleUpgrade> registry = new UpgradeManager<>(
 | 
			
		||||
        "turtle upgrade", "computercraft/turtle_upgrades", ITurtleUpgrade.serialiserRegistryKey()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    private TurtleUpgrades() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static UpgradeManager<TurtleUpgradeSerialiser<?>, ITurtleUpgrade> instance() {
 | 
			
		||||
    public static UpgradeManager<ITurtleUpgrade> instance() {
 | 
			
		||||
        return registry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,27 +31,26 @@ import java.util.stream.Collectors;
 | 
			
		||||
/**
 | 
			
		||||
 * Manages turtle and pocket computer upgrades.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <R> The type of upgrade serialisers.
 | 
			
		||||
 * @param <T> The type of upgrade.
 | 
			
		||||
 * @see TurtleUpgrades
 | 
			
		||||
 * @see PocketUpgrades
 | 
			
		||||
 */
 | 
			
		||||
public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
 | 
			
		||||
public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
 | 
			
		||||
    private static final Logger LOG = LoggerFactory.getLogger(UpgradeManager.class);
 | 
			
		||||
    private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
 | 
			
		||||
 | 
			
		||||
    public record UpgradeWrapper<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase>(
 | 
			
		||||
        String id, T upgrade, R serialiser, String modId
 | 
			
		||||
    public record UpgradeWrapper<T extends UpgradeBase>(
 | 
			
		||||
        String id, T upgrade, UpgradeSerialiser<? extends T> serialiser, String modId
 | 
			
		||||
    ) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final String kind;
 | 
			
		||||
    private final ResourceKey<Registry<R>> registry;
 | 
			
		||||
    private final ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry;
 | 
			
		||||
 | 
			
		||||
    private Map<String, UpgradeWrapper<R, T>> current = Map.of();
 | 
			
		||||
    private Map<T, UpgradeWrapper<R, T>> currentWrappers = Map.of();
 | 
			
		||||
    private Map<String, UpgradeWrapper<T>> current = Map.of();
 | 
			
		||||
    private Map<T, UpgradeWrapper<T>> currentWrappers = Map.of();
 | 
			
		||||
 | 
			
		||||
    public UpgradeManager(String kind, String path, ResourceKey<Registry<R>> registry) {
 | 
			
		||||
    public UpgradeManager(String kind, String path, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry) {
 | 
			
		||||
        super(GSON, path);
 | 
			
		||||
        this.kind = kind;
 | 
			
		||||
        this.registry = registry;
 | 
			
		||||
@@ -64,7 +63,7 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public UpgradeWrapper<R, T> getWrapper(T upgrade) {
 | 
			
		||||
    public UpgradeWrapper<T> getWrapper(T upgrade) {
 | 
			
		||||
        return currentWrappers.get(upgrade);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -92,16 +91,17 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
 | 
			
		||||
        return currentWrappers.keySet();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Map<String, UpgradeWrapper<R, T>> getUpgradeWrappers() {
 | 
			
		||||
    public Map<String, UpgradeWrapper<T>> getUpgradeWrappers() {
 | 
			
		||||
        return current;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void apply(Map<ResourceLocation, JsonElement> upgrades, ResourceManager manager, ProfilerFiller profiler) {
 | 
			
		||||
        Map<String, UpgradeWrapper<R, T>> newUpgrades = new HashMap<>();
 | 
			
		||||
        var registry = RegistryHelper.getRegistry(this.registry);
 | 
			
		||||
        Map<String, UpgradeWrapper<T>> newUpgrades = new HashMap<>();
 | 
			
		||||
        for (var element : upgrades.entrySet()) {
 | 
			
		||||
            try {
 | 
			
		||||
                loadUpgrade(newUpgrades, element.getKey(), element.getValue());
 | 
			
		||||
                loadUpgrade(registry, newUpgrades, element.getKey(), element.getValue());
 | 
			
		||||
            } catch (IllegalArgumentException | JsonParseException e) {
 | 
			
		||||
                LOG.error("Error loading {} {} from JSON file", kind, element.getKey(), e);
 | 
			
		||||
            }
 | 
			
		||||
@@ -112,12 +112,12 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
 | 
			
		||||
        LOG.info("Loaded {} {}s", current.size(), kind);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void loadUpgrade(Map<String, UpgradeWrapper<R, T>> current, ResourceLocation id, JsonElement json) {
 | 
			
		||||
    private void loadUpgrade(Registry<UpgradeSerialiser<? extends T>> registry, Map<String, UpgradeWrapper<T>> current, ResourceLocation id, JsonElement json) {
 | 
			
		||||
        var root = GsonHelper.convertToJsonObject(json, "top element");
 | 
			
		||||
        if (!PlatformHelper.get().shouldLoadResource(root)) return;
 | 
			
		||||
 | 
			
		||||
        var serialiserId = new ResourceLocation(GsonHelper.getAsString(root, "type"));
 | 
			
		||||
        var serialiser = PlatformHelper.get().tryGetRegistryObject(registry, serialiserId);
 | 
			
		||||
        var serialiser = registry.get(serialiserId);
 | 
			
		||||
        if (serialiser == null) throw new JsonSyntaxException("Unknown upgrade type '" + serialiserId + "'");
 | 
			
		||||
 | 
			
		||||
        // TODO: Can we track which mod this resource came from and use that instead? It's theoretically possible,
 | 
			
		||||
@@ -130,11 +130,11 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
 | 
			
		||||
            throw new IllegalArgumentException("Upgrade " + id + " from " + serialiser + " was incorrectly given id " + upgrade.getUpgradeID());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var result = new UpgradeWrapper<R, T>(id.toString(), upgrade, serialiser, modId);
 | 
			
		||||
        var result = new UpgradeWrapper<T>(id.toString(), upgrade, serialiser, modId);
 | 
			
		||||
        current.put(result.id(), result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void loadFromNetwork(Map<String, UpgradeWrapper<R, T>> newUpgrades) {
 | 
			
		||||
    public void loadFromNetwork(Map<String, UpgradeWrapper<T>> newUpgrades) {
 | 
			
		||||
        current = Collections.unmodifiableMap(newUpgrades);
 | 
			
		||||
        currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x));
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@
 | 
			
		||||
package dan200.computercraft.impl.network.wired;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.network.Packet;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredNetwork;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredNode;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.core.util.Nullability;
 | 
			
		||||
@@ -14,7 +13,7 @@ import java.util.*;
 | 
			
		||||
import java.util.concurrent.locks.ReadWriteLock;
 | 
			
		||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
 | 
			
		||||
 | 
			
		||||
final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
final class WiredNetworkImpl {
 | 
			
		||||
    final ReadWriteLock lock = new ReentrantReadWriteLock();
 | 
			
		||||
    Set<WiredNodeImpl> nodes;
 | 
			
		||||
    private Map<String, IPeripheral> peripherals = new HashMap<>();
 | 
			
		||||
@@ -28,7 +27,6 @@ final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
        this.nodes = nodes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean connect(WiredNode nodeU, WiredNode nodeV) {
 | 
			
		||||
        var wiredU = checkNode(nodeU);
 | 
			
		||||
        var wiredV = checkNode(nodeV);
 | 
			
		||||
@@ -88,7 +86,6 @@ final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean disconnect(WiredNode nodeU, WiredNode nodeV) {
 | 
			
		||||
        var wiredU = checkNode(nodeU);
 | 
			
		||||
        var wiredV = checkNode(nodeV);
 | 
			
		||||
@@ -159,7 +156,6 @@ final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean remove(WiredNode node) {
 | 
			
		||||
        var wired = checkNode(node);
 | 
			
		||||
 | 
			
		||||
@@ -316,7 +312,6 @@ final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updatePeripherals(WiredNode node, Map<String, IPeripheral> newPeripherals) {
 | 
			
		||||
        var wired = checkNode(node);
 | 
			
		||||
        Objects.requireNonNull(peripherals, "peripherals cannot be null");
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,10 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.impl.network.wired;
 | 
			
		||||
 | 
			
		||||
import com.google.common.annotations.VisibleForTesting;
 | 
			
		||||
import dan200.computercraft.api.network.Packet;
 | 
			
		||||
import dan200.computercraft.api.network.PacketReceiver;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredElement;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredNetwork;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredNode;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredSender;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
@@ -128,8 +128,8 @@ public final class WiredNodeImpl implements WiredNode {
 | 
			
		||||
        return element;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public WiredNetwork getNetwork() {
 | 
			
		||||
    @VisibleForTesting
 | 
			
		||||
    public WiredNetworkImpl getNetwork() {
 | 
			
		||||
        return network;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,53 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.mixin;
 | 
			
		||||
 | 
			
		||||
import com.mojang.datafixers.DSL;
 | 
			
		||||
import com.mojang.datafixers.schemas.Schema;
 | 
			
		||||
import com.mojang.datafixers.types.templates.TypeTemplate;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.printer.PrinterBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
 | 
			
		||||
import net.minecraft.util.datafix.fixes.References;
 | 
			
		||||
import net.minecraft.util.datafix.schemas.V1460;
 | 
			
		||||
import org.spongepowered.asm.mixin.Mixin;
 | 
			
		||||
import org.spongepowered.asm.mixin.injection.At;
 | 
			
		||||
import org.spongepowered.asm.mixin.injection.Inject;
 | 
			
		||||
import org.spongepowered.asm.mixin.Shadow;
 | 
			
		||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Register our item-contianing block entities.
 | 
			
		||||
 *
 | 
			
		||||
 * @see DiskDriveBlockEntity
 | 
			
		||||
 * @see PrinterBlockEntity
 | 
			
		||||
 * @see TurtleBlockEntity
 | 
			
		||||
 */
 | 
			
		||||
@Mixin(V1460.class)
 | 
			
		||||
class V1460Mixin {
 | 
			
		||||
    @Inject(at = @At("RETURN"), method = "registerBlockEntities")
 | 
			
		||||
    @SuppressWarnings("UnusedMethod")
 | 
			
		||||
    private void registerBlockEntities(Schema schema, CallbackInfoReturnable<Map<String, Supplier<TypeTemplate>>> ci) {
 | 
			
		||||
        var map = ci.getReturnValue();
 | 
			
		||||
 | 
			
		||||
        // Basic inventories
 | 
			
		||||
        registerInventory(schema, map, ModRegistry.BlockEntities.TURTLE_NORMAL.id().toString());
 | 
			
		||||
        registerInventory(schema, map, ModRegistry.BlockEntities.TURTLE_ADVANCED.id().toString());
 | 
			
		||||
        registerInventory(schema, map, ModRegistry.BlockEntities.PRINTER.id().toString());
 | 
			
		||||
 | 
			
		||||
        // Disk drives contain a single item
 | 
			
		||||
        schema.register(map, ModRegistry.BlockEntities.DISK_DRIVE.id().toString(), () -> DSL.optionalFields(
 | 
			
		||||
            "Item", References.ITEM_STACK.in(schema)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Shadow
 | 
			
		||||
    protected static void registerInventory(Schema schema, Map<String, Supplier<TypeTemplate>> map, String name) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -64,6 +64,12 @@ public final class CommonHooks {
 | 
			
		||||
        ComputerMBean.start(server);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void onServerStarted(MinecraftServer server) {
 | 
			
		||||
        // ItemDetails requires creative tabs to be populated, however by default this is done lazily on the client and
 | 
			
		||||
        // not at all on the server! We instead do this once on server startup.
 | 
			
		||||
        CreativeModeTabs.tryRebuildTabContents(server.getWorldData().enabledFeatures(), false, server.registryAccess());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void onServerStopped() {
 | 
			
		||||
        resetState();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,13 +5,15 @@
 | 
			
		||||
package dan200.computercraft.shared;
 | 
			
		||||
 | 
			
		||||
import com.mojang.brigadier.arguments.ArgumentType;
 | 
			
		||||
import com.mojang.serialization.Codec;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.detail.DetailProvider;
 | 
			
		||||
import dan200.computercraft.api.detail.VanillaDetailRegistries;
 | 
			
		||||
import dan200.computercraft.api.media.IMedia;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeData;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.core.util.Colour;
 | 
			
		||||
import dan200.computercraft.impl.PocketUpgrades;
 | 
			
		||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
			
		||||
@@ -35,7 +37,6 @@ import dan200.computercraft.shared.computer.items.ComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
 | 
			
		||||
import dan200.computercraft.shared.config.Config;
 | 
			
		||||
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
 | 
			
		||||
import dan200.computercraft.shared.data.ConstantLootConditionSerializer;
 | 
			
		||||
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
 | 
			
		||||
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
 | 
			
		||||
import dan200.computercraft.shared.details.BlockDetails;
 | 
			
		||||
@@ -262,29 +263,29 @@ public final class ModRegistry {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class TurtleSerialisers {
 | 
			
		||||
        static final RegistrationHelper<TurtleUpgradeSerialiser<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(TurtleUpgradeSerialiser.registryId());
 | 
			
		||||
        static final RegistrationHelper<UpgradeSerialiser<? extends ITurtleUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(ITurtleUpgrade.serialiserRegistryKey());
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleSpeaker>> SPEAKER =
 | 
			
		||||
            REGISTRY.register("speaker", () -> TurtleUpgradeSerialiser.simpleWithCustomItem(TurtleSpeaker::new));
 | 
			
		||||
        public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleCraftingTable>> WORKBENCH =
 | 
			
		||||
            REGISTRY.register("workbench", () -> TurtleUpgradeSerialiser.simpleWithCustomItem(TurtleCraftingTable::new));
 | 
			
		||||
        public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_NORMAL =
 | 
			
		||||
            REGISTRY.register("wireless_modem_normal", () -> TurtleUpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, false)));
 | 
			
		||||
        public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_ADVANCED =
 | 
			
		||||
            REGISTRY.register("wireless_modem_advanced", () -> TurtleUpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, true)));
 | 
			
		||||
        public static final RegistryEntry<UpgradeSerialiser<TurtleSpeaker>> SPEAKER =
 | 
			
		||||
            REGISTRY.register("speaker", () -> UpgradeSerialiser.simpleWithCustomItem(TurtleSpeaker::new));
 | 
			
		||||
        public static final RegistryEntry<UpgradeSerialiser<TurtleCraftingTable>> WORKBENCH =
 | 
			
		||||
            REGISTRY.register("workbench", () -> UpgradeSerialiser.simpleWithCustomItem(TurtleCraftingTable::new));
 | 
			
		||||
        public static final RegistryEntry<UpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_NORMAL =
 | 
			
		||||
            REGISTRY.register("wireless_modem_normal", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, false)));
 | 
			
		||||
        public static final RegistryEntry<UpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_ADVANCED =
 | 
			
		||||
            REGISTRY.register("wireless_modem_advanced", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, true)));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleTool>> TOOL = REGISTRY.register("tool", () -> TurtleToolSerialiser.INSTANCE);
 | 
			
		||||
        public static final RegistryEntry<UpgradeSerialiser<TurtleTool>> TOOL = REGISTRY.register("tool", () -> TurtleToolSerialiser.INSTANCE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class PocketUpgradeSerialisers {
 | 
			
		||||
        static final RegistrationHelper<PocketUpgradeSerialiser<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(PocketUpgradeSerialiser.registryId());
 | 
			
		||||
        static final RegistrationHelper<UpgradeSerialiser<? extends IPocketUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(IPocketUpgrade.serialiserRegistryKey());
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<PocketUpgradeSerialiser<PocketSpeaker>> SPEAKER =
 | 
			
		||||
            REGISTRY.register("speaker", () -> PocketUpgradeSerialiser.simpleWithCustomItem(PocketSpeaker::new));
 | 
			
		||||
        public static final RegistryEntry<PocketUpgradeSerialiser<PocketModem>> WIRELESS_MODEM_NORMAL =
 | 
			
		||||
            REGISTRY.register("wireless_modem_normal", () -> PocketUpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, false)));
 | 
			
		||||
        public static final RegistryEntry<PocketUpgradeSerialiser<PocketModem>> WIRELESS_MODEM_ADVANCED =
 | 
			
		||||
            REGISTRY.register("wireless_modem_advanced", () -> PocketUpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, true)));
 | 
			
		||||
        public static final RegistryEntry<UpgradeSerialiser<PocketSpeaker>> SPEAKER =
 | 
			
		||||
            REGISTRY.register("speaker", () -> UpgradeSerialiser.simpleWithCustomItem(PocketSpeaker::new));
 | 
			
		||||
        public static final RegistryEntry<UpgradeSerialiser<PocketModem>> WIRELESS_MODEM_NORMAL =
 | 
			
		||||
            REGISTRY.register("wireless_modem_normal", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, false)));
 | 
			
		||||
        public static final RegistryEntry<UpgradeSerialiser<PocketModem>> WIRELESS_MODEM_ADVANCED =
 | 
			
		||||
            REGISTRY.register("wireless_modem_advanced", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, true)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class Menus {
 | 
			
		||||
@@ -345,13 +346,13 @@ public final class ModRegistry {
 | 
			
		||||
        static final RegistrationHelper<LootItemConditionType> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.LOOT_CONDITION_TYPE);
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<LootItemConditionType> BLOCK_NAMED = REGISTRY.register("block_named",
 | 
			
		||||
            () -> ConstantLootConditionSerializer.type(BlockNamedEntityLootCondition.INSTANCE));
 | 
			
		||||
            () -> new LootItemConditionType(Codec.unit(BlockNamedEntityLootCondition.INSTANCE)));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<LootItemConditionType> PLAYER_CREATIVE = REGISTRY.register("player_creative",
 | 
			
		||||
            () -> ConstantLootConditionSerializer.type(PlayerCreativeLootCondition.INSTANCE));
 | 
			
		||||
            () -> new LootItemConditionType(Codec.unit(PlayerCreativeLootCondition.INSTANCE)));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<LootItemConditionType> HAS_ID = REGISTRY.register("has_id",
 | 
			
		||||
            () -> ConstantLootConditionSerializer.type(HasComputerIdLootCondition.INSTANCE));
 | 
			
		||||
            () -> new LootItemConditionType(Codec.unit(HasComputerIdLootCondition.INSTANCE)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class RecipeSerializers {
 | 
			
		||||
@@ -464,8 +465,8 @@ public final class ModRegistry {
 | 
			
		||||
     * Register any objects which must be done on the main thread.
 | 
			
		||||
     */
 | 
			
		||||
    public static void registerMainThread() {
 | 
			
		||||
        CauldronInteraction.WATER.put(Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
 | 
			
		||||
        CauldronInteraction.WATER.put(Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
 | 
			
		||||
        CauldronInteraction.WATER.map().put(Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
 | 
			
		||||
        CauldronInteraction.WATER.map().put(Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
 | 
			
		||||
 
 | 
			
		||||
@@ -159,7 +159,6 @@ public final class CommandComputerCraft {
 | 
			
		||||
     */
 | 
			
		||||
    private static int dumpComputer(CommandSourceStack source, ServerComputer computer) {
 | 
			
		||||
        var table = new TableBuilder("Dump");
 | 
			
		||||
        table.row(header("Instance ID"), text(Integer.toString(computer.getInstanceID())));
 | 
			
		||||
        table.row(header("Instance UUID"), text(computer.getInstanceUUID().toString()));
 | 
			
		||||
        table.row(header("Id"), text(Integer.toString(computer.getID())));
 | 
			
		||||
        table.row(header("Label"), text(computer.getLabel()));
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,9 @@ import com.google.gson.JsonObject;
 | 
			
		||||
import com.mojang.brigadier.Message;
 | 
			
		||||
import com.mojang.brigadier.arguments.ArgumentType;
 | 
			
		||||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryWrappers;
 | 
			
		||||
import dan200.computercraft.impl.RegistryHelper;
 | 
			
		||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
 | 
			
		||||
@@ -24,7 +25,7 @@ public class ArgumentUtils {
 | 
			
		||||
    public static <A extends ArgumentType<?>> JsonObject serializeToJson(ArgumentTypeInfo.Template<A> template) {
 | 
			
		||||
        var object = new JsonObject();
 | 
			
		||||
        object.addProperty("type", "argument");
 | 
			
		||||
        object.addProperty("parser", RegistryWrappers.COMMAND_ARGUMENT_TYPES.getKey(template.type()).toString());
 | 
			
		||||
        object.addProperty("parser", RegistryHelper.getKeyOrThrow(BuiltInRegistries.COMMAND_ARGUMENT_TYPE, template.type()).toString());
 | 
			
		||||
 | 
			
		||||
        var properties = new JsonObject();
 | 
			
		||||
        serializeToJson(properties, template.type(), template);
 | 
			
		||||
@@ -44,12 +45,12 @@ public class ArgumentUtils {
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeToNetwork(FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> template) {
 | 
			
		||||
        buffer.writeId(RegistryWrappers.COMMAND_ARGUMENT_TYPES, type);
 | 
			
		||||
        buffer.writeId(BuiltInRegistries.COMMAND_ARGUMENT_TYPE, type);
 | 
			
		||||
        type.serializeToNetwork((T) template, buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ArgumentTypeInfo.Template<?> deserialize(FriendlyByteBuf buffer) {
 | 
			
		||||
        var type = buffer.readById(RegistryWrappers.COMMAND_ARGUMENT_TYPES);
 | 
			
		||||
        var type = buffer.readById(BuiltInRegistries.COMMAND_ARGUMENT_TYPE);
 | 
			
		||||
        Objects.requireNonNull(type, "Unknown argument type");
 | 
			
		||||
        return type.deserializeFromNetwork(buffer);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -18,8 +18,6 @@ import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
import net.minecraft.commands.SharedSuggestionProvider;
 | 
			
		||||
import net.minecraft.commands.arguments.UuidArgument;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.network.chat.ComponentContents;
 | 
			
		||||
import net.minecraft.network.chat.MutableComponent;
 | 
			
		||||
import net.minecraft.world.phys.AABB;
 | 
			
		||||
import net.minecraft.world.phys.Vec3;
 | 
			
		||||
 | 
			
		||||
@@ -37,7 +35,6 @@ import static dan200.computercraft.shared.command.text.ChatHelpers.makeComputerD
 | 
			
		||||
 | 
			
		||||
public record ComputerSelector(
 | 
			
		||||
    String selector,
 | 
			
		||||
    OptionalInt instanceId,
 | 
			
		||||
    @Nullable UUID instanceUuid,
 | 
			
		||||
    OptionalInt computerId,
 | 
			
		||||
    @Nullable String label,
 | 
			
		||||
@@ -45,9 +42,9 @@ public record ComputerSelector(
 | 
			
		||||
    @Nullable AABB bounds,
 | 
			
		||||
    @Nullable MinMaxBounds.Doubles range
 | 
			
		||||
) {
 | 
			
		||||
    private static final ComputerSelector all = new ComputerSelector("@c[]", OptionalInt.empty(), null, OptionalInt.empty(), null, null, null, null);
 | 
			
		||||
    private static final ComputerSelector all = new ComputerSelector("@c[]", null, OptionalInt.empty(), null, null, null, null);
 | 
			
		||||
 | 
			
		||||
    private static UuidArgument uuidArgument = UuidArgument.uuid();
 | 
			
		||||
    private static final UuidArgument uuidArgument = UuidArgument.uuid();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A {@link ComputerSelector} which matches all computers.
 | 
			
		||||
@@ -66,10 +63,6 @@ public record ComputerSelector(
 | 
			
		||||
     */
 | 
			
		||||
    public Stream<ServerComputer> find(CommandSourceStack source) {
 | 
			
		||||
        var context = ServerContext.get(source.getServer());
 | 
			
		||||
        if (instanceId().isPresent()) {
 | 
			
		||||
            var computer = context.registry().get(instanceId().getAsInt());
 | 
			
		||||
            return computer != null && matches(source, computer) ? Stream.of(computer) : Stream.of();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (instanceUuid() != null) {
 | 
			
		||||
            var computer = context.registry().get(instanceUuid());
 | 
			
		||||
@@ -91,7 +84,7 @@ public record ComputerSelector(
 | 
			
		||||
        if (computers.isEmpty()) throw COMPUTER_ARG_NONE.create(selector);
 | 
			
		||||
        if (computers.size() == 1) return computers.iterator().next();
 | 
			
		||||
 | 
			
		||||
        var builder = MutableComponent.create(ComponentContents.EMPTY);
 | 
			
		||||
        var builder = Component.empty();
 | 
			
		||||
        var first = true;
 | 
			
		||||
        for (var computer : computers) {
 | 
			
		||||
            if (first) {
 | 
			
		||||
@@ -116,8 +109,7 @@ public record ComputerSelector(
 | 
			
		||||
     * @return If this computer is matched by the selector.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean matches(CommandSourceStack source, ServerComputer computer) {
 | 
			
		||||
        return (instanceId().isEmpty() || computer.getInstanceID() == instanceId().getAsInt())
 | 
			
		||||
            && (instanceUuid() == null || computer.getInstanceUUID().equals(instanceUuid()))
 | 
			
		||||
        return (instanceUuid() == null || computer.getInstanceUUID().equals(instanceUuid()))
 | 
			
		||||
            && (computerId().isEmpty() || computer.getID() == computerId().getAsInt())
 | 
			
		||||
            && (label == null || Objects.equals(computer.getLabel(), label))
 | 
			
		||||
            && (family == null || computer.getFamily() == family)
 | 
			
		||||
@@ -140,24 +132,12 @@ public record ComputerSelector(
 | 
			
		||||
        if (consume(reader, "@c[")) {
 | 
			
		||||
            parseSelector(builder, reader);
 | 
			
		||||
        } else {
 | 
			
		||||
            // TODO(1.20.5): Only parse computer ids here.
 | 
			
		||||
            var kind = reader.peek();
 | 
			
		||||
            if (kind == '@') {
 | 
			
		||||
                reader.skip();
 | 
			
		||||
                builder.label = reader.readString();
 | 
			
		||||
            } else if (kind == '~') {
 | 
			
		||||
                reader.skip();
 | 
			
		||||
                builder.family = parseFamily(reader);
 | 
			
		||||
            } else if (kind == '#') {
 | 
			
		||||
                reader.skip();
 | 
			
		||||
            if (reader.peek() == '#') reader.skip();
 | 
			
		||||
            builder.computerId = OptionalInt.of(reader.readInt());
 | 
			
		||||
            } else {
 | 
			
		||||
                builder.instanceId = OptionalInt.of(reader.readInt());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var selector = reader.getString().substring(start, reader.getCursor());
 | 
			
		||||
        return new ComputerSelector(selector, builder.instanceId, builder.instanceUuid, builder.computerId, builder.label, builder.family, builder.bounds, builder.range);
 | 
			
		||||
        return new ComputerSelector(selector, builder.instanceUuid, builder.computerId, builder.label, builder.family, builder.bounds, builder.range);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void parseSelector(Builder builder, StringReader reader) throws CommandSyntaxException {
 | 
			
		||||
@@ -224,7 +204,7 @@ public record ComputerSelector(
 | 
			
		||||
        } else if (remaining.startsWith("#")) {
 | 
			
		||||
            return suggestComputers(c -> "#" + c.getID()).suggest(context, builder);
 | 
			
		||||
        } else {
 | 
			
		||||
            return suggestComputers(c -> Integer.toString(c.getInstanceID())).suggest(context, builder);
 | 
			
		||||
            return suggestComputers(c -> Integer.toString(c.getID())).suggest(context, builder);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@ package dan200.computercraft.shared.common;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import net.minecraft.core.NonNullList;
 | 
			
		||||
import net.minecraft.core.RegistryAccess;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.inventory.CraftingContainer;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.Items;
 | 
			
		||||
@@ -20,8 +19,8 @@ import net.minecraft.world.level.Level;
 | 
			
		||||
 * Craft a wet sponge with a {@linkplain IColouredItem dyable item} to remove its dye.
 | 
			
		||||
 */
 | 
			
		||||
public final class ClearColourRecipe extends CustomRecipe {
 | 
			
		||||
    public ClearColourRecipe(ResourceLocation id, CraftingBookCategory category) {
 | 
			
		||||
        super(id, category);
 | 
			
		||||
    public ClearColourRecipe(CraftingBookCategory category) {
 | 
			
		||||
        super(category);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.util.ColourTracker;
 | 
			
		||||
import dan200.computercraft.shared.util.ColourUtils;
 | 
			
		||||
import net.minecraft.core.RegistryAccess;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.inventory.CraftingContainer;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
 | 
			
		||||
@@ -17,8 +16,8 @@ import net.minecraft.world.item.crafting.RecipeSerializer;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
 | 
			
		||||
public final class ColourableRecipe extends CustomRecipe {
 | 
			
		||||
    public ColourableRecipe(ResourceLocation id, CraftingBookCategory category) {
 | 
			
		||||
        super(id, category);
 | 
			
		||||
    public ColourableRecipe(CraftingBookCategory category) {
 | 
			
		||||
        super(category);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -56,14 +56,22 @@ public class CommandAPI implements ILuaAPI {
 | 
			
		||||
        var receiver = computer.getReceiver();
 | 
			
		||||
        try {
 | 
			
		||||
            receiver.clearOutput();
 | 
			
		||||
            var result = commandManager.performPrefixedCommand(computer.getSource(), command);
 | 
			
		||||
            return new Object[]{ result > 0, receiver.copyOutput(), result };
 | 
			
		||||
            var state = new CommandState();
 | 
			
		||||
            var source = computer.getSource().withCallback((success, x) -> {
 | 
			
		||||
                if (success) state.successes++;
 | 
			
		||||
            });
 | 
			
		||||
            commandManager.performPrefixedCommand(source, command);
 | 
			
		||||
            return new Object[]{ state.successes > 0, receiver.copyOutput(), state.successes };
 | 
			
		||||
        } catch (Throwable t) {
 | 
			
		||||
            LOG.error(Logging.JAVA_ERROR, "Error running command.", t);
 | 
			
		||||
            return new Object[]{ false, createOutput("Java Exception Thrown: " + t) };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final class CommandState {
 | 
			
		||||
        int successes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Map<?, ?> getBlockInfo(Level world, BlockPos pos) {
 | 
			
		||||
        // Get the details of the block
 | 
			
		||||
        var block = new BlockReference(world, pos);
 | 
			
		||||
 
 | 
			
		||||
@@ -100,7 +100,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    public ItemStack getCloneItemStack(BlockGetter world, BlockPos pos, BlockState state) {
 | 
			
		||||
    public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state) {
 | 
			
		||||
        var tile = world.getBlockEntity(pos);
 | 
			
		||||
        if (tile instanceof AbstractComputerBlockEntity computer) {
 | 
			
		||||
            var result = getItem(computer);
 | 
			
		||||
@@ -118,9 +118,9 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
 | 
			
		||||
        super.playerWillDestroy(world, pos, state, player);
 | 
			
		||||
        if (!(world instanceof ServerLevel serverWorld)) return;
 | 
			
		||||
    public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
 | 
			
		||||
        var result = super.playerWillDestroy(world, pos, state, player);
 | 
			
		||||
        if (!(world instanceof ServerLevel serverWorld)) return result;
 | 
			
		||||
 | 
			
		||||
        // We drop the item here instead of doing it in the harvest method, as we should
 | 
			
		||||
        // drop computers for creative players too.
 | 
			
		||||
@@ -139,6 +139,8 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
 | 
			
		||||
 | 
			
		||||
            state.spawnAfterBreak(serverWorld, pos, player.getMainHandItem(), true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,10 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.computer.blocks;
 | 
			
		||||
 | 
			
		||||
import com.mojang.serialization.MapCodec;
 | 
			
		||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryEntry;
 | 
			
		||||
import dan200.computercraft.shared.util.BlockCodecs;
 | 
			
		||||
import net.minecraft.world.level.block.GameMasterBlock;
 | 
			
		||||
import net.minecraft.world.level.block.entity.BlockEntityType;
 | 
			
		||||
 | 
			
		||||
@@ -16,7 +19,17 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
 | 
			
		||||
 * @see dan200.computercraft.shared.computer.items.CommandComputerItem
 | 
			
		||||
 */
 | 
			
		||||
public class CommandComputerBlock<T extends CommandComputerBlockEntity> extends ComputerBlock<T> implements GameMasterBlock {
 | 
			
		||||
    private static final MapCodec<CommandComputerBlock<?>> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
 | 
			
		||||
        BlockCodecs.propertiesCodec(),
 | 
			
		||||
        BlockCodecs.blockEntityCodec(x -> x.type)
 | 
			
		||||
    ).apply(instance, CommandComputerBlock::new));
 | 
			
		||||
 | 
			
		||||
    public CommandComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
 | 
			
		||||
        super(settings, type);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected MapCodec<? extends CommandComputerBlock<?>> codec() {
 | 
			
		||||
        return CODEC;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,12 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.computer.blocks;
 | 
			
		||||
 | 
			
		||||
import com.mojang.serialization.MapCodec;
 | 
			
		||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerState;
 | 
			
		||||
import dan200.computercraft.shared.computer.items.ComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryEntry;
 | 
			
		||||
import dan200.computercraft.shared.util.BlockCodecs;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.context.BlockPlaceContext;
 | 
			
		||||
@@ -21,6 +24,11 @@ import net.minecraft.world.level.block.state.properties.EnumProperty;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComputerBlock<T> {
 | 
			
		||||
    private static final MapCodec<ComputerBlock<?>> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
 | 
			
		||||
        BlockCodecs.propertiesCodec(),
 | 
			
		||||
        BlockCodecs.blockEntityCodec(x -> x.type)
 | 
			
		||||
    ).apply(instance, ComputerBlock::new));
 | 
			
		||||
 | 
			
		||||
    public static final EnumProperty<ComputerState> STATE = EnumProperty.create("state", ComputerState.class);
 | 
			
		||||
    public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
 | 
			
		||||
 | 
			
		||||
@@ -37,6 +45,11 @@ public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComput
 | 
			
		||||
        builder.add(FACING, STATE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected MapCodec<? extends ComputerBlock<?>> codec() {
 | 
			
		||||
        return CODEC;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    @Override
 | 
			
		||||
    public BlockState getStateForPlacement(BlockPlaceContext placement) {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
public class ServerComputer implements InputHandler, ComputerEnvironment {
 | 
			
		||||
    private final int instanceID;
 | 
			
		||||
    private final UUID instanceUUID = UUID.randomUUID();
 | 
			
		||||
 | 
			
		||||
    private ServerLevel level;
 | 
			
		||||
@@ -54,7 +53,6 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
 | 
			
		||||
        this.family = family;
 | 
			
		||||
 | 
			
		||||
        var context = ServerContext.get(level.getServer());
 | 
			
		||||
        instanceID = context.registry().getUnusedInstanceID();
 | 
			
		||||
        terminal = new NetworkedTerminal(terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged);
 | 
			
		||||
        metrics = context.metrics().createMetricObserver(this);
 | 
			
		||||
 | 
			
		||||
@@ -148,10 +146,6 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
 | 
			
		||||
    protected void onRemoved() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getInstanceID() {
 | 
			
		||||
        return instanceID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public UUID getInstanceUUID() {
 | 
			
		||||
        return instanceUUID;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.computer.core;
 | 
			
		||||
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
@@ -14,23 +11,12 @@ public class ServerComputerRegistry {
 | 
			
		||||
    private static final Random RANDOM = new Random();
 | 
			
		||||
 | 
			
		||||
    private final int sessionId = RANDOM.nextInt();
 | 
			
		||||
    private final Int2ObjectMap<ServerComputer> computersByInstanceId = new Int2ObjectOpenHashMap<>();
 | 
			
		||||
    private final Map<UUID, ServerComputer> computersByInstanceUuid = new HashMap<>();
 | 
			
		||||
    private int nextInstanceId;
 | 
			
		||||
 | 
			
		||||
    public int getSessionID() {
 | 
			
		||||
        return sessionId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int getUnusedInstanceID() {
 | 
			
		||||
        return nextInstanceId++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public ServerComputer get(int instanceID) {
 | 
			
		||||
        return instanceID >= 0 ? computersByInstanceId.get(instanceID) : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public ServerComputer get(@Nullable UUID instanceID) {
 | 
			
		||||
        return instanceID != null ? computersByInstanceUuid.get(instanceID) : null;
 | 
			
		||||
@@ -56,35 +42,27 @@ public class ServerComputerRegistry {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void add(ServerComputer computer) {
 | 
			
		||||
        var instanceID = computer.getInstanceID();
 | 
			
		||||
        var instanceUUID = computer.getInstanceUUID();
 | 
			
		||||
 | 
			
		||||
        if (computersByInstanceId.containsKey(instanceID)) {
 | 
			
		||||
            throw new IllegalStateException("Duplicate computer " + instanceID);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (computersByInstanceUuid.containsKey(instanceUUID)) {
 | 
			
		||||
            throw new IllegalStateException("Duplicate computer " + instanceUUID);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        computersByInstanceId.put(instanceID, computer);
 | 
			
		||||
        computersByInstanceUuid.put(instanceUUID, computer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void remove(ServerComputer computer) {
 | 
			
		||||
        computer.unload();
 | 
			
		||||
        computer.onRemoved();
 | 
			
		||||
        computersByInstanceId.remove(computer.getInstanceID());
 | 
			
		||||
        computersByInstanceUuid.remove(computer.getInstanceUUID());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void close() {
 | 
			
		||||
        for (var computer : getComputers()) computer.unload();
 | 
			
		||||
        computersByInstanceId.clear();
 | 
			
		||||
        computersByInstanceUuid.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Collection<ServerComputer> getComputers() {
 | 
			
		||||
        return computersByInstanceId.values();
 | 
			
		||||
        return computersByInstanceUuid.values();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import dan200.computercraft.shared.computer.items.IComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.recipe.CustomShapedRecipe;
 | 
			
		||||
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
 | 
			
		||||
import net.minecraft.core.RegistryAccess;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.inventory.CraftingContainer;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
@@ -17,8 +16,8 @@ import net.minecraft.world.level.Level;
 | 
			
		||||
 * A recipe which converts a computer from one form into another.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class ComputerConvertRecipe extends CustomShapedRecipe {
 | 
			
		||||
    public ComputerConvertRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe) {
 | 
			
		||||
        super(identifier, recipe);
 | 
			
		||||
    public ComputerConvertRecipe(ShapedRecipeSpec recipe) {
 | 
			
		||||
        super(recipe);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected abstract ItemStack convert(IComputerItem item, ItemStack stack);
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import com.mojang.serialization.DataResult;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.computer.items.IComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.RecipeSerializer;
 | 
			
		||||
@@ -22,17 +21,17 @@ import net.minecraft.world.item.crafting.RecipeSerializer;
 | 
			
		||||
public final class ComputerUpgradeRecipe extends ComputerConvertRecipe {
 | 
			
		||||
    private final Item result;
 | 
			
		||||
 | 
			
		||||
    private ComputerUpgradeRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe) {
 | 
			
		||||
        super(identifier, recipe);
 | 
			
		||||
    public ComputerUpgradeRecipe(ShapedRecipeSpec recipe) {
 | 
			
		||||
        super(recipe);
 | 
			
		||||
        this.result = recipe.result().getItem();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static DataResult<ComputerUpgradeRecipe> of(ResourceLocation id, ShapedRecipeSpec recipe) {
 | 
			
		||||
    public static DataResult<ComputerUpgradeRecipe> of(ShapedRecipeSpec recipe) {
 | 
			
		||||
        if (!(recipe.result().getItem() instanceof IComputerItem)) {
 | 
			
		||||
            return DataResult.error(() -> recipe.result().getItem() + " is not a computer item");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return DataResult.success(new ComputerUpgradeRecipe(id, recipe));
 | 
			
		||||
        return DataResult.success(new ComputerUpgradeRecipe(recipe));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.data;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonDeserializationContext;
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import com.google.gson.JsonSerializationContext;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.Serializer;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
 | 
			
		||||
 | 
			
		||||
public final class ConstantLootConditionSerializer<T extends LootItemCondition> implements Serializer<T> {
 | 
			
		||||
    private final T instance;
 | 
			
		||||
 | 
			
		||||
    public ConstantLootConditionSerializer(T instance) {
 | 
			
		||||
        this.instance = instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static <T extends LootItemCondition> LootItemConditionType type(T condition) {
 | 
			
		||||
        return new LootItemConditionType(new ConstantLootConditionSerializer<>(condition));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void serialize(JsonObject json, T object, JsonSerializationContext context) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public T deserialize(JsonObject json, JsonDeserializationContext context) {
 | 
			
		||||
        return instance;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
package dan200.computercraft.shared.details;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.detail.BlockReference;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryWrappers;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.world.level.block.state.properties.Property;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
@@ -15,7 +15,7 @@ public class BlockDetails {
 | 
			
		||||
    public static void fillBasic(Map<? super String, Object> data, BlockReference block) {
 | 
			
		||||
        var state = block.state();
 | 
			
		||||
 | 
			
		||||
        data.put("name", DetailHelpers.getId(RegistryWrappers.BLOCKS, state.getBlock()));
 | 
			
		||||
        data.put("name", DetailHelpers.getId(BuiltInRegistries.BLOCK, state.getBlock()));
 | 
			
		||||
 | 
			
		||||
        Map<Object, Object> stateTable = new HashMap<>();
 | 
			
		||||
        for (Map.Entry<Property<?>, ? extends Comparable<?>> entry : state.getValues().entrySet()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,9 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.details;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryWrappers;
 | 
			
		||||
import dan200.computercraft.impl.RegistryHelper;
 | 
			
		||||
import net.minecraft.core.Holder;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
@@ -24,7 +25,7 @@ public final class DetailHelpers {
 | 
			
		||||
        return tags.collect(Collectors.toMap(x -> x.location().toString(), x -> true));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static <T> String getId(RegistryWrappers.RegistryWrapper<T> registry, T entry) {
 | 
			
		||||
        return registry.getKey(entry).toString();
 | 
			
		||||
    public static <T> String getId(Registry<T> registry, T entry) {
 | 
			
		||||
        return RegistryHelper.getKeyOrThrow(registry, entry).toString();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,11 +5,13 @@
 | 
			
		||||
package dan200.computercraft.shared.details;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonParseException;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryWrappers;
 | 
			
		||||
import dan200.computercraft.shared.util.NBTUtil;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.nbt.ListTag;
 | 
			
		||||
import net.minecraft.nbt.Tag;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.item.CreativeModeTab;
 | 
			
		||||
import net.minecraft.world.item.CreativeModeTabs;
 | 
			
		||||
import net.minecraft.world.item.EnchantedBookItem;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
 | 
			
		||||
@@ -22,7 +24,7 @@ import java.util.*;
 | 
			
		||||
 */
 | 
			
		||||
public class ItemDetails {
 | 
			
		||||
    public static void fillBasic(Map<? super String, Object> data, ItemStack stack) {
 | 
			
		||||
        data.put("name", DetailHelpers.getId(RegistryWrappers.ITEMS, stack.getItem()));
 | 
			
		||||
        data.put("name", DetailHelpers.getId(BuiltInRegistries.ITEM, stack.getItem()));
 | 
			
		||||
        data.put("count", stack.getCount());
 | 
			
		||||
        var hash = NBTUtil.getNBTHash(stack.getTag());
 | 
			
		||||
        if (hash != null) data.put("nbt", hash);
 | 
			
		||||
@@ -42,9 +44,7 @@ public class ItemDetails {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        data.put("tags", DetailHelpers.getTags(stack.getTags()));
 | 
			
		||||
 | 
			
		||||
        // Include deprecated itemGroups field
 | 
			
		||||
        data.put("itemGroups", List.of());
 | 
			
		||||
        data.put("itemGroups", getItemGroups(stack));
 | 
			
		||||
 | 
			
		||||
        var tag = stack.getTag();
 | 
			
		||||
        if (tag != null && tag.contains("display", Tag.TAG_COMPOUND)) {
 | 
			
		||||
@@ -83,6 +83,27 @@ public class ItemDetails {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve all item groups an item stack pertains to.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack Stack to analyse
 | 
			
		||||
     * @return A filled list that contains pairs of item group IDs and their display names.
 | 
			
		||||
     */
 | 
			
		||||
    private static List<Map<String, Object>> getItemGroups(ItemStack stack) {
 | 
			
		||||
        return CreativeModeTabs.allTabs().stream()
 | 
			
		||||
            .filter(x -> x.shouldDisplay() && x.getType() == CreativeModeTab.Type.CATEGORY && x.contains(stack))
 | 
			
		||||
            .map(group -> {
 | 
			
		||||
                Map<String, Object> groupData = new HashMap<>(2);
 | 
			
		||||
 | 
			
		||||
                var id = BuiltInRegistries.CREATIVE_MODE_TAB.getKey(group);
 | 
			
		||||
                if (id != null) groupData.put("id", id.toString());
 | 
			
		||||
 | 
			
		||||
                groupData.put("displayName", group.getDisplayName().getString());
 | 
			
		||||
                return groupData;
 | 
			
		||||
            })
 | 
			
		||||
            .toList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve all visible enchantments from given stack. Try to follow all tooltip rules : order and visibility.
 | 
			
		||||
     *
 | 
			
		||||
@@ -126,7 +147,7 @@ public class ItemDetails {
 | 
			
		||||
            var enchantment = entry.getKey();
 | 
			
		||||
            var level = entry.getValue();
 | 
			
		||||
            var enchant = new HashMap<String, Object>(3);
 | 
			
		||||
            enchant.put("name", DetailHelpers.getId(RegistryWrappers.ENCHANTMENTS, enchantment));
 | 
			
		||||
            enchant.put("name", DetailHelpers.getId(BuiltInRegistries.ENCHANTMENT, enchantment));
 | 
			
		||||
            enchant.put("level", level);
 | 
			
		||||
            enchant.put("displayName", enchantment.getFullname(level).getString());
 | 
			
		||||
            enchants.add(enchant);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.integration;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
@@ -15,13 +14,12 @@ import dan200.computercraft.impl.TurtleUpgrades;
 | 
			
		||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
 | 
			
		||||
import net.minecraft.core.NonNullList;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
 | 
			
		||||
import net.minecraft.world.item.crafting.CraftingRecipe;
 | 
			
		||||
import net.minecraft.world.item.crafting.Ingredient;
 | 
			
		||||
import net.minecraft.world.item.crafting.ShapedRecipe;
 | 
			
		||||
import net.minecraft.world.item.crafting.ShapedRecipePattern;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
@@ -38,17 +36,14 @@ import static dan200.computercraft.shared.integration.RecipeModHelpers.TURTLES;
 | 
			
		||||
 * @see RecipeModHelpers
 | 
			
		||||
 */
 | 
			
		||||
public class UpgradeRecipeGenerator<T> {
 | 
			
		||||
    private static final ResourceLocation TURTLE_UPGRADE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade");
 | 
			
		||||
    private static final ResourceLocation POCKET_UPGRADE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade");
 | 
			
		||||
 | 
			
		||||
    private final Function<CraftingRecipe, T> wrap;
 | 
			
		||||
    private final Function<ShapedRecipe, T> wrap;
 | 
			
		||||
 | 
			
		||||
    private final Map<Item, List<UpgradeInfo>> upgradeItemLookup = new HashMap<>();
 | 
			
		||||
    private final List<UpgradeInfo> pocketUpgrades = new ArrayList<>();
 | 
			
		||||
    private final List<UpgradeInfo> turtleUpgrades = new ArrayList<>();
 | 
			
		||||
    private boolean initialised = false;
 | 
			
		||||
 | 
			
		||||
    public UpgradeRecipeGenerator(Function<CraftingRecipe, T> wrap) {
 | 
			
		||||
    public UpgradeRecipeGenerator(Function<ShapedRecipe, T> wrap) {
 | 
			
		||||
        this.wrap = wrap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -235,11 +230,19 @@ public class UpgradeRecipeGenerator<T> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private T pocket(Ingredient upgrade, Ingredient pocketComputer, ItemStack result) {
 | 
			
		||||
        return wrap.apply(new ShapedRecipe(POCKET_UPGRADE, "", CraftingBookCategory.MISC, 1, 2, NonNullList.of(Ingredient.EMPTY, upgrade, pocketComputer), result));
 | 
			
		||||
        return wrap.apply(new ShapedRecipe(
 | 
			
		||||
            "", CraftingBookCategory.MISC,
 | 
			
		||||
            new ShapedRecipePattern(1, 2, NonNullList.of(Ingredient.EMPTY, upgrade, pocketComputer), Optional.empty()),
 | 
			
		||||
            result
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private T turtle(Ingredient left, Ingredient right, ItemStack result) {
 | 
			
		||||
        return wrap.apply(new ShapedRecipe(TURTLE_UPGRADE, "", CraftingBookCategory.MISC, 2, 1, NonNullList.of(Ingredient.EMPTY, left, right), result));
 | 
			
		||||
        return wrap.apply(new ShapedRecipe(
 | 
			
		||||
            "", CraftingBookCategory.MISC,
 | 
			
		||||
            new ShapedRecipePattern(2, 1, NonNullList.of(Ingredient.EMPTY, left, right), Optional.empty()),
 | 
			
		||||
            result
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private class UpgradeInfo {
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ public class JEIComputerCraft implements IModPlugin {
 | 
			
		||||
        // Hide all upgrade recipes
 | 
			
		||||
        var category = registry.createRecipeLookup(RecipeTypes.CRAFTING);
 | 
			
		||||
        category.get().forEach(wrapper -> {
 | 
			
		||||
            if (RecipeModHelpers.shouldRemoveRecipe(wrapper.getId())) {
 | 
			
		||||
            if (RecipeModHelpers.shouldRemoveRecipe(wrapper.id())) {
 | 
			
		||||
                registry.hideRecipes(RecipeTypes.CRAFTING, List.of(wrapper));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.integration.jei;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.shared.integration.UpgradeRecipeGenerator;
 | 
			
		||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
 | 
			
		||||
@@ -12,13 +13,16 @@ import mezz.jei.api.recipe.IFocus;
 | 
			
		||||
import mezz.jei.api.recipe.RecipeType;
 | 
			
		||||
import mezz.jei.api.recipe.advanced.IRecipeManagerPlugin;
 | 
			
		||||
import mezz.jei.api.recipe.category.IRecipeCategory;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.CraftingRecipe;
 | 
			
		||||
import net.minecraft.world.item.crafting.RecipeHolder;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
class RecipeResolver implements IRecipeManagerPlugin {
 | 
			
		||||
    private final UpgradeRecipeGenerator<CraftingRecipe> resolver = new UpgradeRecipeGenerator<>(x -> x);
 | 
			
		||||
    private static final ResourceLocation RECIPE_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "upgrade");
 | 
			
		||||
    private final UpgradeRecipeGenerator<RecipeHolder<CraftingRecipe>> resolver = new UpgradeRecipeGenerator<>(x -> new RecipeHolder<>(RECIPE_ID, x));
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <V> List<RecipeType<?>> getRecipeTypes(IFocus<V> focus) {
 | 
			
		||||
@@ -44,8 +48,8 @@ class RecipeResolver implements IRecipeManagerPlugin {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return switch (focus.getRole()) {
 | 
			
		||||
            case INPUT -> cast(resolver.findRecipesWithInput(stack));
 | 
			
		||||
            case OUTPUT -> cast(resolver.findRecipesWithOutput(stack));
 | 
			
		||||
            case INPUT -> cast(RecipeTypes.CRAFTING, resolver.findRecipesWithInput(stack));
 | 
			
		||||
            case OUTPUT -> cast(RecipeTypes.CRAFTING, resolver.findRecipesWithOutput(stack));
 | 
			
		||||
            default -> List.of();
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
@@ -56,7 +60,7 @@ class RecipeResolver implements IRecipeManagerPlugin {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings({ "unchecked", "rawtypes" })
 | 
			
		||||
    private static <T, U> List<T> cast(List<U> from) {
 | 
			
		||||
    private static <T, U> List<T> cast(RecipeType<U> ignoredType, List<U> from) {
 | 
			
		||||
        return (List) from;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@ import dan200.computercraft.shared.platform.PlatformHelper;
 | 
			
		||||
import dan200.computercraft.shared.util.ColourTracker;
 | 
			
		||||
import dan200.computercraft.shared.util.ColourUtils;
 | 
			
		||||
import net.minecraft.core.RegistryAccess;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.inventory.CraftingContainer;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.Items;
 | 
			
		||||
@@ -24,8 +23,8 @@ import net.minecraft.world.level.Level;
 | 
			
		||||
public class DiskRecipe extends CustomRecipe {
 | 
			
		||||
    private final Ingredient redstone;
 | 
			
		||||
 | 
			
		||||
    public DiskRecipe(ResourceLocation id, CraftingBookCategory category) {
 | 
			
		||||
        super(id, category);
 | 
			
		||||
    public DiskRecipe(CraftingBookCategory category) {
 | 
			
		||||
        super(category);
 | 
			
		||||
        redstone = PlatformHelper.get().getRecipeIngredients().redstone();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.media.items.PrintoutItem;
 | 
			
		||||
import dan200.computercraft.shared.platform.PlatformHelper;
 | 
			
		||||
import net.minecraft.core.RegistryAccess;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.inventory.CraftingContainer;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.Items;
 | 
			
		||||
@@ -22,8 +21,8 @@ public final class PrintoutRecipe extends CustomRecipe {
 | 
			
		||||
    private final Ingredient leather;
 | 
			
		||||
    private final Ingredient string;
 | 
			
		||||
 | 
			
		||||
    public PrintoutRecipe(ResourceLocation id, CraftingBookCategory category) {
 | 
			
		||||
        super(id, category);
 | 
			
		||||
    public PrintoutRecipe(CraftingBookCategory category) {
 | 
			
		||||
        super(category);
 | 
			
		||||
 | 
			
		||||
        var ingredients = PlatformHelper.get().getRecipeIngredients();
 | 
			
		||||
        leather = ingredients.leather();
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,9 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.network;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A type of message to send over the network.
 | 
			
		||||
 * <p>
 | 
			
		||||
@@ -15,4 +18,11 @@ package dan200.computercraft.shared.network;
 | 
			
		||||
 * @see NetworkMessage#type()
 | 
			
		||||
 */
 | 
			
		||||
public interface MessageType<T extends NetworkMessage<?>> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the id of this message type. This will be used as the custom packet channel name.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The id of this message type.
 | 
			
		||||
     * @see CustomPacketPayload#id()
 | 
			
		||||
     */
 | 
			
		||||
    ResourceLocation id();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.shared.network.client.*;
 | 
			
		||||
import dan200.computercraft.shared.network.server.*;
 | 
			
		||||
import dan200.computercraft.shared.platform.PlatformHelper;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntSet;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
@@ -21,50 +19,48 @@ import java.util.*;
 | 
			
		||||
 * @see PlatformHelper The platform helper is used to send packets.
 | 
			
		||||
 */
 | 
			
		||||
public final class NetworkMessages {
 | 
			
		||||
    private static final IntSet seenIds = new IntOpenHashSet();
 | 
			
		||||
    private static final Set<String> seenChannel = new HashSet<>();
 | 
			
		||||
    private static final List<MessageType<? extends NetworkMessage<ServerNetworkContext>>> serverMessages = new ArrayList<>();
 | 
			
		||||
    private static final List<MessageType<? extends NetworkMessage<ClientNetworkContext>>> clientMessages = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    public static final MessageType<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound(0, "computer_action", ComputerActionServerMessage.class, ComputerActionServerMessage::new);
 | 
			
		||||
    public static final MessageType<QueueEventServerMessage> QUEUE_EVENT = registerServerbound(1, "queue_event", QueueEventServerMessage.class, QueueEventServerMessage::new);
 | 
			
		||||
    public static final MessageType<KeyEventServerMessage> KEY_EVENT = registerServerbound(2, "key_event", KeyEventServerMessage.class, KeyEventServerMessage::new);
 | 
			
		||||
    public static final MessageType<MouseEventServerMessage> MOUSE_EVENT = registerServerbound(3, "mouse_event", MouseEventServerMessage.class, MouseEventServerMessage::new);
 | 
			
		||||
    public static final MessageType<UploadFileMessage> UPLOAD_FILE = registerServerbound(4, "upload_file", UploadFileMessage.class, UploadFileMessage::new);
 | 
			
		||||
    public static final MessageType<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound("computer_action", ComputerActionServerMessage::new);
 | 
			
		||||
    public static final MessageType<QueueEventServerMessage> QUEUE_EVENT = registerServerbound("queue_event", QueueEventServerMessage::new);
 | 
			
		||||
    public static final MessageType<KeyEventServerMessage> KEY_EVENT = registerServerbound("key_event", KeyEventServerMessage::new);
 | 
			
		||||
    public static final MessageType<MouseEventServerMessage> MOUSE_EVENT = registerServerbound("mouse_event", MouseEventServerMessage::new);
 | 
			
		||||
    public static final MessageType<UploadFileMessage> UPLOAD_FILE = registerServerbound("upload_file", UploadFileMessage::new);
 | 
			
		||||
 | 
			
		||||
    public static final MessageType<ChatTableClientMessage> CHAT_TABLE = registerClientbound(10, "chat_table", ChatTableClientMessage.class, ChatTableClientMessage::new);
 | 
			
		||||
    public static final MessageType<PocketComputerDataMessage> POCKET_COMPUTER_DATA = registerClientbound(11, "pocket_computer_data", PocketComputerDataMessage.class, PocketComputerDataMessage::new);
 | 
			
		||||
    public static final MessageType<PocketComputerDeletedClientMessage> POCKET_COMPUTER_DELETED = registerClientbound(12, "pocket_computer_deleted", PocketComputerDeletedClientMessage.class, PocketComputerDeletedClientMessage::new);
 | 
			
		||||
    public static final MessageType<ComputerTerminalClientMessage> COMPUTER_TERMINAL = registerClientbound(13, "computer_terminal", ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new);
 | 
			
		||||
    public static final MessageType<PlayRecordClientMessage> PLAY_RECORD = registerClientbound(14, "play_record", PlayRecordClientMessage.class, PlayRecordClientMessage::new);
 | 
			
		||||
    public static final MessageType<MonitorClientMessage> MONITOR_CLIENT = registerClientbound(15, "monitor_client", MonitorClientMessage.class, MonitorClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerAudioClientMessage> SPEAKER_AUDIO = registerClientbound(16, "speaker_audio", SpeakerAudioClientMessage.class, SpeakerAudioClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerMoveClientMessage> SPEAKER_MOVE = registerClientbound(17, "speaker_move", SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerPlayClientMessage> SPEAKER_PLAY = registerClientbound(18, "speaker_play", SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerStopClientMessage> SPEAKER_STOP = registerClientbound(19, "speaker_stop", SpeakerStopClientMessage.class, SpeakerStopClientMessage::new);
 | 
			
		||||
    public static final MessageType<UploadResultMessage> UPLOAD_RESULT = registerClientbound(20, "upload_result", UploadResultMessage.class, UploadResultMessage::new);
 | 
			
		||||
    public static final MessageType<UpgradesLoadedMessage> UPGRADES_LOADED = registerClientbound(21, "upgrades_loaded", UpgradesLoadedMessage.class, UpgradesLoadedMessage::new);
 | 
			
		||||
    public static final MessageType<ChatTableClientMessage> CHAT_TABLE = registerClientbound("chat_table", ChatTableClientMessage::new);
 | 
			
		||||
    public static final MessageType<PocketComputerDataMessage> POCKET_COMPUTER_DATA = registerClientbound("pocket_computer_data", PocketComputerDataMessage::new);
 | 
			
		||||
    public static final MessageType<PocketComputerDeletedClientMessage> POCKET_COMPUTER_DELETED = registerClientbound("pocket_computer_deleted", PocketComputerDeletedClientMessage::new);
 | 
			
		||||
    public static final MessageType<ComputerTerminalClientMessage> COMPUTER_TERMINAL = registerClientbound("computer_terminal", ComputerTerminalClientMessage::new);
 | 
			
		||||
    public static final MessageType<PlayRecordClientMessage> PLAY_RECORD = registerClientbound("play_record", PlayRecordClientMessage::new);
 | 
			
		||||
    public static final MessageType<MonitorClientMessage> MONITOR_CLIENT = registerClientbound("monitor_client", MonitorClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerAudioClientMessage> SPEAKER_AUDIO = registerClientbound("speaker_audio", SpeakerAudioClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerMoveClientMessage> SPEAKER_MOVE = registerClientbound("speaker_move", SpeakerMoveClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerPlayClientMessage> SPEAKER_PLAY = registerClientbound("speaker_play", SpeakerPlayClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerStopClientMessage> SPEAKER_STOP = registerClientbound("speaker_stop", SpeakerStopClientMessage::new);
 | 
			
		||||
    public static final MessageType<UploadResultMessage> UPLOAD_RESULT = registerClientbound("upload_result", UploadResultMessage::new);
 | 
			
		||||
    public static final MessageType<UpgradesLoadedMessage> UPGRADES_LOADED = registerClientbound("upgrades_loaded", UpgradesLoadedMessage::new);
 | 
			
		||||
 | 
			
		||||
    private NetworkMessages() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static <C, T extends NetworkMessage<C>> MessageType<T> register(
 | 
			
		||||
        List<MessageType<? extends NetworkMessage<C>>> messages,
 | 
			
		||||
        int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader
 | 
			
		||||
        String channel, FriendlyByteBuf.Reader<T> reader
 | 
			
		||||
    ) {
 | 
			
		||||
        if (!seenIds.add(id)) throw new IllegalArgumentException("Duplicate id " + id);
 | 
			
		||||
        if (!seenChannel.add(channel)) throw new IllegalArgumentException("Duplicate channel " + channel);
 | 
			
		||||
        var type = PlatformHelper.get().createMessageType(id, new ResourceLocation(ComputerCraftAPI.MOD_ID, channel), klass, reader);
 | 
			
		||||
        var type = PlatformHelper.get().createMessageType(new ResourceLocation(ComputerCraftAPI.MOD_ID, channel), reader);
 | 
			
		||||
        messages.add(type);
 | 
			
		||||
        return type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static <T extends NetworkMessage<ServerNetworkContext>> MessageType<T> registerServerbound(int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
 | 
			
		||||
        return register(serverMessages, id, channel, klass, reader);
 | 
			
		||||
    private static <T extends NetworkMessage<ServerNetworkContext>> MessageType<T> registerServerbound(String id, FriendlyByteBuf.Reader<T> reader) {
 | 
			
		||||
        return register(serverMessages, id, reader);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static <T extends NetworkMessage<ClientNetworkContext>> MessageType<T> registerClientbound(int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
 | 
			
		||||
        return register(clientMessages, id, channel, klass, reader);
 | 
			
		||||
    private static <T extends NetworkMessage<ClientNetworkContext>> MessageType<T> registerClientbound(String id, FriendlyByteBuf.Reader<T> reader) {
 | 
			
		||||
        return register(clientMessages, id, reader);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -5,18 +5,16 @@
 | 
			
		||||
package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.PocketUpgrades;
 | 
			
		||||
import dan200.computercraft.impl.RegistryHelper;
 | 
			
		||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
			
		||||
import dan200.computercraft.impl.UpgradeManager;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.platform.PlatformHelper;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
@@ -24,14 +22,13 @@ import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Syncs turtle and pocket upgrades to the client.
 | 
			
		||||
 */
 | 
			
		||||
public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContext> {
 | 
			
		||||
    private final Map<String, UpgradeManager.UpgradeWrapper<TurtleUpgradeSerialiser<?>, ITurtleUpgrade>> turtleUpgrades;
 | 
			
		||||
    private final Map<String, UpgradeManager.UpgradeWrapper<PocketUpgradeSerialiser<?>, IPocketUpgrade>> pocketUpgrades;
 | 
			
		||||
    private final Map<String, UpgradeManager.UpgradeWrapper<ITurtleUpgrade>> turtleUpgrades;
 | 
			
		||||
    private final Map<String, UpgradeManager.UpgradeWrapper<IPocketUpgrade>> pocketUpgrades;
 | 
			
		||||
 | 
			
		||||
    public UpgradesLoadedMessage() {
 | 
			
		||||
        turtleUpgrades = TurtleUpgrades.instance().getUpgradeWrappers();
 | 
			
		||||
@@ -39,28 +36,28 @@ public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetwork
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public UpgradesLoadedMessage(FriendlyByteBuf buf) {
 | 
			
		||||
        turtleUpgrades = fromBytes(buf, TurtleUpgradeSerialiser.registryId());
 | 
			
		||||
        pocketUpgrades = fromBytes(buf, PocketUpgradeSerialiser.registryId());
 | 
			
		||||
        turtleUpgrades = fromBytes(buf, ITurtleUpgrade.serialiserRegistryKey());
 | 
			
		||||
        pocketUpgrades = fromBytes(buf, IPocketUpgrade.serialiserRegistryKey());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private <R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> Map<String, UpgradeManager.UpgradeWrapper<R, T>> fromBytes(
 | 
			
		||||
        FriendlyByteBuf buf, ResourceKey<Registry<R>> registryKey
 | 
			
		||||
    private <T extends UpgradeBase> Map<String, UpgradeManager.UpgradeWrapper<T>> fromBytes(
 | 
			
		||||
        FriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey
 | 
			
		||||
    ) {
 | 
			
		||||
        var registry = PlatformHelper.get().wrap(registryKey);
 | 
			
		||||
        var registry = RegistryHelper.getRegistry(registryKey);
 | 
			
		||||
 | 
			
		||||
        var size = buf.readVarInt();
 | 
			
		||||
        Map<String, UpgradeManager.UpgradeWrapper<R, T>> upgrades = new HashMap<>(size);
 | 
			
		||||
        Map<String, UpgradeManager.UpgradeWrapper<T>> upgrades = new HashMap<>(size);
 | 
			
		||||
        for (var i = 0; i < size; i++) {
 | 
			
		||||
            var id = buf.readUtf();
 | 
			
		||||
 | 
			
		||||
            var serialiserId = buf.readResourceLocation();
 | 
			
		||||
            var serialiser = registry.tryGet(serialiserId);
 | 
			
		||||
            var serialiser = registry.get(serialiserId);
 | 
			
		||||
            if (serialiser == null) throw new IllegalStateException("Unknown serialiser " + serialiserId);
 | 
			
		||||
 | 
			
		||||
            var upgrade = serialiser.fromNetwork(new ResourceLocation(id), buf);
 | 
			
		||||
            var modId = buf.readUtf();
 | 
			
		||||
 | 
			
		||||
            upgrades.put(id, new UpgradeManager.UpgradeWrapper<R, T>(id, upgrade, serialiser, modId));
 | 
			
		||||
            upgrades.put(id, new UpgradeManager.UpgradeWrapper<T>(id, upgrade, serialiser, modId));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return upgrades;
 | 
			
		||||
@@ -68,14 +65,14 @@ public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetwork
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        toBytes(buf, TurtleUpgradeSerialiser.registryId(), turtleUpgrades);
 | 
			
		||||
        toBytes(buf, PocketUpgradeSerialiser.registryId(), pocketUpgrades);
 | 
			
		||||
        toBytes(buf, ITurtleUpgrade.serialiserRegistryKey(), turtleUpgrades);
 | 
			
		||||
        toBytes(buf, IPocketUpgrade.serialiserRegistryKey(), pocketUpgrades);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private <R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> void toBytes(
 | 
			
		||||
        FriendlyByteBuf buf, ResourceKey<Registry<R>> registryKey, Map<String, UpgradeManager.UpgradeWrapper<R, T>> upgrades
 | 
			
		||||
    private <T extends UpgradeBase> void toBytes(
 | 
			
		||||
        FriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey, Map<String, UpgradeManager.UpgradeWrapper<T>> upgrades
 | 
			
		||||
    ) {
 | 
			
		||||
        var registry = PlatformHelper.get().wrap(registryKey);
 | 
			
		||||
        var registry = RegistryHelper.getRegistry(registryKey);
 | 
			
		||||
 | 
			
		||||
        buf.writeVarInt(upgrades.size());
 | 
			
		||||
        for (var entry : upgrades.entrySet()) {
 | 
			
		||||
@@ -85,7 +82,7 @@ public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetwork
 | 
			
		||||
            @SuppressWarnings("unchecked")
 | 
			
		||||
            var unwrappedSerialiser = (UpgradeSerialiser<T>) serialiser;
 | 
			
		||||
 | 
			
		||||
            buf.writeResourceLocation(Objects.requireNonNull(registry.getKey(serialiser), "Serialiser is not registered!"));
 | 
			
		||||
            buf.writeResourceLocation(RegistryHelper.getKeyOrThrow(registry, serialiser));
 | 
			
		||||
            unwrappedSerialiser.toNetwork(buf, entry.getValue().upgrade());
 | 
			
		||||
 | 
			
		||||
            buf.writeUtf(entry.getValue().modId());
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.peripheral.diskdrive;
 | 
			
		||||
 | 
			
		||||
import com.mojang.serialization.MapCodec;
 | 
			
		||||
import dan200.computercraft.impl.MediaProviders;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.common.HorizontalContainerBlock;
 | 
			
		||||
@@ -26,6 +27,8 @@ import net.minecraft.world.phys.BlockHitResult;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
public class DiskDriveBlock extends HorizontalContainerBlock {
 | 
			
		||||
    private static final MapCodec<DiskDriveBlock> CODEC = simpleCodec(DiskDriveBlock::new);
 | 
			
		||||
 | 
			
		||||
    public static final EnumProperty<DiskDriveState> STATE = EnumProperty.create("state", DiskDriveState.class);
 | 
			
		||||
 | 
			
		||||
    private static final BlockEntityTicker<DiskDriveBlockEntity> serverTicker = (level, pos, state, drive) -> drive.serverTick();
 | 
			
		||||
@@ -43,6 +46,11 @@ public class DiskDriveBlock extends HorizontalContainerBlock {
 | 
			
		||||
        properties.add(FACING, STATE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected MapCodec<? extends BaseEntityBlock> codec() {
 | 
			
		||||
        return CODEC;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,10 +15,8 @@ import javax.annotation.Nullable;
 | 
			
		||||
/**
 | 
			
		||||
 * Extract some component (for instance a capability on Forge, or a {@code BlockApiLookup} on Fabric) from a block and
 | 
			
		||||
 * block entity.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <C> A platform-specific type, used for the invalidation callback.
 | 
			
		||||
 */
 | 
			
		||||
public interface ComponentLookup<C extends Runnable> {
 | 
			
		||||
public interface ComponentLookup {
 | 
			
		||||
    /**
 | 
			
		||||
     * Extract some component from a block in the world.
 | 
			
		||||
     *
 | 
			
		||||
@@ -28,9 +26,8 @@ public interface ComponentLookup<C extends Runnable> {
 | 
			
		||||
     * @param blockEntity The block entity at that position.
 | 
			
		||||
     * @param side        The side of the block to extract the component from. Implementations should try to use a
 | 
			
		||||
     *                    sideless lookup first, but may fall back to a sided lookup if needed.
 | 
			
		||||
     * @param invalidate  An invalidation function to call if this component changes.
 | 
			
		||||
     * @return The found component, or {@code null} if not present.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    Object find(ServerLevel level, BlockPos pos, BlockState state, BlockEntity blockEntity, Direction side, C invalidate);
 | 
			
		||||
    Object find(ServerLevel level, BlockPos pos, BlockState state, BlockEntity blockEntity, Direction side);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,6 @@ import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.server.level.ServerLevel;
 | 
			
		||||
import net.minecraft.world.level.block.entity.BlockEntity;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
@@ -25,39 +23,35 @@ import java.util.Objects;
 | 
			
		||||
 * A peripheral provider which finds methods from various {@linkplain GenericSource generic sources}.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Methods are found using the original block entity itself and a registered list of {@link ComponentLookup}s.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <C> A platform-specific type, used for the invalidation callback.
 | 
			
		||||
 */
 | 
			
		||||
public final class GenericPeripheralProvider<C extends Runnable> {
 | 
			
		||||
    private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class);
 | 
			
		||||
 | 
			
		||||
    private final List<ComponentLookup<? super C>> lookups = new ArrayList<>();
 | 
			
		||||
public final class GenericPeripheralProvider {
 | 
			
		||||
    private final List<ComponentLookup> lookups = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a component lookup function.
 | 
			
		||||
     *
 | 
			
		||||
     * @param lookup The component lookup function.
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized void registerLookup(ComponentLookup<? super C> lookup) {
 | 
			
		||||
    public synchronized void registerLookup(ComponentLookup lookup) {
 | 
			
		||||
        Objects.requireNonNull(lookup);
 | 
			
		||||
        if (!lookups.contains(lookup)) lookups.add(lookup);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void forEachMethod(MethodSupplier<PeripheralMethod> methods, ServerLevel level, BlockPos pos, Direction side, BlockEntity blockEntity, C invalidate, MethodSupplier.TargetedConsumer<PeripheralMethod> consumer) {
 | 
			
		||||
    public void forEachMethod(MethodSupplier<PeripheralMethod> methods, ServerLevel level, BlockPos pos, Direction side, BlockEntity blockEntity, MethodSupplier.TargetedConsumer<PeripheralMethod> consumer) {
 | 
			
		||||
        methods.forEachMethod(blockEntity, consumer);
 | 
			
		||||
 | 
			
		||||
        for (var lookup : lookups) {
 | 
			
		||||
            var contents = lookup.find(level, pos, blockEntity.getBlockState(), blockEntity, side, invalidate);
 | 
			
		||||
            var contents = lookup.find(level, pos, blockEntity.getBlockState(), blockEntity, side);
 | 
			
		||||
            if (contents != null) methods.forEachMethod(contents, consumer);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public IPeripheral getPeripheral(ServerLevel level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity, C invalidate) {
 | 
			
		||||
    public IPeripheral getPeripheral(ServerLevel level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity) {
 | 
			
		||||
        if (blockEntity == null) return null;
 | 
			
		||||
 | 
			
		||||
        var builder = new GenericPeripheralBuilder();
 | 
			
		||||
        forEachMethod(ServerContext.get(level.getServer()).peripheralMethods(), level, pos, side, blockEntity, invalidate, builder::addMethod);
 | 
			
		||||
        forEachMethod(ServerContext.get(level.getServer()).peripheralMethods(), level, pos, side, blockEntity, builder::addMethod);
 | 
			
		||||
        return builder.toPeripheral(blockEntity, side);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -75,17 +75,14 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
 | 
			
		||||
     * <p>
 | 
			
		||||
     * The returned information contains the same information as each item in
 | 
			
		||||
     * {@link #list}, as well as additional details like the display name
 | 
			
		||||
     * (`displayName`), and item and item durability (`damage`, `maxDamage`, `durability`).
 | 
			
		||||
     * (`displayName`), item groups (`itemGroups`), which are the creative tabs
 | 
			
		||||
     * an item will appear under, and item and item durability (`damage`,
 | 
			
		||||
     * `maxDamage`, `durability`).
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Some items include more information (such as enchantments) - it is
 | 
			
		||||
     * recommended to print it out using [`textutils.serialize`] or in the Lua
 | 
			
		||||
     * REPL, to explore what is available.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * > [Deprecated fields][!INFO]
 | 
			
		||||
     * > Older versions of CC: Tweaked exposed an {@code itemGroups} field, listing the
 | 
			
		||||
     * > creative tabs an item was available under. This information is no longer available on
 | 
			
		||||
     * > more recent versions of the game, and so this field will always be empty. Do not use this
 | 
			
		||||
     * > field in new code!
 | 
			
		||||
     *
 | 
			
		||||
     * @param inventory The current inventory.
 | 
			
		||||
     * @param slot      The slot to get information about.
 | 
			
		||||
 
 | 
			
		||||
@@ -139,12 +139,12 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    public ItemStack getCloneItemStack(BlockGetter world, BlockPos pos, BlockState state) {
 | 
			
		||||
    public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state) {
 | 
			
		||||
        return state.getValue(CABLE) ? new ItemStack(ModRegistry.Items.CABLE.get()) : new ItemStack(ModRegistry.Items.WIRED_MODEM.get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ForgeOverride
 | 
			
		||||
    public ItemStack getCloneItemStack(BlockState state, @Nullable HitResult hit, BlockGetter world, BlockPos pos, Player player) {
 | 
			
		||||
    public ItemStack getCloneItemStack(BlockState state, @Nullable HitResult hit, LevelReader world, BlockPos pos, Player player) {
 | 
			
		||||
        var modem = state.getValue(MODEM).getFacing();
 | 
			
		||||
        boolean cable = state.getValue(CABLE);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,6 @@ public class CableBlockEntity extends BlockEntity {
 | 
			
		||||
 | 
			
		||||
    private boolean refreshPeripheral;
 | 
			
		||||
    private final WiredModemLocalPeripheral peripheral = new WiredModemLocalPeripheral(PlatformHelper.get().createPeripheralAccess(this, x -> queueRefreshPeripheral()));
 | 
			
		||||
    private @Nullable Runnable modemChanged;
 | 
			
		||||
 | 
			
		||||
    private boolean refreshConnections = false;
 | 
			
		||||
 | 
			
		||||
@@ -98,7 +97,7 @@ public class CableBlockEntity extends BlockEntity {
 | 
			
		||||
        super.setBlockState(state);
 | 
			
		||||
 | 
			
		||||
        // We invalidate both the modem and element if the modem direction or cable are different.
 | 
			
		||||
        if (modemChanged != null && (hasCable() != hasCable || getModemDirection() != direction)) modemChanged.run();
 | 
			
		||||
        if (hasCable() != hasCable || getModemDirection() != direction) PlatformHelper.get().invalidateComponent(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
@@ -243,10 +242,6 @@ public class CableBlockEntity extends BlockEntity {
 | 
			
		||||
        return getBlockState().getValue(CableBlock.MODEM).isPeripheralOn();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onModemChanged(Runnable callback) {
 | 
			
		||||
        modemChanged = callback;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    boolean hasCable() {
 | 
			
		||||
        return getBlockState().getValue(CableBlock.CABLE);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,11 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.peripheral.modem.wired;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.impl.RegistryHelper;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryWrappers;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.sounds.SoundSource;
 | 
			
		||||
import net.minecraft.world.InteractionResult;
 | 
			
		||||
import net.minecraft.world.item.BlockItem;
 | 
			
		||||
@@ -47,7 +48,7 @@ public abstract class CableBlockItem extends BlockItem {
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getDescriptionId() {
 | 
			
		||||
        if (translationKey == null) {
 | 
			
		||||
            translationKey = Util.makeDescriptionId("block", RegistryWrappers.ITEMS.getKey(this));
 | 
			
		||||
            translationKey = Util.makeDescriptionId("block", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, this));
 | 
			
		||||
        }
 | 
			
		||||
        return translationKey;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user